From 968483bdf6332b382dfc673e6e9545dabf68e765 Mon Sep 17 00:00:00 2001 From: Arcterus Date: Fri, 31 Jan 2014 21:18:57 -0800 Subject: [PATCH 1/7] Added truncate --- Makefile | 1 + truncate/truncate.rs | 235 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 truncate/truncate.rs diff --git a/Makefile b/Makefile index e7eaa95ae..caf18eea9 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ PROGS := \ seq \ tee \ true \ + truncate \ users \ wc \ whoami \ diff --git a/truncate/truncate.rs b/truncate/truncate.rs new file mode 100644 index 000000000..0de8cf42c --- /dev/null +++ b/truncate/truncate.rs @@ -0,0 +1,235 @@ +#[crate_id(name="truncate", vers="1.0.0", author="Arcterus")]; + +/* + * This file is part of the uutils coreutils package. + * + * (c) Arcterus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern mod extra; + +use std::io::{stderr, io_error, File, Open, ReadWrite, Writer, SeekEnd, SeekSet}; +use std::os; +use std::u64; +use extra::getopts::groups; + +#[deriving(Eq)] +enum TruncateMode { + Reference, + Extend, + Reduce, + AtMost, + AtLeast, + RoundDown, + RoundUp +} + +fn main() { + let args = os::args(); + let program = args[0].clone(); + + let opts = ~[ + groups::optflag("c", "no-create", "do not create files that do not exist"), + groups::optflag("o", "io-blocks", "treat SIZE as the number of I/O blocks of the file rather than bytes (NOT IMPLEMENTED)"), + groups::optopt("r", "reference", "base the size of each file on the size of RFILE", "RFILE"), + groups::optopt("s", "size", "set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified", "SIZE"), + groups::optflag("h", "help", "display this help and exit"), + groups::optflag("V", "version", "output version information and exit") + ]; + let matches = match groups::getopts(args.tail(), opts) { + Ok(m) => m, + Err(f) => { + writeln!(&mut stderr() as &mut Writer, "{}", f.to_err_msg()); + os::set_exit_status(1); + return + } + }; + + if matches.opt_present("help") { + println!("truncate 1.0.0"); + println!(""); + println!("Usage:"); + println!(" {0:s} [OPTION]... FILE...", program); + println!(""); + print!("{}", groups::usage("Shrink or extend the size of each file to the specified size.", opts)); + println!(""); + println!("SIZE is an integer with an optional prefix and optional unit."); + println!("The available units (K, M, G, T, P, E, Z, and Y) use the following format:"); + println!("\t'KB' => 1000 (kilobytes)"); + println!("\t'K' => 1024 (kibibytes)"); + println!("\t'MB' => 1000*1000 (megabytes)"); + println!("\t'M' => 1024*1024 (mebibytes)"); + println!("\t'GB' => 1000*1000*1000 (gigabytes)"); + println!("\t'G' => 1024*1024*1024 (gibibytes)"); + println!("SIZE may also be prefixed by one of the following to adjust the size of each"); + println!("file based on its current size:"); + println!("\t'+' => extend by"); + println!("\t'-' => reduce by"); + println!("\t'<' => at most"); + println!("\t'>' => at least"); + println!("\t'/' => round down to multiple of"); + println!("\t'%' => round up to multiple of"); + } else if matches.opt_present("version") { + println!("truncate 1.0.0"); + } else if matches.free.is_empty() { + writeln!(&mut stderr() as &mut Writer, "Missing an argument"); + writeln!(&mut stderr() as &mut Writer, + "For help, try '{0:s} --help'", program); + os::set_exit_status(1); + } else { + let no_create = matches.opt_present("no-create"); + let io_blocks = matches.opt_present("io-blocks"); + let reference = matches.opt_str("reference"); + let size = matches.opt_str("size"); + if reference.is_none() && size.is_none() { + writeln!(&mut stderr() as &mut Writer, "You must specify either --reference or --size."); + os::set_exit_status(1); + } else { + truncate(no_create, io_blocks, reference, size, matches.free); + } + } +} + +fn truncate(no_create: bool, io_blocks: bool, reference: Option<~str>, size: Option<~str>, filenames: ~[~str]) { + let (refsize, mode) = match reference { + Some(rfilename) => { + io_error::cond.trap(|err| { + writeln!(&mut stderr() as &mut Writer, "{}", err.to_str()); + os::set_exit_status(1); + }).inside(|| { + let mut rfile = File::open(&Path::new(rfilename.clone())); + rfile.seek(0, SeekEnd); + (rfile.tell(), Reference) + }) + } + None => { + match parse_size(size.unwrap()) { + Ok(szpair) => szpair, + Err(()) => return + } + } + }; + for filename in filenames.iter() { + let filename: &str = *filename; + let path = Path::new(filename); + if !path.exists() && !no_create { + io_error::cond.trap(|_| { + writeln!(&mut stderr() as &mut Writer, + "Failed to create the file '{}'", filename); + os::set_exit_status(1); + }).inside(|| { + File::create(&path); + }); + } + io_error::cond.trap(|err| { + writeln!(&mut stderr() as &mut Writer, "{}", err.to_str()); + }).inside(|| { + match File::open_mode(&path, Open, ReadWrite) { + Some(mut file) => { + file.seek(0, SeekEnd); + let fsize = file.tell(); + file.seek(0, SeekSet); + let tsize = match mode { + Reference => refsize, + Extend => fsize + refsize, + Reduce => fsize - refsize, + AtMost => if fsize > refsize { refsize } else { fsize }, + AtLeast => if fsize < refsize { refsize } else { fsize }, + RoundDown => fsize - fsize % refsize, + RoundUp => fsize + fsize % refsize + }; + file.truncate(tsize as i64); + } + None => { + writeln!(&mut stderr() as &mut Writer, + "Failed to open the file '{}'", filename); + os::set_exit_status(1); + } + } + }); + } +} + +fn parse_size(size: ~str) -> Result<(u64, TruncateMode), ()> { + let mut err = false; + let mode = match size.char_at(0) { + '+' => Extend, + '-' => Reduce, + '<' => AtMost, + '>' => AtLeast, + '/' => RoundDown, + '*' => RoundUp, + _ => Reference /* assume that the size is just a number */ + }; + let bytes = { + let mut slice = + if mode == Reference { + let size: &str = size; + size + } else { + size.slice_from(1) + }; + if slice.char_at(slice.len() - 1).is_alphabetic() { + slice = slice.slice_to(slice.len() - 1); + if slice.len() > 0 && slice.char_at(slice.len() - 1).is_alphabetic() { + slice = slice.slice_to(slice.len() - 1); + } + } + slice + }.bytes().to_owned_vec(); + let mut number = match u64::parse_bytes(bytes, 10) { + Some(num) => num, + None => { + writeln!(&mut stderr() as &mut Writer, + "'{}' is not a valid number.", size); + os::set_exit_status(1); + err = true; + 0 + } + }; + if !err && size.char_at(size.len() - 1).is_alphabetic() { + number *= match size.char_at(size.len() - 1) { + 'B' => match size.char_at(size.len() - 2) { + 'K' => 1000, + 'M' => 1000 * 1000, + 'G' => 1000 * 1000 * 1000, + 'T' => 1000 * 1000 * 1000 * 1000, + 'P' => 1000 * 1000 * 1000 * 1000 * 1000, + 'E' => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, + 'Z' => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, + 'Y' => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, + letter => { + writeln!(&mut stderr() as &mut Writer, + "'{}B' is not a valid suffix.", letter); + os::set_exit_status(1); + err = true; + 1 + } + }, + 'K' => 1024, + 'M' => 1024 * 1024, + 'G' => 1024 * 1024 * 1024, + 'T' => 1024 * 1024 * 1024 * 1024, + 'P' => 1024 * 1024 * 1024 * 1024 * 1024, + 'E' => 1024 * 1024 * 1024 * 1024 * 1024 * 1024, + 'Z' => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, + 'Y' => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, + letter => { + writeln!(&mut stderr() as &mut Writer, + "'{}' is not a valid suffix.", letter); + os::set_exit_status(1); + err = true; + 1 + } + }; + } + if err { + Err(()) + } else { + Ok((number, mode)) + } +} + From 4a7778caf14b1bea5d2825e89abdfb97e953cab6 Mon Sep 17 00:00:00 2001 From: Arcterus Date: Fri, 31 Jan 2014 21:26:57 -0800 Subject: [PATCH 2/7] Handle lowercase letters --- truncate/truncate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/truncate/truncate.rs b/truncate/truncate.rs index 0de8cf42c..ee1741347 100644 --- a/truncate/truncate.rs +++ b/truncate/truncate.rs @@ -191,8 +191,8 @@ fn parse_size(size: ~str) -> Result<(u64, TruncateMode), ()> { } }; if !err && size.char_at(size.len() - 1).is_alphabetic() { - number *= match size.char_at(size.len() - 1) { - 'B' => match size.char_at(size.len() - 2) { + number *= match size.char_at(size.len() - 1).to_ascii().to_upper().to_char() { + 'B' => match size.char_at(size.len() - 2).to_ascii().to_upper().to_char() { 'K' => 1000, 'M' => 1000 * 1000, 'G' => 1000 * 1000 * 1000, From 2001c5c5eedaa03383145a59c1777544d0d6bafd Mon Sep 17 00:00:00 2001 From: Arcterus Date: Fri, 31 Jan 2014 21:31:10 -0800 Subject: [PATCH 3/7] Properly support --no-create for truncate --- truncate/truncate.rs | 51 +++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/truncate/truncate.rs b/truncate/truncate.rs index ee1741347..0aa76a428 100644 --- a/truncate/truncate.rs +++ b/truncate/truncate.rs @@ -115,38 +115,31 @@ fn truncate(no_create: bool, io_blocks: bool, reference: Option<~str>, size: Opt for filename in filenames.iter() { let filename: &str = *filename; let path = Path::new(filename); - if !path.exists() && !no_create { - io_error::cond.trap(|_| { - writeln!(&mut stderr() as &mut Writer, - "Failed to create the file '{}'", filename); - os::set_exit_status(1); - }).inside(|| { - File::create(&path); - }); - } io_error::cond.trap(|err| { writeln!(&mut stderr() as &mut Writer, "{}", err.to_str()); }).inside(|| { - match File::open_mode(&path, Open, ReadWrite) { - Some(mut file) => { - file.seek(0, SeekEnd); - let fsize = file.tell(); - file.seek(0, SeekSet); - let tsize = match mode { - Reference => refsize, - Extend => fsize + refsize, - Reduce => fsize - refsize, - AtMost => if fsize > refsize { refsize } else { fsize }, - AtLeast => if fsize < refsize { refsize } else { fsize }, - RoundDown => fsize - fsize % refsize, - RoundUp => fsize + fsize % refsize - }; - file.truncate(tsize as i64); - } - None => { - writeln!(&mut stderr() as &mut Writer, - "Failed to open the file '{}'", filename); - os::set_exit_status(1); + if path.exists() || !no_create { + match File::open_mode(&path, Open, ReadWrite) { + Some(mut file) => { + file.seek(0, SeekEnd); + let fsize = file.tell(); + file.seek(0, SeekSet); + let tsize = match mode { + Reference => refsize, + Extend => fsize + refsize, + Reduce => fsize - refsize, + AtMost => if fsize > refsize { refsize } else { fsize }, + AtLeast => if fsize < refsize { refsize } else { fsize }, + RoundDown => fsize - fsize % refsize, + RoundUp => fsize + fsize % refsize + }; + file.truncate(tsize as i64); + } + None => { + writeln!(&mut stderr() as &mut Writer, + "Failed to open the file '{}'", filename); + os::set_exit_status(1); + } } } }); From 28f7f66c0247f46d1f4528e8709e5431929501d0 Mon Sep 17 00:00:00 2001 From: Arcterus Date: Fri, 31 Jan 2014 21:48:43 -0800 Subject: [PATCH 4/7] Removed truncate from the to-do list --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 97d72f3c8..c681d866f 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,6 @@ To do - timeout - touch - tr -- truncate - tsort - uname-arch - uname-uname From 9877f5de19b1e752cfc8fd9f85bcbc42ff4f6e2c Mon Sep 17 00:00:00 2001 From: Arcterus Date: Sat, 1 Feb 2014 10:51:20 -0800 Subject: [PATCH 5/7] Changed truncate to use multi-line println! --- truncate/truncate.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/truncate/truncate.rs b/truncate/truncate.rs index 0aa76a428..db6bd4c21 100644 --- a/truncate/truncate.rs +++ b/truncate/truncate.rs @@ -55,23 +55,24 @@ fn main() { println!(" {0:s} [OPTION]... FILE...", program); println!(""); print!("{}", groups::usage("Shrink or extend the size of each file to the specified size.", opts)); - println!(""); - println!("SIZE is an integer with an optional prefix and optional unit."); - println!("The available units (K, M, G, T, P, E, Z, and Y) use the following format:"); - println!("\t'KB' => 1000 (kilobytes)"); - println!("\t'K' => 1024 (kibibytes)"); - println!("\t'MB' => 1000*1000 (megabytes)"); - println!("\t'M' => 1024*1024 (mebibytes)"); - println!("\t'GB' => 1000*1000*1000 (gigabytes)"); - println!("\t'G' => 1024*1024*1024 (gibibytes)"); - println!("SIZE may also be prefixed by one of the following to adjust the size of each"); - println!("file based on its current size:"); - println!("\t'+' => extend by"); - println!("\t'-' => reduce by"); - println!("\t'<' => at most"); - println!("\t'>' => at least"); - println!("\t'/' => round down to multiple of"); - println!("\t'%' => round up to multiple of"); + print!(" +SIZE is an integer with an optional prefix and optional unit. +The available units (K, M, G, T, P, E, Z, and Y) use the following format: + 'KB' => 1000 (kilobytes) + 'K' => 1024 (kibibytes) + 'MB' => 1000*1000 (megabytes) + 'M' => 1024*1024 (mebibytes) + 'GB' => 1000*1000*1000 (gigabytes) + 'G' => 1024*1024*1024 (gibibytes) +SIZE may also be prefixed by one of the following to adjust the size of each +file based on its current size: + '+' => extend by + '-' => reduce by + '<' => at most + '>' => at least + '/' => round down to multiple of + '%' => round up to multiple of +"); } else if matches.opt_present("version") { println!("truncate 1.0.0"); } else if matches.free.is_empty() { From 810936ac4226dae4aab41626418a2e1f61e3b20b Mon Sep 17 00:00:00 2001 From: Arcterus Date: Mon, 3 Feb 2014 22:54:57 -0800 Subject: [PATCH 6/7] truncate: added simple tests --- Makefile | 1 + truncate/test.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 truncate/test.rs diff --git a/Makefile b/Makefile index caf18eea9..f03f3067e 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ TEST_PROGS := \ cat \ mkdir \ seq \ + truncate \ TEST ?= $(TEST_PROGS) diff --git a/truncate/test.rs b/truncate/test.rs new file mode 100644 index 000000000..2562ab758 --- /dev/null +++ b/truncate/test.rs @@ -0,0 +1,40 @@ +use std::{run, io}; + +static PROG: &'static str = "build/truncate"; +static TESTNAME: &'static str = "THISISARANDOMFILENAME"; + +fn make_file() -> io::File { + while Path::new(TESTNAME).exists() { io::timer::sleep(1000); } + match io::File::create(&Path::new(TESTNAME)) { + Some(f) => f, + None => fail!() + } +} + +#[test] +fn test_increase_file_size() { + let mut file = make_file(); + if !run::process_status(PROG, [~"-s", ~"+5K", TESTNAME.to_owned()]).unwrap().success() { + fail!(); + } + file.seek(0, io::SeekEnd); + if file.tell() != 5 * 1024 { + fail!(); + } + io::fs::unlink(&Path::new(TESTNAME)); +} + +#[test] +fn test_decrease_file_size() { + let mut file = make_file(); + file.write(bytes!("1234567890")); + if !run::process_status(PROG, [~"--size=-4", TESTNAME.to_owned()]).unwrap().success() { + fail!(); + } + file.seek(0, io::SeekEnd); + if file.tell() != 6 { + println!("{}", file.tell()); + fail!(); + } + io::fs::unlink(&Path::new(TESTNAME)); +} From 9197373843c63745a3b3fb6c68e2588396c6e00a Mon Sep 17 00:00:00 2001 From: Arcterus Date: Tue, 4 Feb 2014 20:17:36 -0800 Subject: [PATCH 7/7] Update for latest Rust --- truncate/test.rs | 10 ++--- truncate/truncate.rs | 101 ++++++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 39 deletions(-) diff --git a/truncate/test.rs b/truncate/test.rs index 2562ab758..712152176 100644 --- a/truncate/test.rs +++ b/truncate/test.rs @@ -6,8 +6,8 @@ static TESTNAME: &'static str = "THISISARANDOMFILENAME"; fn make_file() -> io::File { while Path::new(TESTNAME).exists() { io::timer::sleep(1000); } match io::File::create(&Path::new(TESTNAME)) { - Some(f) => f, - None => fail!() + Ok(f) => f, + Err(_) => fail!() } } @@ -18,7 +18,7 @@ fn test_increase_file_size() { fail!(); } file.seek(0, io::SeekEnd); - if file.tell() != 5 * 1024 { + if file.tell().unwrap() != 5 * 1024 { fail!(); } io::fs::unlink(&Path::new(TESTNAME)); @@ -32,8 +32,8 @@ fn test_decrease_file_size() { fail!(); } file.seek(0, io::SeekEnd); - if file.tell() != 6 { - println!("{}", file.tell()); + if file.tell().unwrap() != 6 { + println!("{}", file.tell()); fail!(); } io::fs::unlink(&Path::new(TESTNAME)); diff --git a/truncate/truncate.rs b/truncate/truncate.rs index db6bd4c21..2b698204f 100644 --- a/truncate/truncate.rs +++ b/truncate/truncate.rs @@ -9,13 +9,45 @@ * file that was distributed with this source code. */ +#[feature(macro_rules)]; + extern mod extra; -use std::io::{stderr, io_error, File, Open, ReadWrite, Writer, SeekEnd, SeekSet}; +use std::io::{stderr, File, Open, ReadWrite, Writer, SeekEnd, SeekSet}; use std::os; use std::u64; use extra::getopts::groups; +macro_rules! get_file_size( + ($file:ident, $action:expr) => ({ + match $file.seek(0, SeekEnd) { + Ok(_) => {} + Err(f) => { + writeln!(&mut stderr() as &mut Writer, "{}", f.to_str()); + os::set_exit_status(1); + $action + } + } + let size = match $file.tell() { + Ok(m) => m, + Err(f) => { + writeln!(&mut stderr() as &mut Writer, "{}", f.to_str()); + os::set_exit_status(1); + $action + } + }; + match $file.seek(0, SeekSet) { + Ok(_) => {} + Err(f) => { + writeln!(&mut stderr() as &mut Writer, "{}", f.to_str()); + os::set_exit_status(1); + $action + } + } + size + }) +) + #[deriving(Eq)] enum TruncateMode { Reference, @@ -97,14 +129,15 @@ file based on its current size: fn truncate(no_create: bool, io_blocks: bool, reference: Option<~str>, size: Option<~str>, filenames: ~[~str]) { let (refsize, mode) = match reference { Some(rfilename) => { - io_error::cond.trap(|err| { - writeln!(&mut stderr() as &mut Writer, "{}", err.to_str()); - os::set_exit_status(1); - }).inside(|| { - let mut rfile = File::open(&Path::new(rfilename.clone())); - rfile.seek(0, SeekEnd); - (rfile.tell(), Reference) - }) + let mut rfile = match File::open(&Path::new(rfilename.clone())) { + Ok(m) => m, + Err(f) => { + writeln!(&mut stderr() as &mut Writer, "{}", f.to_str()); + os::set_exit_status(1); + return + } + }; + (get_file_size!(rfile, return), Reference) } None => { match parse_size(size.unwrap()) { @@ -116,34 +149,34 @@ fn truncate(no_create: bool, io_blocks: bool, reference: Option<~str>, size: Opt for filename in filenames.iter() { let filename: &str = *filename; let path = Path::new(filename); - io_error::cond.trap(|err| { - writeln!(&mut stderr() as &mut Writer, "{}", err.to_str()); - }).inside(|| { - if path.exists() || !no_create { - match File::open_mode(&path, Open, ReadWrite) { - Some(mut file) => { - file.seek(0, SeekEnd); - let fsize = file.tell(); - file.seek(0, SeekSet); - let tsize = match mode { - Reference => refsize, - Extend => fsize + refsize, - Reduce => fsize - refsize, - AtMost => if fsize > refsize { refsize } else { fsize }, - AtLeast => if fsize < refsize { refsize } else { fsize }, - RoundDown => fsize - fsize % refsize, - RoundUp => fsize + fsize % refsize - }; - file.truncate(tsize as i64); - } - None => { - writeln!(&mut stderr() as &mut Writer, - "Failed to open the file '{}'", filename); - os::set_exit_status(1); + if path.exists() || !no_create { + match File::open_mode(&path, Open, ReadWrite) { + Ok(mut file) => { + let fsize = get_file_size!(file, continue); + let tsize = match mode { + Reference => refsize, + Extend => fsize + refsize, + Reduce => fsize - refsize, + AtMost => if fsize > refsize { refsize } else { fsize }, + AtLeast => if fsize < refsize { refsize } else { fsize }, + RoundDown => fsize - fsize % refsize, + RoundUp => fsize + fsize % refsize + }; + match file.truncate(tsize as i64) { + Ok(_) => {} + Err(f) => { + writeln!(&mut stderr() as &mut Writer, + "{}", f.to_str()); + os::set_exit_status(1); + } } } + Err(f) => { + writeln!(&mut stderr() as &mut Writer, "{}", f.to_str()); + os::set_exit_status(1); + } } - }); + } } }