skeleton continues
This commit is contained in:
parent
bcd11513ef
commit
e5d0bdf0c0
12
src/color.rs
12
src/color.rs
|
|
@ -34,10 +34,16 @@ impl Display for _LazyLockDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const ANSI_GREEN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
||||||
|
SHOULD_COLOR.then_some("\x1b[32m").unwrap_or_default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
pub(crate) const ANSI_MAGENTA: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
||||||
|
SHOULD_COLOR.then_some("\x1b[35m").unwrap_or_default()
|
||||||
|
}));
|
||||||
|
|
||||||
pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
||||||
SHOULD_COLOR
|
SHOULD_COLOR.then_some("\x1b[36m").unwrap_or_default()
|
||||||
.then_some("\x1b[36m")
|
|
||||||
.unwrap_or_default()
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
||||||
|
|
|
||||||
107
src/lib.rs
107
src/lib.rs
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::{io::BufWriter, sync::Arc};
|
||||||
|
|
||||||
pub(crate) mod prelude {
|
pub(crate) mod prelude {
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
|
@ -27,22 +29,20 @@ pub(crate) mod prelude {
|
||||||
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use std::{
|
|
||||||
io::{BufRead, BufReader},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub use args::Parser;
|
pub use args::Parser;
|
||||||
mod color;
|
mod color;
|
||||||
pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR};
|
pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR};
|
||||||
pub mod line;
|
pub mod line;
|
||||||
|
mod nixcmd;
|
||||||
pub use line::Line;
|
pub use line::Line;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub use source::SourceLine;
|
pub use source::SourceLine;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::source::SourceFile;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct DefinitionWithLocation {
|
pub struct DefinitionWithLocation {
|
||||||
pub file: Box<Path>,
|
pub file: Box<Path>,
|
||||||
|
|
@ -59,57 +59,29 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.collect();
|
.collect();
|
||||||
let result = Command::new("nix-instantiate")
|
|
||||||
.arg("--eval")
|
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
|
||||||
.arg("--json")
|
|
||||||
.arg("--strict")
|
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||||
.arg("--expr")
|
.into_command()
|
||||||
.arg(expr)
|
|
||||||
.arg("-A")
|
|
||||||
.arg(format!("options.{}.definitionsWithLocations", option_name))
|
|
||||||
.output_checked_utf8()?;
|
.output_checked_utf8()?;
|
||||||
let stdout = result.stdout();
|
let stdout = output.stdout();
|
||||||
|
|
||||||
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
|
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
|
||||||
let last_location = definitions.last().unwrap();
|
let last_location = definitions.into_iter().last().unwrap();
|
||||||
let file = &*last_location.file;
|
|
||||||
dbg!(&file);
|
|
||||||
|
|
||||||
Ok(Box::from(file))
|
Ok(Box::from(last_location.file))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_highest_prio(
|
pub fn get_highest_prio(
|
||||||
option_name: &str,
|
option_name: &str,
|
||||||
file_with_definition: &Path,
|
mut source: SourceFile,
|
||||||
) -> Result<SourceLine, BoxDynError> {
|
) -> Result<SourceLine, BoxDynError> {
|
||||||
let mut file = File::options()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create(false)
|
|
||||||
.custom_flags(libc::O_CLOEXEC)
|
|
||||||
.open(file_with_definition)?;
|
|
||||||
|
|
||||||
// TODO: seek and read backwards.
|
|
||||||
|
|
||||||
let mut lines = BufReader::new(&mut file)
|
|
||||||
.lines()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, line_res)| {
|
|
||||||
line_res.map(|line| SourceLine {
|
|
||||||
line: Line::from_index(index as u64),
|
|
||||||
path: Arc::from(file_with_definition),
|
|
||||||
text: Arc::from(line),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<SourceLine>, IoError>>()?;
|
|
||||||
lines.reverse();
|
|
||||||
let lines_reversed = lines;
|
|
||||||
|
|
||||||
// Get the current highest priority.
|
// Get the current highest priority.
|
||||||
|
|
||||||
let expr: OsString = [
|
let expr: OsString = [
|
||||||
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
||||||
file_with_definition.as_os_str(),
|
source.path().as_os_str(),
|
||||||
OsStr::new("; }"),
|
OsStr::new("; }"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -117,41 +89,40 @@ pub fn get_highest_prio(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Get the highest priority, and the file its defined in.
|
// Get the highest priority, and the file its defined in.
|
||||||
let result = Command::new("nix-instantiate")
|
let attrpath = format!("options.{}.highestPrio", option_name);
|
||||||
.arg("--eval")
|
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||||
.arg("--json")
|
.into_command()
|
||||||
.arg("--strict")
|
|
||||||
.arg("--expr")
|
|
||||||
.arg(expr)
|
|
||||||
.arg("-A")
|
|
||||||
.arg(format!("options.{}.highestPrio", option_name))
|
|
||||||
.output_checked_utf8()?;
|
.output_checked_utf8()?;
|
||||||
let stdout = result.stdout();
|
let stdout = output.stdout();
|
||||||
let highest_prio = stdout.trim();
|
let highest_prio = stdout.trim();
|
||||||
|
|
||||||
let needle = format!("lib.mkOverride ({})", highest_prio);
|
let needle = format!("lib.mkOverride ({})", highest_prio);
|
||||||
eprintln!("looking for {needle:?}");
|
|
||||||
|
|
||||||
let line_with_current_highest = lines_reversed
|
let path = source.path();
|
||||||
|
let lines = source.lines()?;
|
||||||
|
let line = lines
|
||||||
.iter()
|
.iter()
|
||||||
.position(|line| {
|
// We're more likely to find it at the end, so let's start there.
|
||||||
eprintln!("looking for {needle} in {line}");
|
.rev()
|
||||||
|
.find(|&line| line.text.contains(&needle))
|
||||||
line.text.contains(&needle)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
"couldn't find override number {highest_prio} in {}",
|
"couldn't find override number {highest_prio} in {}",
|
||||||
file_with_definition.display()
|
path.display(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let line = lines_reversed
|
Ok(line.clone())
|
||||||
.into_iter()
|
}
|
||||||
.nth(line_with_current_highest)
|
|
||||||
.unwrap();
|
pub fn write_next_prio(
|
||||||
|
mut source: SourceFile,
|
||||||
eprintln!("found, on line index {}", line);
|
last_line_def: SourceLine,
|
||||||
|
new_prio: u64,
|
||||||
Ok(line)
|
) -> Result<(), BoxDynError> {
|
||||||
|
let lines = source.lines()?;
|
||||||
|
|
||||||
|
for line in lines {}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/main.rs
21
src/main.rs
|
|
@ -1,17 +1,20 @@
|
||||||
use std::error::Error as StdError;
|
use std::{error::Error as StdError, sync::Arc};
|
||||||
use std::io::IsTerminal;
|
use std::io::{self, IsTerminal};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
|
||||||
|
use append_override::source::SourceFile;
|
||||||
use clap::{ColorChoice, Parser as _};
|
use clap::{ColorChoice, Parser as _};
|
||||||
|
use fs_err::File;
|
||||||
|
use fs_err::os::unix::fs::OpenOptionsExt;
|
||||||
|
|
||||||
fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
|
fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
|
||||||
let args = append_override::Parser::parse();
|
let args = append_override::Parser::parse();
|
||||||
|
|
||||||
let success = append_override::CLI_ENABLE_COLOR.set(match args.color {
|
let success = append_override::CLI_ENABLE_COLOR.set(match args.color {
|
||||||
ColorChoice::Always => true,
|
ColorChoice::Always => true,
|
||||||
ColorChoice::Auto => std::io::stdin().is_terminal(),
|
ColorChoice::Auto => io::stdin().is_terminal(),
|
||||||
ColorChoice::Never => false,
|
ColorChoice::Never => false,
|
||||||
});
|
});
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
|
@ -23,8 +26,18 @@ fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
|
||||||
|
|
||||||
// Get what file that thing is defined in.
|
// Get what file that thing is defined in.
|
||||||
let def_path = append_override::get_where(&args.name, filepath)?;
|
let def_path = append_override::get_where(&args.name, filepath)?;
|
||||||
|
let def_path = Arc::from(def_path);
|
||||||
|
let mut opts = File::options();
|
||||||
|
opts
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(false)
|
||||||
|
.custom_flags(libc::O_CLOEXEC);
|
||||||
|
let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?;
|
||||||
|
|
||||||
append_override::get_highest_prio(&args.name, &def_path)?;
|
let last_def_line = append_override::get_highest_prio(&args.name, source_file)?;
|
||||||
|
|
||||||
|
eprintln!("{last_def_line}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
src/nixcmd.rs
Normal file
27
src/nixcmd.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub(crate) struct NixEvalExpr<E, A> {
|
||||||
|
pub(crate) expr: E,
|
||||||
|
pub(crate) attrpath: A,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, A> NixEvalExpr<E, A>
|
||||||
|
where
|
||||||
|
E: AsRef<OsStr>,
|
||||||
|
A: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
pub(crate) fn into_command(self) -> Command {
|
||||||
|
let mut cmd = Command::new("nix-instantiate");
|
||||||
|
cmd.arg("--eval")
|
||||||
|
.arg("--json")
|
||||||
|
.arg("--strict")
|
||||||
|
.arg("--expr")
|
||||||
|
.arg(self.expr)
|
||||||
|
.arg("-A")
|
||||||
|
.arg(self.attrpath);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
use std::{
|
use std::{
|
||||||
hash::{Hash, Hasher},
|
hash::Hash,
|
||||||
sync::{Arc, LazyLock},
|
io::{BufRead, BufReader},
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Line;
|
use crate::Line;
|
||||||
|
use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::color::{ANSI_CYAN, ANSI_RESET};
|
|
||||||
|
use fs_err::OpenOptions;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub struct SourceLine {
|
pub struct SourceLine {
|
||||||
|
|
@ -17,7 +21,13 @@ pub struct SourceLine {
|
||||||
|
|
||||||
impl Display for SourceLine {
|
impl Display for SourceLine {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
write!(f, "line {:03}: `{ANSI_CYAN}{}{ANSI_RESET}`", self.line.linenr(), self.text.trim())
|
write!(
|
||||||
|
f,
|
||||||
|
"{ANSI_MAGENTA}{}{ANSI_RESET}:{ANSI_GREEN}{}{ANSI_RESET}: `{ANSI_CYAN}{}{ANSI_RESET}`",
|
||||||
|
self.path.display(),
|
||||||
|
self.line.linenr(),
|
||||||
|
self.text.trim(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,31 +39,64 @@ pub struct SourcePath {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SourceFile {
|
pub struct SourceFile {
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
file: Arc<File>,
|
file: Arc<Mutex<File>>,
|
||||||
|
lines: Arc<OnceLock<Vec<SourceLine>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
|
pub fn open_from(path: Arc<Path>, options: OpenOptions) -> Result<Self, IoError> {
|
||||||
|
let file = Arc::new(Mutex::new(options.open(&*path)?));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
path,
|
||||||
|
file,
|
||||||
|
lines: Arc::new(OnceLock::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buf_reader(&mut self) -> Result<BufReader<&mut File>, IoError> {
|
||||||
|
let file_mut = Arc::get_mut(&mut self.file)
|
||||||
|
.unwrap_or_else(|| panic!("'File' for {} has existing handle", self.path.display()))
|
||||||
|
.get_mut()
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("'File' for {} was mutex-poisoned: {e}", self.path.display())
|
||||||
|
});
|
||||||
|
|
||||||
|
let reader = BufReader::new(file_mut);
|
||||||
|
|
||||||
|
Ok(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lines(&self) -> Result<&[SourceLine], IoError> {
|
||||||
|
if let Some(lines) = self.lines.get() {
|
||||||
|
return Ok(lines);
|
||||||
|
}
|
||||||
|
let lines = BufReader::new(&*self.file.lock().unwrap())
|
||||||
|
.lines()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, line_res)| {
|
||||||
|
line_res.map(|line| SourceLine {
|
||||||
|
line: Line::from_index(index as u64),
|
||||||
|
path: Arc::clone(&self.path),
|
||||||
|
text: Arc::from(line),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<SourceLine>, IoError>>()?;
|
||||||
|
// Mutex should have dropped by now.
|
||||||
|
debug_assert!(self.file.try_lock().is_ok());
|
||||||
|
|
||||||
|
self.lines.set(lines).unwrap();
|
||||||
|
|
||||||
|
Ok(self.lines.get().unwrap().as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> Arc<Path> {
|
||||||
|
Arc::clone(&self.path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for SourceFile {
|
impl PartialEq for SourceFile {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
let paths_match = *self.path == *other.path;
|
*self.path == *other.path
|
||||||
if paths_match && self.file.as_raw_fd() != other.file.as_raw_fd() {
|
|
||||||
todo!("handle the case where source file paths match but FD numbers don't");
|
|
||||||
}
|
|
||||||
paths_match
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for SourceFile {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.path.hash(state);
|
|
||||||
self.file.as_raw_fd().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
enum SourceInner {
|
|
||||||
Path(SourcePath),
|
|
||||||
File(SourceFile),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub struct Source(SourceInner);
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue