From 69cb8da6ed1c1833ec6fa1ebcf59ff743e89e869 Mon Sep 17 00:00:00 2001 From: Virgile Andreani Date: Sat, 26 Jul 2014 00:34:45 +0200 Subject: [PATCH] Implement expand --- Cargo.toml | 4 ++ Makefile | 1 + README.md | 1 - src/expand/expand.rs | 168 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/expand/expand.rs diff --git a/Cargo.toml b/Cargo.toml index 8cb645c17..f30e6f22d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,10 @@ path = "echo/echo.rs" name = "env" path = "env/env.rs" +[[bin]] +name = "expand" +path = "expand/expand.rs" + [[bin]] name = "factor" path = "factor/factor.rs" diff --git a/Makefile b/Makefile index 1ef4a9bee..d73c49e4b 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ PROGS := \ echo \ env \ du \ + expand \ factor \ false \ fmt \ diff --git a/README.md b/README.md index 20f04ab5e..afb3d40e0 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,6 @@ To do - dd - df - dircolors -- expand (in progress) - expr - getlimits - install diff --git a/src/expand/expand.rs b/src/expand/expand.rs new file mode 100644 index 000000000..dbd90f14d --- /dev/null +++ b/src/expand/expand.rs @@ -0,0 +1,168 @@ +#![crate_name = "expand"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Virgile Andreani + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#![feature(macro_rules)] + +extern crate getopts; +extern crate libc; + +use std::io; +use std::from_str; + +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "expand"; +static VERSION: &'static str = "0.0.1"; + +static DEFAULT_TABSTOP: uint = 8; + +fn tabstops_parse(s: String) -> Vec { + let words = s.as_slice().split(',').collect::>(); + + let nums = words.move_iter() + .map(|sn| from_str::from_str::(sn) + .unwrap_or_else( + || crash!(1, "{}\n", "tab size contains invalid character(s)")) + ) + .collect::>(); + + if nums.iter().any(|&n| n == 0) { + crash!(1, "{}\n", "tab size cannot be 0"); + } + + match nums.iter().fold((true, 0), |(acc, last), &n| (acc && last <= n, n)) { + (false, _) => crash!(1, "{}\n", "tab sizes must be ascending"), + _ => {} + } + + nums +} + +struct Options { + files: Vec, + tabstops: Vec, + iflag: bool +} + +impl Options { + fn new(matches: getopts::Matches) -> Options { + let tabstops = match matches.opt_str("t") { + None => vec!(DEFAULT_TABSTOP), + Some(s) => tabstops_parse(s) + }; + + let iflag = matches.opt_present("i"); + + let files = + if matches.free.is_empty() { + vec!("-".to_string()) + } else { + matches.free + }; + + Options { files: files, tabstops: tabstops, iflag: iflag } + } +} + +pub fn uumain(args: Vec) -> int { + let opts = [ + getopts::optflag("i", "initial", "do not convert tabs after non blanks"), + getopts::optopt("t", "tabs", "have tabs NUMBER characters apart, not 8", "NUMBER"), + getopts::optopt("t", "tabs", "use comma separated list of explicit tab positions", "LIST"), + getopts::optflag("h", "help", "display this help and exit"), + getopts::optflag("V", "version", "output version information and exit"), + ]; + + let matches = match getopts::getopts(args.tail(), opts) { + Ok(m) => m, + Err(f) => crash!(1, "{}", f) + }; + + if matches.opt_present("help") { + println!("Usage: {:s} [OPTION]... [FILE]...", NAME); + io::print(getopts::usage( + "Convert tabs in each FILE to spaces, writing to standard output.\n\ + With no FILE, or when FILE is -, read standard input.", opts).as_slice()); + return 0; + } + + if matches.opt_present("V") { + println!("{} v{}", NAME, VERSION); + return 0; + } + + expand(Options::new(matches)); + + return 0; +} + +fn open(path: String) -> io::BufferedReader> { + let mut file_buf; + if path.as_slice() == "-" { + io::BufferedReader::new(box io::stdio::stdin_raw() as Box) + } else { + file_buf = match io::File::open(&Path::new(path.as_slice())) { + Ok(a) => a, + _ => crash!(1, "{}: {}\n", path, "No such file or directory") + }; + io::BufferedReader::new(box file_buf as Box) + } +} + +fn to_next_stop(tabstops: &[uint], col: uint) -> uint { + match tabstops.as_slice() { + [tabstop] => tabstop - col % tabstop, + tabstops => match tabstops.iter().skip_while(|&t| *t <= col).next() { + Some(&tabstop) => tabstop - col % tabstop, + None => 1 + } + } +} + +fn expand(options: Options) { + let mut output = io::stdout(); + + for file in options.files.move_iter() { + let mut col = 0; + let mut init = true; + for c in open(file).chars() { + match c { + Ok('\t') if init || !options.iflag => { + let nb_spaces = to_next_stop(options.tabstops.as_slice(), col); + col += nb_spaces; + safe_write!(output, "{:1$s}", "", nb_spaces); + } + Ok('\x08') => { + if col > 0 { + col -= 1; + } + init = false; + safe_write!(output, "{:c}", '\x08'); + } + Ok('\n') => { + col = 0; + init = true; + safe_write!(output, "{:c}", '\n'); + } + Ok(c) => { + col += 1; + if c != ' ' { + init = false; + } + safe_write!(output, "{:c}", c); + } + Err(_) => break + } + } + } +} +