From 7bcac37d987c84b77daaf2eb89bcb6d54ba0b189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20D=2E=20Rodas?= Date: Sat, 10 Feb 2024 00:54:51 -0300 Subject: [PATCH] Improve cli_examples macro (#5589) ## Description Add macro to test argument parsing rather than testing the external command through building and spawning a separated process. This is an improved version of #5519 ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --- forc-plugins/forc-client/src/cmd/deploy.rs | 8 +- forc-plugins/forc-client/src/cmd/submit.rs | 12 +- forc-plugins/forc-client/tests/.gitignore | 2 - forc-plugins/forc-client/tests/Forc.lock | 13 -- forc-plugins/forc-client/tests/Forc.toml | 8 - forc-plugins/forc-client/tests/mint.json | 29 --- forc-plugins/forc-client/tests/src/main.sw | 11 -- forc-plugins/forc-crypto/src/address.rs | 4 +- forc-plugins/forc-crypto/src/args.rs | 10 +- .../forc-crypto/src/keys/get_public_key.rs | 8 +- forc-plugins/forc-crypto/src/keys/new_key.rs | 8 +- .../forc-crypto/src/keys/parse_secret.rs | 6 +- forc-plugins/forc-doc/src/cli.rs | 12 +- forc-plugins/forc-doc/tests/.gitignore | 2 - forc-plugins/forc-doc/tests/Forc.lock | 13 -- forc-plugins/forc-doc/tests/Forc.toml | 8 - forc-plugins/forc-doc/tests/src/main.sw | 11 -- forc-plugins/forc-fmt/src/main.rs | 14 +- forc-plugins/forc-fmt/tests/Forc.lock | 13 -- forc-plugins/forc-fmt/tests/Forc.toml | 8 - forc-plugins/forc-fmt/tests/src/main.sw | 11 -- forc-plugins/forc-tx/src/lib.rs | 25 ++- forc-util/src/cli.rs | 185 ++++++++---------- forc-util/src/lib.rs | 1 + forc/src/cli/commands/build.rs | 8 +- forc/src/cli/commands/check.rs | 8 +- forc/src/cli/commands/clean.rs | 8 + forc/src/cli/commands/contract_id.rs | 8 + forc/src/cli/commands/init.rs | 10 + forc/src/cli/commands/new.rs | 10 + forc/src/cli/commands/parse_bytecode.rs | 7 + forc/src/cli/commands/plugins.rs | 12 +- forc/src/cli/commands/predicate_root.rs | 7 + forc/src/cli/commands/template.rs | 7 + forc/src/cli/commands/test.rs | 11 +- forc/src/cli/commands/update.rs | 11 +- 36 files changed, 237 insertions(+), 292 deletions(-) delete mode 100644 forc-plugins/forc-client/tests/.gitignore delete mode 100644 forc-plugins/forc-client/tests/Forc.lock delete mode 100644 forc-plugins/forc-client/tests/Forc.toml delete mode 100644 forc-plugins/forc-client/tests/mint.json delete mode 100644 forc-plugins/forc-client/tests/src/main.sw delete mode 100644 forc-plugins/forc-doc/tests/.gitignore delete mode 100644 forc-plugins/forc-doc/tests/Forc.lock delete mode 100644 forc-plugins/forc-doc/tests/Forc.toml delete mode 100644 forc-plugins/forc-doc/tests/src/main.sw delete mode 100644 forc-plugins/forc-fmt/tests/Forc.lock delete mode 100644 forc-plugins/forc-fmt/tests/Forc.toml delete mode 100644 forc-plugins/forc-fmt/tests/src/main.sw diff --git a/forc-plugins/forc-client/src/cmd/deploy.rs b/forc-plugins/forc-client/src/cmd/deploy.rs index a3f07417908..2b0a57379fa 100644 --- a/forc-plugins/forc-client/src/cmd/deploy.rs +++ b/forc-plugins/forc-client/src/cmd/deploy.rs @@ -8,9 +8,11 @@ pub use forc_util::tx_utils::Salt; use crate::NodeTarget; forc_util::cli_examples! { - [ Deploy a single contract => deploy "bc09bfa7a11a04ce42b0a5abf04fd437387ee49bf4561d575177e2946468b408" => r#".*Error making HTTP request.*"# ] - [ Deploy a single contract from a different path => deploy "bc09bfa7a11a04ce42b0a5abf04fd437387ee49bf4561d575177e2946468b408 --path ../tests/" => r#".*Error making HTTP request.*"# ] - [ Deploy to a custom network => deploy "--node-url https://beta-5.fuel.network/graphql" => ".*Refused to create a new wallet.*" ] + super::Command { + [ Deploy a single contract => "forc deploy bc09bfa7a11a04ce42b0a5abf04fd437387ee49bf4561d575177e2946468b408" ] + [ Deploy a single contract from a different path => "forc deploy bc09bfa7a11a04ce42b0a5abf04fd437387ee49bf4561d575177e2946468b408 --path {path}" ] + [ Deploy to a custom network => "forc deploy --node-url https://beta-5.fuel.network/graphql" ] + } } #[derive(Debug, Default, Parser)] diff --git a/forc-plugins/forc-client/src/cmd/submit.rs b/forc-plugins/forc-client/src/cmd/submit.rs index 2623c8e3d21..2d54e3642db 100644 --- a/forc-plugins/forc-client/src/cmd/submit.rs +++ b/forc-plugins/forc-client/src/cmd/submit.rs @@ -3,11 +3,13 @@ use devault::Devault; use std::path::PathBuf; forc_util::cli_examples! { - [ Submit a transaction from a json file => submit "./mint.json" => "Submission of tx or awaiting commit failed" ] - [ Submit a transaction from a json file and wait for confirmation => submit "./mint.json --await true" => "Submission of tx or awaiting commit failed" ] - [ Submit a transaction from a json file and get output in json => submit "./mint.json --tx-status-json true" => "Submission of tx or awaiting commit failed" ] - [ Submit a transaction from a json file to testnet => submit "./mint.json --testnet" => "Submission of tx or awaiting commit failed" ] - [ Submit a transaction from a json file to a local net => submit "./mint.json --target local" => "Submission of tx or awaiting commit failed" ] + super::Command { + [ Submit a transaction from a json file => "forc submit {path}/mint.json" ] + [ Submit a transaction from a json file and wait for confirmation => "forc submit {path}/mint.json --await true" ] + [ Submit a transaction from a json file and get output in json => "forc submit {path}/mint.json --tx-status-json true" ] + [ Submit a transaction from a json file to testnet => "forc submit {path}/mint.json --testnet" ] + [ Submit a transaction from a json file to a local net => "forc submit {path}/mint.json --target local" ] + } } /// Submit a transaction to the specified fuel node. diff --git a/forc-plugins/forc-client/tests/.gitignore b/forc-plugins/forc-client/tests/.gitignore deleted file mode 100644 index 77d3844f58c..00000000000 --- a/forc-plugins/forc-client/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -out -target diff --git a/forc-plugins/forc-client/tests/Forc.lock b/forc-plugins/forc-client/tests/Forc.lock deleted file mode 100644 index 69b439832fa..00000000000 --- a/forc-plugins/forc-client/tests/Forc.lock +++ /dev/null @@ -1,13 +0,0 @@ -[[package]] -name = "core" -source = "path+from-root-F252333F9C4A5D78" - -[[package]] -name = "std" -source = "path+from-root-F252333F9C4A5D78" -dependencies = ["core"] - -[[package]] -name = "tests" -source = "member" -dependencies = ["std"] diff --git a/forc-plugins/forc-client/tests/Forc.toml b/forc-plugins/forc-client/tests/Forc.toml deleted file mode 100644 index b7d241e5420..00000000000 --- a/forc-plugins/forc-client/tests/Forc.toml +++ /dev/null @@ -1,8 +0,0 @@ -[project] -authors = ["Fuel Labs "] -entry = "main.sw" -license = "Apache-2.0" -name = "tests" - -[dependencies] -std = { path = "../../../sway-lib-std/" } diff --git a/forc-plugins/forc-client/tests/mint.json b/forc-plugins/forc-client/tests/mint.json deleted file mode 100644 index ff7193c49d9..00000000000 --- a/forc-plugins/forc-client/tests/mint.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "Mint": { - "tx_pointer": { - "block_height": 0, - "tx_index": 0 - }, - "input_contract": { - "utxo_id": { - "tx_id": "c49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e2", - "output_index": 0 - }, - "balance_root": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31", - "state_root": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31", - "contract_id": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31", - "tx_pointer": { - "block_height": 0, - "tx_index": 0 - } - }, - "output_contract": { - "balance_root": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31", - "state_root": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31", - "contract_id": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31", - "input_index": 0 - }, - "mint_amount": 2123123121, - "mint_asset_id": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc31" - } -} diff --git a/forc-plugins/forc-client/tests/src/main.sw b/forc-plugins/forc-client/tests/src/main.sw deleted file mode 100644 index 7d4a75493c6..00000000000 --- a/forc-plugins/forc-client/tests/src/main.sw +++ /dev/null @@ -1,11 +0,0 @@ -contract; - -abi MyContract { - fn test_function() -> bool; -} - -impl MyContract for Contract { - fn test_function() -> bool { - true - } -} diff --git a/forc-plugins/forc-crypto/src/address.rs b/forc-plugins/forc-crypto/src/address.rs index 911cd6cd57a..a1f075daffe 100644 --- a/forc-plugins/forc-crypto/src/address.rs +++ b/forc-plugins/forc-crypto/src/address.rs @@ -5,7 +5,9 @@ use serde_json::json; use std::str::{from_utf8, FromStr}; forc_util::cli_examples! { - [ Convert an address to another format => crypto "address fuel12e0xwx34nfp7jrzvn9mp5qkac3yvp7h8fx37ghl7klf82vv2wkys6wd523" ] + crate::Command { + [ Convert an address to another format => "forc crypto address fuel12e0xwx34nfp7jrzvn9mp5qkac3yvp7h8fx37ghl7klf82vv2wkys6wd523" ] + } } #[derive(Debug, clap::Args)] diff --git a/forc-plugins/forc-crypto/src/args.rs b/forc-plugins/forc-crypto/src/args.rs index 71872bca9ac..8e4bcee25fd 100644 --- a/forc-plugins/forc-crypto/src/args.rs +++ b/forc-plugins/forc-crypto/src/args.rs @@ -7,10 +7,12 @@ use std::{ }; forc_util::cli_examples! { - [ Hashes an argument with SHA256 => crypto "sha256 test" ] - [ Hashes an argument with Keccak256 => crypto "keccak256 test" ] - [ Hashes a file path with SHA256 => crypto "sha256 src/args.rs" ] - [ Hashes a file path with Keccak256 => crypto "keccak256 src/args.rs" ] + crate::Command { + [ Hashes an argument with SHA256 => "forc crypto sha256 test" ] + [ Hashes an argument with Keccak256 => "forc crypto keccak256 test" ] + [ Hashes a file path with SHA256 => "forc crypto sha256 {file}" ] + [ Hashes a file path with Keccak256 => "forc crypto keccak256 {file}" ] + } } #[derive(Debug, Clone, clap::Args)] diff --git a/forc-plugins/forc-crypto/src/keys/get_public_key.rs b/forc-plugins/forc-crypto/src/keys/get_public_key.rs index c02dda04a46..9e5d7ad610c 100644 --- a/forc-plugins/forc-crypto/src/keys/get_public_key.rs +++ b/forc-plugins/forc-crypto/src/keys/get_public_key.rs @@ -5,9 +5,11 @@ use fuels_core::types::bech32::Bech32Address; use serde_json::json; forc_util::cli_examples! { - [ Get the public key from a message and its signature => crypto r#"get-public-key \ - 0x1eff08081394b72239a0cf7ff6b499213dcb7a338bedbd75d072d504588ef27a1f74d5ceb2f111ec02ede097fb09ed00aa9867922ed39299dae0b1afc0fa8661 \ - "This is a message that is signed""# ] + crate::Command { + [ Get the public key from a message and its signature => r#"forc crypto get-public-key \ + 0x1eff08081394b72239a0cf7ff6b499213dcb7a338bedbd75d072d504588ef27a1f74d5ceb2f111ec02ede097fb09ed00aa9867922ed39299dae0b1afc0fa8661 \ + "This is a message that is signed""# ] + } } /// Parse a secret key to view the associated public key diff --git a/forc-plugins/forc-crypto/src/keys/new_key.rs b/forc-plugins/forc-crypto/src/keys/new_key.rs index 449ee8884f5..d37fa107681 100644 --- a/forc-plugins/forc-crypto/src/keys/new_key.rs +++ b/forc-plugins/forc-crypto/src/keys/new_key.rs @@ -14,9 +14,11 @@ use std::ops::Deref; const ABOUT: &str = "Creates a new key for use with fuel-core"; forc_util::cli_examples! { - [ Creates a new key default for block production => crypto "new-key" ] - [ Creates a new key for peering => crypto "new-key -k peering" ] - [ Creates a new key for block production => crypto "new-key -k block-production" ] + crate::Command { + [ Creates a new key default for block production => "forc crypto new-key" ] + [ Creates a new key for peering => "forc crypto new-key -k peering" ] + [ Creates a new key for block production => "forc crypto new-key -k block-production" ] + } } /// Generate a random new secret & public key in the format expected by fuel-core diff --git a/forc-plugins/forc-crypto/src/keys/parse_secret.rs b/forc-plugins/forc-crypto/src/keys/parse_secret.rs index 82f2cbf8c1b..bc435137a88 100644 --- a/forc-plugins/forc-crypto/src/keys/parse_secret.rs +++ b/forc-plugins/forc-crypto/src/keys/parse_secret.rs @@ -8,8 +8,10 @@ use std::{ops::Deref, str::FromStr}; const ABOUT: &str = "Parses a private key to view the associated public key"; forc_util::cli_examples! { - [ Parses the secret of a block production => crypto "parse-secret \"f5204427d0ab9a311266c96a377f7c329cb8a41b9088225b6fcf40eefb423e28\"" ] - [ Parses the secret of a peering => crypto "parse-secret -k peering \"f5204427d0ab9a311266c96a377f7c329cb8a41b9088225b6fcf40eefb423e28\"" ] + crate::Command { + [ Parses the secret of a block production => "forc crypto parse-secret \"f5204427d0ab9a311266c96a377f7c329cb8a41b9088225b6fcf40eefb423e28\"" ] + [ Parses the secret of a peering => "forc crypto parse-secret -k peering \"f5204427d0ab9a311266c96a377f7c329cb8a41b9088225b6fcf40eefb423e28\"" ] + } } /// Parse a secret key to view the associated public key diff --git a/forc-plugins/forc-doc/src/cli.rs b/forc-plugins/forc-doc/src/cli.rs index 015caf25e3b..0817f25c9ff 100644 --- a/forc-plugins/forc-doc/src/cli.rs +++ b/forc-plugins/forc-doc/src/cli.rs @@ -5,11 +5,13 @@ use forc_pkg::source::IPFSNode; const ABOUT: &str = "Forc plugin for building a Sway package's documentation"; forc_util::cli_examples! { - [ Build the docs for a project in the current path => doc ""] - [ Build the docs for a project in the current path and open it in the browser => doc "--open" ] - [ Build the docs for a project located in another path => doc "--manifest-path ../tests_project2" ] - [ Build the docs for the current project exporting private types => doc "--document-private-items" ] - [ Build the docs offline without downloading any dependency from the network => doc "--offline" ] + crate::Command { + [ Build the docs for a project in the current path => "forc doc"] + [ Build the docs for a project in the current path and open it in the browser => "forc doc --open" ] + [ Build the docs for a project located in another path => "forc doc --manifest-path {path}" ] + [ Build the docs for the current project exporting private types => "forc doc --document-private-items" ] + [ Build the docs offline without downloading any dependency from the network => "forc doc --offline" ] + } } #[derive(Debug, Parser, Default)] diff --git a/forc-plugins/forc-doc/tests/.gitignore b/forc-plugins/forc-doc/tests/.gitignore deleted file mode 100644 index 77d3844f58c..00000000000 --- a/forc-plugins/forc-doc/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -out -target diff --git a/forc-plugins/forc-doc/tests/Forc.lock b/forc-plugins/forc-doc/tests/Forc.lock deleted file mode 100644 index 69b439832fa..00000000000 --- a/forc-plugins/forc-doc/tests/Forc.lock +++ /dev/null @@ -1,13 +0,0 @@ -[[package]] -name = "core" -source = "path+from-root-F252333F9C4A5D78" - -[[package]] -name = "std" -source = "path+from-root-F252333F9C4A5D78" -dependencies = ["core"] - -[[package]] -name = "tests" -source = "member" -dependencies = ["std"] diff --git a/forc-plugins/forc-doc/tests/Forc.toml b/forc-plugins/forc-doc/tests/Forc.toml deleted file mode 100644 index b7d241e5420..00000000000 --- a/forc-plugins/forc-doc/tests/Forc.toml +++ /dev/null @@ -1,8 +0,0 @@ -[project] -authors = ["Fuel Labs "] -entry = "main.sw" -license = "Apache-2.0" -name = "tests" - -[dependencies] -std = { path = "../../../sway-lib-std/" } diff --git a/forc-plugins/forc-doc/tests/src/main.sw b/forc-plugins/forc-doc/tests/src/main.sw deleted file mode 100644 index 7d4a75493c6..00000000000 --- a/forc-plugins/forc-doc/tests/src/main.sw +++ /dev/null @@ -1,11 +0,0 @@ -contract; - -abi MyContract { - fn test_function() -> bool; -} - -impl MyContract for Contract { - fn test_function() -> bool { - true - } -} diff --git a/forc-plugins/forc-fmt/src/main.rs b/forc-plugins/forc-fmt/src/main.rs index 895d7686936..28ff0330f3b 100644 --- a/forc-plugins/forc-fmt/src/main.rs +++ b/forc-plugins/forc-fmt/src/main.rs @@ -19,12 +19,14 @@ use sway_utils::{constants, find_parent_manifest_dir, get_sway_files, is_sway_fi use swayfmt::Formatter; forc_util::cli_examples! { - [ Run the formatter in check mode on the current directory => fmt "--check"] - [ Run the formatter in check mode on the current directory with short format => fmt "-c"] - [ Run formatter against a given file => fmt "--file src/main.sw"] - [ Run formatter against a given file with short format => fmt "-f src/main.sw"] - [ Run formatter against a given dir => fmt "--path ../tests/"] - [ Run formatter against a given dir with short format => fmt "-p ../tests"] + crate::App { + [ Run the formatter in check mode on the current directory => "forc fmt --check"] + [ Run the formatter in check mode on the current directory with short format => "forc fmt -c"] + [ Run formatter against a given file => "forc fmt --file {path}/src/main.sw"] + [ Run formatter against a given file with short format => "forc fmt -f {path}/src/main.sw"] + [ Run formatter against a given dir => "forc fmt --path {path}"] + [ Run formatter against a given dir with short format => "forc fmt -p {path}"] + } } #[derive(Debug, Parser)] diff --git a/forc-plugins/forc-fmt/tests/Forc.lock b/forc-plugins/forc-fmt/tests/Forc.lock deleted file mode 100644 index 69b439832fa..00000000000 --- a/forc-plugins/forc-fmt/tests/Forc.lock +++ /dev/null @@ -1,13 +0,0 @@ -[[package]] -name = "core" -source = "path+from-root-F252333F9C4A5D78" - -[[package]] -name = "std" -source = "path+from-root-F252333F9C4A5D78" -dependencies = ["core"] - -[[package]] -name = "tests" -source = "member" -dependencies = ["std"] diff --git a/forc-plugins/forc-fmt/tests/Forc.toml b/forc-plugins/forc-fmt/tests/Forc.toml deleted file mode 100644 index b7d241e5420..00000000000 --- a/forc-plugins/forc-fmt/tests/Forc.toml +++ /dev/null @@ -1,8 +0,0 @@ -[project] -authors = ["Fuel Labs "] -entry = "main.sw" -license = "Apache-2.0" -name = "tests" - -[dependencies] -std = { path = "../../../sway-lib-std/" } diff --git a/forc-plugins/forc-fmt/tests/src/main.sw b/forc-plugins/forc-fmt/tests/src/main.sw deleted file mode 100644 index 7d4a75493c6..00000000000 --- a/forc-plugins/forc-fmt/tests/src/main.sw +++ /dev/null @@ -1,11 +0,0 @@ -contract; - -abi MyContract { - fn test_function() -> bool; -} - -impl MyContract for Contract { - fn test_function() -> bool { - true - } -} diff --git a/forc-plugins/forc-tx/src/lib.rs b/forc-plugins/forc-tx/src/lib.rs index 7907e720aad..3e5281efaae 100644 --- a/forc-plugins/forc-tx/src/lib.rs +++ b/forc-plugins/forc-tx/src/lib.rs @@ -13,10 +13,14 @@ use std::path::PathBuf; use thiserror::Error; forc_util::cli_examples! { - [ Script example => tx r#"script --bytecode "out/debug/tests.bin" --data "data.bin" \ + { + // This parser has a custom parser + super::Command::try_parse_from_args + } { + [ Script example => r#"forc tx script --bytecode "{path}/out/debug/name.bin" --data "{path}/data.bin" \ --receipts-root 0x2222222222222222222222222222222222222222222222222222222222222222"# ] - [ Multiple inputs => tx r#"create --bytecode "out/debug/tests.bin" - --storage-slots out/debug/tests-storage_slots.json + [ Multiple inputs => r#"forc tx create --bytecode "{name}/out/debug/name.bin" + --storage-slots "{path}/out/debug/name-storage_slots.json" --script-gas-limit 100 \ --gas-price 0 \ --maturity 0 \ @@ -59,9 +63,9 @@ forc_util::cli_examples! { --state-root 0x0000000000000000000000000000000000000000000000000000000000000000 "# ] - [ An example constructing a create transaction => tx "create \ - --bytecode ./my-contract/out/debug/my-contract.bin \ - --storage-slots out/debug/tests-storage_slots.json + [ An example constructing a create transaction => r#"forc tx create \ + --bytecode {path}/out/debug/name.bin \ + --storage-slots {path}/out/debug/name-storage_slots.json \ --script-gas-limit 100 \ --gas-price 0 \ --maturity 0 \ @@ -88,9 +92,9 @@ forc_util::cli_examples! { --recipient 0x2222222222222222222222222222222222222222222222222222222222222222 \ --amount 1 \ --nonce 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB \ - --msg-data ./message.dat \ - --predicate ./my-predicate2.bin \ - --predicate-data ./my-predicate2.dat \ + --msg-data {path}/message.dat \ + --predicate {path}/my-predicate2.bin \ + --predicate-data {path}/my-predicate2.dat \ output coin \ --to 0x2222222222222222222222222222222222222222222222222222222222222222 \ --amount 100 \ @@ -109,8 +113,9 @@ forc_util::cli_examples! { --asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \ output contract-created \ --contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \ - --state-root 0x0000000000000000000000000000000000000000000000000000000000000000" + --state-root 0x0000000000000000000000000000000000000000000000000000000000000000"# ] + } } /// The top-level `forc tx` command. diff --git a/forc-util/src/cli.rs b/forc-util/src/cli.rs index c398e3818b8..f8f6ed5662e 100644 --- a/forc-util/src/cli.rs +++ b/forc-util/src/cli.rs @@ -1,141 +1,116 @@ #[macro_export] // Let the user format the help and parse it from that string into arguments to create the unit test macro_rules! cli_examples { - ($( [ $($description:ident)* => $command:tt $args:expr $( => $output:expr )? ] )*) => { - #[cfg(test)] - mod cli_examples { - use $crate::serial_test; + ($st:path { $( [ $($description:ident)* => $command:stmt ] )* }) => { + forc_util::cli_examples! { + { + $crate::paste::paste! { + use clap::Parser; + $st::try_parse_from + } + } { + $( [ $($description)* => $command ] )* + } + } + }; + ( $code:block { $( [ $($description:ident)* => $command:stmt ] )* }) => { + $crate::paste::paste! { + #[cfg(test)] + mod cli_parsing { $( - $crate::paste::paste! { - #[test] - #[serial_test::serial] - #[allow(unreachable_code)] - fn [<$($description:lower _)*:snake example>] () { - let mut proc = std::process::Command::new("cargo"); - proc.arg("run"); - proc.arg("--bin"); - proc.arg(if stringify!($command) == "forc" { - "forc".to_owned() - } else { - format!("forc-{}", stringify!($command)) - }); - proc.arg("--"); - - super::parse_args($args).into_iter().for_each(|arg| { - proc.arg(arg); - }); - - let path = std::path::Path::new("tests"); - if path.is_dir() { - // a tests folder exists, move the cwd of the process to - // be executed there. In that folder all files needed to - // run the cmd should be stored - proc.current_dir(path); - } - let output = proc.output().expect(stringify!($command)); + #[test] + fn [<$($description:lower _)*:snake example>] () { - $( - let expected_output = $crate::Regex::new($output).expect("valid regex"); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - assert!( - expected_output.is_match(&stdout) || - expected_output.is_match(&stderr), - "expected_output: {}\nStdOut:\n{}\nStdErr:\n{}\n", - expected_output, - stdout, - stderr, - ); - return; - )? - // We don't know what to get or how to parse the output, all - // we care is to get a valid exit code - assert!(output.status.success(), "{}: {:?}", stringify!($($description)*), output); + let cli_parser = $code; + let mut args = parse_args($command); + if cli_parser(args.clone()).is_err() { + // Failed to parse, it maybe a plugin. To execute a plugin the first argument needs to be removed, `forc`. + args.remove(0); + cli_parser(args).expect("valid subcommand"); } } + )* - } - #[cfg(test)] - fn parse_args(input: &str) -> Vec { - let mut chars = input.chars().peekable().into_iter(); - let mut args = vec![]; + #[cfg(test)] + fn parse_args(input: &str) -> Vec { + let mut chars = input.chars().peekable().into_iter(); + let mut args = vec![]; - loop { - let character = if let Some(c) = chars.next() { c } else { break }; + loop { + let character = if let Some(c) = chars.next() { c } else { break }; - match character { - ' ' | '\\' | '\t' | '\n' => loop { - match chars.peek() { - Some(' ') | Some('\t') | Some('\n') => chars.next(), - _ => break, - }; - }, - '=' => { - args.push("=".to_string()); - } - '"' | '\'' => { - let end_character = character; - let mut current_word = String::new(); - loop { + match character { + ' ' | '\\' | '\t' | '\n' => loop { match chars.peek() { - Some(character) => { - if *character == end_character { - let _ = chars.next(); - args.push(current_word); - break; - } else if *character == '\\' { - let _ = chars.next(); - if let Some(character) = chars.next() { - current_word.push(character); + Some(' ') | Some('\t') | Some('\n') => chars.next(), + _ => break, + }; + }, + '=' => { + args.push("=".to_string()); + } + '"' | '\'' => { + let end_character = character; + let mut current_word = String::new(); + loop { + match chars.peek() { + Some(character) => { + if *character == end_character { + let _ = chars.next(); + args.push(current_word); + break; + } else if *character == '\\' { + let _ = chars.next(); + if let Some(character) = chars.next() { + current_word.push(character); + } + } else { + current_word.push(*character); + chars.next(); } - } else { - current_word.push(*character); - chars.next(); } - } - None => { - break; + None => { + break; + } } } } - } - character => { - let mut current_word = character.to_string(); - loop { - match chars.peek() { - Some(' ') | Some('\t') | Some('\n') | Some('=') | Some('\'') - | Some('"') | None => { - args.push(current_word); - break; - } - Some(character) => { - current_word.push(*character); - chars.next(); + character => { + let mut current_word = character.to_string(); + loop { + match chars.peek() { + Some(' ') | Some('\t') | Some('\n') | Some('=') | Some('\'') + | Some('"') | None => { + args.push(current_word); + break; + } + Some(character) => { + current_word.push(*character); + chars.next(); + } } } } } } + + args } - args + } } + fn help() -> &'static str { - Box::leak(format!("EXAMPLES:\n{}", examples()).into_boxed_str()) + Box::leak(format!("{}\n{}", forc_util::ansi_term::Colour::Yellow.paint("EXAMPLES:"), examples()).into_boxed_str()) } pub fn examples() -> &'static str { Box::leak( [ $( $crate::paste::paste! { - if stringify!($command) == "forc" { - format!(" #{}\n forc {}\n\n", stringify!($($description)*), $args ) - } else { - format!(" #{}\n forc {} {}\n\n", stringify!($($description)*), stringify!($command), $args ) - } + format!(" # {}\n {}\n\n", stringify!($($description)*), $command) }, )* ].concat().into_boxed_str()) diff --git a/forc-util/src/lib.rs b/forc-util/src/lib.rs index 24a922dc576..aa6b58a6326 100644 --- a/forc-util/src/lib.rs +++ b/forc-util/src/lib.rs @@ -31,6 +31,7 @@ pub mod restricted; #[macro_use] pub mod cli; +pub use ansi_term; pub use paste; pub use regex::Regex; pub use serial_test; diff --git a/forc/src/cli/commands/build.rs b/forc/src/cli/commands/build.rs index 183d77e1764..f92a73aebb3 100644 --- a/forc/src/cli/commands/build.rs +++ b/forc/src/cli/commands/build.rs @@ -3,9 +3,11 @@ use clap::Parser; use forc_util::ForcResult; forc_util::cli_examples! { - [ Compile the current project => forc "build" => r#".*could not find `Forc.toml`.*"# ] - [ Compile the current project with a different path => forc "build --path ../tests/" => r#".*could not find `Forc.toml`.*"# ] - [ Compile the current project without updating dependencies => forc "build --locked" => r#".*could not find `Forc.toml`.*"# ] + crate::cli::Opt { + [ Compile the current projectx => "forc build" ] + [ Compile the current project from a different path => "forc build --path " ] + [ Compile the current project without updating dependencies => "forc build --path --locked" ] + } } /// Compile the current or target project. diff --git a/forc/src/cli/commands/check.rs b/forc/src/cli/commands/check.rs index 5f1061d516b..97627f2c5bc 100644 --- a/forc/src/cli/commands/check.rs +++ b/forc/src/cli/commands/check.rs @@ -5,9 +5,11 @@ use forc_util::{forc_result_bail, ForcResult}; use sway_core::{BuildTarget, Engines}; forc_util::cli_examples! { - [ Check the current project => forc "check" => r#".*could not find `Forc.toml`.*"# ] - [ Check the current project with a different path => forc "check --path ../tests/" => r#".*could not find `Forc.toml`.*"# ] - [ Check the current project without updating dependencies => forc "check --locked" => r#".*could not find `Forc.toml`.*"# ] + crate::cli::Opt { + [ Check the current project => "forc check" ] + [ Check the current project with a different path => "forc check --path " ] + [ Check the current project without updating dependencies => "forc check --locked" ] + } } /// Check the current or target project and all of its dependencies for errors. diff --git a/forc/src/cli/commands/clean.rs b/forc/src/cli/commands/clean.rs index 5a00b0c4ee0..c04ce246ed6 100644 --- a/forc/src/cli/commands/clean.rs +++ b/forc/src/cli/commands/clean.rs @@ -2,8 +2,16 @@ use crate::ops::forc_clean; use clap::Parser; use forc_util::ForcResult; +forc_util::cli_examples! { + crate::cli::Opt { + [Clean project => "forc clean"] + [Clean project with a custom path => "forc clean --path "] + } +} + /// Removes the default forc compiler output artifact directory, i.e. `/out`. #[derive(Debug, Parser)] +#[clap(bin_name = "forc clean", version, after_help = help())] pub struct Command { /// Path to the project, if not specified, current working directory will be used. #[clap(short, long)] diff --git a/forc/src/cli/commands/contract_id.rs b/forc/src/cli/commands/contract_id.rs index 9347a4b05e6..f84771d30cf 100644 --- a/forc/src/cli/commands/contract_id.rs +++ b/forc/src/cli/commands/contract_id.rs @@ -5,8 +5,16 @@ use crate::{ use clap::Parser; use forc_util::{tx_utils::Salt, ForcResult}; +forc_util::cli_examples! { + crate::cli::Opt { + [Get contract id => "forc contract-id"] + [Get contract id from a different path => "forc contract-id --path "] + } +} + /// Determine contract-id for a contract. For workspaces outputs all contract ids in the workspace. #[derive(Debug, Parser)] +#[clap(bin_name = "forc contract-id", version, after_help = help())] pub struct Command { #[clap(flatten)] pub pkg: Pkg, diff --git a/forc/src/cli/commands/init.rs b/forc/src/cli/commands/init.rs index 86d0e767410..8deab58bc32 100644 --- a/forc/src/cli/commands/init.rs +++ b/forc/src/cli/commands/init.rs @@ -2,8 +2,18 @@ use crate::ops::forc_init; use clap::Parser; use forc_util::ForcResult; +forc_util::cli_examples! { + crate::cli::Opt { + [Initialize a new Forc project => "forc init --path "] + [Initialize a new Forc project as workspace => "forc init --path --workspace"] + [Initialize a new Forc project with a predicate => "forc init --path --predicate"] + [Initialize a new Forc library project => "forc init --path --library"] + } +} + /// Create a new Forc project in an existing directory. #[derive(Debug, Parser)] +#[clap(bin_name = "forc init", version, after_help = help())] pub struct Command { /// The directory in which the forc project will be initialized. #[clap(long)] diff --git a/forc/src/cli/commands/new.rs b/forc/src/cli/commands/new.rs index 5c382a55b5f..9a3d69a7492 100644 --- a/forc/src/cli/commands/new.rs +++ b/forc/src/cli/commands/new.rs @@ -4,8 +4,18 @@ use clap::Parser; use forc_util::{forc_result_bail, validate_name, ForcResult}; use std::path::{Path, PathBuf}; +forc_util::cli_examples! { + crate::cli::Opt { + [Create a new project => "forc new --contract --name my_project "] + [Create a new workspace => "forc new --workspace --name my_workspace "] + [Create a new Forc project with a predicate => "forc new --predicate"] + [Create a new Forc library project => "forc new --library"] + } +} + /// Create a new Forc project at ``. #[derive(Debug, Parser)] +#[clap(bin_name = "forc new", version, after_help = help())] pub struct Command { /// The default program type. Excluding all flags or adding this flag creates a basic contract /// program. diff --git a/forc/src/cli/commands/parse_bytecode.rs b/forc/src/cli/commands/parse_bytecode.rs index 94d8bc96371..2b8957e3f2c 100644 --- a/forc/src/cli/commands/parse_bytecode.rs +++ b/forc/src/cli/commands/parse_bytecode.rs @@ -7,8 +7,15 @@ use term_table::row::Row; use term_table::table_cell::{Alignment, TableCell}; use tracing::info; +forc_util::cli_examples! { + crate::cli::Opt { + [Parse bytecode => "forc parse-bytecode "] + } +} + /// Parse bytecode file into a debug format. #[derive(Debug, Parser)] +#[clap(bin_name = "forc parse-bytecode", version, after_help = help())] pub(crate) struct Command { file_path: String, } diff --git a/forc/src/cli/commands/plugins.rs b/forc/src/cli/commands/plugins.rs index c113572385e..04e334cb037 100644 --- a/forc/src/cli/commands/plugins.rs +++ b/forc/src/cli/commands/plugins.rs @@ -10,17 +10,19 @@ use std::{ use tracing::info; forc_util::cli_examples! { - [ List all plugins => forc "plugins" => r#".*Installed Plugins.*"# ] - [ List all plugins with their paths => forc "plugins --paths" => r#".*Installed Plugins.*"# ] - [ List all plugins with their descriptions => forc "plugins --describe" => r#".*Installed Plugins.*"# ] - [ List all plugins with their paths and descriptions => forc "plugins --paths --describe" => r#".*Installed Plugins.*"# ] + crate::cli::Opt { + [ List all plugins => "forc plugins" ] + [ List all plugins with their paths => "forc plugins --paths" ] + [ List all plugins with their descriptions => "forc plugins --describe" ] + [ List all plugins with their paths and descriptions => "forc plugins --paths --describe" ] + } } /// Find all forc plugins available via `PATH`. /// /// Prints information about each discovered plugin. #[derive(Debug, Parser)] -#[clap(name = "plugins", about = "List all forc plugins", version, after_help = help())] +#[clap(name = "forc plugins", about = "List all forc plugins", version, after_help = help())] pub struct Command { /// Prints the absolute path to each discovered plugin. #[clap(long = "paths", short = 'p')] diff --git a/forc/src/cli/commands/predicate_root.rs b/forc/src/cli/commands/predicate_root.rs index d011025a1fe..93e948e3f5d 100644 --- a/forc/src/cli/commands/predicate_root.rs +++ b/forc/src/cli/commands/predicate_root.rs @@ -4,9 +4,16 @@ use forc_util::ForcResult; pub use crate::cli::shared::{BuildOutput, BuildProfile, Minify, Pkg, Print}; use crate::ops::forc_predicate_root; +forc_util::cli_examples! { + crate::cli::Opt { + [Get predicate root => "forc predicate-root"] + } +} + /// Determine predicate-root for a predicate. For workspaces outputs all predicate roots in the /// workspace. #[derive(Debug, Parser)] +#[clap(bin_name = "forc predicate-root", version, after_help = help())] pub struct Command { #[clap(flatten)] pub pkg: Pkg, diff --git a/forc/src/cli/commands/template.rs b/forc/src/cli/commands/template.rs index 01d30a5d994..10a9d905639 100644 --- a/forc/src/cli/commands/template.rs +++ b/forc/src/cli/commands/template.rs @@ -2,8 +2,15 @@ use crate::ops::forc_template; use clap::Parser; use forc_util::ForcResult; +forc_util::cli_examples! { + crate::cli::Opt { + [Create a new Forc project from an option template => "forc template new-path --template-name option"] + } +} + /// Create a new Forc project from a git template. #[derive(Debug, Parser)] +#[clap(bin_name = "forc template", version, after_help = help())] pub struct Command { /// The template url, should be a git repo. #[clap(long, short, default_value = "https://github.com/fuellabs/sway")] diff --git a/forc/src/cli/commands/test.rs b/forc/src/cli/commands/test.rs index 34f1b459e48..c82a055c60a 100644 --- a/forc/src/cli/commands/test.rs +++ b/forc/src/cli/commands/test.rs @@ -8,10 +8,12 @@ use pkg::manifest::ExperimentalFlags; use tracing::info; forc_util::cli_examples! { - [ Run test => forc "test" => ".*could not find `Forc.toml`.*" ] - [ Run test with a filter => forc "test $filter" => ".*could not find `Forc.toml`.*" ] - [ Run test without any output => forc "test --silent" => "^$" ] - [ Run test without creating or update the lock file => forc "test --locked" => ".*could not find `Forc.toml`.*" ] + crate::cli::Opt { + [ Run test => "forc test" ] + [ Run test with a filter => "forc test $filter" ] + [ Run test without any output => "forc test --silent" ] + [ Run test without creating or update the lock file => "forc test --locked" ] + } } /// Run the Sway unit tests for the current project. @@ -32,6 +34,7 @@ forc_util::cli_examples! { /// considered a failure in the case that a revert (`rvrt`) instruction is encountered during /// execution. Otherwise, it is considered a success. #[derive(Debug, Parser)] +#[clap(bin_name = "forc test", version, after_help = help())] pub struct Command { #[clap(flatten)] pub build: cli::shared::Build, diff --git a/forc/src/cli/commands/update.rs b/forc/src/cli/commands/update.rs index 31ca74e2dff..10264d431cb 100644 --- a/forc/src/cli/commands/update.rs +++ b/forc/src/cli/commands/update.rs @@ -3,8 +3,17 @@ use clap::Parser; use forc_pkg::source::IPFSNode; use forc_util::ForcResult; +forc_util::cli_examples! { + crate::cli::Opt { + [Update dependencies => "forc update"] + [Update a specific dependency => "forc update -d std"] + [Check if dependencies have newer versions => "forc update --check"] + } +} + /// Update dependencies in the Forc dependencies directory. -#[derive(Debug, Parser)] +#[derive(Debug, Default, Parser)] +#[clap(bin_name = "forc update", version, after_help = help())] pub struct Command { /// Path to the project, if not specified, current working directory will be used. #[clap(short, long)]