#![crate_type = "bin"]
#![crate_name = "cp"]
#![feature(path_ext,slice_splits)]
static UTIL: Prog = Prog { name: "cp", vers: "0.1.0", yr: "2015", };
extern crate pgetopts;
extern crate rpf;
use pgetopts::{Options};
use rpf::{Status,Styled,Color,PathMod,Prog};
use std::env;
use std::fs;
use std::io;
#[cfg(target_family = "unix")]
use std::os;
use std::fs::{PathExt};
use std::path::{PathBuf,Path};
#[cfg(target_family = "windows")]
use std::os::windows;
struct Opts {
verbose: bool,
force: bool,
inter: bool,
}
enum CopyType {
Norm,
Phys,
Sym,
}
#[derive(PartialEq)]
enum EntryMode {
Norm,
Sym,
}
#[cfg(target_family = "unix")]
fn sym_link<F: AsRef<Path>, T: AsRef<Path>>
(from: &F, to: &T, options: &Opts) {
if options.force && to.as_ref().exists() { fs::remove_file(to).ok(); }
match os::unix::fs::symlink(from, to) {
Ok(_) => {
if options.verbose {
println!("{} {} {}", from.as_ref().as_str().bold(),
"->".paint(Color::Green), to.as_ref().as_str().bold());
}
},
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
}
};
}
#[cfg(target_family = "windows")]
fn sym_link<F: AsRef<Path>, T: AsRef<Path>>
(from: &F, to: &T, options: &Opts) {
if from.as_ref().is_file() {
if options.force { fs::remove_file(to).ok(); }
match windows::fs::symlink_file(from, to) {
Ok(_) => {
if options.verbose {
println!("{} {} {}", from.as_ref().as_str().bold(),
"->".paint(Color::Green), to.as_ref().as_str().bold());
}
},
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
}
};
} else if from.as_ref().is_dir() {
if options.force {
fs::remove_dir_all(to).ok();
}
match windows::fs::symlink_dir(from, to) {
Ok(_) => {
if options.verbose {
println!("{} {} {}", from.as_ref().as_str().bold(),
"->".paint(Color::Green), to.as_ref().as_str().bold());
}
},
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
}
};
}
}
fn hard_link<F: AsRef<Path>, T: AsRef<Path>>
(from: &F, to: &T, options: &Opts) {
if !from.as_ref().exists() {
Status::Error.err(UTIL.name, String::from("\'".to_string() +
from.as_ref().as_str() +
"\' does not exist"));
}
match fs::hard_link(from, to) {
Ok(_) => {
if options.force { fs::remove_file(to).ok(); }
if options.verbose {
println!("{} {} {}", from.as_ref().as_str().bold(),
"->".paint(Color::Green), to.as_ref().as_str().bold());
}
},
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
}
};
}
fn ask<T: AsRef<Path>>(to: &T) -> bool {
if to.as_ref().exists() {
println!("cp: Overwrite '{}'?", &to.as_ref().as_str().bold());
let mut ans = String::new();
io::stdin().read_line(&mut ans).ok();
ans = ans.trim().to_lowercase();
if ans == "y" || ans == "yes" {
fs::remove_file(to).ok();
return true;
}
}
return false;
}
fn copy<F: AsRef<Path>, T: AsRef<Path>>(from: &F, to: &T, options: &Opts) {
match fs::copy(from, to) {
Ok(_) => {
if options.verbose {
println!("{} {} {}", &from.as_ref().as_str().bold(),
"->".paint(Color::Green), to.as_ref().as_str().bold());
}
},
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
panic!();
}
};
}
fn match_copytype<T: AsRef<Path>>
(file: &String, to: &T, options: &Opts, copytype: &CopyType) {
match copytype {
&CopyType::Norm => { copy(&file, &to, options) },
&CopyType::Sym => { sym_link(&file, &to, options) },
&CopyType::Phys => { hard_link(&file, &to, options) },
};
}
fn copy_files<T: AsRef<Path>>
(from: Vec<String>, to: &T, options: &Opts, copytype: &CopyType) {
for file in from.iter() {
let dest = Path::new(file).file_name().unwrap();
if !to.as_ref().is_dir() {
Status::Error.err(UTIL.name, String::from(
to.as_ref().as_str())
+ " is not a directory");
} else if !options.inter {
match_copytype(file, &to.as_ref().join(dest), options, copytype);
} else if options.inter && !to.as_ref().join(dest).exists() {
match_copytype(file, &to.as_ref().join(dest), options, copytype);
} else if options.inter {
if ask(&to.as_ref().join(dest)) {
match_copytype(file, &to.as_ref().join(dest), options, copytype);
}
}
}
}
fn create_dir<F: AsRef<Path>, T: AsRef<Path>>
(from: &F, to: &T, options: &Opts) {
match fs::create_dir_all(&to.as_ref()) {
Ok(_) => {
if options.verbose {
println!("{} {} {}", from.as_ref().as_str().bold(),
"->".paint(Color::Green), to.as_ref().as_str().bold());
}
},
Err(e) => {
panic!(e.to_string());
}
};
}
fn copy_dir<F: AsRef<Path>>
(from: &F, to: &mut PathBuf, options: &Opts, copytype: &CopyType, mode: &EntryMode) {
if !from.as_ref().exists() {
Status::Error.err(UTIL.name,
String::from(from.as_ref().as_string() +
" does not exist"));
}
let dest = from.as_ref().last_component();
if is_symlink(from) && mode == &EntryMode::Sym {
if !to.join(&dest).exists() {
create_dir(&from, &to.join(&dest), options);
}
walk_dir_copy(&from, to.join(&dest), options, ©type).ok();
} else if !is_symlink(from) {
walk_dir_copy(&from, to.join(&dest), options, ©type).ok();
}
}
fn check_endless_copy<F: AsRef<Path>, T: AsRef<Path>>(from: &F, to: &T) {
let mut to_clone = to.as_ref().clone().to_path_buf();
to_clone.pop(); to_clone.pop();
if to_clone == from.as_ref().to_path_buf() {
Status::Error.err(UTIL.name,
"cannot copy a directory into itself".to_string());
}
if from.as_ref().last_component() == to.as_ref().first_component() {
Status::Error.err(UTIL.name,
"cannot copy a directory into itself".to_string());
}
}
fn is_symlink<P: AsRef<Path>>(path: &P) -> bool {
match fs::symlink_metadata(&path) {
Ok(file) => {
if file.file_type().is_symlink() { return true; }
else if file.file_type().is_dir() { return false; }
else if file.file_type().is_file() { return false; }
}
Err(_) => { Status::Error.err(UTIL.name, "could not stat file".to_string()); }
}
return false;
}
fn walk_dir_copy<F: AsRef<Path>>
(from: &F, mut to: PathBuf, options: &Opts, copytype: &CopyType) -> io::Result<()> {
check_endless_copy(&from, &to);
if from.as_ref().is_dir() {
if !to.exists() { create_dir(&from, &to, options); }
for entry in try!(fs::read_dir(&from)) {
let entry = match entry {
Ok(s) => { s },
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
panic!();
}
};
let dest = to.clone().join(entry.path().file_name().unwrap());
let src = entry.path();
if src.is_file() && !options.inter {
match_copytype(&src.as_string(), &dest, options, copytype);
} else if src.is_file() && !dest.exists() && options.inter {
match_copytype(&src.as_string(), &dest, options, copytype);
} else if src.is_file() && dest.exists() && options.inter {
if ask(&dest) {
match_copytype(&src.as_string(), &dest, options, copytype);
}
} else if src.is_dir() {
to.push(src.rel_to(&from));
match walk_dir_copy(&src.clone(), to.clone(), options, ©type) {
Ok(s) => { s },
Err(e) => {
Status::Error.err(UTIL.name, e.to_string());
panic!();
}
};
to.pop();
}
}
}
return Ok(());
}
fn print_usage(opts: Options) {
print!("{0}: {1} {2}... {3} {4}\
\n or: {1} {2}... {3}... {4}\n\
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.", "Usage".bold(),
UTIL.name.bold(), "[OPTION]".underline(), "SOURCE".underline(),
"DEST".underline());
println!("{}", opts.options());
}
fn main() {
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
opts.optflag("f", "force", "Force action");
opts.optflag("i", "interactive", "Prompt before overwriting an existing file");
opts.optflag("l", "link", "Create hard links instead of copying");
opts.optflag("L", "dereference", "Always follow symbolic links in SOURCE");
opts.optflag("P", "no-dereference", "Never follow symbolic links in SOURCE");
opts.optflag("r", "recursive", "Recursively copy a directory");
opts.optflag("s", "symlink", "Create symbolic links instead of copying");
opts.optflag("v", "verbose", "Print operation verbosely");
opts.optflag("h", "help", "Print help information");
opts.optflag("", "version", "Print version information");
let matches = match opts.parse(&args[1..]) {
Ok(m) => { m },
Err(e) => {
Status::OptError.err(UTIL.name, e.to_string());
Status::OptError.exit();
panic!();
}
};
if matches.opt_present("h") {
print_usage(opts);
} else if matches.opt_present("version") {
UTIL.copyright("Copyright (C) 2015 core-utils developers\n\
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n\n",
vec!["Alberto Corona"]);
} else {
let mut copytype = CopyType::Norm;
let mut mode = EntryMode::Norm;
if matches.opt_present("l") {
copytype = CopyType::Phys;
} else if matches.opt_present("s") {
copytype = CopyType::Sym;
}
if matches.opt_present("L") {
mode = EntryMode::Sym;
} else if matches.opt_present("P") {
mode = EntryMode::Norm;
}
let options = &Opts {
verbose: matches.opt_present("v"),
inter: matches.opt_present("i"),
force: matches.opt_present("f"),
};
let src = match matches.free.first().clone() {
Some(s) => { s },
None => {
println!("{}: no arguments given", UTIL.name);
Status::OptError.exit();
panic!();
}
};
if matches.free.len() == 1 {
println!("{}: no destination for {}", UTIL.name, src);
Status::ArgError.exit();
}
if matches.opt_present("r") {
for items in matches.free.split_first().unwrap().1.to_vec() {
copy_dir(src, &mut PathBuf::from(items), options, ©type, &mode);
}
} else {
let dest = matches.free.last().unwrap();
if matches.free.len() > 2 {
copy_files(matches.free.split_last().unwrap().1.to_vec(), &dest, options, ©type);
} else if !options.inter {
copy(src, &matches.free[1], options);
} else if options.inter && ask(&matches.free[1]) {
copy(src, &matches.free[1], options);
}
}
}
}