Initial commit

This commit is contained in:
Nathan Fisher 2022-12-20 12:05:21 -05:00
commit 228e1c779f
28 changed files with 717 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
tags
tags.*

287
Cargo.lock generated Normal file
View File

@ -0,0 +1,287 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "clap"
version = "4.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
dependencies = [
"bitflags",
"clap_lex",
"is-terminal",
"strsim",
"termcolor",
]
[[package]]
name = "clap_complete"
version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da"
dependencies = [
"clap",
]
[[package]]
name = "clap_complete_nushell"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ceeff3734be0ba9e15998f1f1a487cd2928b31444209e8fcd92b30efac13a"
dependencies = [
"clap",
"clap_complete",
]
[[package]]
name = "clap_lex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_mangen"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e503c3058af0a0854668ea01db55c622482a080092fede9dd2e00a00a9436504"
dependencies = [
"clap",
"roff",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
"libc",
"match_cfg",
"winapi",
]
[[package]]
name = "io-lifetimes"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "roff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "rustix"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "shitbox"
version = "0.1.0"
dependencies = [
"clap",
"clap_complete",
"clap_complete_nushell",
"clap_mangen",
"hostname",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "shitbox"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "4.0.29"
clap_complete = "4.0.6"
clap_complete_nushell = "0.1.8"
clap_mangen = "0.2.5"
hostname = { version = "0.3", features = ["set"] }

26
src/cli.rs Normal file
View File

@ -0,0 +1,26 @@
use crate::cmd::{bootstrap, echo, head, hostname, r#false, r#true};
use clap::{value_parser, Arg, ArgAction, Command};
pub fn run() {
let matches = 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(),
r#true::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(("true", _matches)) => r#true::run(),
_ => {}
}
}

69
src/cmd/bootstrap/mod.rs Normal file
View File

@ -0,0 +1,69 @@
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
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\
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"),
Arg::new("bash")
.help("Bash shell completions")
.short('b')
.long("bash"),
Arg::new("fish")
.help("Fish shell completions")
.short('f')
.long("fish"),
Arg::new("nu")
.help("Nushell completions")
.short('n')
.long("nu"),
Arg::new("pwsh")
.help("PowerShell completions")
.short('p')
.long("pwsh"),
]),
])
}
pub fn run(matches: &ArgMatches) {}

1
src/cmd/cat/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/chmod/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/cp/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/date/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/dd/mod.rs Normal file
View File

@ -0,0 +1 @@

41
src/cmd/echo/mod.rs Normal file
View File

@ -0,0 +1,41 @@
use crate::Path;
use clap::{Arg, Command};
use std::env;
pub const PATH: Path = Path::Bin;
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 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);
}
}
if !n {
println!();
}
}

17
src/cmd/false/mod.rs Normal file
View File

@ -0,0 +1,17 @@
use clap::Command;
use crate::Path;
use std::process;
pub const PATH: Path = Path::Bin;
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 fn run() {
process::exit(1);
}

1
src/cmd/getty/mod.rs Normal file
View File

@ -0,0 +1 @@

112
src/cmd/head/mod.rs Normal file
View File

@ -0,0 +1,112 @@
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use crate::Path;
use std::fs;
use std::io::{stdin, Read};
use std::process;
pub const PATH: Path = Path::Bin;
pub fn cli() -> Command {
Command::new("head")
.version(env!("CARGO_PKG_VERSION"))
.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\
With no FILE, or when FILE is -, read standard input.\n\
Mandatory arguments to long options are mandatory for short options too."
)
.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")
.default_value("10")
.value_parser(value_parser!(usize))
])
}
fn head(file: &str, count: usize, header: bool, bytes: bool) {
let mut contents = String::new();
if file == "-" {
match stdin().read_to_string(&mut contents) {
Ok(_) => true,
Err(e) => {
eprintln!("head: {}", e);
process::exit(1);
}
};
} else {
let buf = fs::read_to_string(file);
contents = match buf {
Ok(c) => c,
Err(e) => {
eprintln!("head: {}", e);
process::exit(1);
}
};
}
if header {
println!("==> {} <==", file);
}
if bytes {
for (index, char) in contents.chars().into_iter().enumerate() {
if index < count {
print!("{}", char);
} else {
println!();
return;
}
}
println!();
} else {
for (index, line) in contents.lines().into_iter().enumerate() {
if index < count {
println!("{}", line);
} else {
return;
}
}
}
}
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"),
);
}
}

55
src/cmd/hostname/mod.rs Normal file
View File

@ -0,0 +1,55 @@
use clap::{Arg, Command, ArgMatches, ArgAction};
use crate::Path;
use std::{io, process};
pub const PATH: Path = Path::Bin;
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 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!(
"{}",
match hostname.to_string_lossy().split('.').next() {
Some(c) => c,
None => {
eprintln!("hostname: missing operand");
process::exit(1);
}
}
);
} else {
println!("{}", hostname.to_string_lossy());
}
}
Err(e) => {
eprintln!("{e}");
process::exit(1);
}
}
}
}

1
src/cmd/ln/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/ls/mod.rs Normal file
View File

@ -0,0 +1 @@

21
src/cmd/mod.rs Normal file
View File

@ -0,0 +1,21 @@
pub mod bootstrap;
mod cat;
mod chmod;
mod cp;
mod date;
mod dd;
pub mod echo;
pub mod r#false;
mod getty;
pub mod head;
pub mod hostname;
mod ln;
mod ls;
mod mountpoint;
mod mv;
mod pwd;
mod rm;
mod rmdir;
mod sleep;
mod sync;
pub mod r#true;

View File

@ -0,0 +1 @@

1
src/cmd/mv/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/pwd/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/rm/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/rmdir/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/sleep/mod.rs Normal file
View File

@ -0,0 +1 @@

1
src/cmd/sync/mod.rs Normal file
View File

@ -0,0 +1 @@

17
src/cmd/true/mod.rs Normal file
View File

@ -0,0 +1,17 @@
use clap::Command;
use crate::Path;
use std::process;
pub const PATH: Path = Path::Bin;
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 fn run() {
process::exit(0);
}

38
src/lib.rs Normal file
View File

@ -0,0 +1,38 @@
#![warn(clippy::all, clippy::pedantic)]
use std::{env, path::PathBuf, string::ToString};
mod cli;
pub mod cmd;
pub enum Path {
Bin,
Sbin,
UsrBin,
UsrSbin,
}
#[must_use]
pub fn progname() -> Option<String> {
env::args()
.next()
.and_then(|x| {
PathBuf::from(x)
.file_name()
.map(|x| x.to_str().map(ToString::to_string))
})
.flatten()
}
pub fn run() {
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(),
_ => unimplemented!(),
}
}
}

3
src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
shitbox::run();
}