Major API revamp
This commit is contained in:
parent
480960ce2b
commit
d190050798
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -146,6 +146,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.4.1"
|
version = "6.4.1"
|
||||||
@ -181,6 +187,7 @@ dependencies = [
|
|||||||
"clap_complete_nushell",
|
"clap_complete_nushell",
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"hostname",
|
"hostname",
|
||||||
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -11,6 +11,7 @@ clap_complete = "4.0.6"
|
|||||||
clap_complete_nushell = "0.1.8"
|
clap_complete_nushell = "0.1.8"
|
||||||
clap_mangen = "0.2.5"
|
clap_mangen = "0.2.5"
|
||||||
hostname = { version = "0.3", features = ["set"] }
|
hostname = { version = "0.3", features = ["set"] }
|
||||||
|
once_cell = "1.16.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
31
src/cli.rs
31
src/cli.rs
@ -1,31 +0,0 @@
|
|||||||
use crate::cmd::{bootstrap, echo, head, hostname, r#false, r#true, sleep};
|
|
||||||
use clap::Command;
|
|
||||||
|
|
||||||
pub fn cli() -> Command {
|
|
||||||
Command::new("shitbox")
|
|
||||||
.about("The Harbor Freight multitool of embedded Linux")
|
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.subcommands([
|
|
||||||
bootstrap::cli(),
|
|
||||||
echo::cli(),
|
|
||||||
r#false::cli(),
|
|
||||||
head::cli(),
|
|
||||||
hostname::cli(),
|
|
||||||
sleep::cli(),
|
|
||||||
r#true::cli(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run() {
|
|
||||||
let matches = cli().get_matches();
|
|
||||||
match matches.subcommand() {
|
|
||||||
Some(("echo", _matches)) => echo::run(),
|
|
||||||
Some(("false", _matches)) => r#false::run(),
|
|
||||||
Some(("head", matches)) => head::run(matches),
|
|
||||||
Some(("hostname", matches)) => hostname::run(matches),
|
|
||||||
Some(("sleep", matches)) => sleep::run(matches),
|
|
||||||
Some(("true", _matches)) => r#true::run(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +1,32 @@
|
|||||||
use crate::cmd;
|
use super::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use clap_complete::{generate_to, shells, Generator};
|
use clap_complete::{generate_to, shells, Generator};
|
||||||
use clap_complete_nushell::Nushell;
|
use clap_complete_nushell::Nushell;
|
||||||
use clap_mangen::Man;
|
use clap_mangen::Man;
|
||||||
use std::{
|
use std::{
|
||||||
fs, io,
|
error::Error,
|
||||||
|
fs,
|
||||||
|
io::{self, ErrorKind},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const COMMANDS: [fn() -> clap::Command; 8] = [
|
pub struct Bootstrap {
|
||||||
cmd::bootstrap::cli,
|
name: &'static str,
|
||||||
cmd::echo::cli,
|
path: Option<crate::Path>,
|
||||||
cmd::r#false::cli,
|
}
|
||||||
cmd::head::cli,
|
|
||||||
cmd::hostname::cli,
|
|
||||||
cmd::r#true::cli,
|
|
||||||
cmd::sleep::cli,
|
|
||||||
crate::cli::cli,
|
|
||||||
];
|
|
||||||
|
|
||||||
#[must_use]
|
pub const BOOTSTRAP: Bootstrap = Bootstrap {
|
||||||
pub fn cli() -> Command {
|
name: "bootstrap",
|
||||||
Command::new("bootstrap")
|
path: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for Bootstrap {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.about("Install shitbox into the filesystem")
|
.about("Install shitbox into the filesystem")
|
||||||
@ -92,9 +96,56 @@ pub fn cli() -> Command {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
|
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
let cmd = f();
|
let matches = if let Some(m) = matches {
|
||||||
let fname = match cmd.get_name() {
|
m
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::new(ErrorKind::Other, "No input").into());
|
||||||
|
};
|
||||||
|
if let Some(prefix) = matches.get_one::<String>("prefix") {
|
||||||
|
let commands: Commands = Commands {
|
||||||
|
items: vec![
|
||||||
|
&BOOTSTRAP, &ECHO, &FALSE, &HEAD, &HOSTNAME, &TRUE, &SLEEP, &SHITBOX,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
match matches.subcommand() {
|
||||||
|
Some(("manpages", _matches)) => {
|
||||||
|
commands.manpages(prefix)?;
|
||||||
|
}
|
||||||
|
Some(("completions", matches)) => {
|
||||||
|
commands.completions(prefix, matches)?;
|
||||||
|
}
|
||||||
|
Some(("all", _matches)) => {
|
||||||
|
commands.manpages(prefix)?;
|
||||||
|
commands.completions(prefix, matches)?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Commands<'a> {
|
||||||
|
items: Vec<&'a dyn Cmd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Commands<'a> {
|
||||||
|
fn manpages(&self, prefix: &str) -> Result<(), io::Error> {
|
||||||
|
println!("Generating Unix man pages:");
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.try_for_each(|cmd| Self::manpage(prefix, cmd))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manpage(prefix: &str, cmd: &&dyn Cmd) -> Result<(), io::Error> {
|
||||||
|
let command = cmd.cli();
|
||||||
|
let fname = match cmd.name() {
|
||||||
"bootstrap" => "shitbox-bootstrap.1".to_string(),
|
"bootstrap" => "shitbox-bootstrap.1".to_string(),
|
||||||
s => format!("{s}.1"),
|
s => format!("{s}.1"),
|
||||||
};
|
};
|
||||||
@ -104,7 +155,7 @@ fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
|
|||||||
}
|
}
|
||||||
let mut outfile = outdir;
|
let mut outfile = outdir;
|
||||||
outfile.push(fname);
|
outfile.push(fname);
|
||||||
let man = Man::new(cmd);
|
let man = Man::new(command);
|
||||||
let mut buffer: Vec<u8> = vec![];
|
let mut buffer: Vec<u8> = vec![];
|
||||||
man.render(&mut buffer)?;
|
man.render(&mut buffer)?;
|
||||||
fs::write(&outfile, buffer)?;
|
fs::write(&outfile, buffer)?;
|
||||||
@ -112,87 +163,87 @@ fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manpages(prefix: &str) -> Result<(), io::Error> {
|
fn completion(outdir: &Path, cmd: &&dyn Cmd, gen: impl Generator) -> Result<(), io::Error> {
|
||||||
println!("Generating Unix man pages:");
|
let name = cmd.name();
|
||||||
COMMANDS.iter().try_for_each(|cmd| manpage(prefix, &cmd))?;
|
let mut cmd = cmd.cli();
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_completions(
|
|
||||||
outdir: &Path,
|
|
||||||
f: &dyn Fn() -> Command,
|
|
||||||
gen: impl Generator,
|
|
||||||
) -> Result<(), io::Error> {
|
|
||||||
let cmd = f();
|
|
||||||
let name = cmd.get_name();
|
|
||||||
let mut cmd = cmd.clone();
|
|
||||||
if !outdir.exists() {
|
if !outdir.exists() {
|
||||||
fs::create_dir_all(&outdir)?;
|
fs::create_dir_all(outdir)?;
|
||||||
}
|
}
|
||||||
let path = generate_to(gen, &mut cmd, name, outdir)?;
|
let path = generate_to(gen, &mut cmd, name, outdir)?;
|
||||||
println!(" {}", path.display());
|
println!(" {}", path.display());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completions(prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> {
|
fn completions(&self, prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> {
|
||||||
println!("Generating completions:");
|
println!("Generating completions:");
|
||||||
if matches.get_flag("bash") || matches.get_flag("all") {
|
if matches.get_flag("bash") || matches.get_flag("all") {
|
||||||
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"]
|
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"]
|
||||||
.iter()
|
.iter()
|
||||||
.collect();
|
.collect();
|
||||||
COMMANDS
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Bash))?;
|
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Bash))?;
|
||||||
}
|
}
|
||||||
if matches.get_flag("fish") || matches.get_flag("all") {
|
if matches.get_flag("fish") || matches.get_flag("all") {
|
||||||
let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect();
|
let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect();
|
||||||
COMMANDS
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Fish))?;
|
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Fish))?;
|
||||||
}
|
}
|
||||||
if matches.get_flag("nu") || matches.get_flag("all") {
|
if matches.get_flag("nu") || matches.get_flag("all") {
|
||||||
let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect();
|
let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect();
|
||||||
COMMANDS
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, Nushell))?;
|
.try_for_each(|cmd| Self::completion(&outdir, cmd, Nushell))?;
|
||||||
}
|
}
|
||||||
if matches.get_flag("pwsh") || matches.get_flag("all") {
|
if matches.get_flag("pwsh") || matches.get_flag("all") {
|
||||||
let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect();
|
let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect();
|
||||||
COMMANDS
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::PowerShell))?;
|
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::PowerShell))?;
|
||||||
}
|
}
|
||||||
if matches.get_flag("zsh") || matches.get_flag("all") {
|
if matches.get_flag("zsh") || matches.get_flag("all") {
|
||||||
let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect();
|
let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect();
|
||||||
COMMANDS
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Zsh))?;
|
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Zsh))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(matches: &ArgMatches) {
|
/*
|
||||||
if let Some(prefix) = matches.get_one::<String>("prefix") {
|
fn get_path(prefix: &str, name: &str, usr: bool) -> Option<PathBuf> {
|
||||||
match matches.subcommand() {
|
let mut path = PathBuf::from(prefix);
|
||||||
Some(("manpages", _matches)) => {
|
let binpath = match name {
|
||||||
if let Err(e) = manpages(prefix) {
|
"bootstrap" => Bootstrap::path(),
|
||||||
eprintln!("{e}");
|
"echo" => Echo::path(),
|
||||||
process::exit(1);
|
"false" => False::path(),
|
||||||
|
"head" => Head::path(),
|
||||||
|
"hostname" => Hostname::path(),
|
||||||
|
"true" => True::path(),
|
||||||
|
"sleep" => Sleep::path(),
|
||||||
|
"shitbox" => Shitbox::path(),
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
match binpath {
|
||||||
|
Some(crate::Path::Bin) => path.push("bin"),
|
||||||
|
Some(crate::Path::Sbin) => path.push("sbin"),
|
||||||
|
Some(crate::Path::UsrBin) => {
|
||||||
|
if usr {
|
||||||
|
path.push("usr");
|
||||||
}
|
}
|
||||||
|
path.push("bin");
|
||||||
}
|
}
|
||||||
Some(("completions", matches)) => {
|
Some(crate::Path::UsrSbin) => {
|
||||||
if let Err(e) = completions(prefix, matches) {
|
if usr {
|
||||||
eprintln!("{e}");
|
path.push("usr");
|
||||||
process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
path.push("sbin");
|
||||||
}
|
}
|
||||||
Some(("all", _matches)) => {
|
None => return None,
|
||||||
if let Err(e) = manpages(prefix) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
path.push(name);
|
||||||
|
Some(path)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -1,12 +1,25 @@
|
|||||||
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use std::env;
|
use std::{env, error::Error};
|
||||||
|
|
||||||
pub const PATH: Path = Path::Bin;
|
pub struct Echo {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
pub const ECHO: Echo = Echo {
|
||||||
pub fn cli() -> Command {
|
name: "echo",
|
||||||
Command::new("echo")
|
path: Some(Path::Bin),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for Echo {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
.about("Display a line of text")
|
.about("Display a line of text")
|
||||||
.long_about("Echo the STRING(s) to standard output")
|
.long_about("Echo the STRING(s) to standard output")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
@ -19,10 +32,10 @@ pub fn cli() -> Command {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() {
|
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let idx = match crate::progname() {
|
let idx = match crate::progname() {
|
||||||
Some(s) if s.as_str() == "echo" => 1,
|
Some(s) if s.as_str() == self.name() => 1,
|
||||||
Some(_) => 2,
|
Some(_) => 2,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
@ -39,4 +52,10 @@ pub fn run() {
|
|||||||
if !n {
|
if !n {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,36 @@
|
|||||||
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use clap::Command;
|
use clap::Command;
|
||||||
use std::process;
|
use std::{error::Error, process};
|
||||||
|
|
||||||
pub const PATH: Path = Path::Bin;
|
pub struct False {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
pub const FALSE: False = False {
|
||||||
pub fn cli() -> Command {
|
name: "false",
|
||||||
Command::new("false")
|
path: Some(Path::Bin),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for False {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
.about("Does nothing unsuccessfully")
|
.about("Does nothing unsuccessfully")
|
||||||
.long_about("Exit with a status code indicating failure")
|
.long_about("Exit with a status code indicating failure")
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() {
|
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,30 @@
|
|||||||
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use std::fs;
|
use std::{
|
||||||
use std::io::{stdin, Read};
|
error::Error,
|
||||||
use std::process;
|
fs,
|
||||||
|
io::{self, stdin, Read},
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
pub const PATH: Path = Path::Bin;
|
pub struct Head {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
pub const HEAD: Head = Head {
|
||||||
pub fn cli() -> Command {
|
name: "head",
|
||||||
Command::new("head")
|
path: Some(Path::Bin),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for Head {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> Command {
|
||||||
|
Command::new(self.name)
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.about("Display first lines of a file")
|
.about("Display first lines of a file")
|
||||||
@ -47,6 +63,40 @@ pub fn cli() -> Command {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let matches = if let Some(m) = matches {
|
||||||
|
m
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, "No input").into());
|
||||||
|
};
|
||||||
|
let files = match matches.get_many::<String>("FILES") {
|
||||||
|
Some(c) => c.map(std::string::ToString::to_string).collect(),
|
||||||
|
None => vec!["-".to_string()],
|
||||||
|
};
|
||||||
|
let header =
|
||||||
|
!matches.get_flag("QUIET") && { files.len() > 1 || matches.get_flag("HEADER") };
|
||||||
|
for (index, file) in files.into_iter().enumerate() {
|
||||||
|
if index == 1 && header {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
head(
|
||||||
|
&file,
|
||||||
|
match matches.get_one("LINES") {
|
||||||
|
Some(c) => *c,
|
||||||
|
None => 10,
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
matches.get_flag("BYTES"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn head(file: &str, count: usize, header: bool, bytes: bool) {
|
fn head(file: &str, count: usize, header: bool, bytes: bool) {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
if file == "-" {
|
if file == "-" {
|
||||||
@ -90,25 +140,3 @@ fn head(file: &str, count: usize, header: bool, bytes: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(matches: &ArgMatches) {
|
|
||||||
let files = match matches.get_many::<String>("FILES") {
|
|
||||||
Some(c) => c.map(std::string::ToString::to_string).collect(),
|
|
||||||
None => vec!["-".to_string()],
|
|
||||||
};
|
|
||||||
let header = !matches.get_flag("QUIET") && { files.len() > 1 || matches.get_flag("HEADER") };
|
|
||||||
for (index, file) in files.into_iter().enumerate() {
|
|
||||||
if index == 1 && header {
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
head(
|
|
||||||
&file,
|
|
||||||
match matches.get_one("LINES") {
|
|
||||||
Some(c) => *c,
|
|
||||||
None => 10,
|
|
||||||
},
|
|
||||||
header,
|
|
||||||
matches.get_flag("BYTES"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,55 +1,67 @@
|
|||||||
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||||
use std::process;
|
use std::{error::Error, io};
|
||||||
|
|
||||||
pub const PATH: Path = Path::Bin;
|
pub struct Hostname {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
pub const HOSTNAME: Hostname = Hostname {
|
||||||
pub fn cli() -> Command {
|
name: "hostname",
|
||||||
Command::new("hostname")
|
path: Some(Path::Bin),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for Hostname {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.author("The JeanG3nie <jeang3nie@hitchhiker-linux.org>")
|
.author("The JeanG3nie <jeang3nie@hitchhiker-linux.org>")
|
||||||
.about("Prints the name of the current host. The super-user can set the host name by supplying an argument.")
|
.about("Prints the name of the current host. The super-user can set the host name by supplying an argument.")
|
||||||
.arg(
|
.args([
|
||||||
Arg::new("NAME")
|
Arg::new("NAME")
|
||||||
.help("name to set")
|
.help("name to set"),
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("STRIP")
|
Arg::new("STRIP")
|
||||||
.help("Removes any domain information from the printed name.")
|
.help("Removes any domain information from the printed name.")
|
||||||
.short('s')
|
.short('s')
|
||||||
.long("strip")
|
.long("strip")
|
||||||
.action(ArgAction::SetTrue)
|
.action(ArgAction::SetTrue)
|
||||||
)
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(matches: &ArgMatches) {
|
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let matches = matches.unwrap();
|
||||||
if let Some(name) = matches.get_one::<String>("NAME") {
|
if let Some(name) = matches.get_one::<String>("NAME") {
|
||||||
if let Err(e) = hostname::set(name) {
|
hostname::set(name)?;
|
||||||
eprintln!("{e}");
|
Ok(())
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
match hostname::get() {
|
let hostname = hostname::get()?;
|
||||||
Ok(hostname) => {
|
|
||||||
if matches.get_flag("STRIP") {
|
if matches.get_flag("STRIP") {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
if let Some(s) = hostname.to_string_lossy().split('.').next() {
|
if let Some(s) = hostname.to_string_lossy().split('.').next() {
|
||||||
s
|
s
|
||||||
} else {
|
} else {
|
||||||
eprintln!("hostname: missing operand");
|
return Err(io::Error::new(
|
||||||
process::exit(1);
|
io::ErrorKind::Other,
|
||||||
|
"hostname: missing operand",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("{}", hostname.to_string_lossy());
|
println!("{}", hostname.to_string_lossy());
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
self.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
use clap::ArgMatches;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
pub mod bootstrap;
|
pub mod bootstrap;
|
||||||
mod cat;
|
mod cat;
|
||||||
mod chmod;
|
mod chmod;
|
||||||
@ -16,6 +19,25 @@ mod mv;
|
|||||||
mod pwd;
|
mod pwd;
|
||||||
mod rm;
|
mod rm;
|
||||||
mod rmdir;
|
mod rmdir;
|
||||||
|
pub mod shitbox;
|
||||||
pub mod sleep;
|
pub mod sleep;
|
||||||
mod sync;
|
mod sync;
|
||||||
pub mod r#true;
|
pub mod r#true;
|
||||||
|
|
||||||
|
pub use {
|
||||||
|
self::hostname::{Hostname, HOSTNAME},
|
||||||
|
bootstrap::{Bootstrap, BOOTSTRAP},
|
||||||
|
echo::{Echo, ECHO},
|
||||||
|
head::{Head, HEAD},
|
||||||
|
r#false::{False, FALSE},
|
||||||
|
r#true::{True, TRUE},
|
||||||
|
shitbox::{Shitbox, SHITBOX},
|
||||||
|
sleep::{Sleep, SLEEP},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Cmd {
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
fn cli(&self) -> clap::Command;
|
||||||
|
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>>;
|
||||||
|
fn path(&self) -> Option<crate::Path>;
|
||||||
|
}
|
||||||
|
60
src/cmd/shitbox/mod.rs
Normal file
60
src/cmd/shitbox/mod.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use super::{Cmd, BOOTSTRAP, ECHO, FALSE, HEAD, HOSTNAME, SLEEP, TRUE};
|
||||||
|
use clap::Command;
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
io::{self, ErrorKind},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Shitbox {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<crate::Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SHITBOX: Shitbox = Shitbox {
|
||||||
|
name: "shitbox",
|
||||||
|
path: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for Shitbox {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
|
.about("The Harbor Freight multitool of embedded Linux")
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.arg_required_else_help(true)
|
||||||
|
.subcommands([
|
||||||
|
BOOTSTRAP.cli(),
|
||||||
|
ECHO.cli(),
|
||||||
|
FALSE.cli(),
|
||||||
|
HEAD.cli(),
|
||||||
|
HOSTNAME.cli(),
|
||||||
|
SLEEP.cli(),
|
||||||
|
TRUE.cli(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let matches = if let Some(m) = matches {
|
||||||
|
m
|
||||||
|
} else {
|
||||||
|
return Err(Box::new(io::Error::new(ErrorKind::Other, "No input")));
|
||||||
|
};
|
||||||
|
match matches.subcommand() {
|
||||||
|
Some(("echo", _matches)) => ECHO.run(None)?,
|
||||||
|
Some(("false", _matches)) => FALSE.run(None)?,
|
||||||
|
Some(("head", matches)) => HEAD.run(Some(matches))?,
|
||||||
|
Some(("hostname", matches)) => HOSTNAME.run(Some(matches))?,
|
||||||
|
Some(("sleep", matches)) => SLEEP.run(Some(matches))?,
|
||||||
|
Some(("true", _matches)) => TRUE.run(None)?,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,25 @@
|
|||||||
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use std::{env, thread, time::Duration};
|
use std::{env, error::Error, thread, time::Duration};
|
||||||
|
|
||||||
pub const PATH: Path = Path::Bin;
|
pub struct Sleep {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
pub const SLEEP: Sleep = Sleep {
|
||||||
pub fn cli() -> Command {
|
name: "sleep",
|
||||||
Command::new("sleep")
|
path: Some(Path::Bin),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for Sleep {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
.about("Suspend execution for an interval of time")
|
.about("Suspend execution for an interval of time")
|
||||||
.long_about(
|
.long_about(
|
||||||
"The sleep utility suspends execution for a minimum of the specified number of seconds.\n\
|
"The sleep utility suspends execution for a minimum of the specified number of seconds.\n\
|
||||||
@ -27,11 +40,17 @@ pub fn cli() -> Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||||
pub fn run(matches: &ArgMatches) {
|
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
if let Some(raw) = matches.get_one::<f64>("seconds") {
|
if let Some(raw) = matches.unwrap().get_one::<f64>("seconds") {
|
||||||
let seconds = *raw as u64;
|
let seconds = *raw as u64;
|
||||||
let nanos = ((raw % 1.0) * 10e-9) as u32;
|
let nanos = ((raw % 1.0) * 10e-9) as u32;
|
||||||
let s = Duration::new(seconds, nanos);
|
let s = Duration::new(seconds, nanos);
|
||||||
thread::sleep(s);
|
thread::sleep(s);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,36 @@
|
|||||||
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use clap::Command;
|
use clap::Command;
|
||||||
use std::process;
|
use std::{error::Error, process};
|
||||||
|
|
||||||
pub const PATH: Path = Path::Bin;
|
pub struct True {
|
||||||
|
name: &'static str,
|
||||||
|
path: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
pub const TRUE: True = True {
|
||||||
pub fn cli() -> Command {
|
name: "true",
|
||||||
Command::new("true")
|
path: Some(Path::Bin),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Cmd for True {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new(self.name)
|
||||||
.about("Does nothing successfully")
|
.about("Does nothing successfully")
|
||||||
.long_about("Exit with a status code indicating success")
|
.long_about("Exit with a status code indicating success")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() {
|
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<Path> {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
28
src/lib.rs
28
src/lib.rs
@ -1,9 +1,10 @@
|
|||||||
#![warn(clippy::all, clippy::pedantic)]
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
use std::{env, path::PathBuf, string::ToString};
|
use std::{env, error::Error, path::PathBuf, string::ToString};
|
||||||
|
|
||||||
mod cli;
|
|
||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
|
use cmd::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Path {
|
pub enum Path {
|
||||||
Bin,
|
Bin,
|
||||||
Sbin,
|
Sbin,
|
||||||
@ -23,17 +24,24 @@ pub fn progname() -> Option<String> {
|
|||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() -> Result<(), Box<dyn Error>> {
|
||||||
if let Some(progname) = progname() {
|
if let Some(progname) = progname() {
|
||||||
match progname.as_str() {
|
match progname.as_str() {
|
||||||
"echo" => cmd::echo::run(),
|
"echo" => ECHO.run(None)?,
|
||||||
"false" => cmd::r#false::run(),
|
"false" => FALSE.run(None)?,
|
||||||
"head" => cmd::head::run(&cmd::head::cli().get_matches()),
|
"head" => {
|
||||||
"hostname" => cmd::hostname::run(&cmd::hostname::cli().get_matches()),
|
HEAD.run(Some(&HEAD.cli().get_matches()))?;
|
||||||
"true" => cmd::r#true::run(),
|
}
|
||||||
"shitbox" => cli::run(),
|
"hostname" => HOSTNAME.run(Some(&HOSTNAME.cli().get_matches()))?,
|
||||||
"sleep" => cmd::sleep::run(&cmd::sleep::cli().get_matches()),
|
"true" => TRUE.run(None)?,
|
||||||
|
"shitbox" => {
|
||||||
|
SHITBOX.run(Some(&SHITBOX.cli().get_matches()))?;
|
||||||
|
}
|
||||||
|
"sleep" => {
|
||||||
|
SLEEP.run(Some(&SLEEP.cli().get_matches()))?;
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
use std::process;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
shitbox::run();
|
if let Err(e) = shitbox::run() {
|
||||||
|
eprintln!("{e}");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user