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

Refactor args #805

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SUBXT_URL=wss://westend-rpc.polkadot.io:443
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/target
**/*.rs.bk
**/.DS_Store
cargo-timing*
cargo-timing*
.env*
!.env-example
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ Use the [`subxt-cli`](./cli) tool to download the metadata for your target runti
```bash
cargo install subxt-cli
```
2. Save the encoded metadata to a file:
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>
```
Comment on lines +17 to +20
Copy link
Collaborator

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).

3. Save the encoded metadata to a file:
```bash
subxt metadata -f bytes > metadata.scale
```

This defaults to querying the metadata of a locally running node on the default `http://localhost:9933/`. If querying
a different node then the `metadata` command accepts a `--url` argument.

## Subxt Documentation

For more details regarding utilizing subxt, please visit the [documentation](https://docs.rs/subxt/latest/subxt/).
Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ subxt-codegen = { version = "0.26.0", path = "../codegen" }
# perform node compatibility
subxt-metadata = { version = "0.26.0", path = "../metadata" }
# parse command line args
clap = { version = "4.1.4", features = ["derive", "cargo"] }
clap = { version = "4.1.4", features = ["derive", "cargo", "env"] }
# colourful error reports
color-eyre = "0.6.1"
# serialize the metadata
Expand Down
63 changes: 35 additions & 28 deletions cli/src/commands/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,49 @@
// see LICENSE for license details.

use clap::Parser as ClapParser;
use color_eyre::eyre;
use jsonrpsee::client_transport::ws::Uri;
use std::{
fs,
io::Read,
path::PathBuf,
};
use subxt_codegen::{
DerivesRegistry,
TypeSubstitutes,
};

use super::source::Source;
use crate::CliOpts;

fn source_parser(s: &str) -> Result<Source, String> {
Source::try_from(s)
}

/// Generate runtime API client code from metadata.
///
/// # Example (with code formatting)
///
/// `subxt codegen | rustfmt --edition=2018 --emit=stdout`
#[derive(Debug, ClapParser)]
pub struct Opts {
/// The url of the substrate node to query for metadata for codegen.
#[clap(name = "url", long, value_parser)]
url: Option<Uri>,
/// The path to the encoded metadata file.
#[clap(short, long, value_parser)]
file: Option<PathBuf>,
pub struct CodegenOpts {
/// The url of the substrate node connect to or the local path of the metadata
#[clap(
name = "source",
short,
long,
default_value = "http://localhost:9933",
env = "SUBXT_URL",
value_parser = source_parser )]
source: Source,

/// Additional derives
#[clap(long = "derive")]
derives: Vec<String>,

/// Additional derives for a given type.
///
/// Example `--derive-for-type my_module::my_type=serde::Serialize`.
#[clap(long = "derive-for-type", value_parser = derive_for_type_parser)]
derives_for_type: Vec<(String, String)>,

/// The `subxt` crate access path in the generated code.
/// Defaults to `::subxt`.
#[clap(long = "crate")]
Expand All @@ -50,26 +60,23 @@ fn derive_for_type_parser(src: &str) -> Result<(String, String), String> {
Ok((ty.to_string(), derive.to_string()))
}

pub async fn run(opts: Opts) -> color_eyre::Result<()> {
let bytes = if let Some(file) = opts.file.as_ref() {
if opts.url.is_some() {
eyre::bail!("specify one of `--url` or `--file` but not both")
};

let mut file = fs::File::open(file)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
bytes
} else {
let url = opts.url.unwrap_or_else(|| {
"http://localhost:9933"
.parse::<Uri>()
.expect("default url is valid")
});
subxt_codegen::utils::fetch_metadata_bytes(&url).await?
pub async fn run(_opts: &CliOpts, cmd_opts: &CodegenOpts) -> color_eyre::Result<()> {
let bytes = match &cmd_opts.source {
Source::File(file) => {
let mut file = fs::File::open(file)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
bytes
}
Source::Url(url) => subxt_codegen::utils::fetch_metadata_bytes(url).await?,
};

codegen(&bytes, opts.derives, opts.derives_for_type, opts.crate_path)?;
codegen(
&bytes,
cmd_opts.derives.clone(),
cmd_opts.derives_for_type.clone(),
cmd_opts.crate_path.clone(),
)?;
Ok(())
}

Expand Down
15 changes: 8 additions & 7 deletions cli/src/commands/compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ use subxt_metadata::{
get_pallet_hash,
};

use crate::CliOpts;

/// Verify metadata compatibility between substrate nodes.
#[derive(Debug, ClapParser)]
pub struct Opts {
pub struct CompatOpts {
/// Urls of the substrate nodes to verify for metadata compatibility.
#[clap(name = "nodes", long, use_value_delimiter = true, value_parser)]
nodes: Vec<Uri>,
Expand All @@ -39,12 +41,12 @@ pub struct Opts {
pallet: Option<String>,
}

pub async fn run(opts: Opts) -> color_eyre::Result<()> {
match opts.pallet {
pub async fn run(_opts: &CliOpts, cmd_opts: &CompatOpts) -> color_eyre::Result<()> {
match &cmd_opts.pallet {
Some(pallet) => {
handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str()).await
handle_pallet_metadata(cmd_opts.nodes.as_slice(), pallet.as_str()).await
}
None => handle_full_metadata(opts.nodes.as_slice()).await,
None => handle_full_metadata(cmd_opts.nodes.as_slice()).await,
}
}

Expand Down Expand Up @@ -93,8 +95,7 @@ async fn handle_full_metadata(nodes: &[Uri]) -> color_eyre::Result<()> {
let metadata = fetch_runtime_metadata(node).await?;
let hash = get_metadata_hash(&metadata);
let hex_hash = hex::encode(hash);
println!("Node {node:?} has metadata hash {hex_hash:?}",);

println!("Node {node:?} has metadata hash {hex_hash:?}");
compatibility_map
.entry(hex_hash)
.or_insert_with(Vec::new)
Expand Down
24 changes: 15 additions & 9 deletions cli/src/commands/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,40 @@
use clap::Parser as ClapParser;
use color_eyre::eyre;
use frame_metadata::RuntimeMetadataPrefixed;
use jsonrpsee::client_transport::ws::Uri;
use scale::Decode;
use std::io::{
self,
Write,
};
use subxt_codegen::utils::fetch_metadata_hex;
use subxt_codegen::utils::{
fetch_metadata_hex,
Uri,
};

use crate::CliOpts;

/// Download metadata from a substrate node, for use with `subxt` codegen.
#[derive(Debug, ClapParser)]
pub struct Opts {
/// The url of the substrate node to query for metadata.
pub struct MetadataOpts {
/// The url of the substrate node connect to
#[clap(
name = "url",
long,
value_parser,
default_value = "http://localhost:9933"
default_value = "http://localhost:9933",
env = "SUBXT_URL"
)]
url: Uri,

/// The format of the metadata to display: `json`, `hex` or `bytes`.
#[clap(long, short, default_value = "bytes")]
format: String,
}

pub async fn run(opts: Opts) -> color_eyre::Result<()> {
let hex_data = fetch_metadata_hex(&opts.url).await?;
pub async fn run(_opts: &CliOpts, cmd_opts: &MetadataOpts) -> color_eyre::Result<()> {
let hex_data = fetch_metadata_hex(&cmd_opts.url).await?;

match opts.format.as_str() {
match cmd_opts.format.as_str() {
"json" => {
let bytes = hex::decode(hex_data.trim_start_matches("0x"))?;
let metadata = <RuntimeMetadataPrefixed as Decode>::decode(&mut &bytes[..])?;
Expand All @@ -51,7 +57,7 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
_ => {
Err(eyre::eyre!(
"Unsupported format `{}`, expected `json`, `hex` or `bytes`",
opts.format
cmd_opts.format
))
}
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
pub mod codegen;
pub mod compatibility;
pub mod metadata;
mod source;
pub mod version;
30 changes: 30 additions & 0 deletions cli/src/commands/source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::path::{
Copy link
Collaborator

@jsdw jsdw Feb 1, 2023

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.

Path,
PathBuf,
};
use subxt_codegen::utils::Uri;

/// The Source can be either a valid existing `File` or a `Url`
#[derive(Debug, PartialEq, Clone)]
pub enum Source {
File(PathBuf),
Url(Uri),
}
Comment on lines +9 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

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

Cool; like this!


impl TryFrom<&str> for Source {
type Error = String;
fn try_from(s: &str) -> Result<Self, Self::Error> {
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}")),
}
Comment on lines +17 to +28
Copy link
Collaborator

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 :)

}
}
15 changes: 7 additions & 8 deletions cli/src/commands/version.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use clap::Parser as ClapParser;

use crate::CliOpts;

/// Prints version information
#[derive(Debug, ClapParser)]
pub struct Opts {}
pub struct Version {}

pub fn run(_opts: Opts) -> color_eyre::Result<()> {
pub fn run(_opts: &CliOpts, _cmd_opts: &Version) -> color_eyre::Result<()> {
let git_hash = env!("GIT_HASH");
println!(
"{} {}-{}",
clap::crate_name!(),
clap::crate_version!(),
git_hash
);
let name = clap::crate_name!();
let version = clap::crate_version!();
println!("{name} {version}-{git_hash}");
Ok(())
}
39 changes: 27 additions & 12 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,41 @@
#![deny(unused_crate_dependencies)]

mod commands;
use clap::Parser as ClapParser;
use clap::{
crate_authors,
crate_version,
ColorChoice,
Parser as ClapParser,
Parser,
};

/// Subxt utilities for interacting with Substrate based nodes.
#[derive(Debug, ClapParser)]
enum Command {
Metadata(commands::metadata::Opts),
Codegen(commands::codegen::Opts),
Compatibility(commands::compatibility::Opts),
Version(commands::version::Opts),
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)]
Copy link
Collaborator

@jsdw jsdw Feb 1, 2023

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?

pub struct CliOpts {
#[clap(subcommand)]
pub subcmd: SubCommand,
Comment on lines +18 to +29
Copy link
Collaborator

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?

}

#[tokio::main]
async fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let args = Command::parse();
let opts = CliOpts::parse();

match args {
Command::Metadata(opts) => commands::metadata::run(opts).await,
Command::Codegen(opts) => commands::codegen::run(opts).await,
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,
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),
Comment on lines +38 to +43
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 stop passing &opts to the run commands now since it's pointless :)

}
}