Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoseph Frazer <joseph.frazer@ryansg.com>2020-04-08 16:18:18 -0400
committerJoseph Frazer <joseph.frazer@ryansg.com>2020-04-09 09:46:46 -0400
commit2c587e2d9ddba48092838343ebe8ae5fa5cdc1a4 (patch)
tree9580e9437e99585ea1dd60fd6cacf426c776217d
parentbb0c7486720134c1d6e9404851d28beef6971629 (diff)
downloadtame-2c587e2d9ddba48092838343ebe8ae5fa5cdc1a4.tar.gz
tame-2c587e2d9ddba48092838343ebe8ae5fa5cdc1a4.tar.bz2
tame-2c587e2d9ddba48092838343ebe8ae5fa5cdc1a4.zip
[DEV-7147] Add "tamec" executable
Add a stub executable that will eventually become a full-featured TAME compiler. The first implementation will only copy the source file to an intermediary file that will be compiled by the XSLT compiler.
-rw-r--r--tamer/src/bin/tamec.rs317
-rw-r--r--tamer/tests/tamec.rs96
2 files changed, 413 insertions, 0 deletions
diff --git a/tamer/src/bin/tamec.rs b/tamer/src/bin/tamec.rs
new file mode 100644
index 0000000..3f8623c
--- /dev/null
+++ b/tamer/src/bin/tamec.rs
@@ -0,0 +1,317 @@
+// TAME compiler
+//
+// Copyright (C) 2014-2020 Ryan Specialty Group, LLC.
+//
+// This file is part of TAME.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! This is the TAME compiler. Its job is to take each of the source code
+//! files and produce a compiled file.
+
+extern crate tamer;
+
+use getopts::{Fail, Options};
+use std::env;
+use std::error::Error;
+use std::ffi::OsStr;
+use std::fs;
+use std::path::Path;
+
+/// Types of commands
+enum Command {
+ Compile(String, String, String),
+ Usage,
+}
+
+/// Entrypoint for the compiler
+pub fn main() -> Result<(), Box<dyn Error>> {
+ let args: Vec<String> = env::args().collect();
+ let program = &args[0];
+ let opts = get_opts();
+ let usage = opts.usage(&format!("Usage: {} [OPTIONS] INPUT", program));
+
+ match parse_options(opts, args) {
+ Ok(Command::Compile(input, _, output)) => {
+ let source = Path::new(&input);
+ if source.extension() != Some(OsStr::new("xml")) {
+ panic!("{}: file format not recognized", input);
+ }
+
+ let dest = Path::new(&output);
+ fs::copy(source, dest)?;
+ Ok(())
+ }
+ Ok(Command::Usage) => {
+ println!("{}", usage);
+ std::process::exit(exitcode::OK);
+ }
+ Err(e) => {
+ eprintln!("{}", e);
+ println!("{}", usage);
+ std::process::exit(exitcode::USAGE);
+ }
+ }
+}
+
+/// Get 'Options'
+///
+/// ```
+/// use getopts::Options;
+///
+/// let opts = get_opts();
+/// ```
+fn get_opts() -> Options {
+ let mut opts = Options::new();
+ opts.optopt("o", "output", "set output file name", "NAME");
+ opts.optopt("", "emit", "set output type", "xmlo");
+ opts.optflag("h", "help", "print this help menu");
+
+ opts
+}
+
+/// Option parser
+fn parse_options(opts: Options, args: Vec<String>) -> Result<Command, Fail> {
+ let matches = match opts.parse(&args[1..]) {
+ Ok(m) => m,
+ Err(f) => {
+ return Err(f);
+ }
+ };
+
+ if matches.opt_present("h") {
+ return Ok(Command::Usage);
+ }
+
+ let input = match matches.free.len() {
+ 0 => return Err(Fail::OptionMissing(String::from("INPUT"))),
+ 1 => matches.free[0].clone(),
+ _ => return Err(Fail::UnrecognizedOption(matches.free[1].clone())),
+ };
+
+ let emit = match matches.opt_str("emit") {
+ Some(m) => match &m[..] {
+ "xmlo" => m,
+ _ => {
+ return Err(Fail::ArgumentMissing(String::from("--emit xmlo")))
+ }
+ },
+ None => {
+ return Err(Fail::OptionMissing(String::from("--emit xmlo")));
+ }
+ };
+
+ let output = match matches.opt_str("o") {
+ Some(m) => m,
+ // we check that the extension is "xml" later
+ None => format!("{}o", input),
+ };
+
+ Ok(Command::Compile(input, emit, output))
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn parse_options_help() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![String::from("program"), String::from("-h")],
+ );
+
+ match result {
+ Ok(Command::Usage) => {}
+ _ => panic!("Long help option did not parse"),
+ }
+ }
+
+ #[test]
+ fn parse_options_help_long() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![String::from("program"), String::from("--help")],
+ );
+
+ match result {
+ Ok(Command::Usage) => {}
+ _ => panic!("Help option did not parse"),
+ }
+ }
+
+ #[test]
+ fn parse_options_invalid() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![String::from("program"), String::from("-q")],
+ );
+
+ match result {
+ Err(Fail::UnrecognizedOption(_)) => {}
+ _ => panic!("Invalid option not caught"),
+ }
+ }
+
+ #[test]
+ fn parse_options_missing_input() {
+ let opts = get_opts();
+ let result = parse_options(opts, vec![String::from("program")]);
+
+ match result {
+ Err(Fail::OptionMissing(message)) => {
+ assert_eq!("INPUT", message);
+ }
+ _ => panic!("Missing input not caught"),
+ }
+ }
+
+ #[test]
+ fn parse_options_missing_emit() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![String::from("program"), String::from("filename")],
+ );
+
+ match result {
+ Err(Fail::OptionMissing(message)) => {
+ assert_eq!("--emit xmlo", message);
+ }
+ _ => panic!("Missing emit not caught"),
+ }
+ }
+
+ #[test]
+ fn parse_options_invalid_emit() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("filename.xml"),
+ String::from("--emit"),
+ String::from("foo"),
+ ],
+ );
+
+ match result {
+ Err(Fail::ArgumentMissing(message)) => {
+ assert_eq!("--emit xmlo", message);
+ }
+ _ => panic!("Invalid emit not caught"),
+ }
+ }
+
+ #[test]
+ fn parse_options_too_many_args() {
+ let opts = get_opts();
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("foo"),
+ String::from("--emit"),
+ String::from("bar"),
+ String::from("baz"),
+ ],
+ );
+
+ match result {
+ Err(Fail::UnrecognizedOption(message)) => {
+ assert_eq!("baz", message);
+ }
+ _ => panic!("Extra option not caught"),
+ }
+ }
+
+ #[test]
+ fn parse_options_valid() {
+ let opts = get_opts();
+ let xmlo = String::from("xmlo");
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("foo.xml"),
+ String::from("--emit"),
+ xmlo,
+ ],
+ );
+
+ match result {
+ Ok(Command::Compile(infile, xmlo, outfile)) => {
+ assert_eq!("foo.xml", infile);
+ assert_eq!("foo.xmlo", outfile);
+ assert_eq!("xmlo", xmlo);
+ }
+ _ => panic!("Unexpected result"),
+ }
+ }
+
+ #[test]
+ fn parse_options_valid_custom_out() {
+ let opts = get_opts();
+ let xmlo = String::from("xmlo");
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("foo.xml"),
+ String::from("--emit"),
+ xmlo,
+ String::from("-o"),
+ String::from("foo.xmli"),
+ ],
+ );
+
+ match result {
+ Ok(Command::Compile(infile, xmlo, outfile)) => {
+ assert_eq!("foo.xml", infile);
+ assert_eq!("foo.xmli", outfile);
+ assert_eq!("xmlo", xmlo);
+ }
+ _ => panic!("Unexpected result"),
+ }
+ }
+
+ #[test]
+ fn parse_options_valid_custom_out_long() {
+ let opts = get_opts();
+ let xmlo = String::from("xmlo");
+ let result = parse_options(
+ opts,
+ vec![
+ String::from("program"),
+ String::from("foo.xml"),
+ String::from("--emit"),
+ xmlo,
+ String::from("--output"),
+ String::from("foo.xmli"),
+ ],
+ );
+
+ match result {
+ Ok(Command::Compile(infile, xmlo, outfile)) => {
+ assert_eq!("foo.xml", infile);
+ assert_eq!("foo.xmli", outfile);
+ assert_eq!("xmlo", xmlo);
+ }
+ _ => panic!("Unexpected result"),
+ }
+ }
+}
diff --git a/tamer/tests/tamec.rs b/tamer/tests/tamec.rs
new file mode 100644
index 0000000..4b8c358
--- /dev/null
+++ b/tamer/tests/tamec.rs
@@ -0,0 +1,96 @@
+// Copyright (C) 2014-2020 Ryan Specialty Group, LLC.
+//
+// This file is part of TAME.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use assert_cmd::prelude::*;
+use predicates::prelude::*;
+use std::process::Command;
+
+#[test]
+fn compile_invalid_argument() -> Result<(), Box<dyn std::error::Error>> {
+ let mut cmd = Command::cargo_bin("tamec")?;
+ cmd.arg("-q");
+ cmd.assert()
+ .failure()
+ .code(exitcode::USAGE)
+ .stderr(predicate::str::contains("Unrecognized option:"));
+
+ Ok(())
+}
+
+#[test]
+fn compile_missing_input_file() -> Result<(), Box<dyn std::error::Error>> {
+ let mut cmd = Command::cargo_bin("tamec")?;
+ cmd.arg("--emit").arg("xmlo");
+ cmd.assert()
+ .failure()
+ .code(exitcode::USAGE)
+ .stderr(predicate::str::contains("INPUT"));
+
+ Ok(())
+}
+
+#[test]
+fn compile_missing_emit() -> Result<(), Box<dyn std::error::Error>> {
+ let mut cmd = Command::cargo_bin("tamec")?;
+ cmd.arg("foobar");
+ cmd.assert()
+ .failure()
+ .code(exitcode::USAGE)
+ .stderr(predicate::str::contains("--emit xmlo"));
+
+ Ok(())
+}
+
+#[test]
+fn compile_invalid_emit() -> Result<(), Box<dyn std::error::Error>> {
+ let mut cmd = Command::cargo_bin("tamec")?;
+ cmd.arg("foobar");
+ cmd.arg("--emit").arg("foo");
+ cmd.assert()
+ .failure()
+ .code(exitcode::USAGE)
+ .stderr(predicate::str::contains("--emit xmlo"));
+
+ Ok(())
+}
+
+#[test]
+fn compile_input_file_does_not_exist() -> Result<(), Box<dyn std::error::Error>>
+{
+ let mut cmd = Command::cargo_bin("tamec")?;
+ cmd.arg("foobar.xml");
+ cmd.arg("--emit").arg("xmlo");
+ cmd.assert()
+ .failure()
+ .code(1)
+ .stderr(predicate::str::contains("No such file or directory"));
+
+ Ok(())
+}
+
+#[test]
+fn compile_input_file_wrong_extension() -> Result<(), Box<dyn std::error::Error>>
+{
+ let mut cmd = Command::cargo_bin("tamec")?;
+ cmd.arg("foobar.php");
+ cmd.arg("--emit").arg("xmlo");
+ cmd.assert().failure().stderr(predicate::str::contains(
+ "foobar.php: file format not recognized",
+ ));
+
+ Ok(())
+}