mirror of
https://github.com/RGBCube/alejandra
synced 2025-07-30 12:07:46 +00:00
feat: configuration mechanism
This commit is contained in:
parent
1547665397
commit
e518b23515
12 changed files with 161 additions and 37 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
11
src/alejandra/src/config.rs
Normal file
11
src/alejandra/src/config.rs
Normal 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 {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}/`"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
13
src/alejandra_cli/src/config_options.rs
Normal file
13
src/alejandra_cli/src/config_options.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue