Added colored output for head and base32 applets (file header is green)

This commit is contained in:
Nathan Fisher 2023-01-04 14:26:44 -05:00
parent dff8561875
commit 36835dd322
9 changed files with 191 additions and 69 deletions

24
Cargo.lock generated
View File

@ -2,6 +2,17 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "base64"
version = "0.20.0"
@ -98,6 +109,15 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -134,7 +154,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
dependencies = [
"hermit-abi",
"hermit-abi 0.2.6",
"io-lifetimes",
"rustix",
"windows-sys",
@ -194,6 +214,7 @@ dependencies = [
name = "shitbox"
version = "0.1.0"
dependencies = [
"atty",
"base64",
"clap",
"clap_complete",
@ -202,6 +223,7 @@ dependencies = [
"data-encoding",
"hostname",
"once_cell",
"termcolor",
]
[[package]]

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
atty = "0.2.14"
base64 = "0.20.0"
clap = "4.0.29"
clap_complete = "4.0.6"
@ -14,6 +15,7 @@ clap_mangen = "0.2.5"
data-encoding = "2.3.3"
hostname = { version = "0.3", features = ["set"] }
once_cell = "1.16.0"
termcolor = "1.1.3"
[profile.release]
codegen-units = 1

View File

@ -3,9 +3,10 @@ use clap::{value_parser, Arg, ArgAction, Command};
use data_encoding::BASE32;
use std::{
fs,
io::{self, Read},
io::{self, Read, Write},
process,
};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[derive(Debug)]
pub struct Base32 {
@ -47,6 +48,10 @@ impl Cmd for Base32 {
.long("wrap")
.value_parser(value_parser!(usize))
.default_value("76"),
Arg::new("color")
.short('c')
.long("color")
.value_parser(["always", "ansi", "auto", "never"]),
Arg::new("VERBOSE")
.help("Display a header naming each file")
.short('v')
@ -69,12 +74,27 @@ impl Cmd for Base32 {
None => vec![String::from("-")],
};
let len = files.len();
let color = match matches.get_one::<String>("color").map(|x| x.as_str()) {
Some("always") => ColorChoice::Always,
Some("ansi") => ColorChoice::AlwaysAnsi,
Some("auto") => {
if atty::is(atty::Stream::Stdout) {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}
_ => ColorChoice::Never,
};
for (index, file) in files.into_iter().enumerate() {
if { len > 1 || matches.get_flag("VERBOSE") } && !matches.get_flag("QUIET") {
let mut stdout = StandardStream::stdout(color);
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
match index {
0 => println!("===> {file} <==="),
_ => println!("\n===> {file} <==="),
};
0 => writeln!(stdout, "===> {file} <==="),
_ => writeln!(stdout, "\n===> {file} <==="),
}?;
stdout.reset()?;
} else if index > 0 {
println!();
}

View File

@ -60,10 +60,10 @@ impl Cmd for Factor {
}
fn first_factor(num: u64) -> u64 {
if crate::math::is_prime(num) {
return num;
if num % 2 == 0 {
return 2;
}
for n in 2..=num {
for n in (3..=num).step_by(1) {
if num % n == 0 {
return n;
}

View File

@ -5,9 +5,10 @@ use std::{
env,
error::Error,
fs,
io::{self, stdin, Read},
io::{self, stdin, Read, Write},
process,
};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[derive(Debug)]
pub struct Head {
@ -27,45 +28,49 @@ impl Cmd for Head {
fn cli(&self) -> Command {
Command::new(self.name)
.author("Nathan Fisher")
.about("Display first lines of a file")
.long_about(
"Print the first 10 lines of each FILE to standard output.\n\
With more than one FILE, precede each with a header giving the file name.\n\n\
With no FILE, or when FILE is -, read standard input."
)
.args([
Arg::new("FILES")
.help("The input file to use")
.num_args(1..),
Arg::new("BYTES")
.help("Count bytes instead of lines")
.short('c')
.long("bytes")
.action(ArgAction::SetTrue),
Arg::new("QUIET")
.help("Disable printing a header. Overrides -c")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue),
Arg::new("HEADER")
.help("Each file is preceded by a header consisting of the string \"==> XXX <==\" where \"XXX\" is the name of the file.")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue),
Arg::new("LINES")
.help("Count n number of lines (or bytes if -c is specified).")
.short('n')
.long("lines")
.allow_negative_numbers(false)
.conflicts_with("num")
.value_parser(value_parser!(usize)),
Arg::new("num")
.short('1')
.short_aliases(['2', '3', '4', '5', '6', '7', '8', '9'])
.hide(true)
.action(ArgAction::Append)
])
.author("Nathan Fisher")
.about("Display first lines of a file")
.long_about(
"Print the first 10 lines of each FILE to standard output.\n\
With more than one FILE, precede each with a header giving the file name.\n\n\
With no FILE, or when FILE is -, read standard input."
)
.args([
Arg::new("FILES")
.help("The input file to use")
.num_args(1..),
Arg::new("BYTES")
.help("Count bytes instead of lines")
.short('c')
.long("bytes")
.action(ArgAction::SetTrue),
Arg::new("QUIET")
.help("Disable printing a header. Overrides -c")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue),
Arg::new("HEADER")
.help("Each file is preceded by a header consisting of the string \"==> XXX <==\" where \"XXX\" is the name of the file.")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue),
Arg::new("LINES")
.help("Count n number of lines (or bytes if -c is specified).")
.short('n')
.long("lines")
.allow_negative_numbers(false)
.conflicts_with("num")
.value_parser(value_parser!(usize)),
Arg::new("color")
.short('C')
.long("color")
.value_parser(["always", "ansi", "auto", "never"]),
Arg::new("num")
.short('1')
.short_aliases(['2', '3', '4', '5', '6', '7', '8', '9'])
.hide(true)
.action(ArgAction::Append)
])
}
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
@ -78,16 +83,17 @@ impl Cmd for Head {
if args.len() > idx {
let arg1 = &args[idx];
if arg1.starts_with('-') && arg1.len() > 1 {
let lines: usize = arg1[1..].parse()?;
let files: Vec<_> = if args.len() > idx + 1 {
args[idx + 1..].iter().map(String::as_str).collect()
} else {
vec!["-"]
};
for file in &files {
head(file, lines, false, false);
if let Ok(lines) = arg1[1..].parse::<usize>() {
let files: Vec<_> = if args.len() > idx + 1 {
args[idx + 1..].iter().map(String::as_str).collect()
} else {
vec!["-"]
};
for file in &files {
head(file, lines, false, false, ColorChoice::Never)?;
}
return Ok(());
}
return Ok(());
}
}
let Some(matches) = matches else {
@ -102,11 +108,23 @@ impl Cmd for Head {
};
let header =
!matches.get_flag("QUIET") && { files.len() > 1 || matches.get_flag("HEADER") };
let color = match matches.get_one::<String>("color").map(|x| x.as_str()) {
Some("always") => ColorChoice::Always,
Some("ansi") => ColorChoice::AlwaysAnsi,
Some("auto") => {
if atty::is(atty::Stream::Stdout) {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}
_ => ColorChoice::Never,
};
for (index, file) in files.into_iter().enumerate() {
if index == 1 && header {
println!();
}
head(&file, lines, header, matches.get_flag("BYTES"));
head(&file, lines, header, matches.get_flag("BYTES"), color)?;
}
Ok(())
}
@ -116,7 +134,13 @@ impl Cmd for Head {
}
}
fn head(file: &str, count: usize, header: bool, bytes: bool) {
fn head(
file: &str,
count: usize,
header: bool,
bytes: bool,
color: ColorChoice,
) -> Result<(), Box<dyn Error>> {
let mut contents = String::new();
if file == "-" {
match stdin().read_to_string(&mut contents) {
@ -137,7 +161,10 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
};
}
if header {
println!("==> {file} <==");
let mut stdout = StandardStream::stdout(color);
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
writeln!(stdout, "==> {file} <==")?;
stdout.reset()?;
}
if bytes {
for (index, char) in contents.chars().into_iter().enumerate() {
@ -145,7 +172,7 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
print!("{char}");
} else {
println!();
return;
return Ok(());
}
}
println!();
@ -154,8 +181,9 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
if index < count {
println!("{line}");
} else {
return;
return Ok(());
}
}
}
Ok(())
}

View File

@ -26,7 +26,7 @@ pub mod head;
pub mod hostname;
mod ln;
mod ls;
mod mountpoint;
pub mod mountpoint;
mod mv;
pub mod nologin;
mod pwd;
@ -38,9 +38,9 @@ mod sync;
pub mod r#true;
pub use {
self::base64::{Base64, BASE_64},
self::hostname::{Hostname, HOSTNAME},
base32::{Base32, BASE_32},
self::base64::{Base64, BASE_64},
bootstrap::{Bootstrap, BOOTSTRAP},
dirname::{Dirname, DIRNAME},
echo::{Echo, ECHO},

View File

@ -1 +1,50 @@
use super::Cmd;
use clap::{Arg, ArgAction, Command};
#[derive(Debug)]
pub struct Mountpoint {
name: &'static str,
path: Option<crate::Path>,
}
pub const MOUNTPOINT: Mountpoint = Mountpoint {
name: "mountpoint",
path: Some(crate::Path::Bin),
};
impl Cmd for Mountpoint {
fn name(&self) -> &str {
self.name
}
fn cli(&self) -> clap::Command {
Command::new(self.name)
.about("see if a directory or file is a mountpoint")
.author("Nathan Fisher")
.args([
Arg::new("fs-devno")
.help("Show the major/minor numbers of the device that is mounted on the given directory.")
.short('d')
.long("fs-devno")
.action(ArgAction::SetTrue),
Arg::new("devno")
.help("Show the major/minor numbers of the given blockdevice on standard output.")
.short('x')
.long("devno")
.action(ArgAction::SetTrue),
Arg::new("quiet")
.help("Be quiet - dont print anything.")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue),
])
}
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
unimplemented!();
}
fn path(&self) -> Option<crate::Path> {
self.path
}
}

View File

@ -1,5 +1,6 @@
use super::{
Cmd, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, NOLOGIN, SLEEP, TRUE,
Cmd, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, NOLOGIN, SLEEP,
TRUE,
};
use clap::Command;
use std::{

View File

@ -3,8 +3,8 @@ use std::{env, error::Error, path::PathBuf, string::ToString};
pub mod cmd;
pub use cmd::{
Cmd, Commands, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME, NOLOGIN,
SHITBOX, SLEEP, TRUE,
Cmd, Commands, BASE_32, BASE_64, BOOTSTRAP, DIRNAME, ECHO, FACTOR, FALSE, HEAD, HOSTNAME,
NOLOGIN, SHITBOX, SLEEP, TRUE,
};
pub mod math;
@ -41,8 +41,8 @@ pub fn run() -> Result<(), Box<dyn Error>> {
cmd::COMMANDS
.set(Commands {
items: vec![
&BASE_32, &BASE_64, &BOOTSTRAP, &DIRNAME, &ECHO, &FALSE, &FACTOR, &HEAD, &HOSTNAME,
&NOLOGIN, &TRUE, &SLEEP, &SHITBOX,
&BASE_32, &BASE_64, &BOOTSTRAP, &DIRNAME, &ECHO, &FALSE, &FACTOR, &HEAD,
&HOSTNAME, &NOLOGIN, &TRUE, &SLEEP, &SHITBOX,
],
})
.expect("Cannot register commands");