use std::{iter, sync::Arc}; pub(crate) mod prelude { #![allow(unused_imports)] pub use std::{ error::Error as StdError, ffi::{OsStr, OsString}, fmt::{Display, Formatter, Result as FmtResult}, io::{Error as IoError, Read, Seek, SeekFrom, Write}, path::{Path, PathBuf}, process::{Command, ExitCode}, str::FromStr, }; #[cfg(unix)] pub use std::os::{ fd::AsRawFd, unix::ffi::{OsStrExt, OsStringExt}, }; pub type BoxDynError = Box; pub use command_error::{CommandExt, OutputLike}; pub use fs_err::File; #[cfg(unix)] pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt}; pub use tap::{Pipe, Tap}; pub use tracing::{Level, debug, error, info, trace, warn}; } use prelude::*; pub mod args; pub use args::{AppendCmd, Args, DeltaCmd}; mod color; pub use color::{_CLI_ENABLE_COLOR, SHOULD_COLOR}; pub mod line; mod nixcmd; pub use line::Line; pub mod source; pub use source::SourceLine; use serde::{Deserialize, Serialize}; use crate::source::SourceFile; pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; #[tracing::instrument(level = "debug")] pub fn do_delta(args: Arc, delta_args: DeltaCmd) -> Result<(), BoxDynError> { todo!(); } #[tracing::instrument(level = "debug")] pub fn do_append(args: Arc, append_args: AppendCmd) -> Result<(), BoxDynError> { let filepath = Path::new(&args.file); let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") { iter::once(OsStr::new("./")) .chain(filepath.iter()) .collect() } else { filepath.to_path_buf() }; // Get what file that thing is defined in. let def_path = get_where(&append_args.name, &filepath)?; let mut opts = File::options(); opts.read(true) .write(true) .create(false) .custom_flags(libc::O_CLOEXEC); let source_file = SourceFile::open_from(Arc::from(def_path), opts)?; let pri = get_highest_prio(&append_args.name, source_file.clone())?; let new_pri = pri - 1; let new_pri_line = get_next_prio_line( source_file.clone(), append_args.name.into(), new_pri, append_args.value.into(), )?; eprintln!("new_pri_line={new_pri_line}"); write_next_prio(source_file, new_pri_line)?; Ok(()) } #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] pub struct DefinitionWithLocation { pub file: Box, pub value: Box, } pub fn expr_for_configuration(source_file: &Path) -> OsString { [ OsStr::new("import { configuration = "), source_file.as_os_str(), OsStr::new("; }"), ] .into_iter() .collect() } pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result, BoxDynError> { let expr = expr_for_configuration(configuration_nix); let attrpath = format!("options.{}.definitionsWithLocations", option_name); let output = nixcmd::NixEvalExpr { expr, attrpath } .into_command() .output_checked_utf8()?; let stdout = output.stdout(); let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?; let last_location = definitions.into_iter().last().unwrap(); Ok(Box::from(last_location.file)) } pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result { // Get the current highest priority. let expr = expr_for_configuration(&source.path()); // Get the highest priority, and the file its defined in. let attrpath = format!("options.{}.highestPrio", option_name); let output = nixcmd::NixEvalExpr { expr, attrpath } .into_command() .output_checked_utf8()?; let stdout = output.stdout(); let highest_prio = i64::from_str(stdout.trim())?; Ok(highest_prio) } pub fn get_next_prio_line( source: SourceFile, option_name: Arc, new_prio: i64, new_value: Arc, ) -> Result { let source_lines = source.lines()?; let last_line = source_lines.last(); assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]")); let last_line = last_line.unwrap(); let new_line = SourceLine { line: last_line.line, path: source.path(), text: Arc::from(format!( " {option_name} = lib.mkOverride ({new_prio}) ({new_value});", )), }; Ok(new_line) } pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> { let new_mod_start = SourceLine { line: new_line.line.prev(), path: source.path(), text: Arc::from(" {"), }; let new_mod_end = SourceLine { line: new_line.line.next(), path: source.path(), text: Arc::from(" }"), }; source.insert_lines(&[new_mod_start, new_line, new_mod_end])?; Ok(()) }