diff --git a/Cargo.lock b/Cargo.lock index 30a19f2c138dd8b04c14cf46afe9b9d60eccffd2..590c3180e05d5116d8eab681013719a9669d09ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,7 +996,7 @@ dependencies = [ [[package]] name = "rusty-hektor" -version = "1.1.0" +version = "2.0.0" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "calamine 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 167cab327ee8ead394b03ebf85889f5103530fd1..0fa99913d8f4f91714bca0f69af023f231a26814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusty-hektor" -version = "1.1.0" +version = "2.0.0" authors = ["robinwilliam.hundt <robinwilliam.hundt@stud.uni-goettingen.de>"] license = "MIT OR Apache-2.0" description = "A tool to convert ILIAS exam output" diff --git a/README.md b/README.md index aede5e739f98a97c63adf1a8f70a30cb3489f7a7..01ecd98913d8a465792ee3117ae4f21b688ddddb 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ If `-o` is omitted, the output is printed to stdout. If you call the program with the paths to an `.xml` and `.xls` export, both will be read and compared for inconsistencies. +By default, the program parses the free text questions and submissions of the `.xml` input. Should you +wish to skip those questions simply call the program with the `--skip-text` flag. + ### Word List The anonymised names are generated by selecting random words from the EFF long list, intended for password generation. diff --git a/src/main.rs b/src/main.rs index e12583838884b4f8536c5d2bf9c90bac565c890c..1ae4d54a1087e136f54f8b22b1fc76413c929c64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::fs; use std::io::{self, Write}; use std::path::PathBuf; +use std::ops::Not; use chrono::{DateTime, Utc}; use env_logger::{Builder, Env}; @@ -35,6 +36,10 @@ struct Opt { #[structopt(short, long = "non-interactive")] non_interactive: bool, + /// Skip free text questions + #[structopt(long = "skip-text")] + skip_text: bool, + /// Where to store the anonymisation map file, if enabled #[structopt(short, long = "map-file", default_value = "anon.csv")] map_file_name: PathBuf, @@ -58,6 +63,8 @@ fn main() -> Result<(), Box<dyn Error>> { } let opt = Opt::from_args(); + let parse_text = opt.skip_text.not(); + let parsed_data: Result<Vec<ParsedData>, Box<dyn Error>> = opt .in_files .iter() @@ -72,9 +79,9 @@ fn main() -> Result<(), Box<dyn Error>> { )? { std::process::exit(1) } - Ok(XLS(XLSParser::parse(path)?)) + Ok(XLS(XLSParser::parse(path, parse_text)?)) } else if extension == "zip" { - Ok(XML(XMLParser::parse(path)?)) + Ok(XML(XMLParser::parse(path, parse_text)?)) } else { Err(ParserError::new(format!( "Unsupported filetype: {:?}", diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 95a6d7576b017b824f139832438487a55e17879d..e8ba93630fb51126dfdaedae3ce67b77228e735f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9,7 +9,7 @@ pub mod xml_parser; type Result<T> = std::result::Result<T, ParserError>; pub trait Parser { - fn parse<'a>(path: &Path) -> result::Result<Exam, Box<dyn Error>>; + fn parse<'a>(path: &Path, parse_text_questions: bool) -> result::Result<Exam, Box<dyn Error>>; } #[derive(Debug)] diff --git a/src/parser/xls_parser.rs b/src/parser/xls_parser.rs index d5ab17bcc572c93e16bb35abaf6600d761592805..dbf7965ce0f26a3147d0bc0f8b6777c13bad64ed 100644 --- a/src/parser/xls_parser.rs +++ b/src/parser/xls_parser.rs @@ -28,7 +28,12 @@ const OVERVIEW_SHEET_NAME: &'static str = "Testergebnisse"; const SUBMISSION_IN_NEXT_ROW_MARKER: &'static str = "Quellcode Frage"; impl Parser for XLSParser { - fn parse(path: &Path) -> Result<Exam, Box<dyn Error>> { + fn parse(path: &Path, parse_text_questions: bool) -> Result<Exam, Box<dyn Error>> { + if parse_text_questions { + Err(ParserError::new( + "XLS Parser is deprecated and doesn't support parsing of text questions", + ))? + } let mut workbook: Xls<_> = match open_workbook(path) { Ok(workbook) => workbook, Err(xls_err) => Err(ParserError::from(xls_err))?, diff --git a/src/parser/xml_parser.rs b/src/parser/xml_parser.rs index 5c8d250cbe959a9af71eba3a724a6b27128cac15..f4eb9a704a8f1e7aa042bbae044dcebd3fc5ae70 100644 --- a/src/parser/xml_parser.rs +++ b/src/parser/xml_parser.rs @@ -86,12 +86,17 @@ impl XMLFiles { pub struct XMLParser {} impl Parser for XMLParser { - fn parse(path: &Path) -> result::Result<Exam, Box<dyn Error>> { + fn parse(path: &Path, parse_text_questions: bool) -> result::Result<Exam, Box<dyn Error>> { let file = File::open(path)?; let mut archive = ZipArchive::new(file)?; let xml_files = XMLFiles::new(&mut archive)?; - let sub_types = extract_submission_types(&xml_files, &["assSourceCode"])?; + let allowed_question_types: &[&str] = if parse_text_questions { + &["assSourceCode", "TEXT QUESTION"] + } else { + &["assSourceCode"] + }; + let sub_types = extract_submission_types(&xml_files, allowed_question_types)?; let students = extract_students(&xml_files, &sub_types)?; Ok(Exam { diff --git a/src/submission_type.rs b/src/submission_type.rs index 0c5472881b268afdaeffb254ec146817e8e28cd3..24b17f43b17215ad897804d15fd480448e460c05 100644 --- a/src/submission_type.rs +++ b/src/submission_type.rs @@ -79,11 +79,12 @@ pub enum ProgrammingLang { java, mipsasm, haskell, + text, } #[derive(Debug, Display)] #[display( - fmt = "Unparseable programming language: {}. Allowed: c, java, mipsasm, haskell", + fmt = "Unparseable programming language: {}. Allowed: c, java, mipsasm, haskell, text", input )] pub struct ParseProgrammingLangError { @@ -101,6 +102,7 @@ impl FromStr for ProgrammingLang { "java" => ProgrammingLang::java, "mipsasm" => ProgrammingLang::mipsasm, "haskell" => ProgrammingLang::haskell, + "text" => ProgrammingLang::text, _ => Err(ParseProgrammingLangError { input: s.to_owned(), })?, diff --git a/tests/test_xls_parser.rs b/tests/test_xls_parser.rs index 9d7375538f6dbc4f909ae4145126ec0aea9729e0..8fdf846f9bbb7d185af8fdfc633e4c6a4db49107 100644 --- a/tests/test_xls_parser.rs +++ b/tests/test_xls_parser.rs @@ -7,7 +7,7 @@ use std::path::Path; #[test] fn can_parse_xls_file() -> Result<(), Box<Error>> { - let parsed = XLSParser::parse(Path::new("tests/test_export.xls"))?; + let parsed = XLSParser::parse(Path::new("tests/test_export.xls"), false)?; let serializable = parsed.into_serializable()?; assert_eq!(3, serializable.students.len()); @@ -19,7 +19,7 @@ fn can_parse_xls_file() -> Result<(), Box<Error>> { #[test] fn parsed_xls_contains_coorect_submission_types() -> Result<(), Box<dyn Error>> { - let parsed = XLSParser::parse(Path::new("tests/test_export.xls"))?; + let parsed = XLSParser::parse(Path::new("tests/test_export.xls"), false)?; let serializable = parsed.into_serializable()?; let submission_type_names: HashSet<String> = serializable @@ -47,7 +47,7 @@ fn parsed_xls_contains_coorect_submission_types() -> Result<(), Box<dyn Error>> #[test] fn parsed_xls_contains_correct_students() -> Result<(), Box<dyn Error>> { - let parsed = XLSParser::parse(Path::new("tests/test_export.xls"))?; + let parsed = XLSParser::parse(Path::new("tests/test_export.xls"), false)?; let serializable = parsed.into_serializable()?; let students: BTreeSet<StudentSerializable> = serializable @@ -92,7 +92,7 @@ fn parsed_xls_contains_correct_students() -> Result<(), Box<dyn Error>> { fn correct_mapping_is_generated() -> Result<(), Box<dyn Error>> { use rusty_hektor::anonymizer; - let parsed = XLSParser::parse(Path::new("tests/test_export.xls"))?; + let parsed = XLSParser::parse(Path::new("tests/test_export.xls"), false)?; let mut serializable = parsed.into_serializable()?; let map = anonymizer::replace_students(&mut serializable.students); diff --git a/tests/test_xml_parser.rs b/tests/test_xml_parser.rs index 0735cae924c1da4e17e98e51e0ced821e32963a2..3980767ef6640be7a271eb5a03b13b8b0d421ddf 100644 --- a/tests/test_xml_parser.rs +++ b/tests/test_xml_parser.rs @@ -5,7 +5,7 @@ use rusty_hektor::parser::{xml_parser::XMLParser, Parser}; #[test] fn can_parse_zipped_xml_data() -> Result<(), Box<Error>> { - let parsed = XMLParser::parse(Path::new("tests/test.zip"))?; + let parsed = XMLParser::parse(Path::new("tests/test.zip"), false)?; let serializable = parsed.into_serializable()?; assert_eq!(1, serializable.submission_types.len()); assert_eq!(1, serializable.students.len());