--- title: Subplot and its implementation in Rust author: Lars Wirzenius for the Subplot project date: 2019-10-10 ... Subplot is a set of tools for **specifying** and **documenting** acceptance criteria for a system or project, and how they should be **verified**. **All stakeholders**, including customers and end-users, should understand these. It's not easy. Subplot can't do it alone. You still have to write text that everyone understands. https://subplot.liw.fi/ --- Once upon a time there was a king... ![](Mandarin.duck.arp.jpg) --- It all started with yarn in 2013 ![](Wool_Yarn_Rolls.jpg) --- 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 --- ~~~~markdown --- title: "**echo**(1) acceptance tests" ... # Introduction **echo**(1) is a Unix command line tool, which writes its command line arguments to the standard output. This is a simple acceptance test suite for the `/bin/echo` implementation. # No arguments Run `/bin/echo` without arguments. ```subplot when user runs echo without arguments then exit code is 0 and standard output contains a newline and standard error is empty ``` ~~~~ --- ![](echo.png) --- ![](arch.svg) --- 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. --- ~~~~yaml - when: user runs echo without arguments function: run_echo_without_args - when: user runs echo with arguments (?P.+) function: run_echo_with_args - then: exit code is (?P\d+) function: exit_code_is_zero - then: standard output contains a newline function: stdout_is_a_newline - then: standard output contains "(?P.*)" function: stdout_is_text - then: standard error is empty function: stderr_is_empty ~~~~ --- Currently only **docgen** exists, work on **codegen** will start soon.   > docgen = Pandoc + Pandoc filter, in Rust   Filter modifies document during typesetting to make document more useful for communicating acceptance criteria. Typesets scenario text, later maybe other things. Relies on the `pandoc` and `pandoc_ast` crates for typesetting, `structopt` for command line handling. Really quite simple for now. On crates.io as subplot, but very, very ALPHA level. --- ~~~~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. 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? * Can you help with improvements to website, documentation, code? * Would you be willing to try Subplot in your project? Expect a working, but primitive version by the end of the year. Patches are welcome, but more important right now is constructive feedback, especially from people who use Subplot, or might want to. --- Free software. GPL3+ mostly. Won't affect the licensing of the outputs. OK to use for developing proprietary systems, but Subplot itself will remain free. --- Thank you. This talk is licensed CC-BY-SA 4.0 (International). Copyright 2019 Lars Wirzenius. Images from Wikimedia Commons, used by permission (public domaind, CC0, or CC-BY-SA 4.0). ![](Money_cant_buy_happiness.png)