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:
teor 2020-07-15 16:16:07 +10:00 committed by GitHub
parent e452ba1c13
commit 12b9fa8ae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 17 deletions

View File

@ -79,6 +79,7 @@ impl Application for ZebradApp {
&mut self.state
}
/// Returns the framework components used by this application.
fn framework_components(
&mut self,
command: &Self::Cmd,
@ -135,10 +136,29 @@ impl Application for ZebradApp {
impl ZebradApp {
fn level(&self, command: &EntryPoint<ZebradCmd>) -> String {
if let Ok(level) = std::env::var("ZEBRAD_LOG") {
level
} else if command.verbose {
// `None` outputs zebrad usage information to stdout
let command_uses_stdout = match &command.command {
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()
} 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 {
tracing:
crate::config::TracingSection {

View File

@ -52,6 +52,24 @@ pub enum ZebradCmd {
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.
impl Configurable<ZebradConfig> for ZebradCmd {
/// Location of the configuration file

View File

@ -3,31 +3,44 @@
#![allow(clippy::never_loop)]
use abscissa_core::{Command, Options, Runnable};
use std::io::stdin;
/// `revhex` subcommand
#[derive(Command, Debug, Default, Options)]
pub struct RevhexCmd {
/// The hex string whose endianness will be reversed.
///
/// When input is "-" or empty, reads lines from standard input, and
/// reverses each line.
#[options(free)]
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 {
/// Print endian-reversed hex string.
fn run(&self) {
println!(
"{}",
String::from_utf8(
self.input
.as_bytes()
.chunks(2)
.rev()
.map(|c| c.iter())
.flatten()
.cloned()
.collect::<Vec<u8>>(),
)
.expect("input should be ascii")
);
if self.input.is_empty() || self.input == "-" {
// "-" is a typical command-line argument for "read standard input"
let mut input = String::new();
while stdin().read_line(&mut input).is_ok() {
println!("{}", byte_reverse_hex(&input.trim()));
}
} else {
println!("{}", byte_reverse_hex(&self.input));
}
}
}