--- title: Subplot and its implementation in Rust author: Lars Wirzenius for the Subplot project date: 2019-10-10 ... https://subplot.liw.fi/ --- Subplot? Let me tell you a story... --- ![](arch.svg) --- Acceptance criteria are expressed as scenarios (a la Cucumber): _given_ a system in a specific start state _when_ user does some thing _then_ system state has changed in the intended way _and_ other aspect of state has stayed the same --- FIXME: Markdown example --- Currently only docgen exists, codegen will happen soon. Docgen = Pandoc + Pandoc filter, in Rust Filter modifies document during typesetting to make document more useful for communicating acceptance criteria. Changes how code blocks with scenario text are presented. --- FIXME: Bindings example --- Bindings connect the scenario step to the code that implements the step. Can extract parts of step to pass to the code as an argument. Essentially, scenario step is a function call. --- ~~~~rust use std::path::PathBuf; use structopt::StructOpt; #[derive(Debug, StructOpt)] #[structopt(name = "docgen", about = "...")] struct Opt { // One or more input filename. #[structopt(parse(from_os_str))] filenames: Vec, // Set output file name. #[structopt(short="-o", parse(from_os_str))] output: PathBuf, } // in main() let opt = Opt::from_args(); let output = format!("{}", opt.output.display()); ~~~~ --- ~~~~rust fn main() -> Result<(), Box> { let mut pandoc = pandoc::new(); ... pandoc.add_filter(|json| { pandoc_ast::filter(json, |mut doc| { MyVisitor.walk_pandoc(&mut doc); doc }) }); pandoc.execute()?; ... } ~~~~ --- ~~~~rust struct MyVisitor; impl MutVisitor for MyVisitor { fn visit_vec_block(&mut self, blks: &mut Vec) { for blk in blks { match blk { Block::CodeBlock(attr, s) => { if is_scenario(attr) { *blk = typeset_scenario(s) } } _ => { self.visit_block(blk); } } } } } ~~~~ --- ~~~~rust // Is a code block marked as a scenario snippet? fn is_scenario(attr: &Attr) -> bool { match attr { (_id, classes, _kvpairs) => classes.iter().any(|s| is_scenario_class(s)), } } fn is_scenario_class(class: &str) -> bool { class == "subplot" || class == "subplot-scenario" } fn typeset_scenario(text: &str) -> Block { Block::LineBlock( text.lines() .map(typeset_scenario_line) .collect()) } ~~~~ --- ~~~~rust fn typeset_scenario_line(line: &str) -> Vec { let mut words = line.split_whitespace(); let mut inlines = Vec::new(); if let Some(keyword) = words.next() { typeset_keyword(keyword, &mut inlines); inlines.push(typeset_str(" ")); } for word in words { inlines.push(typeset_str(word)); inlines.push(typeset_str(" ")); } inlines } ~~~~ --- ~~~~rust fn typeset_keyword(word: &str, inl: &mut Vec) { let word = typeset_str(word); let emph = Inline::Emph(vec![word]); inl.push(emph); } fn typeset_str(s: &str) -> Inline { Inline::Str(s.to_string()) } ~~~~ --- The Subplot project is at the very beginning (but Yarn was used for six years). Help is welcome. * What do you think of the concept? * What's good? * What's less good? * Can you see yourself using it? * What would it take for you to try it? * Do you see Subplot filling a gap for you? Expect a working version by the end of the year. Patches are welcome, but more important now is constructive feedback, especially from people who use Subplot, or want to. --- Free software. Won't affect the licensing of the outputs. OK to use for proprietary systmes. --- Thank you --- This talk is licensed CC-BY-SA 4.0 (International)