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
};