mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Implement basic installation of file to directory
Bare minimum functionality of `install file dir` implemented. Also added TODO markers in code for outstanding parameters and split main function into smaller logical chunks.
This commit is contained in:
parent
e72ec4a5bb
commit
8a5719561d
2 changed files with 202 additions and 38 deletions
|
@ -15,42 +15,29 @@ extern crate libc;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
use std::io::{Write};
|
use std::io::{Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::result::Result;
|
||||||
|
|
||||||
static NAME: &'static str = "install";
|
static NAME: &'static str = "install";
|
||||||
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
pub struct Behaviour {
|
||||||
|
main_function: MainFunction,
|
||||||
|
suffix: String,
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub enum MainFunction {
|
||||||
|
Version,
|
||||||
|
Help,
|
||||||
|
Standard
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uumain(args: Vec<String>) -> i32 {
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
let mut opts = getopts::Options::new();
|
let opts = opts();
|
||||||
|
|
||||||
opts.optflagopt("", "backup", "make a backup of each existing destination file", "CONTROL");
|
|
||||||
opts.optflag("b", "", "like --backup but does not accept an argument");
|
|
||||||
opts.optflag("C", "compare", "compare each pair of source and destination files, and in\n \
|
|
||||||
some cases, do not modify the destination at all");
|
|
||||||
opts.optflag("d", "directory", "treat all arguments as directory names; create all\n \
|
|
||||||
components of the specified directories");
|
|
||||||
|
|
||||||
opts.optflag("D", "", "create all leading components of DEST except the last, then copy\n \
|
|
||||||
SOURCE to DEST");
|
|
||||||
opts.optflagopt("g", "group", "set group ownership, instead of process' current group",
|
|
||||||
"GROUP");
|
|
||||||
opts.optflagopt("m", "mode", "set permission mode (as in chmod), instead of rwxr-xr-x",
|
|
||||||
"MODE");
|
|
||||||
opts.optflagopt("o", "owner", "set ownership (super-user only)",
|
|
||||||
"OWNER");
|
|
||||||
opts.optflag("p", "preserve-timestamps", "apply access/modification times of SOURCE files\n \
|
|
||||||
to corresponding destination files");
|
|
||||||
opts.optflag("s", "strip", "strip symbol tables");
|
|
||||||
opts.optflagopt("", "strip-program", "program used to strip binaries", "PROGRAM");
|
|
||||||
opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX");
|
|
||||||
opts.optopt("t", "target-directory", "move all SOURCE arguments into DIRECTORY", "DIRECTORY");
|
|
||||||
opts.optflag("T", "no-target-directory", "treat DEST as a normal file");
|
|
||||||
opts.optflag("v", "verbose", "explain what is being done");
|
|
||||||
opts.optflag("P", "preserve-context", "preserve security context");
|
|
||||||
opts.optflagopt("Z", "context", "set security context of files and directories", "CONTEXT");
|
|
||||||
opts.optflag("h", "help", "display this help and exit");
|
|
||||||
opts.optflag("V", "version", "output version information and exit");
|
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
@ -59,9 +46,17 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let usage = opts.usage("Copy SOURCE to DEST or multiple SOURCE(s) to the existing\n \
|
let usage = opts.usage("Copy SOURCE to DEST or multiple SOURCE(s) to the existing\n \
|
||||||
DIRECTORY, while setting permission modes and owner/group");
|
DIRECTORY, while setting permission modes and owner/group");
|
||||||
|
|
||||||
|
let behaviour = match behaviour(&matches) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(ret) => {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let paths: Vec<PathBuf> = {
|
let paths: Vec<PathBuf> = {
|
||||||
fn string_to_path<'a>(s: &'a String) -> &'a Path {
|
fn string_to_path<'a>(s: &'a String) -> &'a Path {
|
||||||
Path::new(s)
|
Path::new(s)
|
||||||
|
@ -72,17 +67,117 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
arguments.map(to_owned).collect()
|
arguments.map(to_owned).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches.opt_present("version") {
|
match behaviour.main_function {
|
||||||
|
MainFunction::Version => {
|
||||||
println!("{} {}", NAME, VERSION);
|
println!("{} {}", NAME, VERSION);
|
||||||
0
|
0
|
||||||
} else if matches.opt_present("help") {
|
},
|
||||||
|
MainFunction::Help => {
|
||||||
help(&usage);
|
help(&usage);
|
||||||
0
|
0
|
||||||
} else {
|
},
|
||||||
println!("Not implemented.");
|
MainFunction::Standard => {
|
||||||
1
|
exec(&paths[..], behaviour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opts() -> getopts::Options {
|
||||||
|
let mut opts = getopts::Options::new();
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflagopt("", "backup", "make a backup of each existing destination file", "CONTROL");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("b", "", "like --backup but does not accept an argument");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("C", "compare", "compare each pair of source and destination files, and in\n \
|
||||||
|
some cases, do not modify the destination at all");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("d", "directory", "treat all arguments as directory names; create all\n \
|
||||||
|
components of the specified directories");
|
||||||
|
|
||||||
|
|
||||||
|
// TODO implement flagd
|
||||||
|
opts.optflag("D", "", "create all leading components of DEST except the last, then copy\n \
|
||||||
|
SOURCE to DEST");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflagopt("g", "group", "set group ownership, instead of process' current group",
|
||||||
|
"GROUP");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflagopt("m", "mode", "set permission mode (as in chmod), instead of rwxr-xr-x",
|
||||||
|
"MODE");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflagopt("o", "owner", "set ownership (super-user only)",
|
||||||
|
"OWNER");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("p", "preserve-timestamps", "apply access/modification times of SOURCE files\n \
|
||||||
|
to corresponding destination files");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("s", "strip", "strip symbol tables");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflagopt("", "strip-program", "program used to strip binaries", "PROGRAM");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optopt("t", "target-directory", "move all SOURCE arguments into DIRECTORY", "DIRECTORY");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("T", "no-target-directory", "treat DEST as a normal file");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("v", "verbose", "explain what is being done");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflag("P", "preserve-context", "preserve security context");
|
||||||
|
|
||||||
|
// TODO implement flag
|
||||||
|
opts.optflagopt("Z", "context", "set security context of files and directories", "CONTEXT");
|
||||||
|
|
||||||
|
opts.optflag("h", "help", "display this help and exit");
|
||||||
|
opts.optflag("V", "version", "output version information and exit");
|
||||||
|
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn behaviour(matches: &getopts::Matches) -> Result<Behaviour, i32> {
|
||||||
|
let main_function = if matches.opt_present("version") {
|
||||||
|
MainFunction::Version
|
||||||
|
} else if matches.opt_present("help") {
|
||||||
|
MainFunction::Help
|
||||||
|
} else {
|
||||||
|
MainFunction::Standard
|
||||||
|
};
|
||||||
|
|
||||||
|
let backup_suffix = if matches.opt_present("suffix") {
|
||||||
|
match matches.opt_str("suffix") {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
show_error!("option '--suffix' requires an argument\n\
|
||||||
|
Try '{} --help' for more information.", NAME);
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"~".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Behaviour {
|
||||||
|
main_function: main_function,
|
||||||
|
suffix: backup_suffix,
|
||||||
|
verbose: matches.opt_present("v"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn help(usage: &str) {
|
fn help(usage: &str) {
|
||||||
println!("{0} {1}\n\n\
|
println!("{0} {1}\n\n\
|
||||||
|
@ -90,3 +185,55 @@ fn help(usage: &str) {
|
||||||
or: {0} SOURCE... DIRECTORY\n\n\
|
or: {0} SOURCE... DIRECTORY\n\n\
|
||||||
{2}", NAME, VERSION, usage);
|
{2}", NAME, VERSION, usage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
|
||||||
|
if 2 == files.len() {
|
||||||
|
move_files_into_dir(&files[0..1], &files[1], &b)
|
||||||
|
} else {
|
||||||
|
println!("Not implemented.");
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -> i32 {
|
||||||
|
if !target_dir.is_dir() {
|
||||||
|
show_error!("target ‘{}’ is not a directory", target_dir.display());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut all_successful = true;
|
||||||
|
for sourcepath in files.iter() {
|
||||||
|
let targetpath = match sourcepath.as_os_str().to_str() {
|
||||||
|
Some(name) => target_dir.join(name),
|
||||||
|
None => {
|
||||||
|
show_error!("cannot stat ‘{}’: No such file or directory",
|
||||||
|
sourcepath.display());
|
||||||
|
|
||||||
|
all_successful = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = rename(sourcepath, &targetpath, b) {
|
||||||
|
show_error!("mv: cannot move ‘{}’ to ‘{}’: {}",
|
||||||
|
sourcepath.display(), targetpath.display(), e);
|
||||||
|
all_successful = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if all_successful { 0 } else { 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> std::io::Result<()> {
|
||||||
|
let backup_path: Option<PathBuf> = None;
|
||||||
|
|
||||||
|
try!(fs::rename(from, to));
|
||||||
|
|
||||||
|
if b.verbose {
|
||||||
|
print!("‘{}’ -> ‘{}’", from.display(), to.display());
|
||||||
|
match backup_path {
|
||||||
|
Some(path) => println!(" (backup: ‘{}’)", path.display()),
|
||||||
|
None => println!("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -19,3 +19,20 @@ fn test_install_help() {
|
||||||
|
|
||||||
// assert!(result.stdout.contains("Usage:"));
|
// assert!(result.stdout.contains("Usage:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_install_basic() {
|
||||||
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
|
let dir = "test_install_target_dir_dir";
|
||||||
|
let file = "test_install_target_dir_file_a";
|
||||||
|
|
||||||
|
at.touch(file);
|
||||||
|
at.mkdir(dir);
|
||||||
|
let result = ucmd.arg(file).arg(dir).run();
|
||||||
|
|
||||||
|
assert_empty_stderr!(result);
|
||||||
|
assert!(result.success);
|
||||||
|
|
||||||
|
assert!(!at.file_exists(file));
|
||||||
|
assert!(at.file_exists(&format!("{}/{}", dir, file)));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue