diff --git a/src/main.rs b/src/main.rs index c09acd6..6431f70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,9 @@ struct Cli { #[derive(Subcommand, Debug)] enum Command { + #[clap(about = "Format a single file or stdin")] Fmt(FmtArgs), + #[clap(about = "Watch a file or directory for changes")] Watch(WatchArgs), } @@ -28,31 +30,36 @@ enum Command { struct WatchArgs { path: PathBuf, - #[clap(short = 'e', long = "extension", default_values = ["jsonc", "jsoncc"])] + #[clap(short = 'e', long = "extension", default_values = ["jsonc", "jsoncc"], help = "File extensions to track")] extensions: Vec, - #[clap(short = 'r', long = "recursive")] + #[clap(short = 'r', long = "recursive", help = "Recursively search files")] recursive: bool, - #[clap(short = 'I', long = "inplace")] + #[clap(short = 'I', long = "inplace", help = "Replace each file inplace")] inplace: bool, } #[derive(Args, Debug)] struct FmtArgs { // #[clap(short = 'i', long = "input")] - input: PathBuf, + #[clap(help = "Input file, or `-` for stdin")] + input: Option, - #[clap(short = 'o', long = "output")] + #[clap( + short = 'o', + long = "output", + help = "Output file; will be stdout if no output is specified" + )] output: Option, - #[clap(short = 'O', long = "json-output")] + #[clap(short = 'O', long = "json-output", help = "Output file for json")] json_output: Option, - #[clap(short = 'c', long = "compact")] + #[clap(short = 'c', long = "compact", help = "Compact json format")] compact: bool, - #[clap(short = 'I', long = "inplace")] + #[clap(short = 'I', long = "inplace", help = "Replace file contents inplace")] inplace: bool, } @@ -60,7 +67,9 @@ impl FmtArgs { /// Where should we format Jsonc output to? fn jsonc_output(&self) -> Option { if self.inplace { - Some(JsoncOutput::File(&self.input)) + Some(JsoncOutput::File(self.input.as_ref().expect( + "Argument parsing error -- input was empty, but --inplace was specified", + ))) } else if let Some(ref output_file) = &self.output { Some(JsoncOutput::File(output_file)) } else if self.json_output.is_some() { @@ -81,8 +90,6 @@ enum JsoncOutput<'a> { fn main() -> Result<()> { let options = Cli::parse(); - eprintln!("Options: {options:?}"); - match options.command { Command::Fmt(a) => { // TODO: Figure out how to validate this in clap Parser @@ -93,7 +100,7 @@ fn main() -> Result<()> { bail!("Cannot format --inplace when --output is specified"); } format_single_file( - &a.input, + a.input.as_ref(), a.jsonc_output().as_ref(), a.json_output.as_ref(), a.compact, @@ -106,21 +113,25 @@ fn main() -> Result<()> { } fn format_single_file( - input: impl AsRef, + input: Option>, jsonc_output: Option<&JsoncOutput>, json_output: Option>, json_compact: bool, ) -> Result<()> { - let input_str = fs::read_to_string(&input).context("Reading input")?; + let input_str = if let Some(input_filename) = input { + fs::read_to_string(&input_filename).context("Reading input")? + } else { + std::io::read_to_string(std::io::stdin()).context("Reading stdin")? + }; // First, format jsonc if let Some(jsonc_output) = jsonc_output { - let output = fjson::to_jsonc(&input_str).context("Formatting to jsonc")?; + let output = fjson::to_jsonc(&input_str).context("Parsing jsonc")?; match jsonc_output { JsoncOutput::Stdout => print!("{output}"), JsoncOutput::File(output_file) => AtomicFile::new(output_file, AllowOverwrite) - .write(|f| f.write_all(&output.as_bytes())) + .write(|f| f.write_all(output.as_bytes())) .context("Writing jsonc output")?, } } @@ -133,9 +144,13 @@ fn format_single_file( fjson::to_json_compact(&input_str).context("Formatting to json") }?; - AtomicFile::new(json_output_file, AllowOverwrite) - .write(|f| f.write_all(&output.as_bytes())) - .context("Writing jsonc output")?; + if json_output_file.as_ref().as_os_str() == "-" { + print!("{output}"); + } else { + AtomicFile::new(json_output_file, AllowOverwrite) + .write(|f| f.write_all(output.as_bytes())) + .context("Writing jsonc output")?; + } } Ok(()) @@ -181,7 +196,7 @@ fn watch(args: &WatchArgs) -> Result<()> { if !(args .extensions .iter() - .any(|ext| (&path).extension() == Some(ext)) + .any(|ext| path.extension() == Some(ext)) || args.extensions.is_empty()) { // This extension doesn't match their requested extensions @@ -196,7 +211,7 @@ fn watch(args: &WatchArgs) -> Result<()> { eprintln!("Got result: {path:#?}"); match format_single_file( - &path, + Some(&path), Some(&JsoncOutput::File(&path)), None::<&Path>, false,