diff --git a/verifier/src/api.rs b/verifier/src/api.rs index 0aaabc4..9b63ebe 100644 --- a/verifier/src/api.rs +++ b/verifier/src/api.rs @@ -193,6 +193,12 @@ pub enum Question { description_text: String, ranking: RankingChoices, }, + #[serde(rename = "input_list")] + InputList { + question_text: String, + description_text: String, + input_list: InputListInputs, + }, } impl Question { @@ -203,6 +209,7 @@ impl Question { Self::ChoiceTable { question_text, .. } => question_text, Self::RatingScale { question_text, .. } => question_text, Self::Ranking { question_text, .. } => question_text, + Self::InputList { question_text, .. } => question_text, }) } @@ -220,7 +227,12 @@ impl Question { Self::RatingScale { description_text, .. } => description_text, - Self::Ranking { description_text, .. } => description_text, + Self::Ranking { + description_text, .. + } => description_text, + Self::InputList { + description_text, .. + } => description_text, }) } @@ -315,7 +327,7 @@ pub struct Settings { #[derive(Debug, Deserialize)] pub struct RankingChoices { - choices: Vec, + choices: Vec, } impl RankingChoices { @@ -334,10 +346,30 @@ impl RankingChoices { } #[derive(Debug, Deserialize)] -pub struct RankingChoice { +pub struct ChoiceWithLabel { label: String, } +#[derive(Debug, Deserialize)] +pub struct InputListInputs { + inputs: Vec, +} + +impl InputListInputs { + pub fn as_strs(&self) -> impl Iterator + '_ { + self.inputs + .iter() + .map(|c| normalize_surveyhero_text(c.label.as_str())) + } + + pub fn mismatched_answers<'a>(&'a self, answers: &'a [&str]) -> Vec<(String, &'a str)> { + self.as_strs() + .zip(answers.iter().map(|s| normalize_markdown_text(s))) + .filter(|(s1, s2)| s1 != s2) + .collect() + } +} + #[derive(Debug, Deserialize)] pub struct Surveys { pub surveys: Vec, diff --git a/verifier/src/main.rs b/verifier/src/main.rs index 8149a61..f19d2d9 100644 --- a/verifier/src/main.rs +++ b/verifier/src/main.rs @@ -1,4 +1,5 @@ use crate::api::Question; +use crate::markdown::Answers; use crate::render::render_questions; use anyhow::Context; use clap::Parser; @@ -200,6 +201,20 @@ impl markdown::Question<'_> { ); } } + (Answers::InputList(answers), Question::InputList { input_list, .. }) => { + let mismatched = input_list.mismatched_answers(&answers); + if !mismatched.is_empty() { + return Comparison::AnswersDiffer( + mismatched + .into_iter() + .map(|(s1, s2)| AnswerDiff { + sh: s1, + md: s2.to_string(), + }) + .collect(), + ); + } + } _ => { return Comparison::QuestionTypesDiffer { question: self.text.to_owned(), @@ -245,6 +260,7 @@ enum QuestionType { Matrix, RatingScale, Ranking, + InputList, } impl<'a> From<&'a Question> for QuestionType { @@ -262,6 +278,7 @@ impl<'a> From<&'a Question> for QuestionType { match q { Question::RatingScale { .. } => QuestionType::RatingScale, Question::Ranking { .. } => QuestionType::Ranking, + Question::InputList { .. } => QuestionType::InputList, _ => QuestionType::Matrix, } } @@ -276,6 +293,7 @@ impl From<&markdown::Question<'_>> for QuestionType { markdown::Answers::Matrix { .. } => Self::Matrix, markdown::Answers::RatingScale => Self::RatingScale, markdown::Answers::Ranking(_) => Self::Ranking, + markdown::Answers::InputList(_) => Self::InputList, } } } diff --git a/verifier/src/markdown.rs b/verifier/src/markdown.rs index 0dac7bc..c7a7bf4 100644 --- a/verifier/src/markdown.rs +++ b/verifier/src/markdown.rs @@ -59,6 +59,11 @@ pub fn parse(markdown: &str) -> anyhow::Result>> { text, answers: Answers::Ranking(vec![]), }) + } else if typ.starts_with("input list") { + ParserState::Question(Question { + text, + answers: Answers::InputList(vec![]), + }) } else { bail!("illegal question type: type='{}' question='{}'", typ, text); } @@ -81,6 +86,10 @@ pub fn parse(markdown: &str) -> anyhow::Result>> { | ParserState::Question(Question { answers: Answers::Ranking(ref mut a), .. + }) + | ParserState::Question(Question { + answers: Answers::InputList(ref mut a), + .. }) => a.push(trim_answer(line[1..].trim())), ParserState::Question(Question { answers: @@ -198,6 +207,7 @@ pub enum Answers<'a> { FreeForm, RatingScale, Ranking(Vec<&'a str>), + InputList(Vec<&'a str>), SelectOne(Vec<&'a str>), SelectMany(Vec<&'a str>), Matrix { @@ -213,6 +223,7 @@ impl Answers<'_> { Self::SelectOne(a) => a.is_empty(), Self::SelectMany(a) => a.is_empty(), Self::Ranking(a) => a.is_empty(), + Self::InputList(a) => a.is_empty(), Self::Matrix { answers1, answers2, .. } => answers1.is_empty() || answers2.is_empty(), diff --git a/verifier/src/render.rs b/verifier/src/render.rs index 9c1af16..e66c7b3 100644 --- a/verifier/src/render.rs +++ b/verifier/src/render.rs @@ -51,6 +51,12 @@ pub fn render_questions(questions: &[Question], file: &Path) -> io::Result<()> { writeln!(file, "- {variant}")?; } } + Question::InputList { input_list, .. } => { + writeln!(file, "Type: input list\n")?; + for input in input_list.as_strs() { + writeln!(file, "- {input}")?; + } + } } writeln!(file)?; }