1use std::{2 fs,3 io::{self, Write as _},4 path::PathBuf,5 process,6};78use clap::Parser;9use jrsonnet_formatter::{format, FormatOptions};1011#[derive(Parser)]12#[allow(clippy::struct_excessive_bools)]13struct Opts {14 15 #[clap(long, short = 'e')]16 exec: bool,17 18 input: String,19 20 21 #[clap(long, short = 'i')]22 in_place: bool,2324 25 #[arg(long)]26 test: bool,27 28 #[arg(long, default_value = "2")]29 indent: u8,30 31 #[arg(long)]32 hard_tabs: bool,3334 35 36 37 #[arg(long, default_value = "0")]38 conv_limit: usize,39}4041#[derive(thiserror::Error, Debug)]42enum Error {43 #[error("--in-place is incompatible with --exec")]44 InPlaceExec,45 #[error("io: {0}")]46 Io(#[from] io::Error),47 #[error("persist: {0}")]48 Persist(#[from] tempfile::PersistError),49 #[error("parsing failed, refusing to reformat corrupted input")]50 Parse,51}5253fn main_result() -> Result<(), Error> {54 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");55 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");56 let mut opts = Opts::parse();57 let input = if opts.exec {58 if opts.in_place {59 return Err(Error::InPlaceExec);60 }61 opts.input.clone()62 } else {63 fs::read_to_string(&opts.input)?64 };6566 if opts.indent == 0 {67 68 69 opts.hard_tabs = true;70 }7172 let mut iteration = 0;73 let mut formatted = input.clone();74 let mut convergence_tmp;75 76 loop {77 let reformatted = match format(78 &formatted,79 &FormatOptions {80 indent: if opts.indent == 0 || opts.hard_tabs {81 082 } else {83 opts.indent84 },85 },86 ) {87 Ok(v) => v,88 Err(e) => {89 let snippet = e.build();90 let ansi = hi_doc::source_to_ansi(&snippet);91 eprintln!("{ansi}");92 return Err(Error::Parse);93 }94 };95 convergence_tmp = reformatted.trim().to_owned();96 if formatted == convergence_tmp {97 break;98 }99 formatted = convergence_tmp;100 if opts.conv_limit == 0 {101 break;102 }103 iteration += 1;104 assert!(iteration <= opts.conv_limit, "formatting not converged");105 }106 formatted.push('\n');107 if opts.test && formatted != input {108 process::exit(1);109 }110 if opts.in_place {111 let path = PathBuf::from(opts.input);112 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(113 "not failed during read, this path is not a directory, and there is a parent",114 ))?;115 temp.write_all(formatted.as_bytes())?;116 temp.flush()?;117 temp.persist(&path)?;118 } else {119 print!("{formatted}");120 }121 Ok(())122}123124fn main() {125 if let Err(e) = main_result() {126 eprintln!("{e}");127 process::exit(1);128 }129}