Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom indentation length support #431

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/alejandra/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub(crate) struct BuildCtx {
pub pos_old: crate::position::Position,
pub path: String,
pub vertical: bool,
pub indent: String,
}

pub(crate) fn build(
Expand Down Expand Up @@ -96,7 +97,7 @@ fn build_step(
add_token(
builder,
rnix::SyntaxKind::TOKEN_WHITESPACE,
&format!("{0:<1$}", "", 2 * build_ctx.indentation),
&build_ctx.indent.repeat(build_ctx.indentation),
);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/alejandra/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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) {
pub fn in_memory(path: String, before: String, indent: String) -> (Status, String) {
let tokens = rnix::tokenizer::Tokenizer::new(&before);
let ast = rnix::parser::parse(tokens);

Expand All @@ -32,6 +32,7 @@ pub fn in_memory(path: String, before: String) -> (Status, String) {
path,
pos_old: crate::position::Position::default(),
vertical: true,
indent,
};

let after = crate::builder::build(&mut build_ctx, ast.node().into())
Expand All @@ -47,12 +48,12 @@ 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, in_place: bool, indent: String) -> 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, indent);

match status {
Status::Changed(changed) => {
Expand Down
36 changes: 35 additions & 1 deletion src/alejandra/tests/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn cases() {
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;
alejandra::format::in_memory(path_in, content_in.clone(), " ".repeat(2)).1;

if should_update {
std::fs::File::create(&path_out)
Expand All @@ -35,3 +35,37 @@ fn cases() {
);
}
}

#[test]
fn indent() {
let should_update = std::env::var("UPDATE").is_ok();

let cases: std::collections::HashSet<String> =
std::fs::read_dir("tests/indent")
.unwrap()
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
.collect();

for case in cases {
let path_in = format!("tests/indent/{}/in.nix", case);
let path_out = format!("tests/indent/{}/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(), "\t".to_string()).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/indent/{case}/`"
);
}
}
6 changes: 6 additions & 0 deletions src/alejandra/tests/indent/apply/in.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
foo =
bar:
baz:
fnbody;
}
4 changes: 4 additions & 0 deletions src/alejandra/tests/indent/apply/out.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
foo = bar: baz:
fnbody;
}
11 changes: 11 additions & 0 deletions src/alejandra/tests/indent/attr_set/in.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
a = rec {
a = {
a = rec {
a = {
a = rec {a = {a = rec {a = {a = rec {a = {};};};};};};
};
};
};
};
}
11 changes: 11 additions & 0 deletions src/alejandra/tests/indent/attr_set/out.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
a = rec {
a = {
a = rec {
a = {
a = rec {a = {a = rec {a = {a = rec {a = {};};};};};};
};
};
};
};
}
11 changes: 11 additions & 0 deletions src/alejandra/tests/indent/list/in.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
# over indented
1
# under indented
2
# no indentation
[1 2]
[
1 2 3
]
]
13 changes: 13 additions & 0 deletions src/alejandra/tests/indent/list/out.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
# over indented
1
# under indented
2
# no indentation
[1 2]
[
1
2
3
]
]
31 changes: 26 additions & 5 deletions src/alejandra_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ struct CLIArgs {
/// twice to hide error messages.
#[clap(long, short, action = ArgAction::Count)]
quiet: u8,

/// Specifies the number of spaces/tabs to be used for each level of indentation.
/// Possible options: '1t', '4', '4s' (the same as '4')
#[clap(long, short, value_parser = parse_indent, default_value = "2s")]
indent: String,
}

fn parse_indent(s: &str) -> Result<String, String> {
let unit = match s.chars().last() {
Some('t') => "\t",
Some(c) if c == 's' || c.is_ascii_digit() => " ",
None => Err("empty value is not allowed")?,
Some(c) => Err(format!("only 't', 's' or '' are valid units, but got '{}'", c))?,
};
let count = s
.trim_end_matches(['t', 's'])
.parse::<usize>()
.map_err(|e| e.to_string())?;
Ok(unit.repeat(count))
}

#[derive(Clone)]
Expand All @@ -55,7 +74,7 @@ struct FormattedPath {
pub status: alejandra::format::Status,
}

fn format_stdin(verbosity: Verbosity) -> FormattedPath {
fn format_stdin(verbosity: Verbosity, indent: String) -> FormattedPath {
let mut before = String::new();
let path = "<anonymous file on stdin>".to_string();

Expand All @@ -70,7 +89,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(), indent);

print!("{data}");

Expand All @@ -82,6 +101,7 @@ fn format_paths(
in_place: bool,
verbosity: Verbosity,
threads: usize,
indent: String,
) -> Vec<FormattedPath> {
let paths_len = paths.len();

Expand All @@ -102,8 +122,9 @@ fn format_paths(
let futures: FuturesUnordered<RemoteHandle<FormattedPath>> = paths
.into_iter()
.map(|path| {
let indent = indent.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(), in_place, indent);

if let alejandra::format::Status::Changed(changed) = status {
if changed && verbosity.allows_info() {
Expand Down Expand Up @@ -146,12 +167,12 @@ pub fn main() -> std::io::Result<()> {

let formatted_paths = match &include[..] {
&[] | &["-"] => {
vec![crate::cli::format_stdin(verbosity)]
vec![crate::cli::format_stdin(verbosity, args.indent)]
}
include => {
let paths = crate::find::nix_files(include, &args.exclude);

crate::cli::format_paths(paths, in_place, verbosity, threads)
crate::cli::format_paths(paths, in_place, verbosity, threads, args.indent)
}
};

Expand Down