From e518b235150f5f27a9b99830e2b629e832e63cca Mon Sep 17 00:00:00 2001 From: Kevin Amado Date: Sat, 23 Nov 2024 13:36:33 -0700 Subject: [PATCH] feat: configuration mechanism --- Cargo.lock | 26 +++++++++++ README.md | 2 + src/alejandra/Cargo.toml | 5 ++- src/alejandra/src/builder.rs | 3 ++ src/alejandra/src/config.rs | 11 +++++ src/alejandra/src/format.rs | 20 ++++++--- src/alejandra/src/lib.rs | 2 + src/alejandra/tests/fmt.rs | 55 ++++++++++++++--------- src/alejandra_cli/Cargo.toml | 2 + src/alejandra_cli/src/cli.rs | 58 +++++++++++++++++++++---- src/alejandra_cli/src/config_options.rs | 13 ++++++ src/alejandra_cli/src/lib.rs | 1 + 12 files changed, 161 insertions(+), 37 deletions(-) create mode 100644 src/alejandra/src/config.rs create mode 100644 src/alejandra_cli/src/config_options.rs diff --git a/Cargo.lock b/Cargo.lock index a0d1d71..9269c68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,8 @@ dependencies = [ "futures", "num_cpus", "rand", + "serde", + "serde_json", "walkdir", ] @@ -254,6 +256,12 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "itoa" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" + [[package]] name = "libc" version = "0.2.161" @@ -437,6 +445,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "same-file" version = "1.0.6" @@ -466,6 +480,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/README.md b/README.md index 90547a0..dd1e98b 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,8 @@ Our public API consists of: exit codes, and stdout. +With the exception of those explicitly marked as "experimental". + ## Changelog Please read: [CHANGELOG](./CHANGELOG.md). diff --git a/src/alejandra/Cargo.toml b/src/alejandra/Cargo.toml index 0e41c7b..6e6eb7f 100644 --- a/src/alejandra/Cargo.toml +++ b/src/alejandra/Cargo.toml @@ -1,6 +1,7 @@ [dependencies] -rnix = "0.10.2" -rowan = "0.12.6" # follows rnix +rnix = { version = "0.10.2", default-features = false, features = [] } +# rowan follows rnix +rowan = { version = "0.12.6", default-features = false, features = [] } [target.aarch64-unknown-linux-musl.dependencies.mimalloc] default-features = false diff --git a/src/alejandra/src/builder.rs b/src/alejandra/src/builder.rs index 3eb4f0c..47c57ac 100644 --- a/src/alejandra/src/builder.rs +++ b/src/alejandra/src/builder.rs @@ -1,3 +1,5 @@ +use crate::config::Config; + #[derive(PartialEq)] pub(crate) enum Step { Comment(String), @@ -13,6 +15,7 @@ pub(crate) enum Step { #[derive(Clone)] pub(crate) struct BuildCtx { + pub _config: Config, pub force_wide: bool, pub force_wide_success: bool, pub indentation: usize, diff --git a/src/alejandra/src/config.rs b/src/alejandra/src/config.rs new file mode 100644 index 0000000..5e659d3 --- /dev/null +++ b/src/alejandra/src/config.rs @@ -0,0 +1,11 @@ +/// Configuration used by the formatter +#[derive(Clone)] +pub struct Config {} + +use std::default::Default; + +impl Default for Config { + fn default() -> Self { + Self {} + } +} diff --git a/src/alejandra/src/format.rs b/src/alejandra/src/format.rs index e5ccd0a..9675da1 100644 --- a/src/alejandra/src/format.rs +++ b/src/alejandra/src/format.rs @@ -1,7 +1,9 @@ +use crate::config::Config; + /// Possibles results after formatting. #[derive(Clone)] pub enum Status { - /// An error ocurred, and its reason. + /// An error occurred, and its reason. Error(String), /// Formatting was successful, /// the file changed or not according to the boolean. @@ -14,9 +16,13 @@ impl From for Status { } } -/// Formats the content of `before` in-memory, -/// and assume `path` in the displayed error messages -pub fn in_memory(path: String, before: String) -> (Status, String) { +/// Formats the content of `before` in-memory +/// assuming the contents come from `path` when displaying error messages +pub fn in_memory( + path: String, + before: String, + config: Config, +) -> (Status, String) { let tokens = rnix::tokenizer::Tokenizer::new(&before); let ast = rnix::parser::parse(tokens); @@ -26,6 +32,7 @@ pub fn in_memory(path: String, before: String) -> (Status, String) { } let mut build_ctx = crate::builder::BuildCtx { + _config: config, force_wide: false, force_wide_success: true, indentation: 0, @@ -47,12 +54,13 @@ pub fn in_memory(path: String, before: String) -> (Status, String) { /// Formats the file at `path`, /// optionally overriding it's contents if `in_place` is true. -pub fn in_fs(path: String, in_place: bool) -> Status { +pub fn in_fs(path: String, config: Config, in_place: bool) -> Status { use std::io::Write; match std::fs::read_to_string(&path) { Ok(before) => { - let (status, data) = crate::format::in_memory(path.clone(), before); + let (status, data) = + crate::format::in_memory(path.clone(), before, config); match status { Status::Changed(changed) => { diff --git a/src/alejandra/src/lib.rs b/src/alejandra/src/lib.rs index e93506c..cf1a152 100644 --- a/src/alejandra/src/lib.rs +++ b/src/alejandra/src/lib.rs @@ -50,6 +50,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; pub(crate) mod builder; pub(crate) mod children; pub(crate) mod children2; +/// Configuration options for the formatter +pub mod config; /// Functions for formatting Nix code. pub mod format; pub(crate) mod parsers; diff --git a/src/alejandra/tests/fmt.rs b/src/alejandra/tests/fmt.rs index f63cdec..dadaae5 100644 --- a/src/alejandra/tests/fmt.rs +++ b/src/alejandra/tests/fmt.rs @@ -1,37 +1,50 @@ +use std::collections::HashMap; +use std::collections::HashSet; use std::io::Write; +use alejandra::config::Config; use pretty_assertions::assert_eq; #[test] fn cases() { let should_update = std::env::var("UPDATE").is_ok(); - let cases: std::collections::HashSet = - std::fs::read_dir("tests/cases") - .unwrap() - .map(|entry| entry.unwrap().file_name().into_string().unwrap()) - .collect(); + let cases: HashSet = std::fs::read_dir("tests/cases") + .unwrap() + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .collect(); + + let configs = HashMap::from([("", Config::default())]); for case in cases { let path_in = format!("tests/cases/{}/in.nix", case); - let path_out = format!("tests/cases/{}/out.nix", case); let content_in = std::fs::read_to_string(path_in.clone()).unwrap(); - let content_got = - alejandra::format::in_memory(path_in, content_in.clone()).1; - if should_update { - std::fs::File::create(&path_out) - .unwrap() - .write_all(content_got.as_bytes()) - .unwrap(); + for (config_name, config) in &configs { + let path_out = + format!("tests/cases/{}/out{}.nix", case, config_name); + let content_got = alejandra::format::in_memory( + path_in.clone(), + content_in.clone(), + config.clone(), + ) + .1; + + if should_update { + std::fs::File::create(&path_out) + .unwrap() + .write_all(content_got.as_bytes()) + .unwrap(); + } + + let content_out = + std::fs::read_to_string(path_out.clone()).unwrap(); + + assert_eq!( + content_out, content_got, + "Test case `{case}` failed; see \ + `src/alejandra/tests/cases/{case}/`" + ); } - - let content_out = std::fs::read_to_string(path_out.clone()).unwrap(); - - assert_eq!( - content_out, content_got, - "Test case `{case}` failed; see \ - `src/alejandra/tests/cases/{case}/`" - ); } } diff --git a/src/alejandra_cli/Cargo.toml b/src/alejandra_cli/Cargo.toml index 0e39fa0..169d806 100644 --- a/src/alejandra_cli/Cargo.toml +++ b/src/alejandra_cli/Cargo.toml @@ -19,6 +19,8 @@ rand = { version = "*", default-features = false, features = [ "alloc", "getrandom", ] } +serde = { version = "*", default-features = false, features = ["derive"] } +serde_json = { version = "*", default-features = false, features = ["std"] } walkdir = { version = "*", default-features = false, features = [] } [package] diff --git a/src/alejandra_cli/src/cli.rs b/src/alejandra_cli/src/cli.rs index 6448198..e9ca87b 100644 --- a/src/alejandra_cli/src/cli.rs +++ b/src/alejandra_cli/src/cli.rs @@ -1,13 +1,16 @@ +use std::fs::read_to_string; use std::io::Read; +use alejandra::config::Config; +use clap::value_parser; use clap::ArgAction; use clap::Parser; -use clap::value_parser; use futures::future::RemoteHandle; use futures::stream::FuturesUnordered; use futures::task::SpawnExt; use crate::ads::random_ad; +use crate::config_options::ConfigOptions; use crate::verbosity::Verbosity; /// The Uncompromising Nix Code Formatter. @@ -38,6 +41,10 @@ struct CLIArgs { #[clap(long, short)] check: bool, + /// [Experimental] Path to a config file. + #[clap(long)] + experimental_config: Option, + /// Number of formatting threads to spawn. Defaults to the number of /// physical CPUs. #[clap(long, short, value_parser = value_parser!(u8).range(1..))] @@ -55,7 +62,7 @@ struct FormattedPath { pub status: alejandra::format::Status, } -fn format_stdin(verbosity: Verbosity) -> FormattedPath { +fn format_stdin(config: Config, verbosity: Verbosity) -> FormattedPath { let mut before = String::new(); let path = "".to_string(); @@ -70,7 +77,7 @@ fn format_stdin(verbosity: Verbosity) -> FormattedPath { .expect("Unable to read stdin."); let (status, data) = - alejandra::format::in_memory(path.clone(), before.clone()); + alejandra::format::in_memory(path.clone(), before.clone(), config); print!("{data}"); @@ -79,9 +86,10 @@ fn format_stdin(verbosity: Verbosity) -> FormattedPath { fn format_paths( paths: Vec, + config: Config, in_place: bool, - verbosity: Verbosity, threads: usize, + verbosity: Verbosity, ) -> Vec { let paths_len = paths.len(); @@ -102,8 +110,11 @@ fn format_paths( let futures: FuturesUnordered> = paths .into_iter() .map(|path| { + let config = config.clone(); + pool.spawn_with_handle(async move { - let status = alejandra::format::in_fs(path.clone(), in_place); + let status = + alejandra::format::in_fs(path.clone(), config, in_place); if let alejandra::format::Status::Changed(changed) = status { if changed && verbosity.allows_info() { @@ -127,7 +138,7 @@ fn format_paths( futures::executor::block_on_stream(futures).collect() } -pub fn main() -> std::io::Result<()> { +pub fn main() -> ! { let args = CLIArgs::parse(); let in_place = !args.check; @@ -144,14 +155,18 @@ pub fn main() -> std::io::Result<()> { _ => Verbosity::NoErrors, }; + let config = resolve_config(args.experimental_config.as_deref(), verbosity); + let formatted_paths = match &include[..] { &[] | &["-"] => { - vec![crate::cli::format_stdin(verbosity)] + vec![crate::cli::format_stdin(config, verbosity)] } include => { let paths = crate::find::nix_files(include, &args.exclude); - crate::cli::format_paths(paths, in_place, verbosity, threads) + crate::cli::format_paths( + paths, config, in_place, threads, verbosity, + ) } }; @@ -224,3 +239,30 @@ pub fn main() -> std::io::Result<()> { std::process::exit(0); } + +fn resolve_config(path: Option<&str>, verbosity: Verbosity) -> Config { + if let Some(path) = path { + if verbosity.allows_info() { + eprintln!("Using config from: {}", path); + } + + let contents = read_to_string(path).unwrap_or_else(|error| { + if verbosity.allows_errors() { + eprintln!("Unable to read config: {}", error); + } + std::process::exit(1); + }); + + let config_file = serde_json::from_str::(&contents) + .unwrap_or_else(|error| { + if verbosity.allows_errors() { + eprintln!("Errors found in config: {}", error); + } + std::process::exit(1); + }); + + config_file.into() + } else { + Config::default() + } +} diff --git a/src/alejandra_cli/src/config_options.rs b/src/alejandra_cli/src/config_options.rs new file mode 100644 index 0000000..da283b7 --- /dev/null +++ b/src/alejandra_cli/src/config_options.rs @@ -0,0 +1,13 @@ +use alejandra::config::Config; +use serde::Deserialize; + +#[derive(Deserialize)] +pub(crate) struct ConfigOptions {} + +impl From for Config { + fn from(_config_options: ConfigOptions) -> Config { + let config = Config::default(); + + config + } +} diff --git a/src/alejandra_cli/src/lib.rs b/src/alejandra_cli/src/lib.rs index fd49ce8..bbb9109 100644 --- a/src/alejandra_cli/src/lib.rs +++ b/src/alejandra_cli/src/lib.rs @@ -1,4 +1,5 @@ mod ads; pub mod cli; +mod config_options; mod find; mod verbosity;