Skip to content
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

Enable Fuchsia safe command line tool analytics #82

Closed
wants to merge 5 commits into from

Conversation

BobEvans
Copy link

This PR allows Fuchsia DX tools to collect analytics without collecting any user passed data.

Any tool that wants to collect analytics can re-parse the args with an extra flag, "--dump_args_passed" to retrieve a string of the command options suitable for posting to analytics.

@erickt erickt self-requested a review March 25, 2021 03:28
Copy link
Collaborator

@erickt erickt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also make sure that this flag shows up in --help?

README.md Outdated
@@ -174,4 +174,9 @@ struct SubCommandTwo {
}
```

It is also possible to retrieve just the names of the arguments and switches
passed at runtime, by including the switch `--dump_args_passed` in the args, similarly
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than --dump_args_passed, I think it'd be more aligned with the style guide as --dump-args-passed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name updated to match style in latest commit.

Could you also make sure that this flag shows up in --help?

Should this be included in --help output or would it confuse an end user of a tool built using argh since it mirrors back to them the options and subcommands they entered?

@@ -291,7 +291,18 @@ fn impl_from_args_struct(
} else {
__remaining_args
};
#name = Some(<#ty as argh::FromArgs>::from_args(&__command, __remaining_args)?);
if __dump_args {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a feature we have to opt into, rather than it being on by default? I figure this would only be used in certain situations. Something like this (or some better name):

#[derive(FromArgs)]
#[argh(with_dump_args_passed)]
struct Cmd {
    ...
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to reduce code generated by the macro expansion.

One question, in the ffx case, e.g. open-ended set of plugins in separate files defining their own argh subcommand, is there any ordering guarantee in compilation such that the developer would only have to annotate the TopLevel struct with opt-in, or, would each plugin developer need to add the annotation to opt-in as well? I am thinking the latter.

FFX may be a non-standard use case. Do most users of argh define all args in one file?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. According to the ffx docs, all the plugins are declared as subcommands, like:

#[ffx_command()]
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "example", description = "an example")]
pub struct ExampleCommand {
    #[argh(positional)]
    /// example optional positional string parameter
    pub example: Option<String>,
}

I would imagine we'd want something like #[argh(with_dump_args_passed)] would be specified on the main entry point. Do you know how this feature interacts with subcommands?

argh_derive/src/lib.rs Outdated Show resolved Hide resolved
argh_derive/src/lib.rs Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@@ -291,7 +291,18 @@ fn impl_from_args_struct(
} else {
__remaining_args
};
#name = Some(<#ty as argh::FromArgs>::from_args(&__command, __remaining_args)?);
if __dump_args {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command seems pretty specialized. Could we make this an opt-in feature, with something like:

#[derive(FromArgs)]
#[argh(description = "", dump_passed_args)]
struct Cmd { ... }

Happy for suggestions on a better attribute name. We could even make it such that this flag was customizable, with something like:

#[derive(FromArgs)]
#[argh(description = "", dump_passed_args="--dump-passed-args")]
struct Cmd { ... }

To make it easier for people to customize.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above question about a TopLevel opt-in versus per subcommand.

Is the benefit that the macro expansion will not generate the bits of logic to accumulate the passed_args if the user has opted out?

@BobEvans
Copy link
Author

Thanks for the quick turnaround. I updated all the names to comply with the style guide and had a question or two about opt-in and about including a description in the help output.

@BobEvans
Copy link
Author

BobEvans commented Apr 9, 2021

Ping

Bob Evans added 2 commits April 12, 2021 14:56
 Please enter the commit message for your changes. Lines starting
/// The first argument `command_name` is the identifier for the current
/// command, treating each segment as space-separated. The second argument,
/// args, is the rest of the command line arguments.
fn to_args(command_name: &[&str], args: &[&str]) -> String;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you end up using a static method here? I imagined this more to be fn to_args(&self) -> Vec<String>;. This has two advantages:

  • The arguments need to be fully parsed first.
  • This would allow the caller to escape the arguments as necessary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, I mean:

fn to_args(&self) -> Vec<String> {
    vec![]
}

We need to have this be a default method for two reasons. First, adding a non-default method to a trait is not backwards compatible. Second, this defaults custom implementations to not return anything, which is more private.

It is also possible to retrieve just the names of the arguments and switches
passed at runtime, by including the switch `--dump-args-passed` in the args, similarly
to how `--help` can be passed. This elides any user values passed and creates an
output for safely collecting data for analytics.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need --dump-args-passed now that we have FromArgs::to_args(). Then it's up to the caller whether or not to call this function to trigger this functionality, with something like:

#[derive(FromArgs)]
struct Cmd {
    #[argh(rename = "dumped-args-passed")]
    dump_args: bool,
    ...
}

fn main() {
      let cmd = argh::from_env();
      if cmd.dump_args {
          let dumped_args = cmd.to_args();
          println!("{}", cmd.to_args().map(escape_arg).join(" "));
          std::process::exit(0);
      }
}

fn escpe_arg(&str) -> String { ... }

@@ -291,7 +291,18 @@ fn impl_from_args_struct(
} else {
__remaining_args
};
#name = Some(<#ty as argh::FromArgs>::from_args(&__command, __remaining_args)?);
if __dump_args {
__args_dump.push(__subcommand.name.to_string().replace(" ", "\\ "));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned below, I think we should keep the arguments as a vector to allow callers to escape arguments if necessary.

}
)*
unreachable!("no subcommand matched")
}

// Note: This can never get called because enums are only SubCommand containers
fn to_args(command_name: &[&str], args: &[&str]) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to recurse into the enum variants?

/// The first argument `command_name` is the identifier for the current
/// command, treating each segment as space-separated. The second argument,
/// args, is the rest of the command line arguments.
fn to_args(command_name: &[&str], args: &[&str]) -> String;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, I mean:

fn to_args(&self) -> Vec<String> {
    vec![]
}

We need to have this be a default method for two reasons. First, adding a non-default method to a trait is not backwards compatible. Second, this defaults custom implementations to not return anything, which is more private.

@erickt
Copy link
Collaborator

erickt commented Jul 8, 2021

Closing in favor of #91.

@erickt erickt closed this Jul 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants