Add chgrp
applet; Add args
module for commonly used args to increase
consistency
This commit is contained in:
parent
f68ae6df91
commit
9df197a4b9
44
src/args/mod.rs
Normal file
44
src/args/mod.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use clap::{Arg, ArgAction};
|
||||||
|
|
||||||
|
pub fn verbose() -> Arg {
|
||||||
|
Arg::new("verbose")
|
||||||
|
.help("output a diagnostic for every file processed")
|
||||||
|
.short('v')
|
||||||
|
.long("verbose")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn header() -> Arg {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes() -> Arg {
|
||||||
|
Arg::new("changes")
|
||||||
|
.help("report only when a change is made")
|
||||||
|
.short('c')
|
||||||
|
.long("changes")
|
||||||
|
.conflicts_with("verbose")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recursive() -> Arg {
|
||||||
|
Arg::new("recursive")
|
||||||
|
.help("operate on files and directories recursively")
|
||||||
|
.short('R')
|
||||||
|
.long("recursive")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color() -> Arg {
|
||||||
|
Arg::new("color")
|
||||||
|
.short('c')
|
||||||
|
.long("color")
|
||||||
|
.value_parser(["always", "ansi", "auto", "never"])
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
|
use crate::args;
|
||||||
use clap::{value_parser, Arg, ArgAction, Command};
|
use clap::{value_parser, Arg, ArgAction, Command};
|
||||||
use data_encoding::BASE32;
|
use data_encoding::BASE32;
|
||||||
use std::{
|
use std::{
|
||||||
@ -16,41 +17,7 @@ impl Cmd for Base32 {
|
|||||||
Command::new("base32")
|
Command::new("base32")
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.about("Base32 encode/decode data and print to standard output")
|
.about("Base32 encode/decode data and print to standard output")
|
||||||
.args([
|
.args(args())
|
||||||
Arg::new("INPUT")
|
|
||||||
.help("The input file to use")
|
|
||||||
.num_args(1..),
|
|
||||||
Arg::new("DECODE")
|
|
||||||
.help("Decode rather than encode")
|
|
||||||
.short('d')
|
|
||||||
.long("decode")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("IGNORE")
|
|
||||||
.help("Ignore whitespace when decoding")
|
|
||||||
.short('i')
|
|
||||||
.long("ignore-space")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("WRAP")
|
|
||||||
.help("Wrap encoded lines after n characters")
|
|
||||||
.short('w')
|
|
||||||
.long("wrap")
|
|
||||||
.value_parser(value_parser!(usize))
|
|
||||||
.default_value("76"),
|
|
||||||
Arg::new("color")
|
|
||||||
.short('c')
|
|
||||||
.long("color")
|
|
||||||
.value_parser(["always", "ansi", "auto", "never"]),
|
|
||||||
Arg::new("VERBOSE")
|
|
||||||
.help("Display a header naming each file")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("QUIET")
|
|
||||||
.help("Do not display header, even with multiple files")
|
|
||||||
.short('q')
|
|
||||||
.long("quiet")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
|
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@ -75,7 +42,7 @@ impl Cmd for Base32 {
|
|||||||
_ => 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);
|
let mut stdout = StandardStream::stdout(color);
|
||||||
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
||||||
match index {
|
match index {
|
||||||
@ -109,6 +76,37 @@ impl Cmd for Base32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn args() -> [Arg; 7] {
|
||||||
|
[
|
||||||
|
Arg::new("INPUT")
|
||||||
|
.help("The input file to use")
|
||||||
|
.num_args(1..),
|
||||||
|
Arg::new("DECODE")
|
||||||
|
.help("Decode rather than encode")
|
||||||
|
.short('d')
|
||||||
|
.long("decode")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("IGNORE")
|
||||||
|
.help("Ignore whitespace when decoding")
|
||||||
|
.short('i')
|
||||||
|
.long("ignore-space")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("WRAP")
|
||||||
|
.help("Wrap encoded lines after n characters")
|
||||||
|
.short('w')
|
||||||
|
.long("wrap")
|
||||||
|
.value_parser(value_parser!(usize))
|
||||||
|
.default_value("76"),
|
||||||
|
args::color(),
|
||||||
|
args::verbose(),
|
||||||
|
Arg::new("QUIET")
|
||||||
|
.help("Do not display header, even with multiple files")
|
||||||
|
.short('q')
|
||||||
|
.long("quiet")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
fn decode_base32(mut contents: String, ignore: bool) -> Result<(), Box<dyn Error>> {
|
fn decode_base32(mut contents: String, ignore: bool) -> Result<(), Box<dyn Error>> {
|
||||||
if ignore {
|
if ignore {
|
||||||
contents.retain(|c| !c.is_whitespace());
|
contents.retain(|c| !c.is_whitespace());
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::Cmd;
|
use super::{base32::args, Cmd};
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{ArgMatches, Command};
|
||||||
use data_encoding::BASE64;
|
use data_encoding::BASE64;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -16,41 +16,7 @@ impl Cmd for Base64 {
|
|||||||
Command::new("base64")
|
Command::new("base64")
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.about("Base64 encode/decode data and print to standard output")
|
.about("Base64 encode/decode data and print to standard output")
|
||||||
.args([
|
.args(args())
|
||||||
Arg::new("INPUT")
|
|
||||||
.help("The input file to use")
|
|
||||||
.num_args(0..),
|
|
||||||
Arg::new("DECODE")
|
|
||||||
.help("Decode rather than encode")
|
|
||||||
.short('d')
|
|
||||||
.long("decode")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("IGNORE")
|
|
||||||
.help("Ignore whitespace when decoding")
|
|
||||||
.short('i')
|
|
||||||
.long("ignore-space")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("WRAP")
|
|
||||||
.help("Wrap encoded lines after n characters")
|
|
||||||
.short('w')
|
|
||||||
.long("wrap")
|
|
||||||
.default_value("76")
|
|
||||||
.value_parser(value_parser!(usize)),
|
|
||||||
Arg::new("color")
|
|
||||||
.short('c')
|
|
||||||
.long("color")
|
|
||||||
.value_parser(["always", "ansi", "auto", "never"]),
|
|
||||||
Arg::new("VERBOSE")
|
|
||||||
.help("Display a header naming each file")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("QUIET")
|
|
||||||
.help("Do not display header, even with multiple files")
|
|
||||||
.short('q')
|
|
||||||
.long("quiet")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use super::{Cmd, Feedback};
|
use super::{Cmd, Feedback};
|
||||||
use crate::mode::{Mode, Parser};
|
use crate::{
|
||||||
|
args,
|
||||||
|
mode::{Mode, Parser},
|
||||||
|
};
|
||||||
use clap::{Arg, ArgAction, Command};
|
use clap::{Arg, ArgAction, Command};
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -20,32 +23,17 @@ impl Cmd for Chmod {
|
|||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.args([
|
.args([
|
||||||
Arg::new("verbose")
|
args::verbose(),
|
||||||
.short('v')
|
args::changes(),
|
||||||
.long("verbose")
|
|
||||||
.help("output a diagnostic for every file processed")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("changes")
|
|
||||||
.short('c')
|
|
||||||
.long("changes")
|
|
||||||
.help("like verbose but report only when a change is made")
|
|
||||||
.conflicts_with("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("quiet")
|
Arg::new("quiet")
|
||||||
.short('f')
|
.short('f')
|
||||||
.long("silent")
|
.long("silent")
|
||||||
.visible_alias("quiet")
|
.visible_alias("quiet")
|
||||||
.help("suppress most error messages")
|
.help("suppress most error messages")
|
||||||
.action(ArgAction::SetTrue),
|
.action(ArgAction::SetTrue),
|
||||||
Arg::new("recursive")
|
args::recursive(),
|
||||||
.short('R')
|
|
||||||
.long("recursive")
|
|
||||||
.visible_short_alias('r')
|
|
||||||
.help("change files and directories recursively")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("mode")
|
Arg::new("mode")
|
||||||
.value_name("MODE")
|
.value_name("MODE")
|
||||||
.value_delimiter(',')
|
|
||||||
.num_args(1)
|
.num_args(1)
|
||||||
.required(true),
|
.required(true),
|
||||||
Arg::new("file")
|
Arg::new("file")
|
||||||
|
158
src/cmd/chown/chgrp.rs
Normal file
158
src/cmd/chown/chgrp.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use super::{Group, Recurse, Traversal};
|
||||||
|
use crate::cmd::{Cmd, Feedback};
|
||||||
|
use crate::pw;
|
||||||
|
use clap::{Arg, ArgGroup, Command, ValueHint};
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::File,
|
||||||
|
io,
|
||||||
|
os::{fd::AsRawFd, unix::prelude::MetadataExt},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Chgrp;
|
||||||
|
|
||||||
|
impl Cmd for Chgrp {
|
||||||
|
fn cli(&self) -> clap::Command {
|
||||||
|
Command::new("chgrp")
|
||||||
|
.about("change group ownership")
|
||||||
|
.author("Nathan Fisher")
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.arg(
|
||||||
|
Arg::new("group")
|
||||||
|
.value_name("GROUP")
|
||||||
|
.value_hint(ValueHint::Username)
|
||||||
|
.num_args(1)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.args(super::args())
|
||||||
|
.group(
|
||||||
|
ArgGroup::new("links")
|
||||||
|
.args(["cli-traverse", "full-traverse", "no-traverse"])
|
||||||
|
.requires("recursive")
|
||||||
|
.multiple(false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let Some(matches) = matches else {
|
||||||
|
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input")));
|
||||||
|
};
|
||||||
|
let recurse = Recurse::from_matches(matches);
|
||||||
|
let feedback = Feedback::from_matches(matches);
|
||||||
|
let group = if let Some(grp) = matches.get_one::<String>("group") {
|
||||||
|
let gid = pw::get_gid_for_groupname(grp)
|
||||||
|
.ok_or(io::Error::new(io::ErrorKind::Other, "cannot get gid"))?;
|
||||||
|
Group { name: grp, gid }
|
||||||
|
} else {
|
||||||
|
return Err(Box::new(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"no user specified",
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
if let Some(files) = matches.get_many::<String>("file") {
|
||||||
|
for f in files {
|
||||||
|
let action = Action {
|
||||||
|
path: PathBuf::from(f),
|
||||||
|
group: group.clone(),
|
||||||
|
feedback,
|
||||||
|
};
|
||||||
|
if let Some(r) = recurse {
|
||||||
|
if action.path.is_dir() {
|
||||||
|
action.recurse(recurse)?;
|
||||||
|
} else if action.path.is_symlink() {
|
||||||
|
if r.traversal != Traversal::NoLinks {
|
||||||
|
action.recurse(recurse)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action.apply()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<crate::Path> {
|
||||||
|
Some(crate::Path::Bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Action<'a> {
|
||||||
|
path: PathBuf,
|
||||||
|
group: Group<'a>,
|
||||||
|
feedback: Option<Feedback>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action<'_> {
|
||||||
|
fn apply(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let fd = File::open(&self.path)?;
|
||||||
|
let meta = fd.metadata()?;
|
||||||
|
let uid = meta.uid();
|
||||||
|
let gid = meta.gid();
|
||||||
|
unsafe {
|
||||||
|
if libc::fchown(fd.as_raw_fd(), uid, self.group.gid) != 0 {
|
||||||
|
return Err(io::Error::last_os_error().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(fd);
|
||||||
|
if let Some(feedback) = self.feedback {
|
||||||
|
match feedback {
|
||||||
|
Feedback::Full => {
|
||||||
|
if self.group.gid != gid {
|
||||||
|
self.display_changes(gid)?;
|
||||||
|
} else {
|
||||||
|
self.display_retained();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Feedback::Changes => {
|
||||||
|
if self.group.gid != gid {
|
||||||
|
self.display_changes(gid)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_changes(&self, gid: u32) -> Result<(), std::str::Utf8Error> {
|
||||||
|
let groupname = pw::get_groupname_for_gid(gid)?;
|
||||||
|
println!(
|
||||||
|
"{} changed from {groupname} to {}",
|
||||||
|
&self.path.display(),
|
||||||
|
&self.group.name
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_retained(&self) {
|
||||||
|
println!("{} retained as {}", &self.path.display(), &self.group.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_child(&self, entry: DirEntry) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let path = entry.path().to_path_buf();
|
||||||
|
Ok(Self {
|
||||||
|
path,
|
||||||
|
group: self.group.clone(),
|
||||||
|
feedback: self.feedback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recurse(&self, recurse: Option<Recurse>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let walker = WalkDir::new(&self.path)
|
||||||
|
.max_open(1)
|
||||||
|
.same_file_system(recurse.map_or(false, |x| x.same_filesystem))
|
||||||
|
.follow_links(recurse.map_or(false, |x| match x.traversal {
|
||||||
|
Traversal::FullLinks => true,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
for entry in walker {
|
||||||
|
let entry = entry?;
|
||||||
|
let action = self.into_child(entry)?;
|
||||||
|
action.apply()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
use super::{Cmd, Feedback};
|
use super::{Cmd, Feedback};
|
||||||
use crate::pw;
|
use crate::{args, pw};
|
||||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -10,6 +10,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
mod chgrp;
|
||||||
|
pub use chgrp::Chgrp;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Chown;
|
pub struct Chown;
|
||||||
|
|
||||||
@ -19,54 +22,14 @@ impl Cmd for Chown {
|
|||||||
.about("change file owner and group")
|
.about("change file owner and group")
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.args([
|
.arg(
|
||||||
Arg::new("user")
|
Arg::new("user")
|
||||||
.value_name("OWNER[:GROUP]")
|
.value_name("OWNER[:GROUP]")
|
||||||
.value_hint(ValueHint::Username)
|
.value_hint(ValueHint::Username)
|
||||||
.num_args(1)
|
.num_args(1)
|
||||||
.required(true),
|
.required(true),
|
||||||
Arg::new("changes")
|
|
||||||
.help("report only when a change is made")
|
|
||||||
.short('c')
|
|
||||||
.long("changes")
|
|
||||||
.conflicts_with("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("verbose")
|
|
||||||
.help("output a diagnostic for every file processed")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("recursive")
|
|
||||||
.help("operate on files and directories recursively")
|
|
||||||
.short('R')
|
|
||||||
.long("recursive")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("cli-traverse")
|
|
||||||
.help(
|
|
||||||
"if a command line argument is a symbolic link to a directory, traverse it",
|
|
||||||
)
|
)
|
||||||
.short('H')
|
.args(args())
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("full-traverse")
|
|
||||||
.help("traverse every symbolic link encountered in a directory")
|
|
||||||
.short('L')
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("no-traverse")
|
|
||||||
.help("do not traverse any symbolic links (default)")
|
|
||||||
.short('P')
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("same-filesystem")
|
|
||||||
.help("do not cross filesystem boundaries (requires recursive)")
|
|
||||||
.short('s')
|
|
||||||
.requires("recursive")
|
|
||||||
.long("same-filesystem")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("file")
|
|
||||||
.value_name("FILE")
|
|
||||||
.value_hint(ValueHint::AnyPath)
|
|
||||||
.num_args(1..)
|
|
||||||
.required(true),
|
|
||||||
])
|
|
||||||
.group(
|
.group(
|
||||||
ArgGroup::new("links")
|
ArgGroup::new("links")
|
||||||
.args(["cli-traverse", "full-traverse", "no-traverse"])
|
.args(["cli-traverse", "full-traverse", "no-traverse"])
|
||||||
@ -127,6 +90,37 @@ impl Cmd for Chown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn args() -> [Arg; 8] {
|
||||||
|
[
|
||||||
|
args::changes(),
|
||||||
|
args::verbose(),
|
||||||
|
args::recursive(),
|
||||||
|
Arg::new("cli-traverse")
|
||||||
|
.help("if a command line argument is a symbolic link to a directory, traverse it")
|
||||||
|
.short('H')
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("full-traverse")
|
||||||
|
.help("traverse every symbolic link encountered in a directory")
|
||||||
|
.short('L')
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("no-traverse")
|
||||||
|
.help("do not traverse any symbolic links (default)")
|
||||||
|
.short('P')
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("same-filesystem")
|
||||||
|
.help("do not cross filesystem boundaries (requires recursive)")
|
||||||
|
.short('s')
|
||||||
|
.requires("recursive")
|
||||||
|
.long("same-filesystem")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
Arg::new("file")
|
||||||
|
.value_name("FILE")
|
||||||
|
.value_hint(ValueHint::AnyPath)
|
||||||
|
.num_args(1..)
|
||||||
|
.required(true),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
enum Traversal {
|
enum Traversal {
|
||||||
CliLinks,
|
CliLinks,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use crate::Path;
|
use crate::{args, Path};
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
@ -20,7 +20,7 @@ impl Cmd for Head {
|
|||||||
.long_about(
|
.long_about(
|
||||||
"Print the first 10 lines of each FILE to standard output.\n\
|
"Print the first 10 lines of each FILE to standard output.\n\
|
||||||
With more than one FILE, precede each with a header giving the file name.\n\n\
|
With more than one FILE, precede each with a header giving the file name.\n\n\
|
||||||
With no FILE, or when FILE is -, read standard input."
|
With no FILE, or when FILE is -, read standard input.",
|
||||||
)
|
)
|
||||||
.args([
|
.args([
|
||||||
Arg::new("FILES")
|
Arg::new("FILES")
|
||||||
@ -36,11 +36,7 @@ impl Cmd for Head {
|
|||||||
.short('q')
|
.short('q')
|
||||||
.long("quiet")
|
.long("quiet")
|
||||||
.action(ArgAction::SetTrue),
|
.action(ArgAction::SetTrue),
|
||||||
Arg::new("HEADER")
|
args::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")
|
Arg::new("LINES")
|
||||||
.help("Count n number of lines (or bytes if -c is specified).")
|
.help("Count n number of lines (or bytes if -c is specified).")
|
||||||
.short('n')
|
.short('n')
|
||||||
@ -48,15 +44,12 @@ 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")
|
args::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'])
|
||||||
.hide(true)
|
.hide(true)
|
||||||
.action(ArgAction::Append)
|
.action(ArgAction::Append),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use clap::{Arg, ArgAction, Command};
|
use crate::args;
|
||||||
|
use clap::{Arg, Command};
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@ -14,10 +15,7 @@ impl Cmd for Link {
|
|||||||
.args([
|
.args([
|
||||||
Arg::new("file1").required(true).index(1),
|
Arg::new("file1").required(true).index(1),
|
||||||
Arg::new("file2").required(true).index(2),
|
Arg::new("file2").required(true).index(2),
|
||||||
Arg::new("verbose")
|
args::verbose(),
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use crate::mode::Parser;
|
use crate::{args, mode::Parser};
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
use clap::{Arg, ArgMatches, Command};
|
||||||
use std::{error::Error, ffi::CString, io};
|
use std::{error::Error, ffi::CString, io};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@ -30,11 +30,7 @@ impl Cmd for MkFifo {
|
|||||||
.long("mode")
|
.long("mode")
|
||||||
.value_name("MODE")
|
.value_name("MODE")
|
||||||
.num_args(1),
|
.num_args(1),
|
||||||
Arg::new("verbose")
|
args::verbose(),
|
||||||
.help("print a diagnostic for every pipe created")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("file").num_args(1..).required(true),
|
Arg::new("file").num_args(1..).required(true),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use crate::mode::{get_umask, Parser};
|
use crate::{
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
args,
|
||||||
|
mode::{get_umask, Parser},
|
||||||
|
};
|
||||||
|
use clap::{value_parser, Arg, ArgMatches, Command};
|
||||||
use std::{convert::Infallible, error::Error, ffi::CString, io, str::FromStr};
|
use std::{convert::Infallible, error::Error, ffi::CString, io, str::FromStr};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@ -19,10 +22,7 @@ impl Cmd for MkNod {
|
|||||||
.help("set file permission bits to MODE, not a=rw - umask")
|
.help("set file permission bits to MODE, not a=rw - umask")
|
||||||
.value_name("MODE")
|
.value_name("MODE")
|
||||||
.num_args(1),
|
.num_args(1),
|
||||||
Arg::new("verbose")
|
args::verbose(),
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("file").value_name("NAME").required(true),
|
Arg::new("file").value_name("NAME").required(true),
|
||||||
Arg::new("type")
|
Arg::new("type")
|
||||||
.value_name("TYPE")
|
.value_name("TYPE")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use std::{error::Error, fmt};
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
mod base32;
|
pub mod base32;
|
||||||
mod base64;
|
mod base64;
|
||||||
mod basename;
|
mod basename;
|
||||||
mod bootstrap;
|
mod bootstrap;
|
||||||
@ -68,6 +68,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
|||||||
"basename" => Some(Box::new(basename::Basename::default())),
|
"basename" => Some(Box::new(basename::Basename::default())),
|
||||||
"bootstrap" => Some(Box::new(bootstrap::Bootstrap::default())),
|
"bootstrap" => Some(Box::new(bootstrap::Bootstrap::default())),
|
||||||
"chmod" => Some(Box::new(chmod::Chmod::default())),
|
"chmod" => Some(Box::new(chmod::Chmod::default())),
|
||||||
|
"chgrp" => Some(Box::new(chown::Chgrp::default())),
|
||||||
"chown" => Some(Box::new(chown::Chown::default())),
|
"chown" => Some(Box::new(chown::Chown::default())),
|
||||||
"clear" => Some(Box::new(clear::Clear::default())),
|
"clear" => Some(Box::new(clear::Clear::default())),
|
||||||
"cut" => Some(Box::new(cut::Cut::default())),
|
"cut" => Some(Box::new(cut::Cut::default())),
|
||||||
@ -100,12 +101,13 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static COMMANDS: [&str; 34] = [
|
pub static COMMANDS: [&str; 35] = [
|
||||||
"base32",
|
"base32",
|
||||||
"base64",
|
"base64",
|
||||||
"basename",
|
"basename",
|
||||||
"bootstrap",
|
"bootstrap",
|
||||||
"chmod",
|
"chmod",
|
||||||
|
"chgrp",
|
||||||
"chown",
|
"chown",
|
||||||
"clear",
|
"clear",
|
||||||
"cut",
|
"cut",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use clap::{Arg, ArgAction, Command};
|
use crate::args;
|
||||||
|
use clap::{Arg, Command};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, BufRead, BufReader, ErrorKind, Write},
|
io::{self, BufRead, BufReader, ErrorKind, Write},
|
||||||
@ -15,15 +16,8 @@ impl Cmd for Rev {
|
|||||||
.about("reverse lines characterwise")
|
.about("reverse lines characterwise")
|
||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.args([
|
.args([
|
||||||
Arg::new("verbose")
|
args::header(),
|
||||||
.short('v')
|
args::color(),
|
||||||
.long("verbose")
|
|
||||||
.help("print a header between each file")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("color")
|
|
||||||
.short('c')
|
|
||||||
.long("color")
|
|
||||||
.value_parser(["always", "ansi", "auto", "never"]),
|
|
||||||
Arg::new("file")
|
Arg::new("file")
|
||||||
.help("if file is '-' read from stdin")
|
.help("if file is '-' read from stdin")
|
||||||
.num_args(0..),
|
.num_args(0..),
|
||||||
@ -51,7 +45,7 @@ impl Cmd for Rev {
|
|||||||
_ => ColorChoice::Never,
|
_ => ColorChoice::Never,
|
||||||
};
|
};
|
||||||
for (index, file) in files.into_iter().enumerate() {
|
for (index, file) in files.into_iter().enumerate() {
|
||||||
if matches.get_flag("verbose") {
|
if matches.get_flag("header") {
|
||||||
let mut stdout = StandardStream::stdout(color);
|
let mut stdout = StandardStream::stdout(color);
|
||||||
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
||||||
match index {
|
match index {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use crate::fs::FileType;
|
use crate::{args, fs::FileType};
|
||||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -46,17 +46,8 @@ impl Cmd for Rm {
|
|||||||
.long("force")
|
.long("force")
|
||||||
.help("ignore nonexistent files and arguments, never prompt")
|
.help("ignore nonexistent files and arguments, never prompt")
|
||||||
.action(ArgAction::SetTrue),
|
.action(ArgAction::SetTrue),
|
||||||
Arg::new("recursive")
|
args::recursive(),
|
||||||
.short('r')
|
args::verbose(),
|
||||||
.long("recursive")
|
|
||||||
.visible_short_alias('R')
|
|
||||||
.help("remove directories and their contents recursively")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("verbose")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.help("explain what is being done")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("file")
|
Arg::new("file")
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.value_hint(ValueHint::AnyPath)
|
.value_hint(ValueHint::AnyPath)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
|
use crate::args;
|
||||||
use clap::{Arg, ArgAction, Command, ValueHint};
|
use clap::{Arg, ArgAction, Command, ValueHint};
|
||||||
use std::{error::Error, fs, io, path::Path};
|
use std::{error::Error, fs, io, path::Path};
|
||||||
|
|
||||||
@ -22,11 +23,7 @@ impl Cmd for Rmdir {
|
|||||||
.value_name("DIRECTORY")
|
.value_name("DIRECTORY")
|
||||||
.value_hint(ValueHint::DirPath)
|
.value_hint(ValueHint::DirPath)
|
||||||
.required(true),
|
.required(true),
|
||||||
Arg::new("verbose")
|
args::verbose(),
|
||||||
.help("output a diagnostic for every directory processed")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::Cmd;
|
use super::Cmd;
|
||||||
use clap::{Arg, ArgAction, Command};
|
use crate::args;
|
||||||
|
use clap::{Arg, Command};
|
||||||
use std::{ffi::CString, io, process};
|
use std::{ffi::CString, io, process};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@ -12,11 +13,7 @@ impl Cmd for Unlink {
|
|||||||
.author("Nathan Fisher")
|
.author("Nathan Fisher")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.args([
|
.args([
|
||||||
Arg::new("verbose")
|
args::verbose(),
|
||||||
.help("display user feedback upon success")
|
|
||||||
.short('v')
|
|
||||||
.long("verbose")
|
|
||||||
.action(ArgAction::SetTrue),
|
|
||||||
Arg::new("file")
|
Arg::new("file")
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.required(true)
|
.required(true)
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
#![warn(clippy::all, clippy::pedantic)]
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
use std::{env, path::PathBuf, process, string::ToString};
|
use std::{env, path::PathBuf, process, string::ToString};
|
||||||
|
|
||||||
|
pub mod args;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
pub use cmd::Cmd;
|
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod pw;
|
pub mod pw;
|
||||||
|
|
||||||
|
pub use cmd::Cmd;
|
||||||
|
|
||||||
/// Defines the location relative to the binary where a command will be installed
|
/// Defines the location relative to the binary where a command will be installed
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Path {
|
pub enum Path {
|
||||||
|
Loading…
Reference in New Issue
Block a user