From 56f4c1e2736e1097fc1ee104561c287214fd6749 Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Fri, 20 Dec 2013 14:34:45 -0500 Subject: [PATCH 1/7] Implement base64, resolves issue #42 --- Makefile | 1 + README.md | 1 - base64/base64.rs | 172 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 base64/base64.rs diff --git a/Makefile b/Makefile index 9d8d17163..be34ac58c 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ RMFLAGS := # Output names EXES := \ + base64 \ cat \ dirname \ echo \ diff --git a/README.md b/README.md index 759b5d1cb..9de76c4a1 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ The steps above imply that, before starting to work on a utility, you should sea To do ----- -- base64 - basename - chcon - chgrp diff --git a/base64/base64.rs b/base64/base64.rs new file mode 100644 index 000000000..d27e8e84b --- /dev/null +++ b/base64/base64.rs @@ -0,0 +1,172 @@ +#[link(name="base64", vers="1.0.0", author="Jordy Dickinson")] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordy Dickinson + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern mod extra; + +use std::char; +use std::io::{File, stdin, stdout}; +use std::os; +use std::str; + +use extra::base64; +use extra::base64::{FromBase64, ToBase64}; + +fn main() { + let mut conf = Conf::new(os::args()); + + match conf.mode { + Decode => decode(&mut conf), + Encode => encode(&mut conf), + Help => help(&conf), + Version => version() + } +} + +fn decode(conf: &mut Conf) { + let mut to_decode = str::from_utf8_owned(conf.input_file.read_to_end()); + + to_decode = to_decode.replace("\n", ""); + + if conf.ignore_garbage { + let standard_chars = + bytes!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "abcdefghijklmnopqrstuvwxyz", + "0123456789+/").map(|b| char::from_u32(*b as u32).unwrap()); + + to_decode = to_decode + .trim_chars(&|c| !standard_chars.contains(&c)) + .to_owned(); + } + + match to_decode.from_base64() { + Ok(bytes) => { + let mut out = stdout(); + + out.write(bytes); + out.flush(); + } + Err(s) => fail!(s) + } +} + +fn encode(conf: &mut Conf) { + let b64_conf = base64::Config { + char_set: base64::Standard, + pad: true, + line_length: match conf.line_wrap { + 0 => None, + _ => Some(conf.line_wrap) + } + }; + let to_encode = conf.input_file.read_to_end(); + let mut encoded = to_encode.to_base64(b64_conf); + + // To my knowledge, RFC 3548 does not specify which line endings to use. It + // seems that rust's base64 algorithm uses CRLF as prescribed by RFC 2045. + // However, since GNU base64 outputs only LF (presumably because that is + // the standard UNIX line ending), we strip CRs from the output to maintain + // compatibility. + encoded = encoded.replace("\r", ""); + + println(encoded); +} + +fn help(conf: &Conf) { + println!("Usage: {:s} [OPTION]... [FILE]", conf.progname); + println(""); + println(conf.usage); + + let msg = ~"With no FILE, or when FILE is -, read standard input.\n\n" + + "The data are encoded as described for the base64 alphabet in RFC " + + "3548. When\ndecoding, the input may contain newlines in addition " + + "to the bytes of the formal\nbase64 alphabet. Use --ignore-garbage " + + "to attempt to recover from any other\nnon-alphabet bytes in the " + + "encoded stream."; + + println(msg); +} + +fn version() { + println("base64 1.0.0"); +} + +struct Conf { + progname: ~str, + usage: ~str, + mode: Mode, + ignore_garbage: bool, + line_wrap: uint, + input_file: ~Reader +} + +impl Conf { + fn new(args: &[~str]) -> Conf { + // The use statement is here rather than at the top of the file so that + // the reader is made directly aware that we're using getopts::groups, + // and not just getopts. Also some names are somewhat vague taken out + // of context (i.e., "usage"). + use extra::getopts::groups::{ + getopts, + optflag, + optopt, + usage + }; + + let opts = ~[ + optflag("d", "decode", "decode data"), + optflag("i", "ignore-garbage", + "when decoding, ignore non-alphabetic characters"), + optopt("w", "wrap", + "wrap encoded lines after COLS character (default 76, 0 to disable" + + " wrapping)", "COLS"), + optflag("h", "help", "display this help text and exit"), + optflag("V", "version", "output version information and exit") + ]; + // TODO: The user should get a clean error message in the case that + // unwrap() fails. + let matches = getopts(args.tail(), opts).unwrap(); + + Conf { + progname: args[0].clone(), + usage: usage("Base64 encode or decode FILE, or standard input, to " + + "standard output.", opts), + mode: if matches.opt_present("help") { + Help + } else if matches.opt_present("version") { + Version + } else if matches.opt_present("decode") { + Decode + } else { + Encode + }, + ignore_garbage: matches.opt_present("ignore-garbage"), + line_wrap: match matches.opt_str("wrap") { + Some(s) => from_str(s).unwrap(), + None => 76 + }, + input_file: if matches.free.is_empty() + || matches.free[0] == ~"-" { + ~stdin() as ~Reader + } else { + let path = Path::new(matches.free[0]); + ~File::open(&path) as ~Reader + } + } + } +} + +enum Mode { + Decode, + Encode, + Help, + Version +} + From d63c268239297c621653673601b6e565d9d0c9a0 Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Fri, 20 Dec 2013 16:15:27 -0500 Subject: [PATCH 2/7] base64: Improve error handling --- base64/base64.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/base64/base64.rs b/base64/base64.rs index d27e8e84b..af4628cb9 100644 --- a/base64/base64.rs +++ b/base64/base64.rs @@ -53,7 +53,10 @@ fn decode(conf: &mut Conf) { out.write(bytes); out.flush(); } - Err(s) => fail!(s) + Err(s) => { + error!("error: {:s}", s); + fail!() + } } } @@ -130,9 +133,13 @@ impl Conf { optflag("h", "help", "display this help text and exit"), optflag("V", "version", "output version information and exit") ]; - // TODO: The user should get a clean error message in the case that - // unwrap() fails. - let matches = getopts(args.tail(), opts).unwrap(); + let matches = match getopts(args.tail(), opts) { + Ok(m) => m, + Err(e) => { + error!("error: {:s}", e.to_err_msg()); + fail!() + } + }; Conf { progname: args[0].clone(), @@ -149,7 +156,15 @@ impl Conf { }, ignore_garbage: matches.opt_present("ignore-garbage"), line_wrap: match matches.opt_str("wrap") { - Some(s) => from_str(s).unwrap(), + Some(s) => match from_str(s) { + Some(s) => s, + None => { + error!("error: {:s}", + "Argument to option 'wrap' improperly " + + "formatted."); + fail!() + } + }, None => 76 }, input_file: if matches.free.is_empty() From 9cfb0645e842809817e100e9fe5e9f740b1bab0d Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Fri, 20 Dec 2013 22:27:49 -0500 Subject: [PATCH 3/7] Use line continuations instead of string addition I didn't know that line continuations stripped preceding whitespace --- base64/base64.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/base64/base64.rs b/base64/base64.rs index af4628cb9..686f2abf4 100644 --- a/base64/base64.rs +++ b/base64/base64.rs @@ -38,8 +38,8 @@ fn decode(conf: &mut Conf) { if conf.ignore_garbage { let standard_chars = bytes!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "abcdefghijklmnopqrstuvwxyz", - "0123456789+/").map(|b| char::from_u32(*b as u32).unwrap()); + "abcdefghijklmnopqrstuvwxyz", + "0123456789+/").map(|b| char::from_u32(*b as u32).unwrap()); to_decode = to_decode .trim_chars(&|c| !standard_chars.contains(&c)) @@ -87,12 +87,12 @@ fn help(conf: &Conf) { println(""); println(conf.usage); - let msg = ~"With no FILE, or when FILE is -, read standard input.\n\n" - + "The data are encoded as described for the base64 alphabet in RFC " - + "3548. When\ndecoding, the input may contain newlines in addition " - + "to the bytes of the formal\nbase64 alphabet. Use --ignore-garbage " - + "to attempt to recover from any other\nnon-alphabet bytes in the " - + "encoded stream."; + let msg = ~"With no FILE, or when FILE is -, read standard input.\n\n\ + The data are encoded as described for the base64 alphabet in RFC \ + 3548. When\ndecoding, the input may contain newlines in addition \ + to the bytes of the formal\nbase64 alphabet. Use --ignore-garbage \ + to attempt to recover from any other\nnon-alphabet bytes in the \ + encoded stream."; println(msg); } @@ -128,11 +128,11 @@ impl Conf { optflag("i", "ignore-garbage", "when decoding, ignore non-alphabetic characters"), optopt("w", "wrap", - "wrap encoded lines after COLS character (default 76, 0 to disable" - + " wrapping)", "COLS"), + "wrap encoded lines after COLS character (default 76, 0 to \ + disable wrapping)", "COLS"), optflag("h", "help", "display this help text and exit"), optflag("V", "version", "output version information and exit") - ]; + ]; let matches = match getopts(args.tail(), opts) { Ok(m) => m, Err(e) => { @@ -143,8 +143,8 @@ impl Conf { Conf { progname: args[0].clone(), - usage: usage("Base64 encode or decode FILE, or standard input, to " - + "standard output.", opts), + usage: usage("Base64 encode or decode FILE, or standard input, to \ + standard output.", opts), mode: if matches.opt_present("help") { Help } else if matches.opt_present("version") { @@ -159,15 +159,14 @@ impl Conf { Some(s) => match from_str(s) { Some(s) => s, None => { - error!("error: {:s}", - "Argument to option 'wrap' improperly " - + "formatted."); + error!("error: {:s}", "Argument to option 'wrap' \ + improperly formatted."); fail!() } }, None => 76 }, - input_file: if matches.free.is_empty() + input_file: if matches.free.is_empty() || matches.free[0] == ~"-" { ~stdin() as ~Reader } else { From 15e7bf7e641607952829be1b96f619b17516d1be Mon Sep 17 00:00:00 2001 From: Heather Date: Thu, 26 Dec 2013 14:49:31 +0400 Subject: [PATCH 4/7] link -> crate_id, link is deprecated --- cat/cat.rs | 2 +- dirname/dirname.rs | 2 +- echo/echo.rs | 2 +- env/env.rs | 2 +- false/false.rs | 2 +- printenv/printenv.rs | 2 +- pwd/pwd.rs | 2 +- rmdir/rmdir.rs | 2 +- true/true.rs | 2 +- wc/wc.rs | 2 +- whoami/whoami.rs | 2 +- yes/yes.rs | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cat/cat.rs b/cat/cat.rs index aa7f60c20..1a20937a0 100644 --- a/cat/cat.rs +++ b/cat/cat.rs @@ -1,4 +1,4 @@ -#[link(name="cat", vers="1.0.0", author="Seldaek")]; +#[crate_id(name="cat", vers="1.0.0", author="Seldaek")]; #[feature(managed_boxes)]; /* diff --git a/dirname/dirname.rs b/dirname/dirname.rs index f08d67cb5..8a8089d1e 100644 --- a/dirname/dirname.rs +++ b/dirname/dirname.rs @@ -1,4 +1,4 @@ -#[link(name="dirname", vers="1.0.0", author="Derek Chiang")]; +#[crate_id(name="dirname", vers="1.0.0", author="Derek Chiang")]; /* * This file is part of the uutils coreutils package. diff --git a/echo/echo.rs b/echo/echo.rs index 7bb22862c..ddc05ebae 100644 --- a/echo/echo.rs +++ b/echo/echo.rs @@ -1,4 +1,4 @@ -#[link(name="echo", vers="1.0.0", author="Derek Chiang")]; +#[crate_id(name="echo", vers="1.0.0", author="Derek Chiang")]; /* * This file is part of the uutils coreutils package. diff --git a/env/env.rs b/env/env.rs index 4d31620be..677daf65b 100644 --- a/env/env.rs +++ b/env/env.rs @@ -1,4 +1,4 @@ -#[link(name="env", vers="1.0.0", author="LeoTestard")]; +#[crate_id(name="env", vers="1.0.0", author="LeoTestard")]; /* * This file is part of the uutils coreutils package. diff --git a/false/false.rs b/false/false.rs index 12f63207c..9243cc272 100644 --- a/false/false.rs +++ b/false/false.rs @@ -1,4 +1,4 @@ -#[link(name="false", vers="1.0.0", author="Seldaek")]; +#[crate_id(name="false", vers="1.0.0", author="Seldaek")]; /* * This file is part of the uutils coreutils package. diff --git a/printenv/printenv.rs b/printenv/printenv.rs index b2e3fc1f5..f20e532b1 100644 --- a/printenv/printenv.rs +++ b/printenv/printenv.rs @@ -1,4 +1,4 @@ -#[link(name="printenv", vers="1.0.0", author="Seldaek")]; +#[crate_id(name="printenv", vers="1.0.0", author="Seldaek")]; /* * This file is part of the uutils coreutils package. diff --git a/pwd/pwd.rs b/pwd/pwd.rs index 7e2db9417..188ab72fd 100644 --- a/pwd/pwd.rs +++ b/pwd/pwd.rs @@ -1,4 +1,4 @@ -#[link(name="pwd", vers="1.0.0", author="Heather Cynede")]; +#[crate_id(name="pwd", vers="1.0.0", author="Heather Cynede")]; /* * This file is part of the uutils coreutils package. diff --git a/rmdir/rmdir.rs b/rmdir/rmdir.rs index ae299197c..d2e3f17dc 100644 --- a/rmdir/rmdir.rs +++ b/rmdir/rmdir.rs @@ -1,4 +1,4 @@ -#[link(name="rmdir", vers="1.0.0", author="Arcterus")]; +#[crate_id(name="rmdir", vers="1.0.0", author="Arcterus")]; /* * This file is part of the uutils coreutils package. diff --git a/true/true.rs b/true/true.rs index b2d785a02..3be40528c 100644 --- a/true/true.rs +++ b/true/true.rs @@ -1,4 +1,4 @@ -#[link(name="true", vers="1.0.0", author="Seldaek")]; +#[crate_id(name="true", vers="1.0.0", author="Seldaek")]; /* * This file is part of the uutils coreutils package. diff --git a/wc/wc.rs b/wc/wc.rs index ed346b61a..7595afe66 100644 --- a/wc/wc.rs +++ b/wc/wc.rs @@ -1,4 +1,4 @@ -#[link(name="wc", vers="1.0.0", author="Boden Garman")]; +#[crate_id(name="wc", vers="1.0.0", author="Boden Garman")]; /* * This file is part of the uutils coreutils package. diff --git a/whoami/whoami.rs b/whoami/whoami.rs index ff7275cdb..bb3840ef0 100644 --- a/whoami/whoami.rs +++ b/whoami/whoami.rs @@ -1,4 +1,4 @@ -#[link(name="whoami", version="1.0.0", author="KokaKiwi")]; +#[crate_id(name="whoami", version="1.0.0", author="KokaKiwi")]; /* * This file is part of the uutils coreutils package. diff --git a/yes/yes.rs b/yes/yes.rs index a494e1b42..17732eb21 100644 --- a/yes/yes.rs +++ b/yes/yes.rs @@ -1,4 +1,4 @@ -#[link(name="yes", vers="1.0.0", author="Seldaek")]; +#[crate_id(name="yes", vers="1.0.0", author="Seldaek")]; /* * This file is part of the uutils coreutils package. From af57ee71dbe5f3a9ba329b59adf26b32f8607df7 Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Thu, 26 Dec 2013 13:47:19 -0500 Subject: [PATCH 5/7] link -> crate_id; link is deprecated --- base64/base64.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base64/base64.rs b/base64/base64.rs index 686f2abf4..965e864b4 100644 --- a/base64/base64.rs +++ b/base64/base64.rs @@ -1,4 +1,4 @@ -#[link(name="base64", vers="1.0.0", author="Jordy Dickinson")] +#[crate_id = "base64#1.0.0"]; /* * This file is part of the uutils coreutils package. From a71df867bb4dadc33fec953529007dcc20c9ad39 Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Fri, 27 Dec 2013 11:27:41 -0500 Subject: [PATCH 6/7] Remove Conf struct; put option handling in main This is more consistent with the other utilities, and probably less confusing. --- base64/base64.rs | 160 ++++++++++++++++++++--------------------------- 1 file changed, 69 insertions(+), 91 deletions(-) diff --git a/base64/base64.rs b/base64/base64.rs index 965e864b4..bcd3caa60 100644 --- a/base64/base64.rs +++ b/base64/base64.rs @@ -16,26 +16,80 @@ use std::io::{File, stdin, stdout}; use std::os; use std::str; +use extra::getopts::groups::{ + getopts, + optflag, + optopt, + usage +}; use extra::base64; use extra::base64::{FromBase64, ToBase64}; fn main() { - let mut conf = Conf::new(os::args()); + let args = ~os::args(); + let opts = ~[ + optflag("d", "decode", "decode data"), + optflag("i", "ignore-garbage", + "when decoding, ignore non-alphabetic characters"), + optopt("w", "wrap", + "wrap encoded lines after COLS character (default 76, 0 to \ + disable wrapping)", "COLS"), + optflag("h", "help", "display this help text and exit"), + optflag("V", "version", "output version information and exit") + ]; + let matches = match getopts(args.tail(), opts) { + Ok(m) => m, + Err(e) => { + error!("error: {:s}", e.to_err_msg()); + fail!() + } + }; - match conf.mode { - Decode => decode(&mut conf), - Encode => encode(&mut conf), - Help => help(&conf), + let progname = args[0].clone(); + let usage = usage("Base64 encode or decode FILE, or standard input, to \ + standard output.", opts); + let mode = if matches.opt_present("help") { + Help + } else if matches.opt_present("version") { + Version + } else if matches.opt_present("decode") { + Decode + } else { + Encode + }; + let ignore_garbage = matches.opt_present("ignore-garbage"); + let line_wrap = match matches.opt_str("wrap") { + Some(s) => match from_str(s) { + Some(s) => s, + None => { + error!("error: {:s}", "Argument to option 'wrap' \ + improperly formatted."); + fail!() + } + }, + None => 76 + }; + let mut input = if matches.free.is_empty() || matches.free[0] == ~"-" { + ~stdin() as ~Reader + } else { + let path = Path::new(matches.free[0]); + ~File::open(&path) as ~Reader + }; + + match mode { + Decode => decode(input, ignore_garbage), + Encode => encode(input, line_wrap), + Help => help(progname, usage), Version => version() } } -fn decode(conf: &mut Conf) { - let mut to_decode = str::from_utf8_owned(conf.input_file.read_to_end()); +fn decode(input: &mut Reader, ignore_garbage: bool) { + let mut to_decode = str::from_utf8_owned(input.read_to_end()); to_decode = to_decode.replace("\n", ""); - if conf.ignore_garbage { + if ignore_garbage { let standard_chars = bytes!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz", @@ -60,16 +114,16 @@ fn decode(conf: &mut Conf) { } } -fn encode(conf: &mut Conf) { +fn encode(input: &mut Reader, line_wrap: uint) { let b64_conf = base64::Config { char_set: base64::Standard, pad: true, - line_length: match conf.line_wrap { + line_length: match line_wrap { 0 => None, - _ => Some(conf.line_wrap) + _ => Some(line_wrap) } }; - let to_encode = conf.input_file.read_to_end(); + let to_encode = input.read_to_end(); let mut encoded = to_encode.to_base64(b64_conf); // To my knowledge, RFC 3548 does not specify which line endings to use. It @@ -82,10 +136,10 @@ fn encode(conf: &mut Conf) { println(encoded); } -fn help(conf: &Conf) { - println!("Usage: {:s} [OPTION]... [FILE]", conf.progname); +fn help(progname: &str, usage: &str) { + println!("Usage: {:s} [OPTION]... [FILE]", progname); println(""); - println(conf.usage); + println(usage); let msg = ~"With no FILE, or when FILE is -, read standard input.\n\n\ The data are encoded as described for the base64 alphabet in RFC \ @@ -101,82 +155,6 @@ fn version() { println("base64 1.0.0"); } -struct Conf { - progname: ~str, - usage: ~str, - mode: Mode, - ignore_garbage: bool, - line_wrap: uint, - input_file: ~Reader -} - -impl Conf { - fn new(args: &[~str]) -> Conf { - // The use statement is here rather than at the top of the file so that - // the reader is made directly aware that we're using getopts::groups, - // and not just getopts. Also some names are somewhat vague taken out - // of context (i.e., "usage"). - use extra::getopts::groups::{ - getopts, - optflag, - optopt, - usage - }; - - let opts = ~[ - optflag("d", "decode", "decode data"), - optflag("i", "ignore-garbage", - "when decoding, ignore non-alphabetic characters"), - optopt("w", "wrap", - "wrap encoded lines after COLS character (default 76, 0 to \ - disable wrapping)", "COLS"), - optflag("h", "help", "display this help text and exit"), - optflag("V", "version", "output version information and exit") - ]; - let matches = match getopts(args.tail(), opts) { - Ok(m) => m, - Err(e) => { - error!("error: {:s}", e.to_err_msg()); - fail!() - } - }; - - Conf { - progname: args[0].clone(), - usage: usage("Base64 encode or decode FILE, or standard input, to \ - standard output.", opts), - mode: if matches.opt_present("help") { - Help - } else if matches.opt_present("version") { - Version - } else if matches.opt_present("decode") { - Decode - } else { - Encode - }, - ignore_garbage: matches.opt_present("ignore-garbage"), - line_wrap: match matches.opt_str("wrap") { - Some(s) => match from_str(s) { - Some(s) => s, - None => { - error!("error: {:s}", "Argument to option 'wrap' \ - improperly formatted."); - fail!() - } - }, - None => 76 - }, - input_file: if matches.free.is_empty() - || matches.free[0] == ~"-" { - ~stdin() as ~Reader - } else { - let path = Path::new(matches.free[0]); - ~File::open(&path) as ~Reader - } - } - } -} - enum Mode { Decode, Encode, From 54dc1e9aca6c2617f1c17eff071f2f6ff24120b4 Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Fri, 27 Dec 2013 17:01:55 -0500 Subject: [PATCH 7/7] Use same crate_id form as other utils --- base64/base64.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base64/base64.rs b/base64/base64.rs index bcd3caa60..6fed354e7 100644 --- a/base64/base64.rs +++ b/base64/base64.rs @@ -1,4 +1,4 @@ -#[crate_id = "base64#1.0.0"]; +#[crate_id(name="base64", vers="1.0.0", author="Jordy Dickinson")]; /* * This file is part of the uutils coreutils package.