mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-17 05:31:02 +00:00
Cli tool tests (#977)
* add tests and fix some minor things * implement output writer and some tests * formatting * adjust tests and formatting * clippy fix * cargo fmt * revert change in substrate runner * clippy suggestions * mode make_type to test mod
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
use crate::utils::FileOrUrl;
|
use crate::utils::FileOrUrl;
|
||||||
use clap::Parser as ClapParser;
|
use clap::Parser as ClapParser;
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use subxt_codegen::{DerivesRegistry, TypeSubstitutes, TypeSubstitutionError};
|
use subxt_codegen::{DerivesRegistry, TypeSubstitutes, TypeSubstitutionError};
|
||||||
|
|
||||||
/// Generate runtime API client code from metadata.
|
/// Generate runtime API client code from metadata.
|
||||||
@@ -87,7 +88,7 @@ fn substitute_type_parser(src: &str) -> Result<(String, String), String> {
|
|||||||
Ok((from.to_string(), to.to_string()))
|
Ok((from.to_string(), to.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
|
||||||
let bytes = opts.file_or_url.fetch().await?;
|
let bytes = opts.file_or_url.fetch().await?;
|
||||||
|
|
||||||
codegen(
|
codegen(
|
||||||
@@ -102,6 +103,7 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
|||||||
opts.runtime_types_only,
|
opts.runtime_types_only,
|
||||||
opts.no_default_derives,
|
opts.no_default_derives,
|
||||||
opts.no_default_substitutions,
|
opts.no_default_substitutions,
|
||||||
|
output,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -128,6 +130,7 @@ fn codegen(
|
|||||||
runtime_types_only: bool,
|
runtime_types_only: bool,
|
||||||
no_default_derives: bool,
|
no_default_derives: bool,
|
||||||
no_default_substitutions: bool,
|
no_default_substitutions: bool,
|
||||||
|
output: &mut impl std::io::Write,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
let item_mod = syn::parse_quote!(
|
let item_mod = syn::parse_quote!(
|
||||||
pub mod api {}
|
pub mod api {}
|
||||||
@@ -190,15 +193,8 @@ fn codegen(
|
|||||||
crate_path,
|
crate_path,
|
||||||
should_gen_docs,
|
should_gen_docs,
|
||||||
runtime_types_only,
|
runtime_types_only,
|
||||||
);
|
)
|
||||||
match runtime_api {
|
.map_err(|code_gen_err| eyre!("{code_gen_err}"))?;
|
||||||
Ok(runtime_api) => println!("{runtime_api}"),
|
writeln!(output, "{runtime_api}")?;
|
||||||
Err(e) => {
|
|
||||||
// Print the error directly to avoid implementing `Send + Sync` on `CodegenError`.
|
|
||||||
use color_eyre::owo_colors::OwoColorize;
|
|
||||||
println!("{}", e.to_string().red())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,12 +38,13 @@ pub struct Opts {
|
|||||||
version: MetadataVersion,
|
version: MetadataVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
|
||||||
match opts.pallet {
|
match opts.pallet {
|
||||||
Some(pallet) => {
|
Some(pallet) => {
|
||||||
handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str(), opts.version).await
|
handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str(), opts.version, output)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
None => handle_full_metadata(opts.nodes.as_slice(), opts.version).await,
|
None => handle_full_metadata(opts.nodes.as_slice(), opts.version, output).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ async fn handle_pallet_metadata(
|
|||||||
nodes: &[Uri],
|
nodes: &[Uri],
|
||||||
name: &str,
|
name: &str,
|
||||||
version: MetadataVersion,
|
version: MetadataVersion,
|
||||||
|
output: &mut impl std::io::Write,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -67,7 +69,10 @@ async fn handle_pallet_metadata(
|
|||||||
Some(pallet_metadata) => {
|
Some(pallet_metadata) => {
|
||||||
let hash = pallet_metadata.hash();
|
let hash = pallet_metadata.hash();
|
||||||
let hex_hash = hex::encode(hash);
|
let hex_hash = hex::encode(hash);
|
||||||
println!("Node {node:?} has pallet metadata hash {hex_hash:?}");
|
writeln!(
|
||||||
|
output,
|
||||||
|
"Node {node:?} has pallet metadata hash {hex_hash:?}"
|
||||||
|
)?;
|
||||||
|
|
||||||
compatibility
|
compatibility
|
||||||
.pallet_present
|
.pallet_present
|
||||||
@@ -81,22 +86,27 @@ async fn handle_pallet_metadata(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
writeln!(
|
||||||
|
output,
|
||||||
"\nCompatible nodes by pallet\n{}",
|
"\nCompatible nodes by pallet\n{}",
|
||||||
serde_json::to_string_pretty(&compatibility)
|
serde_json::to_string_pretty(&compatibility)
|
||||||
.context("Failed to parse compatibility map")?
|
.context("Failed to parse compatibility map")?
|
||||||
);
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_full_metadata(nodes: &[Uri], version: MetadataVersion) -> color_eyre::Result<()> {
|
async fn handle_full_metadata(
|
||||||
|
nodes: &[Uri],
|
||||||
|
version: MetadataVersion,
|
||||||
|
output: &mut impl std::io::Write,
|
||||||
|
) -> color_eyre::Result<()> {
|
||||||
let mut compatibility_map: HashMap<String, Vec<String>> = HashMap::new();
|
let mut compatibility_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
for node in nodes.iter() {
|
for node in nodes.iter() {
|
||||||
let metadata = fetch_runtime_metadata(node, version).await?;
|
let metadata = fetch_runtime_metadata(node, version).await?;
|
||||||
let hash = metadata.hasher().hash();
|
let hash = metadata.hasher().hash();
|
||||||
let hex_hash = hex::encode(hash);
|
let hex_hash = hex::encode(hash);
|
||||||
println!("Node {node:?} has metadata hash {hex_hash:?}",);
|
writeln!(output, "Node {node:?} has metadata hash {hex_hash:?}",)?;
|
||||||
|
|
||||||
compatibility_map
|
compatibility_map
|
||||||
.entry(hex_hash)
|
.entry(hex_hash)
|
||||||
@@ -104,11 +114,12 @@ async fn handle_full_metadata(nodes: &[Uri], version: MetadataVersion) -> color_
|
|||||||
.push(node.to_string());
|
.push(node.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
writeln!(
|
||||||
|
output,
|
||||||
"\nCompatible nodes\n{}",
|
"\nCompatible nodes\n{}",
|
||||||
serde_json::to_string_pretty(&compatibility_map)
|
serde_json::to_string_pretty(&compatibility_map)
|
||||||
.context("Failed to parse compatibility map")?
|
.context("Failed to parse compatibility map")?
|
||||||
);
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ use color_eyre::eyre::eyre;
|
|||||||
use scale_info::form::PortableForm;
|
use scale_info::form::PortableForm;
|
||||||
use scale_info::{PortableRegistry, Type, TypeDef, TypeDefVariant};
|
use scale_info::{PortableRegistry, Type, TypeDef, TypeDefVariant};
|
||||||
use scale_value::{Composite, ValueDef};
|
use scale_value::{Composite, ValueDef};
|
||||||
use std::fmt::Write;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::write;
|
|
||||||
|
|
||||||
use subxt::tx;
|
use subxt::tx;
|
||||||
use subxt::utils::H256;
|
use subxt::utils::H256;
|
||||||
@@ -26,10 +24,11 @@ pub struct CallsSubcommand {
|
|||||||
trailing_args: Vec<String>,
|
trailing_args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn explore_calls(
|
pub fn explore_calls(
|
||||||
command: CallsSubcommand,
|
command: CallsSubcommand,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
pallet_metadata: PalletMetadata,
|
pallet_metadata: PalletMetadata,
|
||||||
|
output: &mut impl std::io::Write,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
let pallet_name = pallet_metadata.name();
|
let pallet_name = pallet_metadata.name();
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ pub(crate) fn explore_calls(
|
|||||||
// if no call specified, show user the calls to choose from:
|
// if no call specified, show user the calls to choose from:
|
||||||
let Some(call_name) = command.call else {
|
let Some(call_name) = command.call else {
|
||||||
let available_calls = print_available_calls(calls_enum_type_def, pallet_name);
|
let available_calls = print_available_calls(calls_enum_type_def, pallet_name);
|
||||||
println!("Usage:\n subxt explore {pallet_name} calls <CALL>\n explore a specific call within this pallet\n\n{available_calls}", );
|
writeln!(output, "Usage:\n subxt explore {pallet_name} calls <CALL>\n explore a specific call within this pallet\n\n{available_calls}")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,13 +59,19 @@ pub(crate) fn explore_calls(
|
|||||||
type_description = with_indent(type_description, 4);
|
type_description = with_indent(type_description, 4);
|
||||||
let mut type_examples = print_type_examples(&call.fields, metadata.types(), "SCALE_VALUE")?;
|
let mut type_examples = print_type_examples(&call.fields, metadata.types(), "SCALE_VALUE")?;
|
||||||
type_examples = with_indent(type_examples, 4);
|
type_examples = with_indent(type_examples, 4);
|
||||||
let mut output = String::new();
|
writeln!(output, "Usage:")?;
|
||||||
write!(output, "Usage:\n subxt explore {pallet_name} calls {call_name} <SCALE_VALUE>\n construct the call by providing a valid argument\n\n")?;
|
writeln!(
|
||||||
write!(
|
output,
|
||||||
|
" subxt explore {pallet_name} calls {call_name} <SCALE_VALUE>"
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
output,
|
||||||
|
" construct the call by providing a valid argument\n"
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"The call expect expects a <SCALE_VALUE> with this shape:\n{type_description}\n\n{}\n\nYou may need to surround the value in single quotes when providing it as an argument."
|
"The call expect expects a <SCALE_VALUE> with this shape:\n{type_description}\n\n{}\n\nYou may need to surround the value in single quotes when providing it as an argument."
|
||||||
, &type_examples[4..])?;
|
, &type_examples[4..])?;
|
||||||
println!("{output}");
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +82,7 @@ pub(crate) fn explore_calls(
|
|||||||
let payload = tx::dynamic(pallet_name, call_name, value_as_composite);
|
let payload = tx::dynamic(pallet_name, call_name, value_as_composite);
|
||||||
let unsigned_extrinsic = offline_client.tx().create_unsigned(&payload)?;
|
let unsigned_extrinsic = offline_client.tx().create_unsigned(&payload)?;
|
||||||
let hex_bytes = format!("0x{}", hex::encode(unsigned_extrinsic.encoded()));
|
let hex_bytes = format!("0x{}", hex::encode(unsigned_extrinsic.encoded()));
|
||||||
println!("Encoded call data:\n {hex_bytes}");
|
writeln!(output, "Encoded call data:\n {hex_bytes}")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,64 @@
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::write;
|
|
||||||
use subxt::metadata::{types::PalletMetadata, Metadata};
|
use subxt::metadata::{types::PalletMetadata, Metadata};
|
||||||
|
|
||||||
use crate::utils::type_description::print_type_description;
|
use crate::utils::type_description::print_type_description;
|
||||||
use crate::utils::{print_docs_with_indent, with_indent};
|
use crate::utils::{print_first_paragraph_with_indent, with_indent};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
#[derive(Debug, Clone, Args)]
|
||||||
pub struct ConstantsSubcommand {
|
pub struct ConstantsSubcommand {
|
||||||
constant: Option<String>,
|
constant: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn explore_constants(
|
pub fn explore_constants(
|
||||||
command: ConstantsSubcommand,
|
command: ConstantsSubcommand,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
pallet_metadata: PalletMetadata,
|
pallet_metadata: PalletMetadata,
|
||||||
|
output: &mut impl std::io::Write,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
let pallet_name = pallet_metadata.name();
|
let pallet_name = pallet_metadata.name();
|
||||||
let Some(constant_name) = command.constant else {
|
let Some(constant_name) = command.constant else {
|
||||||
let available_constants = print_available_constants(pallet_metadata, pallet_name);
|
let available_constants = print_available_constants(pallet_metadata, pallet_name);
|
||||||
println!("Usage:\n subxt explore {pallet_name} constants <CONSTANT>\n explore a specific call within this pallet\n\n{available_constants}", );
|
writeln!(output, "Usage:")?;
|
||||||
|
writeln!(output, " subxt explore {pallet_name} constants <CONSTANT>")?;
|
||||||
|
writeln!(output, " explore a specific call within this pallet\n\n{available_constants}")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// if specified constant is wrong, show user the constants to choose from (but this time as an error):
|
// if specified constant is wrong, show user the constants to choose from (but this time as an error):
|
||||||
let Some(constant) = pallet_metadata.constants().find(|constant| constant.name().to_lowercase() == constant_name.to_lowercase()) else {
|
let Some(constant) = pallet_metadata.constants().find(|constant| constant.name().to_lowercase() == constant_name.to_lowercase()) else {
|
||||||
let available_constants = print_available_constants(pallet_metadata, pallet_name);
|
let available_constants = print_available_constants(pallet_metadata, pallet_name);
|
||||||
let description = format!("Usage:\n subxt explore {pallet_name} constants <CONSTANT>\n explore a specific call within this pallet\n\n{available_constants}", );
|
let mut description = "Usage:".to_string();
|
||||||
|
writeln!(description, " subxt explore {pallet_name} constants <CONSTANT>")?;
|
||||||
|
writeln!(description, " explore a specific call within this pallet\n\n{available_constants}")?;
|
||||||
let err = eyre!("constant \"{constant_name}\" not found in \"{pallet_name}\" pallet!\n\n{description}");
|
let err = eyre!("constant \"{constant_name}\" not found in \"{pallet_name}\" pallet!\n\n{description}");
|
||||||
return Err(err);
|
return Err(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
// docs
|
// docs
|
||||||
let mut output = String::new();
|
let doc_string = print_first_paragraph_with_indent(constant.docs(), 4);
|
||||||
let doc_string = print_docs_with_indent(constant.docs(), 4);
|
|
||||||
if !doc_string.is_empty() {
|
if !doc_string.is_empty() {
|
||||||
write!(output, "Description:\n{doc_string}")?;
|
writeln!(output, "Description:\n{doc_string}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// shape
|
// shape
|
||||||
let mut type_description = print_type_description(&constant.ty(), metadata.types())?;
|
let mut type_description = print_type_description(&constant.ty(), metadata.types())?;
|
||||||
type_description = with_indent(type_description, 4);
|
type_description = with_indent(type_description, 4);
|
||||||
write!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"\n\nThe constant has the following shape:\n{type_description}"
|
"\nThe constant has the following shape:\n{type_description}"
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// value
|
// value
|
||||||
let scale_val =
|
let scale_val =
|
||||||
scale_value::scale::decode_as_type(&mut constant.value(), constant.ty(), metadata.types())?;
|
scale_value::scale::decode_as_type(&mut constant.value(), constant.ty(), metadata.types())?;
|
||||||
write!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"\n\nThe value of the constant is:\n {}",
|
"\nThe value of the constant is:\n {}",
|
||||||
scale_value::stringify::to_string(&scale_val)
|
scale_value::stringify::to_string(&scale_val)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("{output}");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use crate::utils::{print_docs_with_indent, FileOrUrl};
|
use crate::utils::{print_first_paragraph_with_indent, FileOrUrl};
|
||||||
use clap::{Parser as ClapParser, Subcommand};
|
use clap::{Parser as ClapParser, Subcommand};
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use std::write;
|
use std::write;
|
||||||
|
|
||||||
use codec::Decode;
|
use codec::Decode;
|
||||||
@@ -25,22 +24,22 @@ mod storage;
|
|||||||
/// ## Pallets
|
/// ## Pallets
|
||||||
///
|
///
|
||||||
/// Show the pallets that are available:
|
/// Show the pallets that are available:
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore --file=polkadot_metadata.scale
|
/// subxt explore --file=polkadot_metadata.scale
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Calls
|
/// ## Calls
|
||||||
///
|
///
|
||||||
/// Show the calls in a pallet:
|
/// Show the calls in a pallet:
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore Balances calls
|
/// subxt explore Balances calls
|
||||||
/// ```
|
/// ```
|
||||||
/// Show the call parameters a call expects:
|
/// Show the call parameters a call expects:
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore Balances calls transfer
|
/// subxt explore Balances calls transfer
|
||||||
/// ```
|
/// ```
|
||||||
/// Create an unsigned extrinsic from a scale value, validate it and output its hex representation
|
/// Create an unsigned extrinsic from a scale value, validate it and output its hex representation
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore Grandpa calls note_stalled { "delay": 5, "best_finalized_block_number": 5 }
|
/// subxt explore Grandpa calls note_stalled { "delay": 5, "best_finalized_block_number": 5 }
|
||||||
/// # Encoded call data:
|
/// # Encoded call data:
|
||||||
/// # 0x2c0411020500000005000000
|
/// # 0x2c0411020500000005000000
|
||||||
@@ -51,17 +50,17 @@ mod storage;
|
|||||||
/// ## Constants
|
/// ## Constants
|
||||||
///
|
///
|
||||||
/// Show the constants in a pallet:
|
/// Show the constants in a pallet:
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore Balances constants
|
/// subxt explore Balances constants
|
||||||
/// ```
|
/// ```
|
||||||
/// ## Storage
|
/// ## Storage
|
||||||
///
|
///
|
||||||
/// Show the storage entries in a pallet
|
/// Show the storage entries in a pallet
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore Alliance storage
|
/// subxt explore Alliance storage
|
||||||
/// ```
|
/// ```
|
||||||
/// Show the types and value of a specific storage entry
|
/// Show the types and value of a specific storage entry
|
||||||
/// ```
|
/// ```text
|
||||||
/// subxt explore Alliance storage Announcements [KEY_SCALE_VALUE]
|
/// subxt explore Alliance storage Announcements [KEY_SCALE_VALUE]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@@ -82,7 +81,7 @@ pub enum PalletSubcommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// cargo run -- explore --file=../artifacts/polkadot_metadata.scale
|
/// cargo run -- explore --file=../artifacts/polkadot_metadata.scale
|
||||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
|
||||||
// get the metadata
|
// get the metadata
|
||||||
let bytes = opts.file_or_url.fetch().await?;
|
let bytes = opts.file_or_url.fetch().await?;
|
||||||
let metadata = Metadata::decode(&mut &bytes[..])?;
|
let metadata = Metadata::decode(&mut &bytes[..])?;
|
||||||
@@ -90,7 +89,10 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
|||||||
// if no pallet specified, show user the pallets to choose from:
|
// if no pallet specified, show user the pallets to choose from:
|
||||||
let Some(pallet_name) = opts.pallet else {
|
let Some(pallet_name) = opts.pallet else {
|
||||||
let available_pallets = print_available_pallets(&metadata);
|
let available_pallets = print_available_pallets(&metadata);
|
||||||
println!("Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\n{available_pallets}", );
|
writeln!(output, "Usage:", )?;
|
||||||
|
writeln!(output, " subxt explore <PALLET>", )?;
|
||||||
|
writeln!(output, " explore a specific pallet", )?;
|
||||||
|
writeln!(output, "\n{available_pallets}", )?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,28 +103,31 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
// if correct pallet was specified but no subcommand, instruct the user how to proceed:
|
// if correct pallet was specified but no subcommand, instruct the user how to proceed:
|
||||||
let Some(pallet_subcomand) = opts.pallet_subcommand else {
|
let Some(pallet_subcomand) = opts.pallet_subcommand else {
|
||||||
let docs_string = print_docs_with_indent(pallet_metadata.docs(), 4);
|
let docs_string = print_first_paragraph_with_indent(pallet_metadata.docs(), 4);
|
||||||
let mut output = String::new();
|
|
||||||
if !docs_string.is_empty() {
|
if !docs_string.is_empty() {
|
||||||
write!(output, "Description:\n{docs_string}")?;
|
writeln!(output, "Description:\n{docs_string}")?;
|
||||||
}
|
}
|
||||||
write!(output, "Usage:")?;
|
writeln!(output, "Usage:")?;
|
||||||
write!(output, "\n subxt explore {pallet_name} calls\n explore the calls that can be made into this pallet")?;
|
writeln!(output, " subxt explore {pallet_name} calls")?;
|
||||||
write!(output, "\n subxt explore {pallet_name} constants\n explore the constants held in this pallet")?;
|
writeln!(output, " explore the calls that can be made into this pallet")?;
|
||||||
write!(output, "\n subxt explore {pallet_name} storage\n explore the storage values held in this pallet")?;
|
writeln!(output, " subxt explore {pallet_name} constants")?;
|
||||||
println!("{output}");
|
writeln!(output, " explore the constants held in this pallet")?;
|
||||||
|
writeln!(output, " subxt explore {pallet_name} storage")?;
|
||||||
|
writeln!(output, " explore the storage values held in this pallet")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
match pallet_subcomand {
|
match pallet_subcomand {
|
||||||
PalletSubcommand::Calls(command) => explore_calls(command, &metadata, pallet_metadata),
|
PalletSubcommand::Calls(command) => {
|
||||||
|
explore_calls(command, &metadata, pallet_metadata, output)
|
||||||
|
}
|
||||||
PalletSubcommand::Constants(command) => {
|
PalletSubcommand::Constants(command) => {
|
||||||
explore_constants(command, &metadata, pallet_metadata)
|
explore_constants(command, &metadata, pallet_metadata, output)
|
||||||
}
|
}
|
||||||
PalletSubcommand::Storage(command) => {
|
PalletSubcommand::Storage(command) => {
|
||||||
// if the metadata came from some url, we use that same url to make storage calls against.
|
// if the metadata came from some url, we use that same url to make storage calls against.
|
||||||
let node_url = opts.file_or_url.url.map(|url| url.to_string());
|
let node_url = opts.file_or_url.url.map(|url| url.to_string());
|
||||||
explore_storage(command, &metadata, pallet_metadata, node_url).await
|
explore_storage(command, &metadata, pallet_metadata, node_url, output).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,3 +145,63 @@ fn print_available_pallets(metadata: &Metadata) -> String {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::{run, Opts};
|
||||||
|
|
||||||
|
async fn simulate_run(cli_command: &str) -> color_eyre::Result<String> {
|
||||||
|
let mut args = vec![
|
||||||
|
"explore",
|
||||||
|
"--file=../artifacts/polkadot_metadata_small.scale",
|
||||||
|
];
|
||||||
|
let mut split: Vec<&str> = cli_command.split(' ').filter(|e| !e.is_empty()).collect();
|
||||||
|
args.append(&mut split);
|
||||||
|
let opts: Opts = clap::Parser::try_parse_from(args)?;
|
||||||
|
let mut output: Vec<u8> = Vec::new();
|
||||||
|
run(opts, &mut output)
|
||||||
|
.await
|
||||||
|
.map(|_| String::from_utf8(output).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_commands() {
|
||||||
|
// show pallets:
|
||||||
|
let output = simulate_run("").await;
|
||||||
|
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n Staking\n System\n");
|
||||||
|
// if incorrect pallet, error:
|
||||||
|
let output = simulate_run("abc123").await;
|
||||||
|
assert!(output.is_err());
|
||||||
|
// if correct pallet, show options (calls, constants, storage)
|
||||||
|
let output = simulate_run("Balances").await;
|
||||||
|
assert_eq!(output.unwrap(), "Usage:\n subxt explore Balances calls\n explore the calls that can be made into this pallet\n subxt explore Balances constants\n explore the constants held in this pallet\n subxt explore Balances storage\n explore the storage values held in this pallet\n");
|
||||||
|
// check that exploring calls, storage entries and constants is possible:
|
||||||
|
let output = simulate_run("Balances calls").await;
|
||||||
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls <CALL>\n explore a specific call within this pallet\n\nAvailable <CALL>'s in the \"Balances\" pallet:\n"));
|
||||||
|
let output = simulate_run("Balances storage").await;
|
||||||
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances storage <STORAGE_ENTRY>\n view details for a specific storage entry\n\nAvailable <STORAGE_ENTRY>'s in the \"Balances\" pallet:\n"));
|
||||||
|
let output = simulate_run("Balances constants").await;
|
||||||
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances constants <CONSTANT>\n explore a specific call within this pallet\n\nAvailable <CONSTANT>'s in the \"Balances\" pallet:\n"));
|
||||||
|
// check that invalid subcommands don't work:
|
||||||
|
let output = simulate_run("Balances abc123").await;
|
||||||
|
assert!(output.is_err());
|
||||||
|
// check that we can explore a certain call:
|
||||||
|
let output = simulate_run("Balances calls transfer").await;
|
||||||
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls transfer <SCALE_VALUE>\n construct the call by providing a valid argument\n\nThe call expect expects a <SCALE_VALUE> with this shape:\n {\n dest: enum MultiAddress"));
|
||||||
|
// check that unsigned extrinsic can be constructed:
|
||||||
|
let output =
|
||||||
|
simulate_run("Balances calls transfer {\"dest\":v\"Raw\"((255,255, 255)),\"value\":0}")
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
output.unwrap(),
|
||||||
|
"Encoded call data:\n 0x24040507020cffffff00\n"
|
||||||
|
);
|
||||||
|
// check that we can explore a certain constant:
|
||||||
|
let output = simulate_run("Balances constants ExistentialDeposit").await;
|
||||||
|
assert_eq!(output.unwrap(), "Description:\n The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!\n\nThe constant has the following shape:\n u128\n\nThe value of the constant is:\n 10000000000\n");
|
||||||
|
// check that we can explore a certain storage entry:
|
||||||
|
let output = simulate_run("System storage Account").await;
|
||||||
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore System storage Account <KEY_VALUE>\n\nDescription:\n The full account information for a particular account ID."));
|
||||||
|
// in the future we could also integrate with substrate-testrunner to spawn up a node and send an actual storage query to it: e.g. `subxt explore System storage Digest`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use subxt::{
|
|||||||
|
|
||||||
use crate::utils::type_description::print_type_description;
|
use crate::utils::type_description::print_type_description;
|
||||||
use crate::utils::type_example::print_type_examples;
|
use crate::utils::type_example::print_type_examples;
|
||||||
use crate::utils::{print_docs_with_indent, with_indent};
|
use crate::utils::{print_first_paragraph_with_indent, with_indent};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
#[derive(Debug, Clone, Args)]
|
||||||
pub struct StorageSubcommand {
|
pub struct StorageSubcommand {
|
||||||
@@ -23,25 +23,26 @@ pub struct StorageSubcommand {
|
|||||||
trailing_args: Vec<String>,
|
trailing_args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn explore_storage(
|
pub async fn explore_storage(
|
||||||
command: StorageSubcommand,
|
command: StorageSubcommand,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
pallet_metadata: PalletMetadata<'_>,
|
pallet_metadata: PalletMetadata<'_>,
|
||||||
custom_online_client_url: Option<String>,
|
custom_online_client_url: Option<String>,
|
||||||
|
output: &mut impl std::io::Write,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
let pallet_name = pallet_metadata.name();
|
let pallet_name = pallet_metadata.name();
|
||||||
let trailing_args = command.trailing_args.join(" ");
|
let trailing_args = command.trailing_args.join(" ");
|
||||||
let trailing_args = trailing_args.trim();
|
let trailing_args = trailing_args.trim();
|
||||||
|
|
||||||
let Some(storage_metadata) = pallet_metadata.storage() else {
|
let Some(storage_metadata) = pallet_metadata.storage() else {
|
||||||
println!("The \"{pallet_name}\" pallet has no storage entries.");
|
writeln!(output, "The \"{pallet_name}\" pallet has no storage entries.")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// if no storage entry specified, show user the calls to choose from:
|
// if no storage entry specified, show user the calls to choose from:
|
||||||
let Some(entry_name) = command.storage_entry else {
|
let Some(entry_name) = command.storage_entry else {
|
||||||
let storage_entries = print_available_storage_entries(storage_metadata, pallet_name);
|
let storage_entries = print_available_storage_entries(storage_metadata, pallet_name);
|
||||||
println!("Usage:\n subxt explore {pallet_name} storage <STORAGE_ENTRY>\n view details for a specific storage entry\n\n{storage_entries}");
|
writeln!(output, "Usage:\n subxt explore {pallet_name} storage <STORAGE_ENTRY>\n view details for a specific storage entry\n\n{storage_entries}")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,20 +60,18 @@ pub(crate) async fn explore_storage(
|
|||||||
} => (*value_ty, Some(*key_ty)),
|
} => (*value_ty, Some(*key_ty)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// get the type and type description for the return and key type:
|
|
||||||
let mut output = String::new();
|
|
||||||
|
|
||||||
// only inform user about usage if a key can be provided:
|
// only inform user about usage if a key can be provided:
|
||||||
if key_ty_id.is_some() && trailing_args.is_empty() {
|
if key_ty_id.is_some() && trailing_args.is_empty() {
|
||||||
write!(
|
writeln!(output, "Usage:")?;
|
||||||
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"Usage:\n subxt explore {pallet_name} storage {entry_name} <KEY_VALUE>\n\n"
|
" subxt explore {pallet_name} storage {entry_name} <KEY_VALUE>\n"
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let docs_string = print_docs_with_indent(storage.docs(), 4);
|
let docs_string = print_first_paragraph_with_indent(storage.docs(), 4);
|
||||||
if !docs_string.is_empty() {
|
if !docs_string.is_empty() {
|
||||||
write!(output, "Description:\n{docs_string}")?;
|
writeln!(output, "Description:\n{docs_string}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// inform user about shape of key if it can be provided:
|
// inform user about shape of key if it can be provided:
|
||||||
@@ -81,15 +80,15 @@ pub(crate) async fn explore_storage(
|
|||||||
key_ty_description = with_indent(key_ty_description, 4);
|
key_ty_description = with_indent(key_ty_description, 4);
|
||||||
let mut key_ty_examples = print_type_examples(&key_ty_id, metadata.types(), "<KEY_VALUE>")?;
|
let mut key_ty_examples = print_type_examples(&key_ty_id, metadata.types(), "<KEY_VALUE>")?;
|
||||||
key_ty_examples = with_indent(key_ty_examples, 4);
|
key_ty_examples = with_indent(key_ty_examples, 4);
|
||||||
write!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"\n\nThe <KEY_VALUE> has the following shape:\n {key_ty_description}\n\n{}",
|
"\nThe <KEY_VALUE> has the following shape:\n {key_ty_description}\n"
|
||||||
&key_ty_examples[4..]
|
|
||||||
)?;
|
)?;
|
||||||
|
writeln!(output, "{}", &key_ty_examples[4..])?;
|
||||||
} else {
|
} else {
|
||||||
write!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"\n\nThe constant can be accessed without providing a key."
|
"The constant can be accessed without providing a key."
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,9 +98,9 @@ pub(crate) async fn explore_storage(
|
|||||||
} else {
|
} else {
|
||||||
return_ty_description
|
return_ty_description
|
||||||
};
|
};
|
||||||
write!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"\n\nThe storage entry has the following shape: {}",
|
"\nThe storage entry has the following shape: {}",
|
||||||
return_ty_description
|
return_ty_description
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -110,7 +109,7 @@ pub(crate) async fn explore_storage(
|
|||||||
let key_scale_values = if let Some(key_ty_id) = key_ty_id.filter(|_| !trailing_args.is_empty())
|
let key_scale_values = if let Some(key_ty_id) = key_ty_id.filter(|_| !trailing_args.is_empty())
|
||||||
{
|
{
|
||||||
let key_scale_value = scale_value::stringify::from_str(trailing_args).0.map_err(|err| eyre!("scale_value::stringify::from_str led to a ParseError.\n\ntried parsing: \"{}\"\n\n{}", trailing_args, err))?;
|
let key_scale_value = scale_value::stringify::from_str(trailing_args).0.map_err(|err| eyre!("scale_value::stringify::from_str led to a ParseError.\n\ntried parsing: \"{}\"\n\n{}", trailing_args, err))?;
|
||||||
write!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"\n\nYou submitted the following value as a key:\n{}",
|
"\n\nYou submitted the following value as a key:\n{}",
|
||||||
with_indent(scale_value::stringify::to_string(&key_scale_value), 4)
|
with_indent(scale_value::stringify::to_string(&key_scale_value), 4)
|
||||||
@@ -129,9 +128,8 @@ pub(crate) async fn explore_storage(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if key_ty_id.is_none() && !trailing_args.is_empty() {
|
if key_ty_id.is_none() && !trailing_args.is_empty() {
|
||||||
write!(output, "\n\nWarning: You submitted the following value as a key, but it will be ignored, because the storage entry does not require a key: \"{}\"", trailing_args)?;
|
writeln!(output, "\n\nWarning: You submitted the following value as a key, but it will be ignored, because the storage entry does not require a key: \"{}\"", trailing_args)?;
|
||||||
}
|
}
|
||||||
println!("{output}");
|
|
||||||
|
|
||||||
// construct and submit the storage entry request if either no key is needed or som key was provided as a scale value
|
// construct and submit the storage entry request if either no key is needed or som key was provided as a scale value
|
||||||
if key_ty_id.is_none() || !key_scale_values.is_empty() {
|
if key_ty_id.is_none() || !key_scale_values.is_empty() {
|
||||||
@@ -153,7 +151,10 @@ pub(crate) async fn explore_storage(
|
|||||||
let value = decoded_value_thunk.to_value()?;
|
let value = decoded_value_thunk.to_value()?;
|
||||||
let mut value_string = scale_value::stringify::to_string(&value);
|
let mut value_string = scale_value::stringify::to_string(&value);
|
||||||
value_string = with_indent(value_string, 4);
|
value_string = with_indent(value_string, 4);
|
||||||
println!("\nThe value of the storage entry is:\n{value_string}");
|
writeln!(
|
||||||
|
output,
|
||||||
|
"\nThe value of the storage entry is:\n{value_string}"
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use clap::Parser as ClapParser;
|
|||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use color_eyre::eyre::{self, bail};
|
use color_eyre::eyre::{self, bail};
|
||||||
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
|
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||||
use std::io::{self, Write};
|
use std::io::Write;
|
||||||
use subxt_metadata::Metadata;
|
use subxt_metadata::Metadata;
|
||||||
|
|
||||||
/// Download metadata from a substrate node, for use with `subxt` codegen.
|
/// Download metadata from a substrate node, for use with `subxt` codegen.
|
||||||
@@ -34,7 +34,7 @@ pub struct Opts {
|
|||||||
runtime_apis: Option<Vec<String>>,
|
runtime_apis: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
pub async fn run(opts: Opts, output: &mut impl Write) -> color_eyre::Result<()> {
|
||||||
let bytes = opts.file_or_url.fetch().await?;
|
let bytes = opts.file_or_url.fetch().await?;
|
||||||
let mut metadata = RuntimeMetadataPrefixed::decode(&mut &bytes[..])?;
|
let mut metadata = RuntimeMetadataPrefixed::decode(&mut &bytes[..])?;
|
||||||
|
|
||||||
@@ -72,17 +72,18 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
|||||||
match opts.format.as_str() {
|
match opts.format.as_str() {
|
||||||
"json" => {
|
"json" => {
|
||||||
let json = serde_json::to_string_pretty(&metadata)?;
|
let json = serde_json::to_string_pretty(&metadata)?;
|
||||||
println!("{json}");
|
write!(output, "{json}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"hex" => {
|
"hex" => {
|
||||||
let hex_data = format!("0x{:?}", hex::encode(metadata.encode()));
|
let hex_data = format!("0x{:?}", hex::encode(metadata.encode()));
|
||||||
println!("{hex_data}");
|
write!(output, "{hex_data}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"bytes" => {
|
"bytes" => {
|
||||||
let bytes = metadata.encode();
|
let bytes = metadata.encode();
|
||||||
Ok(io::stdout().write_all(&bytes)?)
|
output.write_all(&bytes)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(eyre::eyre!(
|
_ => Err(eyre::eyre!(
|
||||||
"Unsupported format `{}`, expected `json`, `hex` or `bytes`",
|
"Unsupported format `{}`, expected `json`, `hex` or `bytes`",
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use clap::Parser as ClapParser;
|
|||||||
#[derive(Debug, ClapParser)]
|
#[derive(Debug, ClapParser)]
|
||||||
pub struct Opts {}
|
pub struct Opts {}
|
||||||
|
|
||||||
pub fn run(_opts: Opts) -> color_eyre::Result<()> {
|
pub fn run(_opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
|
||||||
let git_hash = env!("GIT_HASH");
|
let git_hash = env!("GIT_HASH");
|
||||||
println!(
|
writeln!(
|
||||||
|
output,
|
||||||
"{} {}-{}",
|
"{} {}-{}",
|
||||||
clap::crate_name!(),
|
clap::crate_name!(),
|
||||||
clap::crate_version!(),
|
clap::crate_version!(),
|
||||||
git_hash
|
git_hash
|
||||||
);
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use clap::Parser as ClapParser;
|
use clap::Parser as ClapParser;
|
||||||
|
|
||||||
/// Subxt utilities for interacting with Substrate based nodes.
|
/// Subxt utilities for interacting with Substrate based nodes.
|
||||||
@@ -22,12 +23,12 @@ enum Command {
|
|||||||
async fn main() -> color_eyre::Result<()> {
|
async fn main() -> color_eyre::Result<()> {
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
let args = Command::parse();
|
let args = Command::parse();
|
||||||
|
let mut output = std::io::stdout();
|
||||||
match args {
|
match args {
|
||||||
Command::Metadata(opts) => commands::metadata::run(opts).await,
|
Command::Metadata(opts) => commands::metadata::run(opts, &mut output).await,
|
||||||
Command::Codegen(opts) => commands::codegen::run(opts).await,
|
Command::Codegen(opts) => commands::codegen::run(opts, &mut output).await,
|
||||||
Command::Compatibility(opts) => commands::compatibility::run(opts).await,
|
Command::Compatibility(opts) => commands::compatibility::run(opts, &mut output).await,
|
||||||
Command::Version(opts) => commands::version::run(opts),
|
Command::Version(opts) => commands::version::run(opts, &mut output),
|
||||||
Command::Explore(opts) => commands::explore::run(opts).await,
|
Command::Explore(opts) => commands::explore::run(opts, &mut output).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -16,10 +16,10 @@ pub mod type_example;
|
|||||||
pub struct FileOrUrl {
|
pub struct FileOrUrl {
|
||||||
/// The url of the substrate node to query for metadata for codegen.
|
/// The url of the substrate node to query for metadata for codegen.
|
||||||
#[clap(long, value_parser)]
|
#[clap(long, value_parser)]
|
||||||
pub(crate) url: Option<Uri>,
|
pub url: Option<Uri>,
|
||||||
/// The path to the encoded metadata file.
|
/// The path to the encoded metadata file.
|
||||||
#[clap(long, value_parser)]
|
#[clap(long, value_parser)]
|
||||||
pub(crate) file: Option<PathBuf>,
|
pub file: Option<PathBuf>,
|
||||||
/// Specify the metadata version.
|
/// Specify the metadata version.
|
||||||
///
|
///
|
||||||
/// - unstable:
|
/// - unstable:
|
||||||
@@ -32,7 +32,7 @@ pub struct FileOrUrl {
|
|||||||
///
|
///
|
||||||
/// Defaults to 14.
|
/// Defaults to 14.
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
version: Option<MetadataVersion>,
|
pub version: Option<MetadataVersion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileOrUrl {
|
impl FileOrUrl {
|
||||||
@@ -76,7 +76,7 @@ impl FileOrUrl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_docs_with_indent(docs: &[String], indent: usize) -> String {
|
pub fn print_first_paragraph_with_indent(docs: &[String], indent: usize) -> String {
|
||||||
// take at most the first paragraph of documentation, such that it does not get too long.
|
// take at most the first paragraph of documentation, such that it does not get too long.
|
||||||
let docs_str = docs
|
let docs_str = docs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -87,7 +87,7 @@ pub(crate) fn print_docs_with_indent(docs: &[String], indent: usize) -> String {
|
|||||||
with_indent(docs_str, indent)
|
with_indent(docs_str, indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_indent(s: String, indent: usize) -> String {
|
pub fn with_indent(s: String, indent: usize) -> String {
|
||||||
let indent_str = " ".repeat(indent);
|
let indent_str = " ".repeat(indent);
|
||||||
s.lines()
|
s.lines()
|
||||||
.map(|line| format!("{indent_str}{line}"))
|
.map(|line| format!("{indent_str}{line}"))
|
||||||
|
|||||||
@@ -131,9 +131,6 @@ impl TypeDescription for TypeDefPrimitive {
|
|||||||
|
|
||||||
impl TypeDescription for TypeDefVariant<PortableForm> {
|
impl TypeDescription for TypeDefVariant<PortableForm> {
|
||||||
fn type_description(&self, registry: &PortableRegistry) -> color_eyre::Result<String> {
|
fn type_description(&self, registry: &PortableRegistry) -> color_eyre::Result<String> {
|
||||||
const MIN_VARIANT_COUNT_FOR_TRAILING_COMMA: usize = 100;
|
|
||||||
let add_trailing_comma = self.variants.len() >= MIN_VARIANT_COUNT_FOR_TRAILING_COMMA;
|
|
||||||
|
|
||||||
let mut variants_string = String::new();
|
let mut variants_string = String::new();
|
||||||
variants_string.push('{');
|
variants_string.push('{');
|
||||||
let mut iter = self.variants.iter().peekable();
|
let mut iter = self.variants.iter().peekable();
|
||||||
@@ -141,7 +138,7 @@ impl TypeDescription for TypeDefVariant<PortableForm> {
|
|||||||
let variant_string = variant.type_description(registry)?;
|
let variant_string = variant.type_description(registry)?;
|
||||||
variants_string.push_str(&variant_string);
|
variants_string.push_str(&variant_string);
|
||||||
|
|
||||||
if iter.peek().is_some() || add_trailing_comma {
|
if iter.peek().is_some() {
|
||||||
variants_string.push(',');
|
variants_string.push(',');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,9 +167,6 @@ impl TypeDescription for Vec<Field<PortableForm>> {
|
|||||||
return Ok("()".to_string());
|
return Ok("()".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN_FIELD_COUNT_FOR_TRAILING_COMMA: usize = 100;
|
|
||||||
let add_trailing_comma = self.len() >= MIN_FIELD_COUNT_FOR_TRAILING_COMMA;
|
|
||||||
|
|
||||||
let all_fields_named = self.iter().all(|f| f.name.is_some());
|
let all_fields_named = self.iter().all(|f| f.name.is_some());
|
||||||
let all_fields_unnamed = self.iter().all(|f| f.name.is_none());
|
let all_fields_unnamed = self.iter().all(|f| f.name.is_none());
|
||||||
let brackets = match (all_fields_named, all_fields_unnamed) {
|
let brackets = match (all_fields_named, all_fields_unnamed) {
|
||||||
@@ -192,7 +186,7 @@ impl TypeDescription for Vec<Field<PortableForm>> {
|
|||||||
let field_description = field.type_description(registry)?;
|
let field_description = field.type_description(registry)?;
|
||||||
fields_string.push_str(&field_description);
|
fields_string.push_str(&field_description);
|
||||||
|
|
||||||
if iter.peek().is_some() || add_trailing_comma {
|
if iter.peek().is_some() {
|
||||||
fields_string.push(',')
|
fields_string.push(',')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,3 +265,39 @@ fn format_type_description(input: &str) -> String {
|
|||||||
}
|
}
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::utils::type_description::print_type_description;
|
||||||
|
use scale_info::scale::{Decode, Encode};
|
||||||
|
use scale_info::TypeInfo;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::write;
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Debug, Clone, TypeInfo)]
|
||||||
|
pub struct Foo {
|
||||||
|
hello: String,
|
||||||
|
num: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a type definition, return type ID and registry representing it.
|
||||||
|
fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
|
||||||
|
let m = scale_info::MetaType::new::<T>();
|
||||||
|
let mut types = scale_info::Registry::new();
|
||||||
|
let id = types.register_type(&m);
|
||||||
|
let portable_registry: scale_info::PortableRegistry = types.into();
|
||||||
|
(id.id, portable_registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_description() {
|
||||||
|
let (foo_type_id, foo_registry) = make_type::<Foo>();
|
||||||
|
let description = print_type_description(&foo_type_id, &foo_registry).unwrap();
|
||||||
|
let mut output = String::new();
|
||||||
|
writeln!(output, "struct Foo {{").unwrap();
|
||||||
|
writeln!(output, " hello: String,").unwrap();
|
||||||
|
writeln!(output, " num: i32").unwrap();
|
||||||
|
write!(output, "}}").unwrap();
|
||||||
|
assert_eq!(description, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user