mirror of
https://github.com/RGBCube/alejandra
synced 2025-07-29 19:47:45 +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",
|
||||
"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"
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
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.
|
||||
#[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<std::io::Error> 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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String> =
|
||||
std::fs::read_dir("tests/cases")
|
||||
.unwrap()
|
||||
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
|
||||
.collect();
|
||||
let cases: HashSet<String> = 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}/`"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<String>,
|
||||
|
||||
/// 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 = "<anonymous file on stdin>".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<String>,
|
||||
config: Config,
|
||||
in_place: bool,
|
||||
verbosity: Verbosity,
|
||||
threads: usize,
|
||||
verbosity: Verbosity,
|
||||
) -> Vec<FormattedPath> {
|
||||
let paths_len = paths.len();
|
||||
|
||||
|
@ -102,8 +110,11 @@ fn format_paths(
|
|||
let futures: FuturesUnordered<RemoteHandle<FormattedPath>> = 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::<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;
|
||||
pub mod cli;
|
||||
mod config_options;
|
||||
mod find;
|
||||
mod verbosity;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue