mirror of
https://github.com/RGBCube/alejandra
synced 2025-07-31 04:27:45 +00:00
feat: exit codes as public api
This commit is contained in:
parent
424e300ab5
commit
391e33c537
7 changed files with 177 additions and 94 deletions
|
@ -195,6 +195,7 @@ Our public API consists of:
|
||||||
- The CLI tool (`$ alejandra`),
|
- The CLI tool (`$ alejandra`),
|
||||||
command line flags,
|
command line flags,
|
||||||
positional arguments,
|
positional arguments,
|
||||||
|
exit codes,
|
||||||
and stdout.
|
and stdout.
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
|
@ -33,6 +33,10 @@ steps:
|
||||||
- formatting-before-vs-after.patch.txt
|
- formatting-before-vs-after.patch.txt
|
||||||
- formatting-after.patch.txt
|
- formatting-after.patch.txt
|
||||||
command:
|
command:
|
||||||
|
- echo +++ Formatting - demo
|
||||||
|
- nix run . -- flake.nix
|
||||||
|
|
||||||
|
- echo --- Cloning nixpkgs
|
||||||
- git config --global user.email CI/CD
|
- git config --global user.email CI/CD
|
||||||
- git config --global user.name CI/CD
|
- git config --global user.name CI/CD
|
||||||
- git clone --branch=master --depth 1 --origin=upstream file:///data/nixpkgs
|
- git clone --branch=master --depth 1 --origin=upstream file:///data/nixpkgs
|
||||||
|
|
|
@ -12,5 +12,5 @@ pub fn main() -> Result<(), JsValue> {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn format(before: String, path: String) -> String {
|
pub fn format(before: String, path: String) -> String {
|
||||||
alejandra_engine::format::string_or_passthrough(path, before)
|
alejandra_engine::format::in_memory(path, before).1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FormattedPath {
|
||||||
|
pub path: String,
|
||||||
|
pub status: alejandra_engine::format::Status,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse(args: Vec<String>) -> clap::ArgMatches {
|
pub fn parse(args: Vec<String>) -> clap::ArgMatches {
|
||||||
clap::Command::new("Alejandra")
|
clap::Command::new("Alejandra")
|
||||||
.about("The Uncompromising Nix Code Formatter.")
|
.about("The Uncompromising Nix Code Formatter.")
|
||||||
|
@ -15,6 +21,11 @@ pub fn parse(args: Vec<String>) -> clap::ArgMatches {
|
||||||
.multiple_occurrences(true)
|
.multiple_occurrences(true)
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("check")
|
||||||
|
.help("Check if the input is already formatted.")
|
||||||
|
.long("--check"),
|
||||||
|
)
|
||||||
.term_width(80)
|
.term_width(80)
|
||||||
.after_help(indoc::indoc!(
|
.after_help(indoc::indoc!(
|
||||||
// Let's just use the same sorting as on GitHub
|
// Let's just use the same sorting as on GitHub
|
||||||
|
@ -24,65 +35,65 @@ pub fn parse(args: Vec<String>) -> clap::ArgMatches {
|
||||||
//
|
//
|
||||||
// Feel free to add here your contact/blog/links if you want
|
// Feel free to add here your contact/blog/links if you want
|
||||||
"
|
"
|
||||||
|
The program will exit with status code:
|
||||||
|
1, if any error occurs.
|
||||||
|
2, if --check was used and any file was changed.
|
||||||
|
0, otherwise.
|
||||||
|
|
||||||
Shaped with love by:
|
Shaped with love by:
|
||||||
- Kevin Amado ~ @kamadorueda on GitHub, matrix.org and Gmail.
|
Kevin Amado ~ @kamadorueda on GitHub, matrix.org and Gmail.
|
||||||
- Thomas Bereknyei ~ @tomberek on GitHub and matrix.org.
|
Thomas Bereknyei ~ @tomberek on GitHub and matrix.org.
|
||||||
- David Arnold ~ @blaggacao on GitHub and matrix.org.
|
David Arnold ~ @blaggacao on GitHub and matrix.org.
|
||||||
- Vincent Ambo ~ @tazjin on GitHub.
|
Vincent Ambo ~ @tazjin on GitHub.
|
||||||
- Mr Hedgehog ~ @ModdedGamers on GitHub.
|
Mr Hedgehog ~ @ModdedGamers on GitHub.
|
||||||
"
|
"
|
||||||
))
|
))
|
||||||
.get_matches_from(args)
|
.get_matches_from(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stdin() -> std::io::Result<()> {
|
pub fn stdin() -> FormattedPath {
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
let mut before = String::new();
|
||||||
|
let path = "<anonymous file on stdin>".to_string();
|
||||||
|
|
||||||
eprintln!("Formatting stdin, run with --help to see all options.");
|
eprintln!("Formatting stdin, run with --help to see all options.");
|
||||||
let mut stdin = String::new();
|
|
||||||
std::io::stdin().read_to_string(&mut stdin).unwrap();
|
|
||||||
|
|
||||||
let stdout = alejandra_engine::format::string("stdin".to_string(), stdin)?;
|
std::io::stdin().read_to_string(&mut before).unwrap();
|
||||||
print!("{}", stdout);
|
|
||||||
|
|
||||||
Ok(())
|
let (status, data) =
|
||||||
|
alejandra_engine::format::in_memory(path.clone(), before.clone());
|
||||||
|
|
||||||
|
print!("{}", data);
|
||||||
|
|
||||||
|
FormattedPath { path, status }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple(paths: Vec<String>) -> std::io::Result<()> {
|
pub fn simple(paths: Vec<String>) -> Vec<FormattedPath> {
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
eprintln!("Formatting {} files.", paths.len());
|
eprintln!("Formatting: {} files", paths.len());
|
||||||
|
|
||||||
let (results, errors): (Vec<_>, Vec<_>) = paths
|
paths
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.map(|path| -> std::io::Result<bool> {
|
.map(|path| {
|
||||||
alejandra_engine::format::file(path.to_string()).map(|changed| {
|
let status = alejandra_engine::format::in_place(path.clone());
|
||||||
|
|
||||||
|
if let alejandra_engine::format::Status::Changed(changed) = status {
|
||||||
if changed {
|
if changed {
|
||||||
eprintln!("Formatted: {}", &path);
|
eprintln!("Changed: {}", &path);
|
||||||
}
|
}
|
||||||
changed
|
}
|
||||||
})
|
|
||||||
|
FormattedPath { path: path.clone(), status }
|
||||||
})
|
})
|
||||||
.partition(Result::is_ok);
|
.collect()
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"Changed: {}",
|
|
||||||
results.into_iter().map(Result::unwrap).filter(|&x| x).count(),
|
|
||||||
);
|
|
||||||
eprintln!("Errors: {}", errors.len(),);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
pub fn tui(paths: Vec<String>) -> std::io::Result<Vec<FormattedPath>> {
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use termion::{input::TermRead, raw::IntoRawMode};
|
use termion::{input::TermRead, raw::IntoRawMode};
|
||||||
|
|
||||||
struct FormattedPath {
|
|
||||||
path: String,
|
|
||||||
result: std::io::Result<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
FormattedPath(FormattedPath),
|
FormattedPath(FormattedPath),
|
||||||
FormattingFinished,
|
FormattingFinished,
|
||||||
|
@ -91,11 +102,12 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let paths_to_format = paths.len();
|
let paths_to_format = paths.len();
|
||||||
|
let mut formatted_paths = std::collections::LinkedList::new();
|
||||||
|
|
||||||
let stdout = std::io::stderr().into_raw_mode()?;
|
let stdout = std::io::stderr().into_raw_mode()?;
|
||||||
// let stdout = termion::screen::AlternateScreen::from(stdout);
|
|
||||||
let backend = tui::backend::TermionBackend::new(stdout);
|
let backend = tui::backend::TermionBackend::new(stdout);
|
||||||
let mut terminal = tui::Terminal::new(backend)?;
|
let mut terminal = tui::Terminal::new(backend)?;
|
||||||
|
terminal.clear()?;
|
||||||
|
|
||||||
let (sender, receiver) = std::sync::mpsc::channel();
|
let (sender, receiver) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
@ -128,10 +140,10 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
||||||
let sender_finished = sender;
|
let sender_finished = sender;
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
paths.into_par_iter().for_each_with(sender_paths, |sender, path| {
|
paths.into_par_iter().for_each_with(sender_paths, |sender, path| {
|
||||||
let result = alejandra_engine::format::file(path.clone());
|
let status = alejandra_engine::format::in_place(path.clone());
|
||||||
|
|
||||||
if let Err(error) = sender
|
if let Err(error) = sender
|
||||||
.send(Event::FormattedPath(FormattedPath { path, result }))
|
.send(Event::FormattedPath(FormattedPath { path, status }))
|
||||||
{
|
{
|
||||||
eprintln!("{}", error);
|
eprintln!("{}", error);
|
||||||
}
|
}
|
||||||
|
@ -142,36 +154,32 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
terminal.clear()?;
|
|
||||||
|
|
||||||
let mut finished = false;
|
let mut finished = false;
|
||||||
let mut paths_with_errors: usize = 0;
|
let mut paths_with_errors: usize = 0;
|
||||||
let mut paths_changed: usize = 0;
|
let mut paths_changed: usize = 0;
|
||||||
let mut paths_unchanged: usize = 0;
|
let mut paths_unchanged: usize = 0;
|
||||||
let mut formatted_paths = std::collections::LinkedList::new();
|
|
||||||
|
|
||||||
while !finished {
|
while !finished {
|
||||||
loop {
|
loop {
|
||||||
if let Ok(event) = receiver.try_recv() {
|
if let Ok(event) = receiver.try_recv() {
|
||||||
match event {
|
match event {
|
||||||
Event::FormattedPath(formatted_path) => {
|
Event::FormattedPath(formatted_path) => {
|
||||||
match formatted_path.result {
|
match &formatted_path.status {
|
||||||
Ok(changed) => {
|
alejandra_engine::format::Status::Changed(
|
||||||
if changed {
|
changed,
|
||||||
|
) => {
|
||||||
|
if *changed {
|
||||||
paths_changed += 1;
|
paths_changed += 1;
|
||||||
} else {
|
} else {
|
||||||
paths_unchanged += 1;
|
paths_unchanged += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
alejandra_engine::format::Status::Error(_) => {
|
||||||
paths_with_errors += 1;
|
paths_with_errors += 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
formatted_paths.push_back(formatted_path);
|
formatted_paths.push_back(formatted_path);
|
||||||
if formatted_paths.len() > 8 {
|
|
||||||
formatted_paths.pop_front();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::FormattingFinished => {
|
Event::FormattingFinished => {
|
||||||
finished = true;
|
finished = true;
|
||||||
|
@ -253,23 +261,29 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
||||||
let logger = tui::widgets::Paragraph::new(
|
let logger = tui::widgets::Paragraph::new(
|
||||||
formatted_paths
|
formatted_paths
|
||||||
.iter()
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.take(8)
|
||||||
.map(|formatted_path| {
|
.map(|formatted_path| {
|
||||||
tui::text::Spans::from(vec![
|
tui::text::Spans::from(vec![
|
||||||
match &formatted_path.result {
|
match formatted_path.status {
|
||||||
Ok(changed) => tui::text::Span::styled(
|
alejandra_engine::format::Status::Changed(
|
||||||
if *changed {
|
changed,
|
||||||
"CHANGED "
|
) => tui::text::Span::styled(
|
||||||
|
if changed {
|
||||||
|
"CHANGED "
|
||||||
} else {
|
} else {
|
||||||
"UNCHANGED "
|
"OK "
|
||||||
},
|
},
|
||||||
tui::style::Style::default()
|
tui::style::Style::default()
|
||||||
.fg(tui::style::Color::Green),
|
.fg(tui::style::Color::Green),
|
||||||
),
|
),
|
||||||
Err(_) => tui::text::Span::styled(
|
alejandra_engine::format::Status::Error(_) => {
|
||||||
"ERROR ",
|
tui::text::Span::styled(
|
||||||
tui::style::Style::default()
|
"ERROR ",
|
||||||
.fg(tui::style::Color::Red),
|
tui::style::Style::default()
|
||||||
),
|
.fg(tui::style::Color::Red),
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
tui::text::Span::raw(formatted_path.path.clone()),
|
tui::text::Span::raw(formatted_path.path.clone()),
|
||||||
])
|
])
|
||||||
|
@ -289,5 +303,5 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(formatted_paths.iter().cloned().collect())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
let matches = alejandra_cli::cli::parse(std::env::args().collect());
|
let matches = alejandra_cli::cli::parse(std::env::args().collect());
|
||||||
|
|
||||||
match matches.values_of("include") {
|
let check = matches.is_present("check");
|
||||||
|
|
||||||
|
let formatted_paths = match matches.values_of("include") {
|
||||||
Some(include) => {
|
Some(include) => {
|
||||||
let include = include.collect();
|
let include = include.collect();
|
||||||
let exclude = match matches.values_of("exclude") {
|
let exclude = match matches.values_of("exclude") {
|
||||||
|
@ -16,15 +18,65 @@ fn main() -> std::io::Result<()> {
|
||||||
&& atty::is(atty::Stream::Stdin)
|
&& atty::is(atty::Stream::Stdin)
|
||||||
&& atty::is(atty::Stream::Stdout)
|
&& atty::is(atty::Stream::Stdout)
|
||||||
{
|
{
|
||||||
alejandra_cli::cli::tui(paths)?;
|
alejandra_cli::cli::tui(paths)?
|
||||||
} else {
|
} else {
|
||||||
alejandra_cli::cli::simple(paths)?;
|
alejandra_cli::cli::simple(paths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => vec![alejandra_cli::cli::stdin()],
|
||||||
alejandra_cli::cli::stdin()?;
|
};
|
||||||
|
|
||||||
|
let errors = formatted_paths
|
||||||
|
.iter()
|
||||||
|
.filter(|formatted_path| {
|
||||||
|
matches!(
|
||||||
|
formatted_path.status,
|
||||||
|
alejandra_engine::format::Status::Error(_)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if errors > 0 {
|
||||||
|
eprintln!();
|
||||||
|
eprintln!(
|
||||||
|
"Failed! We encountered {} error{} at:",
|
||||||
|
errors,
|
||||||
|
if errors > 0 { "s" } else { "" }
|
||||||
|
);
|
||||||
|
for formatted_path in formatted_paths {
|
||||||
|
if let alejandra_engine::format::Status::Error(error) =
|
||||||
|
formatted_path.status
|
||||||
|
{
|
||||||
|
eprintln!(" {}: {}", formatted_path.path, &error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = formatted_paths
|
||||||
|
.iter()
|
||||||
|
.filter(|formatted_path| match formatted_path.status {
|
||||||
|
alejandra_engine::format::Status::Changed(changed) => changed,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if changed > 0 {
|
||||||
|
eprintln!();
|
||||||
|
eprintln!(
|
||||||
|
"Success! {} file{} {} changed",
|
||||||
|
changed,
|
||||||
|
if changed > 0 { "s" } else { "" },
|
||||||
|
if changed > 0 { "were" } else { "was" },
|
||||||
|
);
|
||||||
|
if check {
|
||||||
|
std::process::exit(2);
|
||||||
|
} else {
|
||||||
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Success! Your code complies the Alejandra style");
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,56 @@
|
||||||
pub fn string(path: String, string: String) -> std::io::Result<String> {
|
#[derive(Clone)]
|
||||||
let tokens = rnix::tokenizer::Tokenizer::new(&string);
|
pub enum Status {
|
||||||
|
Error(String),
|
||||||
|
Changed(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Status {
|
||||||
|
fn from(error: std::io::Error) -> Status {
|
||||||
|
Status::Error(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn in_memory(path: String, before: String) -> (Status, String) {
|
||||||
|
let tokens = rnix::tokenizer::Tokenizer::new(&before);
|
||||||
let ast = rnix::parser::parse(tokens);
|
let ast = rnix::parser::parse(tokens);
|
||||||
|
|
||||||
let errors = ast.errors();
|
let errors = ast.errors();
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
return Err(std::io::Error::new(
|
return (Status::Error(errors[0].to_string()), before);
|
||||||
std::io::ErrorKind::InvalidInput,
|
|
||||||
errors[0].to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let green_node =
|
let after = crate::builder::build(ast.node().into(), false, path, true)
|
||||||
crate::builder::build(ast.node().into(), false, path, true).unwrap();
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
Ok(green_node.to_string())
|
if before == after {
|
||||||
}
|
(Status::Changed(false), after)
|
||||||
|
} else {
|
||||||
pub fn string_or_passthrough(path: String, before: String) -> String {
|
(Status::Changed(true), after)
|
||||||
match crate::format::string(path, before.clone()) {
|
|
||||||
Ok(after) => after,
|
|
||||||
Err(_) => before,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file(path: String) -> std::io::Result<bool> {
|
pub fn in_place(path: String) -> Status {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
let input = std::fs::read_to_string(&path)?;
|
match std::fs::read_to_string(&path) {
|
||||||
let input_clone = input.clone();
|
Ok(before) => {
|
||||||
let input_bytes = input_clone.as_bytes();
|
let (status, data) = crate::format::in_memory(path.clone(), before);
|
||||||
|
|
||||||
let output = crate::format::string(path.clone(), input)?;
|
if let Status::Changed(changed) = status {
|
||||||
let output_bytes = output.as_bytes();
|
if changed {
|
||||||
|
return match std::fs::File::create(path) {
|
||||||
|
Ok(mut file) => match file.write_all(data.as_bytes()) {
|
||||||
|
Ok(_) => status,
|
||||||
|
Err(error) => Status::from(error),
|
||||||
|
},
|
||||||
|
Err(error) => Status::from(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let changed = input_bytes != output_bytes;
|
status
|
||||||
if changed {
|
}
|
||||||
std::fs::File::create(path)?.write_all(output_bytes)?;
|
Err(error) => Status::from(error),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(changed)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,8 @@ fn cases() {
|
||||||
let path_in = format!("tests/cases/{}/in", case);
|
let path_in = format!("tests/cases/{}/in", case);
|
||||||
let path_out = format!("tests/cases/{}/out", case);
|
let path_out = format!("tests/cases/{}/out", 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_engine::format::string_or_passthrough(
|
let content_got =
|
||||||
path_in,
|
alejandra_engine::format::in_memory(path_in, content_in.clone()).1;
|
||||||
content_in.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if should_update {
|
if should_update {
|
||||||
std::fs::File::create(&path_out)
|
std::fs::File::create(&path_out)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue