refactor(cli,formatter): more idiomatic rust
ADD
crates/cli/fixtures/input.json
+{
+ "string": "foo",
+ "boolean": false,
+ "number": 15,
+ "object": {
+ "something": 15
+ }
+}

ADD
crates/cli/src/bin/cli.rs
+use cli::run_cli;
+
+///
+/// To run this example, run:
+///
+/// ```bash
+/// cargo run --example run_cli
+/// ```
+/// Add arguments like:
+///
+/// ```bash
+/// cargo run --example run_cli -- --help
+/// cargo run --example run_cli format
+/// ```
+///
+/// To run a valid example:
+///
+/// ```bash
+/// cargo run --example run_cli format examples/input.json
+/// ```
+///
+fn main() {
+ run_cli();
+}

MODIFY
crates/cli/src/lib.rs
crates/cli/src/lib.rs
-use clap::{crate_version, App, Arg};
+use clap::{crate_version, App, AppSettings, Arg};
use rome_formatter::{format, FormatOptions, IndentStyle};
-use std::path::PathBuf;
+use std::{path::PathBuf, str::FromStr};
/// Main function to run Rome CLI
pub fn run_cli() {
let matches = App::new("rome")
.about("The official Rome CLI")
.version(crate_version!())
+ .setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
App::new("format")
.about("Format a file")
.arg(
- Arg::new("indentStyle")
+ Arg::new("indent_style")
.long("indent-style")
.about("The style of indentation")
.value_name("tab|space")
.default_value("tab")
- .validator(|value| {
- if !value.is_empty() {
- if value.eq("tab") || value.eq("space") {
- return Ok(());
- }
- return Err(
- r#"Invalid indent-style value. Only "tabs" and "spaces" are supported. "#,
- );
- }
- Ok(())
- }),
+ .validator(|value| IndentStyle::from_str(value).map(|_| ())),
)
.arg(
- Arg::new("indentSize")
+ Arg::new("indent_size")
.long("indent-size")
.about("The size of the indent.")
.value_name("NUMBER")
.default_value("2")
.validator(|value| {
- let number = value.parse::<u8>();
- match number {
- Ok(_) => Ok(()),
- Err(_) => Err("Invalid indent-size value. Try using a number"),
- }
+ value
+ .parse::<u8>()
+ .map_err(|_| "Invalid indent-size value. Try using a number")
}),
)
.arg(
@@ -55,28 +44,36 @@ pub fn run_cli() {
}),
),
)
- .try_get_matches()
- .unwrap_or_else(|error| {
- error.exit();
- });
+ .try_get_matches();
+ let subcommand_matches = match &matches {
+ Ok(r) => r.subcommand(),
+ Err(err) => err.exit(),
+ };
- if let Some(matches) = matches.subcommand_matches("format") {
- let size = matches.value_of("indentSize");
- let style = matches.value_of("indentStyle");
+ match subcommand_matches {
+ Some(("format", matches)) => {
+ let size = matches.value_of("indent_size");
+ let style = matches.value_of("indent_style");
let input = matches.value_of("input").unwrap();
let input = PathBuf::from(&input);
- let options = match style {
- Some(style) => match style {
+ let options: IndentStyle = style
+ .map(|s| match s {
"tab" => IndentStyle::Tab,
"space" => {
- // default value is already set in the argument
- let size = size.unwrap();
+ let size = size.unwrap_or("2");
IndentStyle::Space(size.parse::<u8>().unwrap_or(2))
}
- _ => IndentStyle::Tab,
- },
- None => IndentStyle::Tab,
- };
+ _ => IndentStyle::default(),
+ })
+ .unwrap_or_default();
+
format(input, FormatOptions::new(options));
}
+ // Thanks to the settings AppSettings::SubcommandRequiredElseHelp we should not be there
+ _ => clap::Error::with_description(
+ "Sub command not found".to_string(),
+ clap::ErrorKind::InvalidSubcommand,
+ )
+ .exit(),
+ }
}

ADD
crates/cli/tests/main.rs
+use std::{env, path::PathBuf, process::Command};
+
+fn cargo_bin(name: &str) -> PathBuf {
+ env::current_exe()
+ .ok()
+ .map(|mut path| {
+ path.pop();
+ if path.ends_with("deps") {
+ path.pop();
+ }
+ path.join(name)
+ })
+ .expect("cannot get current exe")
+}
+
+#[test]
+fn test_format_cli() {
+ let res = Command::new(cargo_bin("cli"))
+ .args(&["format", "fixtures/input.json"])
+ .output()
+ .expect("fail to run cli format");
+
+ assert!(res.status.success(), "cli format command failed");
+ let output = String::from_utf8(res.stdout).expect("cannot read stdout, not utf8 compliant");
+
+ assert_eq!(
+ output,
+ r#"Running formatter to:
+- file "fixtures/input.json"
+- with options Tab
+{"string": "foo", "boolean": false, "number": 15, "object": {"something": 15}}
+"#
+ );
+}

MODIFY
crates/formatter/src/format_json.rs
crates/formatter/src/format_json.rs
@@ -75,7 +75,7 @@ fn escape_string(string: &str) -> String {
}
pub fn json_to_tokens(content: &str) -> FormatToken {
- let json: Value = serde_json::from_str(content).unwrap();
+ let json: Value = serde_json::from_str(content).expect("cannot convert json to tokens");
FormatToken::from(ListToken::concat(vec![
json.format(),

MODIFY
crates/formatter/src/lib.rs
crates/formatter/src/lib.rs
@@ -25,7 +25,7 @@ pub enum IndentStyle {
impl Default for IndentStyle {
fn default() -> Self {
- IndentStyle::Tab
+ Self::Tab
}
}
@@ -53,7 +53,7 @@ impl FormatOptions {
Self { indent_style }
}
}
-// TODO: implement me
+// TODO: implement me + handle errors
/// Main function
pub fn format(path: PathBuf, options: FormatOptions) {
println!(
@@ -61,10 +61,11 @@ pub fn format(path: PathBuf, options: FormatOptions) {
path, options.indent_style
);
// we assume that file exists
- let mut file = File::open(&path).unwrap();
+ let mut file = File::open(&path).expect("cannot open the file to format");
let mut buffer = String::new();
// we assume we have permissions
- file.read_to_string(&mut buffer).unwrap();
+ file.read_to_string(&mut buffer)
+ .expect("cannot read the file to format");
let tokens = json_to_tokens(buffer.as_str());
let print_result = format_token(&tokens, options);

MODIFY
crates/formatter/src/printer.rs
crates/formatter/src/printer.rs
@@ -149,7 +149,7 @@ impl Printer {
self.state.pending_spaces = 0;
}
- self.print_str(content.as_str());
+ self.print_str(content);
}
vec![]
}
@@ -254,12 +254,12 @@ impl Printer {
}
// We want a flat structure, so omit soft line wraps
LineMode::Soft => vec![],
- LineMode::Hard => return Err(LineBreakRequiredError {}),
+ LineMode::Hard => return Err(LineBreakRequiredError),
}
}
FormatToken::Group(GroupToken {
should_break: true, ..
- }) => return Err(LineBreakRequiredError {}),
+ }) => return Err(LineBreakRequiredError),
FormatToken::Group(group) => vec![PrintTokenCall::new(group.content.as_ref(), args)],

refactor(cli,formatter): more idiomatic rust (#1675)