1use std::{fs, io};23use clap::Parser;4use jrsonnet_formatter::{format, FormatOptions};56#[derive(Parser)]7#[allow(clippy::struct_excessive_bools)]8struct Opts {9 10 #[clap(long, short = 'e')]11 exec: bool,12 13 input: String,14 15 16 #[clap(long, short = 'i')]17 in_place: bool,1819 20 #[arg(long)]21 test: bool,22 23 #[arg(long, default_value = "2")]24 indent: u8,25 26 #[arg(long)]27 hard_tabs: bool,2829 30 31 32 #[arg(long, default_value = "0")]33 conv_limit: usize,34}3536#[derive(thiserror::Error, Debug)]37enum Error {38 #[error("--in-place is incompatible with --exec")]39 InPlaceExec,40 #[error("io: {0}")]41 Io(#[from] io::Error),42 #[error("persist: {0}")]43 Persist(#[from] tempfile::PersistError),44 #[error("parsing failed, refusing to reformat corrupted input")]45 Parse,46}4748fn main_result() -> Result<(), Error> {49 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");50 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");51 let mut opts = Opts::parse();52 let input = if opts.exec {53 if opts.in_place {54 return Err(Error::InPlaceExec);55 }56 opts.input.clone()57 } else {58 fs::read_to_string(&opts.input)?59 };6061 if opts.indent == 0 {62 63 64 opts.hard_tabs = true;65 }6667 let mut iteration = 0;68 let mut formatted = input.clone();69 let mut convergence_tmp;70 71 loop {72 let reformatted = match format(73 &formatted,74 &FormatOptions {75 indent: if opts.indent == 0 || opts.hard_tabs {76 077 } else {78 opts.indent79 },80 },81 ) {82 Ok(v) => v,83 Err(e) => {84 let snippet = e.build();85 let ansi = hi_doc::source_to_ansi(&snippet);86 eprintln!("{ansi}");87 return Err(Error::Parse);88 }89 };90 convergence_tmp = reformatted.trim().to_owned();91 if formatted == convergence_tmp {92 break;93 }94 formatted = convergence_tmp;95 if opts.conv_limit == 0 {96 break;97 }98 iteration += 1;99 assert!(iteration <= opts.conv_limit, "formatting not converged");100 }101 formatted.push('\n');102 if opts.test && formatted != input {103 process::exit(1);104 }105 if opts.in_place {106 let path = PathBuf::from(opts.input);107 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(108 "not failed during read, this path is not a directory, and there is a parent",109 ))?;110 temp.write_all(formatted.as_bytes())?;111 temp.flush()?;112 temp.persist(&path)?;113 } else {114 print!("{formatted}");115 }116 Ok(())117}118119fn main() {120 if let Err(e) = main_result() {121 eprintln!("{e}");122 process::exit(1);123 }124}