diff --git a/forc-plugins/forc-client/src/cmd/run.rs b/forc-plugins/forc-client/src/cmd/run.rs index ce1c28da7b5..6537ee251c0 100644 --- a/forc-plugins/forc-client/src/cmd/run.rs +++ b/forc-plugins/forc-client/src/cmd/run.rs @@ -6,8 +6,7 @@ pub use super::submit::Network; pub use forc::cli::shared::{BuildOutput, BuildProfile, Minify, Pkg, Print}; pub use forc_tx::{Gas, Maturity}; -forc::cli_examples! { -} +forc::cli_examples! {} /// Run script project. /// Crafts a script transaction then sends it to a running node. diff --git a/forc-plugins/forc-crypto/src/keygen/parse_secret.rs b/forc-plugins/forc-crypto/src/keygen/parse_secret.rs index 8f02b88f028..1f6b9be669e 100644 --- a/forc-plugins/forc-crypto/src/keygen/parse_secret.rs +++ b/forc-plugins/forc-crypto/src/keygen/parse_secret.rs @@ -11,11 +11,11 @@ const ABOUT: &str = "Parses a private key to view the associated public key"; #[cfg(test)] #[allow(non_upper_case_globals)] -const PrivateKey: &str = "f5204427d0ab9a311266c96a377f7c329cb8a41b9088225b6fcf40eefb423e28"; +const PRIVATE_KEY: &str = "f5204427d0ab9a311266c96a377f7c329cb8a41b9088225b6fcf40eefb423e28"; forc::cli_examples! { - [ Parses the secret of a block production => crypto "parse-secret" PrivateKey ] - [ Parses the secret of a peering => crypto "parse-secret" "-k" "peering" PrivateKey ] + [ Parses the secret of a block production => crypto "parse-secret" PRIVATE_KEY ] + [ Parses the secret of a peering => crypto "parse-secret" "-k" "peering" PRIVATE_KEY ] } /// Parse a secret key to view the associated public key diff --git a/forc-plugins/forc-tx/src/lib.rs b/forc-plugins/forc-tx/src/lib.rs index 9b70aade7b5..014813014ee 100644 --- a/forc-plugins/forc-tx/src/lib.rs +++ b/forc-plugins/forc-tx/src/lib.rs @@ -18,35 +18,48 @@ const ROOT: &str = "0x2222222222222222222222222222222222222222222222222222222222 const DATA_PATH: &str = "out/debug/tests.bin"; #[cfg(test)] const BYTECODE_PATH: &str = "out/debug/tests.bin"; +#[cfg(test)] +const STORAGE_SLOTS: &str = "out/debug/tests-storage_slots.json"; +#[cfg(test)] +const BALANCE_ROOT_ADDR: &str = + "0x0000000000000000000000000000000000000000000000000000000000000000"; +#[cfg(test)] +const STATE_ROOT_ADDR: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; +#[cfg(test)] +const CONTRACT_ID: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; +#[cfg(test)] +const COIN_TO_ADDR: &str = "0x2222222222222222222222222222222222222222222222222222222222222222"; +#[cfg(test)] +const ASSET_ID: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; -forc::cli_examples!{ +forc::cli_examples! { [ Script example => tx "script" "--bytecode" BYTECODE_PATH "--data" DATA_PATH "--receipts-root" ROOT ] - [ Multiple inputs => tx "create" "--bytecode" "out/debug/tests.bin" - "--storage-slots" "out/debug/tests-storage_slots.json" + [ Multiple inputs => tx "create" "--bytecode" BYTECODE_PATH + "--storage-slots" STORAGE_SLOTS "--script-gas-limit" "100" "--gas-price" "0" "--maturity" "0" - "--witness" "ADFD" - "--witness" "DFDA" + "--witness" "adfd" + "--witness" "dfda" "input" "contract" "--utxo-id" "1" "--output-ix" "1" - "--balance-root" "0x0000000000000000000000000000000000000000000000000000000000000000" - "--state-root" "0x0000000000000000000000000000000000000000000000000000000000000000" + "--balance-root" BALANCE_ROOT_ADDR + "--state-root" STATE_ROOT_ADDR "--tx-ptr" "89ACBDEFBDEF" - "--contract-id" "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + "--contract-id" CONTRACT_ID "output" "coin" - "--to" "0x2222222222222222222222222222222222222222222222222222222222222222" + "--to" COIN_TO_ADDR, "--amount" "100" - "--asset-id" "0x0000000000000000000000000000000000000000000000000000000000000000" + "--asset-id" ASSET_ID "output" "contract" "--input-ix" "1" - "--balance-root" "0x0000000000000000000000000000000000000000000000000000000000000000" - "--state-root" "0x0000000000000000000000000000000000000000000000000000000000000000" + "--balance-root" BALANCE_ROOT_ADDR + "--state-root" STATE_ROOT_ADDR "output" "change" - "--to" "0x2222222222222222222222222222222222222222222222222222222222222222" + "--to" COIN_TO_ADDR "--amount" "100" - "--asset-id" "0x0000000000000000000000000000000000000000000000000000000000000000" + "--asset-id" ASSET_ID "output" "variable" "--to" "0x2222222222222222222222222222222222222222222222222222222222222222" "--amount" "100" @@ -59,7 +72,7 @@ forc::cli_examples!{ /// The top-level `forc tx` command. #[derive(Debug, Parser, Deserialize, Serialize)] -#[clap(about, version, after_help = EXAMPLES)] +#[clap(about, version, after_help = help())] pub struct Command { #[clap(long, short = 'o')] pub output_path: Option, @@ -433,7 +446,7 @@ pub enum ConvertInputError { WitnessPredicateMismatch, } -const EXAMPLES: &str = r"EXAMPLES: +const _EXAMPLES: &str = r"EXAMPLES: # An example constructing a `create` transaction. forc tx create \ --bytecode ./my-contract/out/debug/my-contract.bin \ diff --git a/forc/src/cli/help.rs b/forc/src/cli/help.rs index cf068e1b0b2..e5ed8f1ea0b 100644 --- a/forc/src/cli/help.rs +++ b/forc/src/cli/help.rs @@ -32,11 +32,145 @@ macro_rules! cli_examples { Box::leak(format!("EXAMPLES:\n{}", examples()).into_boxed_str()) } + fn print_args(args: Vec) -> String { + let mut result = String::new(); + let mut iter = args.iter().peekable(); + let mut length = 0; + let mut is_multiline = false; + let equal = "=".to_owned(); + loop { + let arg = if let Some(arg) = iter.next() { + arg + } else { + break; + }; + if length + arg.len() > 60 && iter.peek().is_some() && arg.chars().next() != Some('-') { + // too long, break it into a new line + result.push_str("\\\n "); + is_multiline = true; + length = 5; + } + if is_multiline && arg.chars().next() == Some('-') { + // it a multiline arg and the next arg is a flag, put them in their own line + result.push_str("\\\n "); + length = 5; + } + result.push_str(arg); + length += arg.len(); + if arg != &equal && iter.peek() != Some(&&equal) { + result.push(' '); + length += 1; + } + } + result + } + + fn quote_str(s: &str) -> String { + let mut result = String::with_capacity(s.len() + 2); // Initial capacity with room for quotes + + result.push('"'); + for c in s.chars() { + match c { + '\\' | '"' => result.push('\\'), // Escape backslashes and quotes + _ => (), + } + result.push(c); + } + result.push('"'); + + result + } + + fn is_variable(s: &str) -> bool { + s.chars().all(|x| x.is_uppercase() || x == '_') + } + + fn format_arguments(input: &str) -> String { + let mut chars = input.chars().peekable().into_iter(); + let mut args = vec![]; + + loop { + let c = if let Some(c) = chars.next() { c } else { break }; + + match c { + ' ' | '\t' | '\n' => loop { + match chars.peek() { + Some(' ') | Some('\t') | Some('\n') => chars.next(), + _ => break, + }; + }, + '=' => { + args.push("=".to_string()); + } + '"' | '\'' => { + let end_character = c; + let mut current_word = String::new(); + loop { + match chars.peek() { + Some(c) => { + if *c == end_character { + let _ = chars.next(); + args.push(current_word); + break; + } else if *c == '\\' { + let _ = chars.next(); + if let Some(c) = chars.next() { + current_word.push(c); + } + } else { + current_word.push(*c); + chars.next(); + } + } + None => { + break; + } + } + } + } + c => { + let mut current_word = c.to_string(); + loop { + match chars.peek() { + Some(' ') | Some('\t') | Some('\n') | Some('=') | Some('\'') + | Some('"') | None => { + args.push(current_word); + break; + } + Some(c) => { + current_word.push(*c); + chars.next(); + } + } + } + } + } + } + + print_args( + args.into_iter() + .map(|arg| { + if arg.contains(char::is_whitespace) { + // arg has spaces, quote the string + quote_str(&arg) + } else if is_variable(&arg) { + // format as a unix variable + format!("${}{}{}", "{", arg, "}") + } else { + // it is fine to show as is + arg + } + }) + .collect::>(), + ) + } + + pub fn examples() -> &'static str { Box::leak( [ $( $crate::paste::paste! { - format!("\t#{}\n\tforc {} {}\n\n", stringify!($($description)*), stringify!($command), stringify!($($arg)*) ) + format!(" #{}\n forc {} {}\n\n", stringify!($($description)*), stringify!($command), format_arguments(stringify!($($arg)*)) ) }, )* ].concat().into_boxed_str()) diff --git a/forc/src/main.rs b/forc/src/main.rs index 9fc502156d6..696207098e2 100644 --- a/forc/src/main.rs +++ b/forc/src/main.rs @@ -2,5 +2,9 @@ use forc_util::ForcCliResult; #[tokio::main] async fn main() -> ForcCliResult<()> { + let input = + "-a --long-argument=value --input-file='file.txt' -o 'foo/path with space/output.txt' "; + let formatted_args = argument_format(input); + panic!("{}", formatted_args); forc::cli::run_cli().await.into() }