core-utils 0.1.2

A reimplementation of the GNU core utils in the Rust programming language
// Copyright (C) 2015, Alberto Corona <ac@albertocorona.com>
// All rights reserved. This file is part of core-utils, distributed under the
// GPL v3 license. For full terms please see the LICENSE file.

#![crate_type = "bin"]
#![crate_name = "ln"]
#![feature(path_ext,slice_splits)]

static UTIL: Prog = Prog { name: "ln", vers: "0.1.0", yr: "2015" };

extern crate pgetopts;
extern crate rpf;

use pgetopts::{Options};
use rpf::{AsPath,Status,Color,Styled,PathMod,Prog};

use std::env;
use std::fs;
#[cfg(target_family = "unix")]
use std::os;
use std::path::{PathBuf};
use std::fs::{PathExt};
#[cfg(target_family = "windows")]
use std::os::windows;

struct Opts {
    verbose: bool,
    force: bool,
    inter: bool,
}

// Unix family specific symbolic link function
#[cfg(target_family = "unix")]
fn create_sym_link(from: &PathBuf, to: &PathBuf, options: &Opts) {
    match os::unix::fs::symlink(from, to) {
        Ok(_) => {
            if options.verbose {
                println!("{} {} {}", from.as_str().bold(),
                "->".paint(Color::Green), to.as_str().bold());
            }
        },
        Err(e) => {
            Status::Error.err(UTIL.name, e.to_string());
        }
    };
}

// Windows specific symbolic link function
#[cfg(target_family = "windows")]
fn create_sym_link(from: &PathBuf, to: &PathBuf, options: &Opts) {
    if from.is_file() {
        match windows::fs::symlink_file(from, to) {
            Ok(_) => {
                if options.verbose {
                    println!("{} {} {}", from.as_str().bold(),
                    "->".paint(Color::Green), to.as_str().bold());
                }
            },
            Err(e) => {
                Status::Error.err(UTIL.name, e.to_string());
            }
        };
    } else if from.is_dir() {
        match windows::fs::symlink_dir(from, to) {
            Ok(_) => {
                if options.verbose {
                    println!("{} {} {}", from.as_str().bold(),
                    "->".paint(Color::Green), to.as_str().bold());
                }
            },
            Err(e) => {
                Status::Error.err(UTIL.name, e.to_string());
            }
        };
    }
}

// Creates a symbolic link for a list of paths
fn sym_link_files(files: Vec<String>, to: &PathBuf, options: &Opts) {
    for item in files.iter() {
        if !to.is_dir() {
            Status::Error.err(UTIL.name, String::from(String::from(
                        to.as_str()) + " is not a directory"));
        } else {
            soft_link(&item.as_pathbuf(), &to.join(item.as_pathbuf()), options);
        }
    }
}

fn hard_link_files(files: Vec<String>, to: &PathBuf, options: &Opts) {
    for item in files.iter() {
        if !to.is_dir() {
            Status::Error.err(UTIL.name, String::from(String::from(
                        to.as_str()) + " is not a directory"));
        } else {
            let to_path = item.as_pathbuf();
            hard_link(&to_path, &to.join(&to_path), options);
        }
    }
}

// Calls platform specific symbolic link functions
fn soft_link(from: &PathBuf, to: &PathBuf, options: &Opts) {
    if !from.exists() {
        Status::Error.err(UTIL.name, String::from("\'".to_string() +
                                                  from.as_str() +
                                                  "\' does not exist"));
    }
    if !options.force {
        create_sym_link(from, to, options);
    } else {
        fs::remove_file(to).ok();
        create_sym_link(from, to, options);
    }
}

fn hard_link(from: &PathBuf, to: &PathBuf, options: &Opts) {
    if !from.exists() {
        Status::Error.err(UTIL.name, String::from("\'".to_string() +
                                                  from.as_str() +
                                                  "\' does not exist"));
    }
    if !options.force {
        match fs::hard_link(from, to) {
            Ok(_) => {
                if options.verbose {
                    println!("{} {} {}", from.as_str().bold(),
                    "->".paint(Color::Green), to.as_str().bold());
                }
            },
            Err(e) => {
                Status::Error.err(UTIL.name, e.to_string());
            }
        };
    } else {
        if to.as_path().exists() {
            fs::remove_file(to).ok();
            match fs::hard_link(from, to) {
                Ok(_) => { },
                Err(e) => {
                    Status::Error.err(UTIL.name, e.to_string());
                }
            };
        }
    }
}

fn print_usage(opts: Options) {
    print!("{}: {} {} {} {}\n\
    Create links between files", "Usage".bold(), UTIL.name.bold(),
    "[OPTION]".underline(), "TARGET".underline(), "LINK_NAME".underline());
    println!("{}", opts.options());
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();

    opts.optflag("f", "force", "Force operation. Removes existsing target files");
    opts.optflag("h", "help", "Print help information");
    opts.optflag("p", "physical", "Create a hard link");
    opts.optflag("s", "soft", "Create a symbolic link");
    opts.optflag("v", "verbose", "Verbose print the operation");
    opts.optflag("", "version", "Print version information");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m },
        Err(f) => {
            Status::OptError.err(UTIL.name, f.to_string());
            panic!(f.to_string());
        }
    };

    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 options = &Opts {
            verbose: matches.opt_present("v"),
            force: matches.opt_present("f"),
            inter: false,
        };
        let src = match matches.free.first() {
            Some(s) => { s.as_pathbuf() },
            None => {
                UTIL.prog_try();
                Status::OptError.exit();
                panic!();
            },
        };
        let dest = matches.free.last().unwrap().as_pathbuf();
        if matches.free.len() == 1 {
            println!("{}: No link name for {}", UTIL.name, src.display());
            Status::ArgError.exit();
        }
        let files = matches.free.split_last().unwrap().1.to_vec();
        if matches.opt_present("s") {
            if matches.free.len() > 2 {
                sym_link_files(files, &dest, options);
            } else {
                soft_link(&src, &matches.free[1].as_pathbuf(), options);
            }
        } else if matches.opt_present("p") {
            if matches.free.len() > 2 {
                hard_link_files(files, &dest, options);
            } else {
                hard_link(&src, &matches.free[1].as_pathbuf(), options);
            }
        }
    }
}