Skip to content

Commit

Permalink
Taking a different approach
Browse files Browse the repository at this point in the history
Let the dev write the help with a single string, a human know how to
format things nicely.

At testing write a rudimentary args parser.
  • Loading branch information
crodas committed Dec 16, 2023
1 parent e47e1fd commit 45bd774
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 60 deletions.
101 changes: 46 additions & 55 deletions forc-plugins/forc-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,52 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use thiserror::Error;

#[cfg(test)]
const ROOT: &str = "0x2222222222222222222222222222222222222222222222222222222222222222";
#[cfg(test)]
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! {
[ Script example => tx "script" "--bytecode" BYTECODE_PATH "--data" DATA_PATH "--receipts-root" ROOT ]
[ Multiple inputs => tx "create" "--bytecode" BYTECODE_PATH
"--storage-slots" STORAGE_SLOTS
"--script-gas-limit" "100"
"--gas-price" "0"
"--maturity" "0"
"--witness" "adfd"
"--witness" "dfda"
"input" "contract"
"--utxo-id" "1"
"--output-ix" "1"
"--balance-root" BALANCE_ROOT_ADDR
"--state-root" STATE_ROOT_ADDR
"--tx-ptr" "89ACBDEFBDEF"
"--contract-id" CONTRACT_ID
"output" "coin"
"--to" COIN_TO_ADDR,
"--amount" "100"
"--asset-id" ASSET_ID
"output" "contract"
"--input-ix" "1"
"--balance-root" BALANCE_ROOT_ADDR
"--state-root" STATE_ROOT_ADDR
"output" "change"
"--to" COIN_TO_ADDR
"--amount" "100"
"--asset-id" ASSET_ID
"output" "variable"
"--to" "0x2222222222222222222222222222222222222222222222222222222222222222"
"--amount" "100"
"--asset-id" "0x0000000000000000000000000000000000000000000000000000000000000000"
"output" "contract-created"
"--contract-id" "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
"--state-root" "0x0000000000000000000000000000000000000000000000000000000000000000"
forc::cli_examples_v2! {
[ Script example => tx r#"script --bytecode "out/debug/tests.bin" --data "data.bin" \
--receipts-root 0x2222222222222222222222222222222222222222222222222222222222222222"# ]
[ Multiple inputs => tx r#"create --bytecode "out/debug/tests.bin"
--storage-slots out/debug/tests-storage_slots.json
--script-gas-limit 100 \
--gas-price 0 \
--maturity 0 \
--witness ADFD \
--witness DFDA \
input coin \
--utxo-id 0 \
--output-ix 0 \
--owner 0x0000000000000000000000000000000000000000000000000000000000000000 \
--amount 100 \
--asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
--tx-ptr 89ACBDEFBDEF \
--witness-ix 0 \
--maturity 0 \
input contract \
--utxo-id 1 \
--output-ix 1 \
--balance-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
--state-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
--tx-ptr 89ACBDEFBDEF \
--contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
output coin \
--to 0x2222222222222222222222222222222222222222222222222222222222222222 \
--amount 100 \
--asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
output contract \
--input-ix 1 \
--balance-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
--state-root 0x0000000000000000000000000000000000000000000000000000000000000000 \
output change \
--to 0x2222222222222222222222222222222222222222222222222222222222222222 \
--amount 100 \
--asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
output variable \
--to 0x2222222222222222222222222222222222222222222222222222222222222222 \
--amount 100 \
--asset-id 0x0000000000000000000000000000000000000000000000000000000000000000 \
output contract-created \
--contract-id 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
--state-root 0x0000000000000000000000000000000000000000000000000000000000000000
"#
]
}

Expand Down
Empty file.
116 changes: 115 additions & 1 deletion forc/src/cli/help.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,117 @@
#[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_v2 {
($( [ $($description:ident)* => $command:tt $args:expr ] )*) => {
#[cfg(test)]
use $crate::serial_test;
$(
$crate::paste::paste! {
#[cfg(test)]
#[test]
#[serial_test::serial]
fn [<$($description:lower _)*:snake example>] () {
let mut proc = std::process::Command::new("cargo");
proc.arg("run");
proc.arg("--bin");
proc.arg(format!("forc-{}", stringify!($command)));
proc.arg("--");

parse_args($args).into_iter().for_each(|arg| {
proc.arg(arg);
});

let path = std::path::Path::new("tests");
if path.is_dir() {
proc.current_dir(path);
}
let output = proc.output().expect(stringify!($command));
assert!(output.status.success(), "{}: {:?}", stringify!($($description)*), output);
}
}
)*

#[cfg(test)]
fn parse_args(input: &str) -> Vec<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();
}
}
}
}
}
}

args
}

fn help() -> &'static str {
Box::leak(format!("EXAMPLES:\n{}", examples()).into_boxed_str())
}

pub fn examples() -> &'static str {
Box::leak( [
$(
$crate::paste::paste! {
format!(" #{}\n forc {} {}\n\n", stringify!($($description)*), stringify!($command), $args )
},
)*
].concat().into_boxed_str())
}
}
}

#[macro_export]
macro_rules! cli_examples {
($( [ $($description:ident)* => $command:tt $($arg:expr)* ] )*) => {
Expand Down Expand Up @@ -44,7 +158,7 @@ macro_rules! cli_examples {
} else {
break;
};
if length + arg.len() > 60 && iter.peek().is_some() && arg.chars().next() != Some('-') {
if length + arg.len() > 70 && iter.peek().is_some() && arg.chars().next() != Some('-') {
// too long, break it into a new line
result.push_str("\\\n ");
is_multiline = true;
Expand Down
4 changes: 0 additions & 4 deletions forc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,5 @@ 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()
}

0 comments on commit 45bd774

Please sign in to comment.