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. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "base64" name = "base64"
version = "0.20.0" version = "0.20.0"
@ -98,6 +109,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.2.6" version = "0.2.6"
@ -134,7 +154,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi 0.2.6",
"io-lifetimes", "io-lifetimes",
"rustix", "rustix",
"windows-sys", "windows-sys",
@ -194,6 +214,7 @@ dependencies = [
name = "shitbox" name = "shitbox"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"atty",
"base64", "base64",
"clap", "clap",
"clap_complete", "clap_complete",
@ -202,6 +223,7 @@ dependencies = [
"data-encoding", "data-encoding",
"hostname", "hostname",
"once_cell", "once_cell",
"termcolor",
] ]
[[package]] [[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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
atty = "0.2.14"
base64 = "0.20.0" base64 = "0.20.0"
clap = "4.0.29" clap = "4.0.29"
clap_complete = "4.0.6" clap_complete = "4.0.6"
@ -14,6 +15,7 @@ clap_mangen = "0.2.5"
data-encoding = "2.3.3" data-encoding = "2.3.3"
hostname = { version = "0.3", features = ["set"] } hostname = { version = "0.3", features = ["set"] }
once_cell = "1.16.0" once_cell = "1.16.0"
termcolor = "1.1.3"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1

View File

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

View File

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

View File

@ -5,9 +5,10 @@ use std::{
env, env,
error::Error, error::Error,
fs, fs,
io::{self, stdin, Read}, io::{self, stdin, Read, Write},
process, process,
}; };
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[derive(Debug)] #[derive(Debug)]
pub struct Head { pub struct Head {
@ -60,6 +61,10 @@ impl Cmd for Head {
.allow_negative_numbers(false) .allow_negative_numbers(false)
.conflicts_with("num") .conflicts_with("num")
.value_parser(value_parser!(usize)), .value_parser(value_parser!(usize)),
Arg::new("color")
.short('C')
.long("color")
.value_parser(["always", "ansi", "auto", "never"]),
Arg::new("num") Arg::new("num")
.short('1') .short('1')
.short_aliases(['2', '3', '4', '5', '6', '7', '8', '9']) .short_aliases(['2', '3', '4', '5', '6', '7', '8', '9'])
@ -78,18 +83,19 @@ impl Cmd for Head {
if args.len() > idx { if args.len() > idx {
let arg1 = &args[idx]; let arg1 = &args[idx];
if arg1.starts_with('-') && arg1.len() > 1 { if arg1.starts_with('-') && arg1.len() > 1 {
let lines: usize = arg1[1..].parse()?; if let Ok(lines) = arg1[1..].parse::<usize>() {
let files: Vec<_> = if args.len() > idx + 1 { let files: Vec<_> = if args.len() > idx + 1 {
args[idx + 1..].iter().map(String::as_str).collect() args[idx + 1..].iter().map(String::as_str).collect()
} else { } else {
vec!["-"] vec!["-"]
}; };
for file in &files { for file in &files {
head(file, lines, false, false); head(file, lines, false, false, ColorChoice::Never)?;
} }
return Ok(()); return Ok(());
} }
} }
}
let Some(matches) = matches else { let Some(matches) = matches else {
return Err(io::Error::new(io::ErrorKind::Other, "No input").into()); return Err(io::Error::new(io::ErrorKind::Other, "No input").into());
}; };
@ -102,11 +108,23 @@ impl Cmd for Head {
}; };
let header = let header =
!matches.get_flag("QUIET") && { files.len() > 1 || matches.get_flag("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() { for (index, file) in files.into_iter().enumerate() {
if index == 1 && header { if index == 1 && header {
println!(); println!();
} }
head(&file, lines, header, matches.get_flag("BYTES")); head(&file, lines, header, matches.get_flag("BYTES"), color)?;
} }
Ok(()) 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(); let mut contents = String::new();
if file == "-" { if file == "-" {
match stdin().read_to_string(&mut contents) { match stdin().read_to_string(&mut contents) {
@ -137,7 +161,10 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
}; };
} }
if header { 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 { if bytes {
for (index, char) in contents.chars().into_iter().enumerate() { 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}"); print!("{char}");
} else { } else {
println!(); println!();
return; return Ok(());
} }
} }
println!(); println!();
@ -154,8 +181,9 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
if index < count { if index < count {
println!("{line}"); println!("{line}");
} else { } else {
return; return Ok(());
} }
} }
} }
Ok(())
} }

View File

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

View File

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