d248f7d17b
derive(Default) from applets
155 lines
4.6 KiB
Rust
155 lines
4.6 KiB
Rust
use super::{Cmd, Feedback};
|
|
use clap::{Arg, ArgAction, Command};
|
|
use mode::{Mode, Parser};
|
|
use shitbox::args;
|
|
use std::{
|
|
error::Error,
|
|
fs::{self, File, Permissions},
|
|
io,
|
|
os::unix::prelude::{MetadataExt, PermissionsExt},
|
|
path::PathBuf,
|
|
};
|
|
use walkdir::{DirEntry, WalkDir};
|
|
|
|
#[derive(Debug)]
|
|
pub struct Chmod;
|
|
|
|
impl Cmd for Chmod {
|
|
fn cli(&self) -> clap::Command {
|
|
Command::new("chmod")
|
|
.about("change file mode bits")
|
|
.author("Nathan Fisher")
|
|
.version(env!("CARGO_PKG_VERSION"))
|
|
.args([
|
|
args::verbose(),
|
|
args::changes(),
|
|
Arg::new("quiet")
|
|
.short('f')
|
|
.long("silent")
|
|
.visible_alias("quiet")
|
|
.help("suppress most error messages")
|
|
.action(ArgAction::SetTrue),
|
|
args::recursive(),
|
|
Arg::new("mode")
|
|
.value_name("MODE")
|
|
.num_args(1)
|
|
.required(true),
|
|
Arg::new("file")
|
|
.value_name("FILE")
|
|
.num_args(1..)
|
|
.required(true),
|
|
])
|
|
}
|
|
|
|
fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
|
let feedback = Feedback::from_matches(matches);
|
|
let mode = matches
|
|
.get_one::<String>("mode")
|
|
.ok_or(io::Error::new(io::ErrorKind::Other, "no mode given"))?;
|
|
if let Some(files) = matches.get_many::<String>("file") {
|
|
for f in files {
|
|
let mut path = PathBuf::from(f);
|
|
if path.is_symlink() {
|
|
path = fs::read_link(&path)?;
|
|
}
|
|
let action = Action {
|
|
path,
|
|
feedback,
|
|
mode,
|
|
};
|
|
if let Err(e) = action.apply() {
|
|
if !matches.get_flag("quiet") {
|
|
return Err(e);
|
|
}
|
|
}
|
|
if matches.get_flag("recursive") && action.path.is_dir() {
|
|
if let Err(e) = action.recurse() {
|
|
if !matches.get_flag("quiet") {
|
|
return Err(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn path(&self) -> Option<shitbox::Path> {
|
|
Some(shitbox::Path::Bin)
|
|
}
|
|
}
|
|
|
|
struct Action<'a> {
|
|
path: PathBuf,
|
|
feedback: Option<Feedback>,
|
|
mode: &'a str,
|
|
}
|
|
|
|
impl Action<'_> {
|
|
fn apply(&self) -> Result<(), Box<dyn Error>> {
|
|
let oldmode = {
|
|
let fd = File::open(&self.path)?;
|
|
let meta = fd.metadata()?;
|
|
meta.mode()
|
|
};
|
|
let mut parser = Parser::new(oldmode);
|
|
let mode = parser.parse(self.mode)?;
|
|
let permissions = Permissions::from_mode(mode);
|
|
fs::set_permissions(&self.path, permissions)?;
|
|
if let Some(f) = self.feedback {
|
|
match f {
|
|
Feedback::Full => {
|
|
if oldmode == mode {
|
|
self.display_retained(oldmode)?;
|
|
} else {
|
|
self.display_changes(oldmode, mode)?;
|
|
}
|
|
}
|
|
Feedback::Changes => {
|
|
if oldmode != mode {
|
|
self.display_changes(oldmode, mode)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn display_changes(&self, oldmode: u32, mode: u32) -> Result<(), Box<dyn Error>> {
|
|
let oldstring = oldmode.mode_string()?;
|
|
let newstring = mode.mode_string()?;
|
|
println!(
|
|
"mode of '{}' changed from {oldmode:o} ({oldstring}) to {mode:o} ({newstring})",
|
|
self.path.display()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn display_retained(&self, mode: u32) -> Result<(), Box<dyn Error>> {
|
|
let modestring = mode.mode_string()?;
|
|
println!(
|
|
"mode of '{}' retained as {mode:o} ({modestring})",
|
|
self.path.display()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn get_child(&self, entry: DirEntry) -> Self {
|
|
Self {
|
|
path: entry.path().to_path_buf(),
|
|
feedback: self.feedback,
|
|
mode: self.mode,
|
|
}
|
|
}
|
|
|
|
fn recurse(&self) -> Result<(), Box<dyn Error>> {
|
|
let walker = WalkDir::new(&self.path).max_open(1);
|
|
for entry in walker {
|
|
let entry = entry?;
|
|
let action = self.get_child(entry);
|
|
action.apply()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|