mirror of
https://github.com/RGBCube/alejandra
synced 2025-07-30 12:07:46 +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`),
|
||||
command line flags,
|
||||
positional arguments,
|
||||
exit codes,
|
||||
and stdout.
|
||||
|
||||
## Changelog
|
||||
|
|
|
@ -33,6 +33,10 @@ steps:
|
|||
- formatting-before-vs-after.patch.txt
|
||||
- formatting-after.patch.txt
|
||||
command:
|
||||
- echo +++ Formatting - demo
|
||||
- nix run . -- flake.nix
|
||||
|
||||
- echo --- Cloning nixpkgs
|
||||
- git config --global user.email CI/CD
|
||||
- git config --global user.name CI/CD
|
||||
- git clone --branch=master --depth 1 --origin=upstream file:///data/nixpkgs
|
||||
|
|
|
@ -12,5 +12,5 @@ pub fn main() -> Result<(), JsValue> {
|
|||
|
||||
#[wasm_bindgen]
|
||||
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 {
|
||||
clap::Command::new("Alejandra")
|
||||
.about("The Uncompromising Nix Code Formatter.")
|
||||
|
@ -15,6 +21,11 @@ pub fn parse(args: Vec<String>) -> clap::ArgMatches {
|
|||
.multiple_occurrences(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("check")
|
||||
.help("Check if the input is already formatted.")
|
||||
.long("--check"),
|
||||
)
|
||||
.term_width(80)
|
||||
.after_help(indoc::indoc!(
|
||||
// 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
|
||||
"
|
||||
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:
|
||||
- Kevin Amado ~ @kamadorueda on GitHub, matrix.org and Gmail.
|
||||
- Thomas Bereknyei ~ @tomberek on GitHub and matrix.org.
|
||||
- David Arnold ~ @blaggacao on GitHub and matrix.org.
|
||||
- Vincent Ambo ~ @tazjin on GitHub.
|
||||
- Mr Hedgehog ~ @ModdedGamers on GitHub.
|
||||
Kevin Amado ~ @kamadorueda on GitHub, matrix.org and Gmail.
|
||||
Thomas Bereknyei ~ @tomberek on GitHub and matrix.org.
|
||||
David Arnold ~ @blaggacao on GitHub and matrix.org.
|
||||
Vincent Ambo ~ @tazjin on GitHub.
|
||||
Mr Hedgehog ~ @ModdedGamers on GitHub.
|
||||
"
|
||||
))
|
||||
.get_matches_from(args)
|
||||
}
|
||||
|
||||
pub fn stdin() -> std::io::Result<()> {
|
||||
pub fn stdin() -> FormattedPath {
|
||||
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.");
|
||||
let mut stdin = String::new();
|
||||
std::io::stdin().read_to_string(&mut stdin).unwrap();
|
||||
|
||||
let stdout = alejandra_engine::format::string("stdin".to_string(), stdin)?;
|
||||
print!("{}", stdout);
|
||||
std::io::stdin().read_to_string(&mut before).unwrap();
|
||||
|
||||
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::*;
|
||||
|
||||
eprintln!("Formatting {} files.", paths.len());
|
||||
eprintln!("Formatting: {} files", paths.len());
|
||||
|
||||
let (results, errors): (Vec<_>, Vec<_>) = paths
|
||||
paths
|
||||
.par_iter()
|
||||
.map(|path| -> std::io::Result<bool> {
|
||||
alejandra_engine::format::file(path.to_string()).map(|changed| {
|
||||
.map(|path| {
|
||||
let status = alejandra_engine::format::in_place(path.clone());
|
||||
|
||||
if let alejandra_engine::format::Status::Changed(changed) = status {
|
||||
if changed {
|
||||
eprintln!("Formatted: {}", &path);
|
||||
eprintln!("Changed: {}", &path);
|
||||
}
|
||||
changed
|
||||
})
|
||||
}
|
||||
|
||||
FormattedPath { path: path.clone(), status }
|
||||
})
|
||||
.partition(Result::is_ok);
|
||||
|
||||
eprintln!(
|
||||
"Changed: {}",
|
||||
results.into_iter().map(Result::unwrap).filter(|&x| x).count(),
|
||||
);
|
||||
eprintln!("Errors: {}", errors.len(),);
|
||||
|
||||
Ok(())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
||||
pub fn tui(paths: Vec<String>) -> std::io::Result<Vec<FormattedPath>> {
|
||||
use rayon::prelude::*;
|
||||
use termion::{input::TermRead, raw::IntoRawMode};
|
||||
|
||||
struct FormattedPath {
|
||||
path: String,
|
||||
result: std::io::Result<bool>,
|
||||
}
|
||||
|
||||
enum Event {
|
||||
FormattedPath(FormattedPath),
|
||||
FormattingFinished,
|
||||
|
@ -91,11 +102,12 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
|||
}
|
||||
|
||||
let paths_to_format = paths.len();
|
||||
let mut formatted_paths = std::collections::LinkedList::new();
|
||||
|
||||
let stdout = std::io::stderr().into_raw_mode()?;
|
||||
// let stdout = termion::screen::AlternateScreen::from(stdout);
|
||||
let backend = tui::backend::TermionBackend::new(stdout);
|
||||
let mut terminal = tui::Terminal::new(backend)?;
|
||||
terminal.clear()?;
|
||||
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
|
||||
|
@ -128,10 +140,10 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
|||
let sender_finished = sender;
|
||||
std::thread::spawn(move || {
|
||||
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
|
||||
.send(Event::FormattedPath(FormattedPath { path, result }))
|
||||
.send(Event::FormattedPath(FormattedPath { path, status }))
|
||||
{
|
||||
eprintln!("{}", error);
|
||||
}
|
||||
|
@ -142,36 +154,32 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
|||
}
|
||||
});
|
||||
|
||||
terminal.clear()?;
|
||||
|
||||
let mut finished = false;
|
||||
let mut paths_with_errors: usize = 0;
|
||||
let mut paths_changed: usize = 0;
|
||||
let mut paths_unchanged: usize = 0;
|
||||
let mut formatted_paths = std::collections::LinkedList::new();
|
||||
|
||||
while !finished {
|
||||
loop {
|
||||
if let Ok(event) = receiver.try_recv() {
|
||||
match event {
|
||||
Event::FormattedPath(formatted_path) => {
|
||||
match formatted_path.result {
|
||||
Ok(changed) => {
|
||||
if changed {
|
||||
match &formatted_path.status {
|
||||
alejandra_engine::format::Status::Changed(
|
||||
changed,
|
||||
) => {
|
||||
if *changed {
|
||||
paths_changed += 1;
|
||||
} else {
|
||||
paths_unchanged += 1;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
alejandra_engine::format::Status::Error(_) => {
|
||||
paths_with_errors += 1;
|
||||
}
|
||||
};
|
||||
|
||||
formatted_paths.push_back(formatted_path);
|
||||
if formatted_paths.len() > 8 {
|
||||
formatted_paths.pop_front();
|
||||
}
|
||||
}
|
||||
Event::FormattingFinished => {
|
||||
finished = true;
|
||||
|
@ -253,23 +261,29 @@ pub fn tui(paths: Vec<String>) -> std::io::Result<()> {
|
|||
let logger = tui::widgets::Paragraph::new(
|
||||
formatted_paths
|
||||
.iter()
|
||||
.rev()
|
||||
.take(8)
|
||||
.map(|formatted_path| {
|
||||
tui::text::Spans::from(vec![
|
||||
match &formatted_path.result {
|
||||
Ok(changed) => tui::text::Span::styled(
|
||||
if *changed {
|
||||
"CHANGED "
|
||||
match formatted_path.status {
|
||||
alejandra_engine::format::Status::Changed(
|
||||
changed,
|
||||
) => tui::text::Span::styled(
|
||||
if changed {
|
||||
"CHANGED "
|
||||
} else {
|
||||
"UNCHANGED "
|
||||
"OK "
|
||||
},
|
||||
tui::style::Style::default()
|
||||
.fg(tui::style::Color::Green),
|
||||
),
|
||||
Err(_) => tui::text::Span::styled(
|
||||
"ERROR ",
|
||||
tui::style::Style::default()
|
||||
.fg(tui::style::Color::Red),
|
||||
),
|
||||
alejandra_engine::format::Status::Error(_) => {
|
||||
tui::text::Span::styled(
|
||||
"ERROR ",
|
||||
tui::style::Style::default()
|
||||
.fg(tui::style::Color::Red),
|
||||
)
|
||||
}
|
||||
},
|
||||
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<()> {
|
||||
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) => {
|
||||
let include = include.collect();
|
||||
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::Stdout)
|
||||
{
|
||||
alejandra_cli::cli::tui(paths)?;
|
||||
alejandra_cli::cli::tui(paths)?
|
||||
} else {
|
||||
alejandra_cli::cli::simple(paths)?;
|
||||
alejandra_cli::cli::simple(paths)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
alejandra_cli::cli::stdin()?;
|
||||
None => vec![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);
|
||||
}
|
||||
|
|
|
@ -1,42 +1,56 @@
|
|||
pub fn string(path: String, string: String) -> std::io::Result<String> {
|
||||
let tokens = rnix::tokenizer::Tokenizer::new(&string);
|
||||
#[derive(Clone)]
|
||||
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 errors = ast.errors();
|
||||
if !errors.is_empty() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
errors[0].to_string(),
|
||||
));
|
||||
return (Status::Error(errors[0].to_string()), before);
|
||||
}
|
||||
|
||||
let green_node =
|
||||
crate::builder::build(ast.node().into(), false, path, true).unwrap();
|
||||
let after = crate::builder::build(ast.node().into(), false, path, true)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
Ok(green_node.to_string())
|
||||
}
|
||||
|
||||
pub fn string_or_passthrough(path: String, before: String) -> String {
|
||||
match crate::format::string(path, before.clone()) {
|
||||
Ok(after) => after,
|
||||
Err(_) => before,
|
||||
if before == after {
|
||||
(Status::Changed(false), after)
|
||||
} else {
|
||||
(Status::Changed(true), after)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file(path: String) -> std::io::Result<bool> {
|
||||
pub fn in_place(path: String) -> Status {
|
||||
use std::io::Write;
|
||||
|
||||
let input = std::fs::read_to_string(&path)?;
|
||||
let input_clone = input.clone();
|
||||
let input_bytes = input_clone.as_bytes();
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(before) => {
|
||||
let (status, data) = crate::format::in_memory(path.clone(), before);
|
||||
|
||||
let output = crate::format::string(path.clone(), input)?;
|
||||
let output_bytes = output.as_bytes();
|
||||
if let Status::Changed(changed) = status {
|
||||
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;
|
||||
if changed {
|
||||
std::fs::File::create(path)?.write_all(output_bytes)?;
|
||||
status
|
||||
}
|
||||
Err(error) => Status::from(error),
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
|
|
@ -14,10 +14,8 @@ fn cases() {
|
|||
let path_in = format!("tests/cases/{}/in", case);
|
||||
let path_out = format!("tests/cases/{}/out", case);
|
||||
let content_in = std::fs::read_to_string(path_in.clone()).unwrap();
|
||||
let content_got = alejandra_engine::format::string_or_passthrough(
|
||||
path_in,
|
||||
content_in.clone(),
|
||||
);
|
||||
let content_got =
|
||||
alejandra_engine::format::in_memory(path_in, content_in.clone()).1;
|
||||
|
||||
if should_update {
|
||||
std::fs::File::create(&path_out)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue