diff --git a/Makefile b/Makefile index 387ecedc4..d48cdabaa 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ PROGS := \ echo \ env \ false \ + mkdir \ printenv \ pwd \ rm \ @@ -37,6 +38,7 @@ EXES := \ # Programs with usable tests TEST_PROGS := \ cat \ + mkdir \ TEST ?= $(TEST_PROGS) diff --git a/README.md b/README.md index 99141ebee..ec92f38b5 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,6 @@ To do - ls - make-prime-list - md5sum -- mkdir - mkfifo - mknod - mktemp diff --git a/cat/cat.rs b/cat/cat.rs index a53693b25..fea871e4d 100644 --- a/cat/cat.rs +++ b/cat/cat.rs @@ -86,6 +86,7 @@ pub enum NumberingMode { } static TAB: u8 = '\t' as u8; +#[allow(dead_code)] static CR: u8 = '\r' as u8; static LF: u8 = '\n' as u8; diff --git a/mkdir/mkdir.rs b/mkdir/mkdir.rs new file mode 100644 index 000000000..3dfd8488c --- /dev/null +++ b/mkdir/mkdir.rs @@ -0,0 +1,161 @@ +#[crate_id(name="mkdir", vers="1.0.0", author="Nicholas Juszczak")]; + +/** + * This file is part of the uutils coreutils package. + * + * (c) Nicholas Juszczak + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern mod extra; + +use std::os; +use std::io::{fs, result, stderr}; +use std::num::strconv; +use extra::getopts::groups; + +static VERSION: &'static str = "1.0.0"; + +/** + * Handles option parsing + */ +fn main() { + let args = os::args(); + + let opts = ~[ + // Linux-specific options, not implemented + // groups::optflag("Z", "context", "set SELinux secutiry context" + + // " of each created directory to CTX"), + groups::optopt("m", "mode", "set file mode", "755"), + groups::optflag("p", "parents", "make parent directories as needed"), + groups::optflag("v", "verbose", + "print a message for each printed directory"), + groups::optflag("h", "help", "display this help"), + groups::optflag("", "version", "display this version") + ]; + + let matches = match groups::getopts(args.tail(), opts) { + Ok(m) => m, + Err(f) => { + writeln!(&mut stderr() as &mut Writer, + "Invalid options\n{}", f.to_err_msg()); + os::set_exit_status(1); + return; + } + }; + + if args.len() == 1 || matches.opt_present("help") { + print_help(opts); + return; + } + if matches.opt_present("version") { + println("mkdir v" + VERSION); + return; + } + let verbose_flag = matches.opt_present("verbose"); + let mk_parents = matches.opt_present("parents"); + + // Translate a ~str in octal form to u32, default to 755 + // Not tested on Windows + let mode_match = matches.opts_str(&[~"mode"]); + let mode: u32 = if mode_match.is_some() { + let m = mode_match.unwrap(); + let res = strconv::from_str_common(m, 8, false, false, false, + strconv::ExpNone, + false, false); + if res.is_some() { + res.unwrap() + } else { + writeln!(&mut stderr() as &mut Writer, + "Error: no mode given"); + os::set_exit_status(1); + return; + } + } else { + 0o755 + }; + + let dirs = matches.free; + exec(dirs, mk_parents, mode, verbose_flag); +} + +fn print_help(opts: &[groups::OptGroup]) { + println!("mkdir v{} - make a new directory with the given path", VERSION); + println(""); + println("Usage:"); + print(groups::usage("Create the given DIRECTORY(ies)" + + " if they do not exist", opts)); +} + +/** + * Create the list of new directories + */ +fn exec(dirs: ~[~str], mk_parents: bool, mode: u32, verbose: bool) { + let mut parent_dirs: ~[~str] = ~[]; + if mk_parents { + for dir in dirs.iter() { + let path = Path::new((*dir).clone()); + // Build list of parent dirs which need to be created + let parent = path.dirname_str(); + match parent { + Some(p) => { + if !Path::new(p).exists() { + parent_dirs.push(p.into_owned()) + } + }, + None => () + } + } + } + // Recursively build parent dirs that are needed + if !parent_dirs.is_empty() { + exec(parent_dirs, mk_parents, mode, verbose); + } + + for dir in dirs.iter() { + let path = Path::new((*dir).clone()); + // Determine if parent directory to the one to + // be created exists + let parent = match path.dirname_str() { + Some(p) => p, + None => "" + }; + let parent_exists = Path::new(parent).exists(); + if parent_exists && !path.exists() { + // if mkdir failed return + if !mkdir(&path, mode) {return;} + if verbose {println(*dir);} + } else { + let mut error_msg = ~""; + if !parent_exists { + error_msg.push_str("Error: parent directory '"); + error_msg.push_str(parent); + error_msg.push_str("' does not exist"); + } else { + error_msg.push_str("Error: directory '"); + error_msg.push_str(*dir); + error_msg.push_str("' already exists"); + } + writeln!(&mut stderr() as &mut Writer, + "{}", error_msg); + os::set_exit_status(1); + } + } +} + +/** + * Wrapper to catch errors, return false if failed + */ +fn mkdir(path: &Path, mode: u32) -> bool { + match result(|| fs::mkdir(path, mode)) { + Ok(_) => true, + Err(e) => { + writeln!(&mut stderr() as &mut Writer, + "mkdir: test {}", e.to_str()); + os::set_exit_status(1); + false + } + } +} diff --git a/mkdir/test.rs b/mkdir/test.rs new file mode 100644 index 000000000..c3cf9c9ac --- /dev/null +++ b/mkdir/test.rs @@ -0,0 +1,68 @@ +use std::{run}; +use std::io::fs::rmdir; + +static exe: &'static str = "build/mkdir"; +static test_dir1: &'static str = "tmp/mkdir_test1"; +static test_dir2: &'static str = "tmp/mkdir_test2"; +static test_dir3: &'static str = "tmp/mkdir_test3"; +static test_dir4: &'static str = "tmp/mkdir_test4/mkdir_test4_1"; +static test_dir5: &'static str = "tmp/mkdir_test5/mkdir_test5_1"; + +fn cleanup(dir: &'static str) { + let d = dir.into_owned(); + let p = Path::new(d.into_owned()); + if p.exists() { + rmdir(&p); + } +} + +#[test] +fn test_mkdir_mkdir() { + cleanup(test_dir1); + let prog = run::process_status(exe.into_owned(), [test_dir1.into_owned()]); + let exit_success = prog.unwrap().success(); + cleanup(test_dir1); + assert_eq!(exit_success, true); +} + +#[test] +fn test_mkdir_dup_dir() { + cleanup(test_dir2); + let prog = run::process_status(exe.into_owned(), [test_dir2.into_owned()]); + let exit_success = prog.unwrap().success(); + if !exit_success { + cleanup(test_dir2); + fail!(); + } + let prog2 = run::process_status(exe.into_owned(), [test_dir2.into_owned()]); + let exit_success2 = prog2.unwrap().success(); + cleanup(test_dir2); + assert_eq!(exit_success2, false); +} + +#[test] +fn test_mkdir_mode() { + cleanup(test_dir3); + let prog = run::process_status(exe.into_owned(), [~"-m", ~"755", test_dir3.into_owned()]); + let exit_success = prog.unwrap().success(); + cleanup(test_dir3); + assert_eq!(exit_success, true); +} + +#[test] +fn test_mkdir_parent() { + cleanup(test_dir4); + let prog = run::process_status(exe.into_owned(), [~"-p", test_dir4.into_owned()]); + let exit_success = prog.unwrap().success(); + cleanup(test_dir4); + assert_eq!(exit_success, true); +} + +#[test] +fn test_mkdir_no_parent() { + cleanup(test_dir5); + let prog = run::process_status(exe.into_owned(), [test_dir5.into_owned()]); + let exit_success = prog.unwrap().success(); + cleanup(test_dir5); + assert_eq!(exit_success, false); +}