-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Improve CLI robustness, safety, and readability #14333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1be6fc2
1c4b69e
9f80c86
6c4ef91
a22c2de
0cb21b9
00024f8
8c58d83
918537f
8bf6bf3
0defd86
dfcefc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,28 +65,23 @@ impl FromStr for ConfigValue { | |
| let path = PathBuf::from(config); | ||
| let raw = | ||
| read_to_string(&path).fs_context("failed to read configuration file", path.clone())?; | ||
| match path.extension() { | ||
| Some(ext) if ext == "toml" => { | ||
| Ok(Self(::toml::from_str(&raw).with_context(|| { | ||
| format!("failed to parse config at {} as TOML", path.display()) | ||
| })?)) | ||
| } | ||
| Some(ext) if ext == "json5" => { | ||
| Ok(Self(::json5::from_str(&raw).with_context(|| { | ||
| format!("failed to parse config at {} as JSON5", path.display()) | ||
| })?)) | ||
| } | ||
| // treat all other extensions as json | ||
| _ => Ok(Self( | ||
| // from tauri-utils/src/config/parse.rs: | ||
| // we also want to support **valid** json5 in the .json extension | ||
| // if the json5 is not valid the serde_json error for regular json will be returned. | ||
| match ::json5::from_str(&raw) { | ||
| Ok(json5) => json5, | ||
| Err(_) => serde_json::from_str(&raw) | ||
| .with_context(|| format!("failed to parse config at {} as JSON", path.display()))?, | ||
| }, | ||
| )), | ||
|
|
||
| // treat all other extensions as json | ||
| // from tauri-utils/src/config/parse.rs: | ||
| // we also want to support **valid** json5 in the .json extension | ||
| // if the json5 is not valid the serde_json error for regular json will be returned. | ||
| match path.extension().and_then(|ext| ext.to_str()) { | ||
| Some("toml") => Ok(Self(::toml::from_str(&raw).with_context(|| { | ||
| format!("failed to parse config at {} as TOML", path.display()) | ||
| })?)), | ||
| Some("json5") => Ok(Self(::json5::from_str(&raw).with_context(|| { | ||
| format!("failed to parse config at {} as JSON5", path.display()) | ||
| })?)), | ||
| _ => Ok(Self(match ::json5::from_str(&raw) { | ||
| Ok(json5) => json5, | ||
| Err(_) => serde_json::from_str(&raw) | ||
| .with_context(|| format!("failed to parse config at {} as JSON", path.display()))?, | ||
| })), | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -178,6 +173,13 @@ fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error { | |
| err.format(&mut app) | ||
| } | ||
|
|
||
| fn get_verbosity(cli_verbose: u8) -> u8 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This function is only used once, where the code was inlined before. I don't mind having this in a separate function but wanted to mention this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this function because the logic may be reused (e.g. if we later need verbosity in other commands), |
||
| std::env::var("TAURI_CLI_VERBOSITY") | ||
| .ok() | ||
| .and_then(|v| v.parse().ok()) | ||
| .unwrap_or(cli_verbose) | ||
| } | ||
|
|
||
| /// Run the Tauri CLI with the passed arguments, exiting if an error occurs. | ||
| /// | ||
| /// The passed arguments should have the binary argument(s) stripped out before being passed. | ||
|
|
@@ -222,23 +224,17 @@ where | |
| Err(e) => e.exit(), | ||
| }; | ||
|
|
||
| let verbosity_number = std::env::var("TAURI_CLI_VERBOSITY") | ||
| .ok() | ||
| .and_then(|v| v.parse().ok()) | ||
| .unwrap_or(cli.verbose); | ||
| // set the verbosity level so subsequent CLI calls (xcode-script, android-studio-script) refer to it | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add back those comments? Not sure why you removed them
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could swear all your marked comments were added back... Maybe it's time for me to retire lol
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops! I overlooked these comments, I shall stop using neovim... Jokes aside, I will have them added back through another Pr (since this is merged). |
||
| let verbosity_number = get_verbosity(cli.verbose); | ||
| std::env::set_var("TAURI_CLI_VERBOSITY", verbosity_number.to_string()); | ||
|
|
||
| let mut builder = Builder::from_default_env(); | ||
| let init_res = builder | ||
| if let Err(err) = builder | ||
| .format_indent(Some(12)) | ||
| .filter(None, verbosity_level(verbosity_number).to_level_filter()) | ||
| // golbin spams an insane amount of really technical logs on the debug level so we're reducing one level | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
| .filter( | ||
| Some("goblin"), | ||
| verbosity_level(verbosity_number.saturating_sub(1)).to_level_filter(), | ||
| ) | ||
| // handlebars is not that spammy but its debug logs are typically far from being helpful | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
| .filter( | ||
| Some("handlebars"), | ||
| verbosity_level(verbosity_number.saturating_sub(1)).to_level_filter(), | ||
|
|
@@ -250,7 +246,6 @@ where | |
| is_command_output = action == "stdout" || action == "stderr"; | ||
| if !is_command_output { | ||
| let style = Style::new().fg_color(Some(AnsiColor::Green.into())).bold(); | ||
|
|
||
| write!(f, "{style}{action:>12}{style:#} ")?; | ||
| } | ||
| } else { | ||
|
|
@@ -264,15 +259,13 @@ where | |
|
|
||
| if !is_command_output && log::log_enabled!(Level::Debug) { | ||
| let style = Style::new().fg_color(Some(AnsiColor::Black.into())); | ||
|
|
||
| write!(f, "[{style}{}{style:#}] ", record.target())?; | ||
| } | ||
|
|
||
| writeln!(f, "{}", record.args()) | ||
| }) | ||
| .try_init(); | ||
|
|
||
| if let Err(err) = init_res { | ||
| .try_init() | ||
| { | ||
| eprintln!("Failed to attach logger: {err}"); | ||
| } | ||
|
|
||
|
|
@@ -305,7 +298,7 @@ fn verbosity_level(num: u8) -> Level { | |
| match num { | ||
| 0 => Level::Info, | ||
| 1 => Level::Debug, | ||
| 2.. => Level::Trace, | ||
| _ => Level::Trace, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -321,8 +314,6 @@ fn prettyprint_level(lvl: Level) -> &'static str { | |
| } | ||
|
|
||
| pub trait CommandExt { | ||
| // The `pipe` function sets the stdout and stderr to properly | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
| // show the command output in the Node.js wrapper. | ||
| fn piped(&mut self) -> std::io::Result<ExitStatus>; | ||
| fn output_ok(&mut self) -> crate::Result<Output>; | ||
| } | ||
|
|
@@ -332,18 +323,25 @@ impl CommandExt for Command { | |
| self.stdin(os_pipe::dup_stdin()?); | ||
| self.stdout(os_pipe::dup_stdout()?); | ||
| self.stderr(os_pipe::dup_stderr()?); | ||
|
|
||
| let program = self.get_program().to_string_lossy().into_owned(); | ||
| log::debug!(action = "Running"; "Command `{} {}`", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!("{acc} {arg}"))); | ||
| let args = self | ||
| .get_args() | ||
| .map(|a| a.to_string_lossy()) | ||
| .collect::<Vec<_>>() | ||
| .join(" "); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i wonder if that's slower than fold but i agree that it's easier on the eyes. |
||
|
|
||
| log::debug!(action = "Running"; "Command `{program} {args}`"); | ||
| self.status() | ||
| } | ||
|
|
||
| fn output_ok(&mut self) -> crate::Result<Output> { | ||
| let program = self.get_program().to_string_lossy().into_owned(); | ||
| let args = self | ||
| .get_args() | ||
| .map(|arg| arg.to_string_lossy()) | ||
| .fold(String::new(), |acc, arg| format!("{acc} {arg}")); | ||
| .map(|a| a.to_string_lossy()) | ||
| .collect::<Vec<_>>() | ||
| .join(" "); | ||
| let cmdline = format!("{program} {args}"); | ||
| log::debug!(action = "Running"; "Command `{cmdline}`"); | ||
|
|
||
|
|
@@ -359,16 +357,17 @@ impl CommandExt for Command { | |
| let stdout_lines_ = stdout_lines.clone(); | ||
| std::thread::spawn(move || { | ||
| let mut line = String::new(); | ||
| let mut lines = stdout_lines_.lock().unwrap(); | ||
| loop { | ||
| line.clear(); | ||
| match stdout.read_line(&mut line) { | ||
| Ok(0) => break, | ||
| Ok(_) => { | ||
| log::debug!(action = "stdout"; "{}", line.trim_end()); | ||
| lines.extend(line.as_bytes().to_vec()); | ||
| if let Ok(mut lines) = stdout_lines_.lock() { | ||
| loop { | ||
| line.clear(); | ||
| match stdout.read_line(&mut line) { | ||
| Ok(0) => break, | ||
| Ok(_) => { | ||
| log::debug!(action = "stdout"; "{}", line.trim_end()); | ||
| lines.extend(line.as_bytes()); | ||
| } | ||
| Err(_) => (), | ||
| } | ||
| Err(_) => (), | ||
| } | ||
| } | ||
| }); | ||
|
|
@@ -378,16 +377,17 @@ impl CommandExt for Command { | |
| let stderr_lines_ = stderr_lines.clone(); | ||
| std::thread::spawn(move || { | ||
| let mut line = String::new(); | ||
| let mut lines = stderr_lines_.lock().unwrap(); | ||
| loop { | ||
| line.clear(); | ||
| match stderr.read_line(&mut line) { | ||
| Ok(0) => break, | ||
| Ok(_) => { | ||
| log::debug!(action = "stderr"; "{}", line.trim_end()); | ||
| lines.extend(line.as_bytes().to_vec()); | ||
| if let Ok(mut lines) = stderr_lines_.lock() { | ||
| loop { | ||
| line.clear(); | ||
| match stderr.read_line(&mut line) { | ||
| Ok(0) => break, | ||
| Ok(_) => { | ||
| log::debug!(action = "stderr"; "{}", line.trim_end()); | ||
| lines.extend(line.as_bytes()); | ||
| } | ||
| Err(_) => (), | ||
| } | ||
| Err(_) => (), | ||
| } | ||
| } | ||
| }); | ||
|
|
@@ -423,4 +423,10 @@ mod tests { | |
| fn verify_cli() { | ||
| Cli::command().debug_assert(); | ||
| } | ||
|
|
||
| #[test] | ||
| fn help_output_includes_build() { | ||
| let help = Cli::command().render_help().to_string(); | ||
| assert!(help.contains("Build")); | ||
| } | ||
|
Comment on lines
+427
to
+431
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm kinda failing to see the value of this test but it's not technically wrong so fair enough i guess |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since the logic is still the same we should probably keep the comments as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KushalMeghani1644 can you copy these comments over as well?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes! I missed these!
thoughNow I have added them back now. Sorry for the inconsistency