diff --git a/contrib/clarity-cli/src/lib.rs b/contrib/clarity-cli/src/lib.rs index 827c863d97..0d5cf0665e 100644 --- a/contrib/clarity-cli/src/lib.rs +++ b/contrib/clarity-cli/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::io::Write; +use std::io::{Read as _, Write}; use std::path::PathBuf; use std::{fs, io}; @@ -114,6 +114,46 @@ fn friendly_expect_opt(input: Option, msg: &str) -> A { }) } +/// Read text content from a file path or stdin if path is "-" +pub fn read_file_or_stdin(path: &str) -> String { + if path == "-" { + let mut buffer = String::new(); + io::stdin() + .read_to_string(&mut buffer) + .expect("Error reading from stdin"); + buffer + } else { + fs::read_to_string(path).unwrap_or_else(|e| panic!("Error reading file {path}: {e}")) + } +} + +/// Read binary content from a file path or stdin if path is "-" +pub fn read_file_or_stdin_bytes(path: &str) -> Vec { + if path == "-" { + let mut buffer = vec![]; + io::stdin() + .read_to_end(&mut buffer) + .expect("Error reading from stdin"); + buffer + } else { + fs::read(path).unwrap_or_else(|e| panic!("Error reading file {path}: {e}")) + } +} + +/// Read content from an optional file path, defaulting to stdin if None or "-" +pub fn read_optional_file_or_stdin(path: Option<&PathBuf>) -> String { + match path { + Some(p) => read_file_or_stdin(p.to_str().expect("Invalid UTF-8 in path")), + None => { + let mut buffer = String::new(); + io::stdin() + .read_to_string(&mut buffer) + .expect("Error reading from stdin"); + buffer + } + } +} + /// Represents an initial allocation entry from JSON #[derive(Deserialize)] pub struct InitialAllocation { diff --git a/contrib/clarity-cli/src/main.rs b/contrib/clarity-cli/src/main.rs index e248fed985..51c02f7433 100644 --- a/contrib/clarity-cli/src/main.rs +++ b/contrib/clarity-cli/src/main.rs @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::io::Read; use std::path::PathBuf; -use std::{fs, io, process}; +use std::process; use clap::{Parser, Subcommand}; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; @@ -24,37 +23,11 @@ use clarity::vm::{ClarityVersion, SymbolicExpression}; use clarity_cli::{ DEFAULT_CLI_EPOCH, execute_check, execute_eval, execute_eval_at_block, execute_eval_at_chaintip, execute_eval_raw, execute_execute, execute_generate_address, - execute_initialize, execute_launch, execute_repl, vm_execute_in_epoch, + execute_initialize, execute_launch, execute_repl, read_file_or_stdin, + read_optional_file_or_stdin, vm_execute_in_epoch, }; use stacks_common::types::StacksEpochId; -/// Read content from a file path or stdin if path is "-" -fn read_file_or_stdin(path: &str) -> String { - if path == "-" { - let mut buffer = String::new(); - io::stdin() - .read_to_string(&mut buffer) - .expect("Error reading from stdin"); - buffer - } else { - fs::read_to_string(path).unwrap_or_else(|e| panic!("Error reading file {path}: {e}")) - } -} - -/// Read content from an optional file path, defaulting to stdin if None or "-" -fn read_optional_file_or_stdin(path: Option<&PathBuf>) -> String { - match path { - Some(p) => read_file_or_stdin(p.to_str().expect("Invalid UTF-8 in path")), - None => { - let mut buffer = String::new(); - io::stdin() - .read_to_string(&mut buffer) - .expect("Error reading from stdin"); - buffer - } - } -} - /// Parse epoch string to StacksEpochId fn parse_epoch(epoch_str: Option<&String>) -> StacksEpochId { if let Some(s) = epoch_str { diff --git a/contrib/stacks-inspect/src/lib.rs b/contrib/stacks-inspect/src/lib.rs index cd3b6dc5da..34d38d979a 100644 --- a/contrib/stacks-inspect/src/lib.rs +++ b/contrib/stacks-inspect/src/lib.rs @@ -20,6 +20,7 @@ use std::{fs, io, process}; use clarity::types::chainstate::SortitionId; use clarity::util::hash::{Sha512Trunc256Sum, to_hex}; +use clarity_cli::read_file_or_stdin; use regex::Regex; use rusqlite::{Connection, OpenFlags}; use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId}; @@ -773,18 +774,22 @@ pub fn command_contract_hash(argv: &[String], _conf: Option<&Config>) { let print_help_and_exit = || -> ! { let n = &argv[0]; eprintln!("Usage:"); - eprintln!(" {n} "); + eprintln!(" {n} "); process::exit(1); }; // Process CLI args let contract_path = argv.get(1).unwrap_or_else(|| print_help_and_exit()); - let contract_source = fs::read_to_string(contract_path) - .unwrap_or_else(|e| panic!("Failed to read contract file {contract_path:?}: {e}")); + let contract_source = read_file_or_stdin(contract_path); let hash = Sha512Trunc256Sum::from_data(contract_source.as_bytes()); let hex_string = to_hex(hash.as_bytes()); - println!("Contract hash for {contract_path}:\n{hex_string}"); + let source_name = if contract_path == "-" { + "stdin" + } else { + contract_path + }; + println!("Contract hash for {source_name}:\n{hex_string}"); } /// Fetch and process a `StagingBlock` from database and call `replay_block()` to validate diff --git a/contrib/stacks-inspect/src/main.rs b/contrib/stacks-inspect/src/main.rs index 9e5b98a0db..e00bd3004e 100644 --- a/contrib/stacks-inspect/src/main.rs +++ b/contrib/stacks-inspect/src/main.rs @@ -19,7 +19,7 @@ extern crate stacks_common; use clarity::consts::CHAIN_ID_MAINNET; use clarity::types::StacksEpochId; use clarity::types::chainstate::StacksPrivateKey; -use clarity_cli::DEFAULT_CLI_EPOCH; +use clarity_cli::{DEFAULT_CLI_EPOCH, read_file_or_stdin, read_file_or_stdin_bytes}; use stacks_inspect::{ command_contract_hash, command_replay_mock_mining, command_try_mine, command_validate_block, drain_common_opts, @@ -394,12 +394,21 @@ fn main() { if argv[1] == "decode-tx" { if argv.len() < 3 { - eprintln!("Usage: {} decode-tx TRANSACTION", argv[0]); + eprintln!( + "Usage: {} decode-tx ", + argv[0] + ); process::exit(1); } - let tx_str = &argv[2]; - let tx_bytes = hex_bytes(tx_str) + let tx_arg = &argv[2]; + let tx_str = if tx_arg == "-" || std::path::Path::new(tx_arg).exists() { + read_file_or_stdin(tx_arg).trim().to_string() + } else { + // Treat as hex string directly + tx_arg.clone() + }; + let tx_bytes = hex_bytes(&tx_str) .map_err(|_e| { eprintln!("Failed to decode transaction: must be a hex string"); process::exit(1); @@ -430,13 +439,12 @@ fn main() { if argv[1] == "decode-block" { if argv.len() < 3 { - eprintln!("Usage: {} decode-block BLOCK_PATH", argv[0]); + eprintln!("Usage: {} decode-block ", argv[0]); process::exit(1); } let block_path = &argv[2]; - let block_data = - fs::read(block_path).unwrap_or_else(|_| panic!("Failed to open {block_path}")); + let block_data = read_file_or_stdin_bytes(block_path); let block = StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_data)) .map_err(|_e| { @@ -451,12 +459,21 @@ fn main() { if argv[1] == "decode-nakamoto-block" { if argv.len() < 3 { - eprintln!("Usage: {} decode-nakamoto-block BLOCK_HEX", argv[0]); + eprintln!( + "Usage: {} decode-nakamoto-block ", + argv[0] + ); process::exit(1); } - let block_hex = &argv[2]; - let block_data = hex_bytes(block_hex).unwrap_or_else(|_| panic!("Failed to decode hex")); + let block_arg = &argv[2]; + let block_hex = if block_arg == "-" || std::path::Path::new(block_arg).exists() { + read_file_or_stdin(block_arg).trim().to_string() + } else { + // Treat as hex string directly + block_arg.clone() + }; + let block_data = hex_bytes(&block_hex).unwrap_or_else(|_| panic!("Failed to decode hex")); let block = NakamotoBlock::consensus_deserialize(&mut io::Cursor::new(&block_data)) .map_err(|_e| { eprintln!("Failed to decode block"); @@ -470,11 +487,10 @@ fn main() { if argv[1] == "decode-net-message" { let data: String = argv[2].clone(); - let buf = if data == "-" { - let mut buffer = vec![]; - io::stdin().read_to_end(&mut buffer).unwrap(); - buffer + let buf = if data == "-" || std::path::Path::new(&data).exists() { + read_file_or_stdin_bytes(&data) } else { + // Parse as JSON array of bytes let data: serde_json::Value = serde_json::from_str(data.as_str()).unwrap(); let data_array = data.as_array().unwrap(); let mut buf = vec![]; @@ -804,15 +820,14 @@ check if the associated microblocks can be downloaded if argv[1] == "decode-microblocks" { if argv.len() < 3 { eprintln!( - "Usage: {} decode-microblocks MICROBLOCK_STREAM_PATH", + "Usage: {} decode-microblocks ", argv[0] ); process::exit(1); } let mblock_path = &argv[2]; - let mblock_data = - fs::read(mblock_path).unwrap_or_else(|_| panic!("Failed to open {mblock_path}")); + let mblock_data = read_file_or_stdin_bytes(mblock_path); let mut cursor = io::Cursor::new(&mblock_data); let mut debug_cursor = LogReader::from_reader(&mut cursor); @@ -1028,7 +1043,7 @@ check if the associated microblocks can be downloaded if argv[1] == "post-stackerdb" { if argv.len() < 4 { eprintln!( - "Usage: {} post-stackerdb slot_id slot_version privkey data", + "Usage: {} post-stackerdb slot_id slot_version privkey ", &argv[0] ); process::exit(1); @@ -1038,11 +1053,10 @@ check if the associated microblocks can be downloaded let privkey: String = argv[4].clone(); let data: String = argv[5].clone(); - let buf = if data == "-" { - let mut buffer = vec![]; - io::stdin().read_to_end(&mut buffer).unwrap(); - buffer + let buf = if data == "-" || std::path::Path::new(&data).exists() { + read_file_or_stdin_bytes(&data) } else { + // Use the argument directly as data data.as_bytes().to_vec() }; @@ -1221,7 +1235,7 @@ check if the associated microblocks can be downloaded if argv[1] == "shadow-chainstate-patch" { if argv.len() < 5 { eprintln!( - "Usage: {} shadow-chainstate-patch CHAINSTATE_DIR NETWORK SHADOW_BLOCKS_PATH.JSON", + "Usage: {} shadow-chainstate-patch CHAINSTATE_DIR NETWORK ", &argv[0] ); process::exit(1); @@ -1232,10 +1246,7 @@ check if the associated microblocks can be downloaded let shadow_blocks_json_path = argv[4].as_str(); let shadow_blocks_hex = { - let mut blocks_json_file = - File::open(shadow_blocks_json_path).expect("Unable to open file"); - let mut buffer = vec![]; - blocks_json_file.read_to_end(&mut buffer).unwrap(); + let buffer = read_file_or_stdin_bytes(shadow_blocks_json_path); let shadow_blocks_hex: Vec = serde_json::from_slice(&buffer).unwrap(); shadow_blocks_hex };