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"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.4.1"
|
||||
@ -181,6 +187,7 @@ dependencies = [
|
||||
"clap_complete_nushell",
|
||||
"clap_mangen",
|
||||
"hostname",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -11,6 +11,7 @@ clap_complete = "4.0.6"
|
||||
clap_complete_nushell = "0.1.8"
|
||||
clap_mangen = "0.2.5"
|
||||
hostname = { version = "0.3", features = ["set"] }
|
||||
once_cell = "1.16.0"
|
||||
|
||||
[profile.release]
|
||||
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,198 +1,249 @@
|
||||
use crate::cmd;
|
||||
use super::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
|
||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_complete::{generate_to, shells, Generator};
|
||||
use clap_complete_nushell::Nushell;
|
||||
use clap_mangen::Man;
|
||||
use std::{
|
||||
fs, io,
|
||||
error::Error,
|
||||
fs,
|
||||
io::{self, ErrorKind},
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
|
||||
const COMMANDS: [fn() -> clap::Command; 8] = [
|
||||
cmd::bootstrap::cli,
|
||||
cmd::echo::cli,
|
||||
cmd::r#false::cli,
|
||||
cmd::head::cli,
|
||||
cmd::hostname::cli,
|
||||
cmd::r#true::cli,
|
||||
cmd::sleep::cli,
|
||||
crate::cli::cli,
|
||||
];
|
||||
pub struct Bootstrap {
|
||||
name: &'static str,
|
||||
path: Option<crate::Path>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("bootstrap")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nathan Fisher")
|
||||
.about("Install shitbox into the filesystem")
|
||||
.long_about("Install symlinks, manpages and shell completions")
|
||||
.args([
|
||||
Arg::new("prefix")
|
||||
.help("The directory path under which to install")
|
||||
.short('p')
|
||||
.long("prefix")
|
||||
.num_args(1)
|
||||
.default_value("/")
|
||||
.required(false),
|
||||
Arg::new("usr")
|
||||
.help("Use /usr")
|
||||
.long_help(
|
||||
"Split the installation so that some applets go into /bin | /sbin\n\
|
||||
pub const BOOTSTRAP: Bootstrap = Bootstrap {
|
||||
name: "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"))
|
||||
.author("Nathan Fisher")
|
||||
.about("Install shitbox into the filesystem")
|
||||
.long_about("Install symlinks, manpages and shell completions")
|
||||
.args([
|
||||
Arg::new("prefix")
|
||||
.help("The directory path under which to install")
|
||||
.short('p')
|
||||
.long("prefix")
|
||||
.num_args(1)
|
||||
.default_value("/")
|
||||
.required(false),
|
||||
Arg::new("usr")
|
||||
.help("Use /usr")
|
||||
.long_help(
|
||||
"Split the installation so that some applets go into /bin | /sbin\n\
|
||||
while others are placed into /usr/bin | /usr/sbin",
|
||||
)
|
||||
.short('u')
|
||||
.long("usr")
|
||||
.default_value("true")
|
||||
.value_parser(value_parser!(bool)),
|
||||
])
|
||||
.subcommands([
|
||||
Command::new("all").about("Install everything"),
|
||||
Command::new("links")
|
||||
.about("Install links for each applet")
|
||||
.arg(
|
||||
Arg::new("soft")
|
||||
.help("Install soft links instead of hardlinks")
|
||||
.short('s')
|
||||
.long("soft"),
|
||||
),
|
||||
Command::new("manpages")
|
||||
.about("Install Unix man pages")
|
||||
.alias("man"),
|
||||
Command::new("completions")
|
||||
.about("Install shell completions")
|
||||
.alias("comp")
|
||||
.args([
|
||||
Arg::new("all")
|
||||
.help("Install completions for all supported shells")
|
||||
.short('a')
|
||||
.long("all")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("bash")
|
||||
.help("Bash shell completions")
|
||||
.short('b')
|
||||
.long("bash")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("fish")
|
||||
.help("Fish shell completions")
|
||||
.short('f')
|
||||
.long("fish")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("nu")
|
||||
.help("Nushell completions")
|
||||
.short('n')
|
||||
.long("nu")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("pwsh")
|
||||
.help("PowerShell completions")
|
||||
.short('p')
|
||||
.long("pwsh")
|
||||
.action(ArgAction::SetTrue),
|
||||
]),
|
||||
])
|
||||
}
|
||||
)
|
||||
.short('u')
|
||||
.long("usr")
|
||||
.default_value("true")
|
||||
.value_parser(value_parser!(bool)),
|
||||
])
|
||||
.subcommands([
|
||||
Command::new("all").about("Install everything"),
|
||||
Command::new("links")
|
||||
.about("Install links for each applet")
|
||||
.arg(
|
||||
Arg::new("soft")
|
||||
.help("Install soft links instead of hardlinks")
|
||||
.short('s')
|
||||
.long("soft"),
|
||||
),
|
||||
Command::new("manpages")
|
||||
.about("Install Unix man pages")
|
||||
.alias("man"),
|
||||
Command::new("completions")
|
||||
.about("Install shell completions")
|
||||
.alias("comp")
|
||||
.args([
|
||||
Arg::new("all")
|
||||
.help("Install completions for all supported shells")
|
||||
.short('a')
|
||||
.long("all")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("bash")
|
||||
.help("Bash shell completions")
|
||||
.short('b')
|
||||
.long("bash")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("fish")
|
||||
.help("Fish shell completions")
|
||||
.short('f')
|
||||
.long("fish")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("nu")
|
||||
.help("Nushell completions")
|
||||
.short('n')
|
||||
.long("nu")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("pwsh")
|
||||
.help("PowerShell completions")
|
||||
.short('p')
|
||||
.long("pwsh")
|
||||
.action(ArgAction::SetTrue),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
fn manpage(prefix: &str, f: &dyn Fn() -> Command) -> Result<(), io::Error> {
|
||||
let cmd = f();
|
||||
let fname = match cmd.get_name() {
|
||||
"bootstrap" => "shitbox-bootstrap.1".to_string(),
|
||||
s => format!("{s}.1"),
|
||||
};
|
||||
let outdir: PathBuf = [prefix, "usr", "share", "man", "man1"].iter().collect();
|
||||
if !outdir.exists() {
|
||||
fs::create_dir_all(&outdir)?;
|
||||
}
|
||||
let mut outfile = outdir;
|
||||
outfile.push(fname);
|
||||
let man = Man::new(cmd);
|
||||
let mut buffer: Vec<u8> = vec![];
|
||||
man.render(&mut buffer)?;
|
||||
fs::write(&outfile, buffer)?;
|
||||
println!(" {}", outfile.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn manpages(prefix: &str) -> Result<(), io::Error> {
|
||||
println!("Generating Unix man pages:");
|
||||
COMMANDS.iter().try_for_each(|cmd| manpage(prefix, &cmd))?;
|
||||
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() {
|
||||
fs::create_dir_all(&outdir)?;
|
||||
}
|
||||
let path = generate_to(gen, &mut cmd, name, outdir)?;
|
||||
println!(" {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn completions(prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> {
|
||||
println!("Generating completions:");
|
||||
if matches.get_flag("bash") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"]
|
||||
.iter()
|
||||
.collect();
|
||||
COMMANDS
|
||||
.iter()
|
||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Bash))?;
|
||||
}
|
||||
if matches.get_flag("fish") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect();
|
||||
COMMANDS
|
||||
.iter()
|
||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Fish))?;
|
||||
}
|
||||
if matches.get_flag("nu") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect();
|
||||
COMMANDS
|
||||
.iter()
|
||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, Nushell))?;
|
||||
}
|
||||
if matches.get_flag("pwsh") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect();
|
||||
COMMANDS
|
||||
.iter()
|
||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::PowerShell))?;
|
||||
}
|
||||
if matches.get_flag("zsh") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect();
|
||||
COMMANDS
|
||||
.iter()
|
||||
.try_for_each(|cmd| generate_completions(&outdir, &cmd, shells::Zsh))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(matches: &ArgMatches) {
|
||||
if let Some(prefix) = matches.get_one::<String>("prefix") {
|
||||
match matches.subcommand() {
|
||||
Some(("manpages", _matches)) => {
|
||||
if let Err(e) = manpages(prefix) {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
let matches = if let Some(m) = matches {
|
||||
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)) => {
|
||||
if let Err(e) = completions(prefix, matches) {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
Some(("completions", matches)) => {
|
||||
commands.completions(prefix, matches)?;
|
||||
}
|
||||
}
|
||||
Some(("all", _matches)) => {
|
||||
if let Err(e) = manpages(prefix) {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
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(),
|
||||
s => format!("{s}.1"),
|
||||
};
|
||||
let outdir: PathBuf = [prefix, "usr", "share", "man", "man1"].iter().collect();
|
||||
if !outdir.exists() {
|
||||
fs::create_dir_all(&outdir)?;
|
||||
}
|
||||
let mut outfile = outdir;
|
||||
outfile.push(fname);
|
||||
let man = Man::new(command);
|
||||
let mut buffer: Vec<u8> = vec![];
|
||||
man.render(&mut buffer)?;
|
||||
fs::write(&outfile, buffer)?;
|
||||
println!(" {}", outfile.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn completion(outdir: &Path, cmd: &&dyn Cmd, gen: impl Generator) -> Result<(), io::Error> {
|
||||
let name = cmd.name();
|
||||
let mut cmd = cmd.cli();
|
||||
if !outdir.exists() {
|
||||
fs::create_dir_all(outdir)?;
|
||||
}
|
||||
let path = generate_to(gen, &mut cmd, name, outdir)?;
|
||||
println!(" {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn completions(&self, prefix: &str, matches: &ArgMatches) -> Result<(), io::Error> {
|
||||
println!("Generating completions:");
|
||||
if matches.get_flag("bash") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "bash-completion", "completion"]
|
||||
.iter()
|
||||
.collect();
|
||||
self.items
|
||||
.iter()
|
||||
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Bash))?;
|
||||
}
|
||||
if matches.get_flag("fish") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "fish", "completions"].iter().collect();
|
||||
self.items
|
||||
.iter()
|
||||
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Fish))?;
|
||||
}
|
||||
if matches.get_flag("nu") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "nu", "completions"].iter().collect();
|
||||
self.items
|
||||
.iter()
|
||||
.try_for_each(|cmd| Self::completion(&outdir, cmd, Nushell))?;
|
||||
}
|
||||
if matches.get_flag("pwsh") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "pwsh", "completions"].iter().collect();
|
||||
self.items
|
||||
.iter()
|
||||
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::PowerShell))?;
|
||||
}
|
||||
if matches.get_flag("zsh") || matches.get_flag("all") {
|
||||
let outdir: PathBuf = [prefix, "share", "zsh", "site-functions"].iter().collect();
|
||||
self.items
|
||||
.iter()
|
||||
.try_for_each(|cmd| Self::completion(&outdir, cmd, shells::Zsh))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn get_path(prefix: &str, name: &str, usr: bool) -> Option<PathBuf> {
|
||||
let mut path = PathBuf::from(prefix);
|
||||
let binpath = match name {
|
||||
"bootstrap" => Bootstrap::path(),
|
||||
"echo" => Echo::path(),
|
||||
"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(crate::Path::UsrSbin) => {
|
||||
if usr {
|
||||
path.push("usr");
|
||||
}
|
||||
path.push("sbin");
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
path.push(name);
|
||||
Some(path)
|
||||
}
|
||||
*/
|
||||
|
@ -1,42 +1,61 @@
|
||||
use super::Cmd;
|
||||
use crate::Path;
|
||||
use clap::{Arg, Command};
|
||||
use std::env;
|
||||
use std::{env, error::Error};
|
||||
|
||||
pub const PATH: Path = Path::Bin;
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("echo")
|
||||
.about("Display a line of text")
|
||||
.long_about("Echo the STRING(s) to standard output")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nathan Fisher")
|
||||
.args([
|
||||
Arg::new("inline")
|
||||
.short('n')
|
||||
.help("Do not output a trailing newline"),
|
||||
Arg::new("STRING").num_args(1..),
|
||||
])
|
||||
pub struct Echo {
|
||||
name: &'static str,
|
||||
path: Option<Path>,
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let idx = match crate::progname() {
|
||||
Some(s) if s.as_str() == "echo" => 1,
|
||||
Some(_) => 2,
|
||||
None => unreachable!(),
|
||||
};
|
||||
let len = args.len();
|
||||
let n = len > idx && args[idx] == "-n";
|
||||
let i = if n { idx + 1 } else { idx };
|
||||
for (index, arg) in args.iter().enumerate().skip(i) {
|
||||
if index < len - 1 {
|
||||
print!("{arg} ");
|
||||
} else {
|
||||
print!("{arg}");
|
||||
pub const ECHO: Echo = Echo {
|
||||
name: "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")
|
||||
.long_about("Echo the STRING(s) to standard output")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nathan Fisher")
|
||||
.args([
|
||||
Arg::new("inline")
|
||||
.short('n')
|
||||
.help("Do not output a trailing newline"),
|
||||
Arg::new("STRING").num_args(1..),
|
||||
])
|
||||
}
|
||||
|
||||
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let idx = match crate::progname() {
|
||||
Some(s) if s.as_str() == self.name() => 1,
|
||||
Some(_) => 2,
|
||||
None => unreachable!(),
|
||||
};
|
||||
let len = args.len();
|
||||
let n = len > idx && args[idx] == "-n";
|
||||
let i = if n { idx + 1 } else { idx };
|
||||
for (index, arg) in args.iter().enumerate().skip(i) {
|
||||
if index < len - 1 {
|
||||
print!("{arg} ");
|
||||
} else {
|
||||
print!("{arg}");
|
||||
}
|
||||
}
|
||||
if !n {
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
if !n {
|
||||
println!();
|
||||
|
||||
fn path(&self) -> Option<crate::Path> {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,36 @@
|
||||
use super::Cmd;
|
||||
use crate::Path;
|
||||
use clap::Command;
|
||||
use std::process;
|
||||
use std::{error::Error, process};
|
||||
|
||||
pub const PATH: Path = Path::Bin;
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("false")
|
||||
.about("Does nothing unsuccessfully")
|
||||
.long_about("Exit with a status code indicating failure")
|
||||
.author("Nathan Fisher")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
pub struct False {
|
||||
name: &'static str,
|
||||
path: Option<Path>,
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
process::exit(1);
|
||||
pub const FALSE: False = False {
|
||||
name: "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")
|
||||
.long_about("Exit with a status code indicating failure")
|
||||
.author("Nathan Fisher")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
|
||||
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Path> {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,30 @@
|
||||
use super::Cmd;
|
||||
use crate::Path;
|
||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||
use std::fs;
|
||||
use std::io::{stdin, Read};
|
||||
use std::process;
|
||||
use std::{
|
||||
error::Error,
|
||||
fs,
|
||||
io::{self, stdin, Read},
|
||||
process,
|
||||
};
|
||||
|
||||
pub const PATH: Path = Path::Bin;
|
||||
pub struct Head {
|
||||
name: &'static str,
|
||||
path: Option<Path>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("head")
|
||||
pub const HEAD: Head = Head {
|
||||
name: "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"))
|
||||
.author("Nathan Fisher")
|
||||
.about("Display first lines of a file")
|
||||
@ -45,6 +61,40 @@ pub fn cli() -> Command {
|
||||
.allow_negative_numbers(false)
|
||||
.value_parser(value_parser!(usize))
|
||||
])
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -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 clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use std::process;
|
||||
use std::{error::Error, io};
|
||||
|
||||
pub const PATH: Path = Path::Bin;
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("hostname")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.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.")
|
||||
.arg(
|
||||
Arg::new("NAME")
|
||||
.help("name to set")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("STRIP")
|
||||
.help("Removes any domain information from the printed name.")
|
||||
.short('s')
|
||||
.long("strip")
|
||||
.action(ArgAction::SetTrue)
|
||||
)
|
||||
pub struct Hostname {
|
||||
name: &'static str,
|
||||
path: Option<Path>,
|
||||
}
|
||||
|
||||
pub fn run(matches: &ArgMatches) {
|
||||
if let Some(name) = matches.get_one::<String>("NAME") {
|
||||
if let Err(e) = hostname::set(name) {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
} else {
|
||||
match hostname::get() {
|
||||
Ok(hostname) => {
|
||||
if matches.get_flag("STRIP") {
|
||||
println!(
|
||||
"{}",
|
||||
if let Some(s) = hostname.to_string_lossy().split('.').next() {
|
||||
s
|
||||
} else {
|
||||
eprintln!("hostname: missing operand");
|
||||
process::exit(1);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
println!("{}", hostname.to_string_lossy());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
pub const HOSTNAME: Hostname = Hostname {
|
||||
name: "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"))
|
||||
.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.")
|
||||
.args([
|
||||
Arg::new("NAME")
|
||||
.help("name to set"),
|
||||
Arg::new("STRIP")
|
||||
.help("Removes any domain information from the printed name.")
|
||||
.short('s')
|
||||
.long("strip")
|
||||
.action(ArgAction::SetTrue)
|
||||
])
|
||||
}
|
||||
|
||||
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
let matches = matches.unwrap();
|
||||
if let Some(name) = matches.get_one::<String>("NAME") {
|
||||
hostname::set(name)?;
|
||||
Ok(())
|
||||
} else {
|
||||
let hostname = hostname::get()?;
|
||||
if matches.get_flag("STRIP") {
|
||||
println!(
|
||||
"{}",
|
||||
if let Some(s) = hostname.to_string_lossy().split('.').next() {
|
||||
s
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"hostname: missing operand",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
println!("{}", hostname.to_string_lossy());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<crate::Path> {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
use clap::ArgMatches;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod bootstrap;
|
||||
mod cat;
|
||||
mod chmod;
|
||||
@ -16,6 +19,25 @@ mod mv;
|
||||
mod pwd;
|
||||
mod rm;
|
||||
mod rmdir;
|
||||
pub mod shitbox;
|
||||
pub mod sleep;
|
||||
mod sync;
|
||||
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,37 +1,56 @@
|
||||
use super::Cmd;
|
||||
use crate::Path;
|
||||
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;
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("sleep")
|
||||
.about("Suspend execution for an interval of time")
|
||||
.long_about(
|
||||
"The sleep utility suspends execution for a minimum of the specified number of seconds.\n\
|
||||
This number must be positive and may contain a decimal fraction.\n\
|
||||
sleep is commonly used to schedule the execution of other commands"
|
||||
)
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.arg(
|
||||
Arg::new("seconds")
|
||||
.help("The number of seconds to sleep")
|
||||
.num_args(1)
|
||||
.allow_negative_numbers(false)
|
||||
.value_parser(value_parser!(f64))
|
||||
.required(true)
|
||||
.action(ArgAction::Set)
|
||||
)
|
||||
pub struct Sleep {
|
||||
name: &'static str,
|
||||
path: Option<Path>,
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
pub fn run(matches: &ArgMatches) {
|
||||
if let Some(raw) = matches.get_one::<f64>("seconds") {
|
||||
let seconds = *raw as u64;
|
||||
let nanos = ((raw % 1.0) * 10e-9) as u32;
|
||||
let s = Duration::new(seconds, nanos);
|
||||
thread::sleep(s);
|
||||
pub const SLEEP: Sleep = Sleep {
|
||||
name: "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")
|
||||
.long_about(
|
||||
"The sleep utility suspends execution for a minimum of the specified number of seconds.\n\
|
||||
This number must be positive and may contain a decimal fraction.\n\
|
||||
sleep is commonly used to schedule the execution of other commands"
|
||||
)
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.arg(
|
||||
Arg::new("seconds")
|
||||
.help("The number of seconds to sleep")
|
||||
.num_args(1)
|
||||
.allow_negative_numbers(false)
|
||||
.value_parser(value_parser!(f64))
|
||||
.required(true)
|
||||
.action(ArgAction::Set)
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(raw) = matches.unwrap().get_one::<f64>("seconds") {
|
||||
let seconds = *raw as u64;
|
||||
let nanos = ((raw % 1.0) * 10e-9) as u32;
|
||||
let s = Duration::new(seconds, nanos);
|
||||
thread::sleep(s);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Path> {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,36 @@
|
||||
use super::Cmd;
|
||||
use crate::Path;
|
||||
use clap::Command;
|
||||
use std::process;
|
||||
use std::{error::Error, process};
|
||||
|
||||
pub const PATH: Path = Path::Bin;
|
||||
|
||||
#[must_use]
|
||||
pub fn cli() -> Command {
|
||||
Command::new("true")
|
||||
.about("Does nothing successfully")
|
||||
.long_about("Exit with a status code indicating success")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nathan Fisher")
|
||||
pub struct True {
|
||||
name: &'static str,
|
||||
path: Option<Path>,
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
process::exit(0);
|
||||
pub const TRUE: True = True {
|
||||
name: "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")
|
||||
.long_about("Exit with a status code indicating success")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nathan Fisher")
|
||||
}
|
||||
|
||||
fn run(&self, _matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
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)]
|
||||
use std::{env, path::PathBuf, string::ToString};
|
||||
use std::{env, error::Error, path::PathBuf, string::ToString};
|
||||
|
||||
mod cli;
|
||||
pub mod cmd;
|
||||
use cmd::{Cmd, ECHO, FALSE, HEAD, HOSTNAME, SHITBOX, SLEEP, TRUE};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Path {
|
||||
Bin,
|
||||
Sbin,
|
||||
@ -23,17 +24,24 @@ pub fn progname() -> Option<String> {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
pub fn run() -> Result<(), Box<dyn Error>> {
|
||||
if let Some(progname) = progname() {
|
||||
match progname.as_str() {
|
||||
"echo" => cmd::echo::run(),
|
||||
"false" => cmd::r#false::run(),
|
||||
"head" => cmd::head::run(&cmd::head::cli().get_matches()),
|
||||
"hostname" => cmd::hostname::run(&cmd::hostname::cli().get_matches()),
|
||||
"true" => cmd::r#true::run(),
|
||||
"shitbox" => cli::run(),
|
||||
"sleep" => cmd::sleep::run(&cmd::sleep::cli().get_matches()),
|
||||
"echo" => ECHO.run(None)?,
|
||||
"false" => FALSE.run(None)?,
|
||||
"head" => {
|
||||
HEAD.run(Some(&HEAD.cli().get_matches()))?;
|
||||
}
|
||||
"hostname" => HOSTNAME.run(Some(&HOSTNAME.cli().get_matches()))?,
|
||||
"true" => TRUE.run(None)?,
|
||||
"shitbox" => {
|
||||
SHITBOX.run(Some(&SHITBOX.cli().get_matches()))?;
|
||||
}
|
||||
"sleep" => {
|
||||
SLEEP.run(Some(&SLEEP.cli().get_matches()))?;
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
use std::process;
|
||||
|
||||
fn main() {
|
||||
shitbox::run();
|
||||
if let Err(e) = shitbox::run() {
|
||||
eprintln!("{e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user