1
Fork 0
mirror of https://github.com/RGBCube/alejandra synced 2025-07-30 12:07:46 +00:00

feat: configuration mechanism

This commit is contained in:
Kevin Amado 2024-11-23 13:36:33 -07:00
parent 1547665397
commit e518b23515
12 changed files with 161 additions and 37 deletions

26
Cargo.lock generated
View file

@ -21,6 +21,8 @@ dependencies = [
"futures", "futures",
"num_cpus", "num_cpus",
"rand", "rand",
"serde",
"serde_json",
"walkdir", "walkdir",
] ]
@ -254,6 +256,12 @@ dependencies = [
"hashbrown 0.12.3", "hashbrown 0.12.3",
] ]
[[package]]
name = "itoa"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.161" version = "0.2.161"
@ -437,6 +445,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@ -466,6 +480,18 @@ dependencies = [
"syn 2.0.87", "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]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"

View file

@ -239,6 +239,8 @@ Our public API consists of:
exit codes, exit codes,
and stdout. and stdout.
With the exception of those explicitly marked as "experimental".
## Changelog ## Changelog
Please read: [CHANGELOG](./CHANGELOG.md). Please read: [CHANGELOG](./CHANGELOG.md).

View file

@ -1,6 +1,7 @@
[dependencies] [dependencies]
rnix = "0.10.2" rnix = { version = "0.10.2", default-features = false, features = [] }
rowan = "0.12.6" # follows rnix # rowan follows rnix
rowan = { version = "0.12.6", default-features = false, features = [] }
[target.aarch64-unknown-linux-musl.dependencies.mimalloc] [target.aarch64-unknown-linux-musl.dependencies.mimalloc]
default-features = false default-features = false

View file

@ -1,3 +1,5 @@
use crate::config::Config;
#[derive(PartialEq)] #[derive(PartialEq)]
pub(crate) enum Step { pub(crate) enum Step {
Comment(String), Comment(String),
@ -13,6 +15,7 @@ pub(crate) enum Step {
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct BuildCtx { pub(crate) struct BuildCtx {
pub _config: Config,
pub force_wide: bool, pub force_wide: bool,
pub force_wide_success: bool, pub force_wide_success: bool,
pub indentation: usize, pub indentation: usize,

View file

@ -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 {}
}
}

View file

@ -1,7 +1,9 @@
use crate::config::Config;
/// Possibles results after formatting. /// Possibles results after formatting.
#[derive(Clone)] #[derive(Clone)]
pub enum Status { pub enum Status {
/// An error ocurred, and its reason. /// An error occurred, and its reason.
Error(String), Error(String),
/// Formatting was successful, /// Formatting was successful,
/// the file changed or not according to the boolean. /// the file changed or not according to the boolean.
@ -14,9 +16,13 @@ impl From<std::io::Error> for Status {
} }
} }
/// Formats the content of `before` in-memory, /// Formats the content of `before` in-memory
/// and assume `path` in the displayed error messages /// assuming the contents come from `path` when displaying error messages
pub fn in_memory(path: String, before: String) -> (Status, String) { pub fn in_memory(
path: String,
before: String,
config: Config,
) -> (Status, String) {
let tokens = rnix::tokenizer::Tokenizer::new(&before); let tokens = rnix::tokenizer::Tokenizer::new(&before);
let ast = rnix::parser::parse(tokens); 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 { let mut build_ctx = crate::builder::BuildCtx {
_config: config,
force_wide: false, force_wide: false,
force_wide_success: true, force_wide_success: true,
indentation: 0, indentation: 0,
@ -47,12 +54,13 @@ pub fn in_memory(path: String, before: String) -> (Status, String) {
/// Formats the file at `path`, /// Formats the file at `path`,
/// optionally overriding it's contents if `in_place` is true. /// 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; use std::io::Write;
match std::fs::read_to_string(&path) { match std::fs::read_to_string(&path) {
Ok(before) => { 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 { match status {
Status::Changed(changed) => { Status::Changed(changed) => {

View file

@ -50,6 +50,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
pub(crate) mod builder; pub(crate) mod builder;
pub(crate) mod children; pub(crate) mod children;
pub(crate) mod children2; pub(crate) mod children2;
/// Configuration options for the formatter
pub mod config;
/// Functions for formatting Nix code. /// Functions for formatting Nix code.
pub mod format; pub mod format;
pub(crate) mod parsers; pub(crate) mod parsers;

View file

@ -1,37 +1,50 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::io::Write; use std::io::Write;
use alejandra::config::Config;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn cases() { fn cases() {
let should_update = std::env::var("UPDATE").is_ok(); let should_update = std::env::var("UPDATE").is_ok();
let cases: std::collections::HashSet<String> = let cases: HashSet<String> = std::fs::read_dir("tests/cases")
std::fs::read_dir("tests/cases") .unwrap()
.unwrap() .map(|entry| entry.unwrap().file_name().into_string().unwrap())
.map(|entry| entry.unwrap().file_name().into_string().unwrap()) .collect();
.collect();
let configs = HashMap::from([("", Config::default())]);
for case in cases { for case in cases {
let path_in = format!("tests/cases/{}/in.nix", case); 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_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 { for (config_name, config) in &configs {
std::fs::File::create(&path_out) let path_out =
.unwrap() format!("tests/cases/{}/out{}.nix", case, config_name);
.write_all(content_got.as_bytes()) let content_got = alejandra::format::in_memory(
.unwrap(); 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}/`"
);
} }
} }

View file

@ -19,6 +19,8 @@ rand = { version = "*", default-features = false, features = [
"alloc", "alloc",
"getrandom", "getrandom",
] } ] }
serde = { version = "*", default-features = false, features = ["derive"] }
serde_json = { version = "*", default-features = false, features = ["std"] }
walkdir = { version = "*", default-features = false, features = [] } walkdir = { version = "*", default-features = false, features = [] }
[package] [package]

View file

@ -1,13 +1,16 @@
use std::fs::read_to_string;
use std::io::Read; use std::io::Read;
use alejandra::config::Config;
use clap::value_parser;
use clap::ArgAction; use clap::ArgAction;
use clap::Parser; use clap::Parser;
use clap::value_parser;
use futures::future::RemoteHandle; use futures::future::RemoteHandle;
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::task::SpawnExt; use futures::task::SpawnExt;
use crate::ads::random_ad; use crate::ads::random_ad;
use crate::config_options::ConfigOptions;
use crate::verbosity::Verbosity; use crate::verbosity::Verbosity;
/// The Uncompromising Nix Code Formatter. /// The Uncompromising Nix Code Formatter.
@ -38,6 +41,10 @@ struct CLIArgs {
#[clap(long, short)] #[clap(long, short)]
check: bool, check: bool,
/// [Experimental] Path to a config file.
#[clap(long)]
experimental_config: Option<String>,
/// Number of formatting threads to spawn. Defaults to the number of /// Number of formatting threads to spawn. Defaults to the number of
/// physical CPUs. /// physical CPUs.
#[clap(long, short, value_parser = value_parser!(u8).range(1..))] #[clap(long, short, value_parser = value_parser!(u8).range(1..))]
@ -55,7 +62,7 @@ struct FormattedPath {
pub status: alejandra::format::Status, 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 mut before = String::new();
let path = "<anonymous file on stdin>".to_string(); let path = "<anonymous file on stdin>".to_string();
@ -70,7 +77,7 @@ fn format_stdin(verbosity: Verbosity) -> FormattedPath {
.expect("Unable to read stdin."); .expect("Unable to read stdin.");
let (status, data) = let (status, data) =
alejandra::format::in_memory(path.clone(), before.clone()); alejandra::format::in_memory(path.clone(), before.clone(), config);
print!("{data}"); print!("{data}");
@ -79,9 +86,10 @@ fn format_stdin(verbosity: Verbosity) -> FormattedPath {
fn format_paths( fn format_paths(
paths: Vec<String>, paths: Vec<String>,
config: Config,
in_place: bool, in_place: bool,
verbosity: Verbosity,
threads: usize, threads: usize,
verbosity: Verbosity,
) -> Vec<FormattedPath> { ) -> Vec<FormattedPath> {
let paths_len = paths.len(); let paths_len = paths.len();
@ -102,8 +110,11 @@ fn format_paths(
let futures: FuturesUnordered<RemoteHandle<FormattedPath>> = paths let futures: FuturesUnordered<RemoteHandle<FormattedPath>> = paths
.into_iter() .into_iter()
.map(|path| { .map(|path| {
let config = config.clone();
pool.spawn_with_handle(async move { 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 let alejandra::format::Status::Changed(changed) = status {
if changed && verbosity.allows_info() { if changed && verbosity.allows_info() {
@ -127,7 +138,7 @@ fn format_paths(
futures::executor::block_on_stream(futures).collect() futures::executor::block_on_stream(futures).collect()
} }
pub fn main() -> std::io::Result<()> { pub fn main() -> ! {
let args = CLIArgs::parse(); let args = CLIArgs::parse();
let in_place = !args.check; let in_place = !args.check;
@ -144,14 +155,18 @@ pub fn main() -> std::io::Result<()> {
_ => Verbosity::NoErrors, _ => Verbosity::NoErrors,
}; };
let config = resolve_config(args.experimental_config.as_deref(), verbosity);
let formatted_paths = match &include[..] { let formatted_paths = match &include[..] {
&[] | &["-"] => { &[] | &["-"] => {
vec![crate::cli::format_stdin(verbosity)] vec![crate::cli::format_stdin(config, verbosity)]
} }
include => { include => {
let paths = crate::find::nix_files(include, &args.exclude); 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); 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::<ConfigOptions>(&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()
}
}

View file

@ -0,0 +1,13 @@
use alejandra::config::Config;
use serde::Deserialize;
#[derive(Deserialize)]
pub(crate) struct ConfigOptions {}
impl From<ConfigOptions> for Config {
fn from(_config_options: ConfigOptions) -> Config {
let config = Config::default();
config
}
}

View file

@ -1,4 +1,5 @@
mod ads; mod ads;
pub mod cli; pub mod cli;
mod config_options;
mod find; mod find;
mod verbosity; mod verbosity;