Let zebrad revhex read from stdin (#648)
* Log at warn level for commands that use stdout * Let zebrad revhex read from stdin Most unix tools support reading from stdin, so they can be used in pipelines. Part of #564.
This commit is contained in:
parent
e452ba1c13
commit
12b9fa8ae2
|
|
@ -79,6 +79,7 @@ impl Application for ZebradApp {
|
||||||
&mut self.state
|
&mut self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the framework components used by this application.
|
||||||
fn framework_components(
|
fn framework_components(
|
||||||
&mut self,
|
&mut self,
|
||||||
command: &Self::Cmd,
|
command: &Self::Cmd,
|
||||||
|
|
@ -135,10 +136,29 @@ impl Application for ZebradApp {
|
||||||
|
|
||||||
impl ZebradApp {
|
impl ZebradApp {
|
||||||
fn level(&self, command: &EntryPoint<ZebradCmd>) -> String {
|
fn level(&self, command: &EntryPoint<ZebradCmd>) -> String {
|
||||||
if let Ok(level) = std::env::var("ZEBRAD_LOG") {
|
// `None` outputs zebrad usage information to stdout
|
||||||
level
|
let command_uses_stdout = match &command.command {
|
||||||
} else if command.verbose {
|
None => true,
|
||||||
|
Some(c) => c.uses_stdout(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow users to:
|
||||||
|
// - override all other configs and defaults using the command line
|
||||||
|
// - see command outputs without spurious log messages, by default
|
||||||
|
// - override the config file using an environmental variable
|
||||||
|
if command.verbose {
|
||||||
"debug".to_string()
|
"debug".to_string()
|
||||||
|
} else if command_uses_stdout {
|
||||||
|
// Tracing sends output to stdout, so we disable info-level logs for
|
||||||
|
// some commands.
|
||||||
|
//
|
||||||
|
// TODO: send tracing output to stderr. This change requires an abscissa
|
||||||
|
// update, because `abscissa_core::component::Tracing` uses
|
||||||
|
// `tracing_subscriber::fmt::Formatter`, which has `Stdout` as a
|
||||||
|
// type parameter. We need `MakeWriter` or a similar type.
|
||||||
|
"warn".to_string()
|
||||||
|
} else if let Ok(level) = std::env::var("ZEBRAD_LOG") {
|
||||||
|
level
|
||||||
} else if let Some(ZebradConfig {
|
} else if let Some(ZebradConfig {
|
||||||
tracing:
|
tracing:
|
||||||
crate::config::TracingSection {
|
crate::config::TracingSection {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,24 @@ pub enum ZebradCmd {
|
||||||
Version(VersionCmd),
|
Version(VersionCmd),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ZebradCmd {
|
||||||
|
/// Returns true if this command sends output to stdout.
|
||||||
|
///
|
||||||
|
/// For example, `Generate` sends a default config file to stdout.
|
||||||
|
///
|
||||||
|
/// Usage note: `abscissa_core::EntryPoint` stores an `Option<ZerbradCmd>`.
|
||||||
|
/// If the command is `None`, then abscissa writes zebrad usage information
|
||||||
|
/// to stdout.
|
||||||
|
pub(crate) fn uses_stdout(&self) -> bool {
|
||||||
|
use ZebradCmd::*;
|
||||||
|
match self {
|
||||||
|
// List all the commands, so new commands have to make a choice here
|
||||||
|
Generate(_) | Help(_) | Revhex(_) | Version(_) => true,
|
||||||
|
Connect(_) | Seed(_) | Start(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This trait allows you to define how application configuration is loaded.
|
/// This trait allows you to define how application configuration is loaded.
|
||||||
impl Configurable<ZebradConfig> for ZebradCmd {
|
impl Configurable<ZebradConfig> for ZebradCmd {
|
||||||
/// Location of the configuration file
|
/// Location of the configuration file
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,44 @@
|
||||||
#![allow(clippy::never_loop)]
|
#![allow(clippy::never_loop)]
|
||||||
|
|
||||||
use abscissa_core::{Command, Options, Runnable};
|
use abscissa_core::{Command, Options, Runnable};
|
||||||
|
use std::io::stdin;
|
||||||
|
|
||||||
/// `revhex` subcommand
|
/// `revhex` subcommand
|
||||||
#[derive(Command, Debug, Default, Options)]
|
#[derive(Command, Debug, Default, Options)]
|
||||||
pub struct RevhexCmd {
|
pub struct RevhexCmd {
|
||||||
/// The hex string whose endianness will be reversed.
|
/// The hex string whose endianness will be reversed.
|
||||||
|
///
|
||||||
|
/// When input is "-" or empty, reads lines from standard input, and
|
||||||
|
/// reverses each line.
|
||||||
#[options(free)]
|
#[options(free)]
|
||||||
input: String,
|
input: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the hexadecimal-encoded string `s` in byte-reversed order.
|
||||||
|
fn byte_reverse_hex(s: &str) -> String {
|
||||||
|
String::from_utf8(
|
||||||
|
s.as_bytes()
|
||||||
|
.chunks(2)
|
||||||
|
.rev()
|
||||||
|
.map(|c| c.iter())
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<u8>>(),
|
||||||
|
)
|
||||||
|
.expect("input should be ascii")
|
||||||
|
}
|
||||||
|
|
||||||
impl Runnable for RevhexCmd {
|
impl Runnable for RevhexCmd {
|
||||||
/// Print endian-reversed hex string.
|
/// Print endian-reversed hex string.
|
||||||
fn run(&self) {
|
fn run(&self) {
|
||||||
println!(
|
if self.input.is_empty() || self.input == "-" {
|
||||||
"{}",
|
// "-" is a typical command-line argument for "read standard input"
|
||||||
String::from_utf8(
|
let mut input = String::new();
|
||||||
self.input
|
while stdin().read_line(&mut input).is_ok() {
|
||||||
.as_bytes()
|
println!("{}", byte_reverse_hex(&input.trim()));
|
||||||
.chunks(2)
|
}
|
||||||
.rev()
|
} else {
|
||||||
.map(|c| c.iter())
|
println!("{}", byte_reverse_hex(&self.input));
|
||||||
.flatten()
|
}
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<u8>>(),
|
|
||||||
)
|
|
||||||
.expect("input should be ascii")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue