-
Notifications
You must be signed in to change notification settings - Fork 248
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
Refactor args #805
Refactor args #805
Conversation
cli/src/main.rs
Outdated
default_value = "http://localhost:9933", | ||
env = "SUBXT_URL" | ||
)] | ||
url: Uri, |
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.
I don't like this being moved to be a global arg; yes, perhaps at the moment eac hsubcommand requires a URL, but that definitely need not always be the case (eg why should a version
commad need a url).
I'd much rather that each command specifies every option it cares about, and I don't care if that happens to lead to some code duplication because I think it also makes it easier to maintain :)
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.
(something that you can do I think is to have a custom Uri
type that has these clap attrs on and such so that you can just use the custom type in each command where we want it. I'd prefer that sort of thing (assuming it's actually possible as I described it)
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.
Sure not all the commands may need a url but it is unlikely that the core of subxt
will not work without a URL. With the URL as part of each command, it is rather hard to inject the URL when it is not the default.
Making the URL global makes it available to all commands, they don't have to use it.
It makes the experience for users and devs also nicer as you can define the ENV once and then keep using commands without specifying the --url
in various places.
Even without the ENV, it allows using a command that always start with subxt --url <foobar> <the command>
and users can easily recall their command history and run another command by changing only the command part.
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.
Won't the ENV var part work if the variable is part of some subcommand? (and in that case, if somebody always wants to have some URL set then they can just set the ENV var and not worry about the --url
at all).
And I don't feel that being able to put --url
first is worth it anyway (also, iirc it also requires that URL comes first, unlike all other params which must follow the subcommand, and I don't like that either; it's not consistent).
So I am not convinced I'm afraid!
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.
I am with you for things like file
since most of the commands do not need a file (if that last assumption is wrong, then file should be IMO global as well). I am basing this PR on the fact that subxt
is a tool that mostly will need to connect to an endpoint (besides a few utility commands such as version, help, etc..).
If the args are local to the commands, you cannot call the following: subxt --url <foo> <command_1>
and you must pass: subxt <command_1> --url <foo>
.
Having --url
global however allows both and the support as ENV make the flag vanish from the user's view.
cargo
for instance has lots of global level ENV such as RUSTDOCFLAGS
that is totally irrelevant to some of the cargo commands such as cargo --version
and if the user would happen to provide the flags or have the ENV set, this is simply ignored.
cli/src/main.rs
Outdated
Command::Compatibility(opts) => commands::compatibility::run(opts).await, | ||
Command::Version(opts) => commands::version::run(opts), | ||
match &opts.subcmd { | ||
SubCommand::Metadata(cmd_opts) => commands::metadata::run(&opts, &cmd_opts).await, |
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.
Also you end up with this sort of thing which I find a bit weird; commands::metadata::run(&opts, &cmd_opts)
is now taking in all of the opts, but it's also taking in part of the same thing again, the SubCommand opts.
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.
Yes. I think this is a trade off and also you sharing some global flags (--json
is another typical one) with all the commands. Sure there are cases where it is non-sense (ie subxst --url <bla> version
) but that would nonetheless work and not get in the way of the user.
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.
Are you concerned about the amount of extra data we pass ?
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 nothing like that. For me, I think that in general, moving out the url from each command reduces the clarity and maintainability of the code. It's very simple to understand at present that for each command you'd like to add/work on, you can specify exactly the options that it wants and you can see all options specified in the same file as the command logic.
I don't want us to go down a path that will result in "global" options eg url (because most commands use them) and then the extra muddle in the code that this introduces. For me it's just not worth it. What I am much more in favour of is things like helper structs which encompass "special URL command logic" that we can use to easily gain that URL logic in any command we want to use it for.
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.
I understand your logic but I respectfully disagree for the following reasons:
- code and doc duplication: We can see the drawback currently with the doc of the codegen's url flag that mentions the metadata command, it is probably the remain of a copy/paste
- it suggests that the url for the
metadata
command does not have the same meaning than the url of thecodegen
command. I would be in favor of keeping--url
local if the url would be pointing to different things but for subxt, they point to the endpoint url AFAIK
I don't know all the plans for subxt
and if it is not aimed at becoming primarily a tool to send transactions or query a node reached via the url
of its endpoint, then I would agree that keeping things local and dealing with the duplication'costs could be worth it. But I think subxt
is primarily targetted at connecting to a node, even if some utility commands may be part of it and not require a url.
If you plan on adding later for instance a --json
flag so all outputs are automation friendly, it is MUCH easier to have that also global that spreading dupllication is most or all commands. That also means you'd need to add this --json
flag (or --url
) in every single upcoming new command, which is quite some hassle for a small gain.
While I am not in favour of "globalising" args like URL, I do agree that if we are repeating the same arg in multiple commands (and it has the same meaning), then we can do better. Eventually it might be nice to factor out some repeated commands like so: // opts for some subcommand
#[derive(Debug, ClapParser)]
pub struct Opts {
/// The url/path of the substrate node to query for metadata for codegen.
#[clap(flatten)]
metadata_opts: MetadataOpts,
// ... other opts
}
// This custom struct can live in some general location and be reused anywhere we want to be obtaining metadata
#[derive(Debug, Clone, clap::Args)]
pub struct MetadataOpts {
/// The url of the substrate node to query for metadata.
#[clap(name = "url", long, value_parser, env = "SUBXT_METADATA_URL", conflicts_with = "file")]
url: Option<Uri>,
/// The path to the encoded metadata file.
#[clap(short, long, value_parser, env = "SUBXT_METADATA_PATH", conflicts_with = "url")]
file: Option<PathBuf>,
}
impl MetadataOpts {
// perhaps this even has a helper fn or whatever..
fn fetch_metadata_bytes(&self) -> Result<Vec<u8>, ...> { ... }
} Maybe at some point we want to submit transactions and then we have just a URL that's needed (and can be some shared thing to be consistent toomaybe). Currently we are only interested in obtaining metadata, and so actually "url"/"file" do sortof live together and if they were consistent across commands it'd be nice. Regardless of whether it's a top level or subcommand, Ultimately I think that is the best place to be. I don't want to start making options global. |
Ok, I was about to bring a short-coming of the current PR related to this issue before reading your comment. I removed a piece of code to let clap handles the conflicts between This is a non issue if we bring
and here comes the problem if you keep them local, each new command will have to use this code to say that Using both args as global makes it really trivial and we let clap deal with args, default and conflicts, this is what it is meant to do and makes the code also simpler as you no longer get a Besides the global vs local topic, there is another elegant option which is to merge both
Yes I agree. The 2 topics global vs local and using ENV are not related. Using local variable based on the ENV would already go into the right direction. |
To summarize the options I see (leaving aside the
I excluded the unbalanced option (such as |
I will accept a PR for 4 (2 is already true), but won't go for any form of global args, my reasons against are summarised as:
Overall this is very much bikeshedding IMO given how small the cli tool is and not something I want to be spending much time thinking on :) |
OK, I will make changes towards option 4. then. Note however that the following is not correct:
with
|
Ah that's cool; I geuss |
- add new Source enum for File | Url - move back Source arg to local args for the metadata and codegen commands
2. Set the URL you want to use, you may skip this step and the default `http://localhost:9933/` will be used: | ||
``` | ||
export SUBXT_URL=<your endpoint> | ||
``` |
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.
I prefer the original doc format (in other words; show the simplest steps here and mention below using --url
or the new SUBXT_URL
env var to set the URL).
SubCommand::Metadata(cmd_opts) => commands::metadata::run(&opts, cmd_opts).await, | ||
SubCommand::Codegen(cmd_opts) => commands::codegen::run(&opts, cmd_opts).await, | ||
SubCommand::Compatibility(cmd_opts) => { | ||
commands::compatibility::run(&opts, cmd_opts).await | ||
} | ||
SubCommand::Version(cmd_opts) => commands::version::run(&opts, cmd_opts), |
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.
Can we stop passing &opts
to the run
commands now since it's pointless :)
pub enum SubCommand { | ||
Metadata(commands::metadata::MetadataOpts), | ||
Codegen(commands::codegen::CodegenOpts), | ||
Compatibility(commands::compatibility::CompatOpts), | ||
Version(commands::version::Version), | ||
} | ||
|
||
#[derive(Debug, Parser)] | ||
#[clap(version = crate_version!(), author = crate_authors!(), color=ColorChoice::Always)] | ||
pub struct CliOpts { | ||
#[clap(subcommand)] | ||
pub subcmd: SubCommand, |
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.
Is there any advantage to these being two structs and not one struct like before now?
} | ||
|
||
#[derive(Debug, Parser)] | ||
#[clap(version = crate_version!(), author = crate_authors!(), color=ColorChoice::Always)] |
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.
ColorChoice::Always
may be annoying for this in CI scripts (ie lots of command codes emitted); perhaps best to leave it at the default?
match s { | ||
s if (s.starts_with("ws://") | ||
| s.starts_with("wss://") | ||
| s.starts_with("http://") | ||
| s.starts_with("https://")) | ||
&& s.parse::<Uri>().is_ok() => | ||
{ | ||
Ok(Source::Url(s.parse().unwrap())) | ||
} | ||
p if Path::new(p).exists() => Ok(Source::File(PathBuf::from(p))), | ||
_ => Err(format!("File does not exist: {s}")), | ||
} |
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.
I'd prefer to just stick with if
over a match
that is just mimicking if's :)
pub enum Source { | ||
File(PathBuf), | ||
Url(Uri), | ||
} |
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.
Cool; like this!
@@ -0,0 +1,30 @@ | |||
use std::path::{ |
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.
source.rs
isn't a command; it's some utility thing. Can we move it into a utils.rs
or similar please?
I'd like to keep the convention that all files in commands
are actually subcommands that the otol can run, for clarity.
I'll close this for now (and can understand that it was a lot more back and forth than you probably wanted, so sorry about that!). It'd be nice to take the |
Yeah no worry, I just did not have the bandwidth to allocate to that so far. |
File
andUrl
by anenum
:Source
Source
to be passed asENV