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",
"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"

View file

@ -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).

View file

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

View file

@ -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,

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.
#[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) => {

View file

@ -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;

View file

@ -1,23 +1,34 @@
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")
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;
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)
@ -26,7 +37,8 @@ fn cases() {
.unwrap();
}
let content_out = std::fs::read_to_string(path_out.clone()).unwrap();
let content_out =
std::fs::read_to_string(path_out.clone()).unwrap();
assert_eq!(
content_out, content_got,
@ -35,3 +47,4 @@ fn cases() {
);
}
}
}

View file

@ -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]

View file

@ -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()
}
}

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;
pub mod cli;
mod config_options;
mod find;
mod verbosity;