Smartdown
Smartdown is an external DSL for representing a series of questions and logical rules which determine the order in which the questions are asked, according to user input.
The language is designed to look like Markdown, with some extensions to allow expression of logical rules, questions and conditional blocks of text.
Overview
A single smartdown flow has a cover sheet, a set of questions, a set of outcomes and a set of test scenarios. Cover sheets, questions and outcomes are all types of node. A node represents a single user interaction (normally a web page, but in other media may be presented differently).
Each question and outcome is held in a separate file. The name of the files are used to identify each question. Here's an example of the check-uk-visa flow:
-- check-uk-visa
|-- outcomes
| |-- general_y.txt
| |-- joining_family_m.txt
| |-- joining_family_y.txt
| |-- marriage.txt
| |-- medical_n.txt
| |-- medical_y.txt
| `-- ...
|-- scenarios
| |-- 1.txt
| |-- 2.txt
| `-- 3.txt
|-- questions
| |-- planning_to_leave_airport.txt
| |-- purpose_of_visit.txt
| |-- staying_for_how_long.txt
| |-- what_passport_do_you_have.txt
`-- check-uk-visa.txt
General node file syntax
Each file has three parts: front-matter, a model definition, rules/logic. Only the model definition is required.
- front-matter defines metadata in the form
property: value
. Note: this does not support full YAML syntax. - the *model definition is a markdown-like block which defines a flow, question or outcome.
- rules/logic defines 'next node' transition rules or other logic/predicate definitions
Cover sheet node
The cover sheet starts the flow off, its filename should match the flow name, e.g. 'check-uk-visa.txt'.
It has initial 'front matter' which defines metadata for the flow. It then defines the copy for the cover sheet in markdown format. The h1 title is compulsory and used as the title for the smart answer.
A start button determines which question node is presented first.
meta_description: You may need a visa to come to the UK to visit, study or work.
satisfies_need: 100982
# Check if you need a UK visa
You may need a visa to come to the UK to visit, study or work.
[start_button: what_passport_do_you_have]
Question nodes
Question nodes follow the same standard structure outlined above.
Smartdown currently allows multiple questions to be defined per node, but this feature has only recently been introduced and may still change.
The next sections define the various question types available.
Note that at present only the 'choice' question type has been implemented. Unimplemented question types are marked with (tbd) in the heading. For these question types, consider this documentation to be a proposal of how they might work.
"Choice" questions (aka. radio buttons)
A choice question allows the user to select a single option from a list of choices.
## Will you pass through UK Border Control?
You might pass through UK Border Control even if you don't leave the airport -
eg your bags aren't checked through and you need to collect them before transferring
to your outbound flight.
[choice: uk_border_control]
* yes: Yes
* no: No
'Country' question
A 'country' question allows the user to select a country from a drop-down list.
## Where do you want to get married?
[country: marriage_country, countries: all_countries]
Where all_countries
is the name of a data-plugin method that will return a hash of
country slugs/names.
Date
## What is the baby’s due date?
[date: baby_due_date]
To control the range of years selected you can supply 2 optional arguments to date questions: from
and to
.
These can take the form of absolute values, eg.
[date: baby_due_date, from: 2010, to: 2015]
Or relative values (from the current year), eg.
[date: baby_due_date, from: -4, to: 1]
The default values for from
and to
are relative years: -1
and 3
respectively.
Text
[text: text_value]
Asks for an arbitrary text input.
Salary
[salary: salary_value]
Aliases
An alias lets you referrer to any question identifier by its question intentifer or its alias.
## Are you Clark Kent?
[choice: clark_kent, alias: superman]
* yes: Yes
* no: No
Next steps
Markdown to be displayed as part of an outcome to direct the users to other information of potential interest to them.
[next_steps]
* Any kind of markdown
[A link](https://gov.uk/somewhere)
[end_next_steps]
Next node rules
Logical rules for transitioning to the next node are defined in 'Next node' section. This is declared using a markdown h1 'Next node'.
There are two constructs for defining rules:
# Next node
* predicate => outcome
defines a conditional transition
# Next node
* reddish?
* yellowish? => orange
* blueish? => purple
defines nested rules.
In the example above the node orange
would be selected if both reddish?
and yellowish?
were true.
Predicates
As well as 'named' predicates which might be defined by a plugin or other mechanism, there's also a basic expression language for predicates.
The currently supported operations are:
variable_name is 'string'
variable_name in {this that the-other}
Date comparison predicates (tbd)
date_variable_name >= '14/07/2014'
date_variable_name < '14/07/2014'
Logical connectives
There are operators that can be used to combine predicates, or invert their value. Namely NOT, OR and AND.
eg.
variable_name is 'string' OR NOT variable name is 'date'
OR
connectives join a sequence of predicates and will return true if
any of them evaluate to true, otherwise false.
AND
connectives join a sequence of predicates and will return true if
all of them evaluate to true, otherwise false.
NOT
connectives will invert the return value of a predicate. ie turn
true to false and vice versa. They have high precedence so bind to a single
predicate in chain eg in:
NOT variable_name is 'lovely name' OR variable_name is 'special name'
The implied parentheses around the experssion are:
(NOT variable_name is 'lovely name') OR variable_name is 'special name'
For more information on Logical Connectives see:
http://en.wikipedia.org/wiki/Logical_connective
Processing model
Each response to a question is assigned to a variable which corresponds to the question name (as determined by the filename).
Conditional blocks in outcomes
The syntax is:
$IF pred?
Text if true
more text if you like
$ENDIF
You can also have an else clause:
$IF pred?
Text if true
$ELSE
Text if false
$ENDIF
It's required to have a blank line between each if statement and the next paragraph of text, in other words this would be invalid:
$IF pred?
Text if true
$ENDIF
Similarly, it is also possible to specify an elseif clause. These can be chained together indefinitely. It is also possible to keep an else clause at the end like so:
$IF pred1?
Text if pred1 true
$ELSEIF pred2?
Text if pred1 false and pred2 true
$ELSEIF pred3?
Text if pred1 and pred2 false, and pred3 true
$ELSE
Text if pred1, pred2, pred3 are false
$ENDIF
It is also possible to nest if statements: like so.
$IF pred1?
$IF pred2?
Text if both true
$ENDIF
$ENDIF
Interpolation
It's possible to interpolate values from calculations, responses to questions, plugins etc.
Interpolations are currently supported into headings and paragraphs using the following syntax:
# State pension age calculation for %{name}
Your state pension age is %{state_pension_age}.
Snippets
Snippets work like partials, to re-use common text. They can be block or inline, can be called recursivelty and can contain interpolation and conditional logic
They're called like so:
## My header
Markdown copy..
{{snippet: my_snippet}}
More copy...
Where snippet_name
is in a snippets/
directory in the flow root with a .txt
extension, eg my-flow-name/snippets/my_snippet.txt
.
The contents of my_snippet
will be inserted into the outcome/question.
Snippet Organisation
You can organise related snippets into a sub-directory of arbitrary depth
For example:
## My header
Markdown copy..
{{snippet: my_sub_directory/my_snippet}}
More copy...
Where snippet_name
is in a snippets/
directory in the flow root with a .txt
extension, eg my-flow-name/snippets/my_sub_directory/my_snippet.txt
.
Scenarios
Scenarios are meant to be run as test to check that a Smartdown flow behaves in a certain way given some input data.
Scenario files
There can be as many scenario files as one wishes, with no restriction on name. Each scenario file should contain scenarios written as documented below.
Format
Each scenario is made of:
- a description (optional)
- list of questions pages (each question page starts with a -), inside which questions to answers are defined
- name of the outcome
# Description
- name_of_q1_p1: answer_to_q1_p1
- name_of_q1_p2: answer_to_q1_p2
name_of_q2_p2: answer_to_q2_p2
outcome_the_result
# Descriptions
# can have several lines
- name_of_q1_p1: answer_to_q1_p1_2
- name_of_q1_p3: answer_to_q1_p3
name_of_q2_p3: answer_to_q2_p3
outcome_the_other_result
Code terminology
Answers vs responses
The words 'answers' and 'responses' are used for various variable names and method names throughout the gem. Both are used to describe an answer to a question, but indicate two different formats:
response
is used for raw string inputsanswer
is used for Model::Answer objects
Software design
The initial plan for software design can be seen in this diagram: