smartdown 0.11.2 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,444 +1,131 @@
1
1
  # Smartdown [![Build Status](https://travis-ci.org/alphagov/smartdown.svg?branch=master)](https://travis-ci.org/alphagov/smartdown)
2
2
 
3
- Smartdown is an [external
4
- DSL](http://www.martinfowler.com/bliki/DomainSpecificLanguage.html) for
5
- representing a series of questions and logical rules which determine the order
6
- in which the questions are asked, according to user input.
3
+ Smartdown is a [custom formatting language](http://www.martinfowler.com/bliki/DomainSpecificLanguage.html) designed to generate HTML formatted questions. These questions can then be joined in a manner that articulates a full user journey.
7
4
 
8
- The language is designed to look like
9
- [Markdown](http://daringfireball.net/projects/markdown/), with some extensions
10
- to allow expression of logical rules, questions and conditional blocks of
11
- text.
5
+ Implementation details for each kind of question can be found in the [documentation directory](doc).
12
6
 
13
- ## Overview
14
-
15
- A single smartdown flow has a cover sheet, a set of questions, a set of
16
- outcomes and a set of test scenarios. Cover sheets, questions and outcomes are
17
- all types of node. A node represents a single user interaction (normally a web
18
- page, but in other media may be presented differently).
19
-
20
- Each question and outcome is held in a separate file. The name of the files
21
- are used to identify each question. Here's an example of the check-uk-visa
22
- flow:
23
-
24
- ```
25
- -- check-uk-visa
26
- |-- outcomes
27
- | |-- general_y.txt
28
- | |-- joining_family_m.txt
29
- | |-- joining_family_y.txt
30
- | |-- marriage.txt
31
- | |-- medical_n.txt
32
- | |-- medical_y.txt
33
- | `-- ...
34
- |-- scenarios
35
- | |-- 1.txt
36
- | |-- 2.txt
37
- | `-- 3.txt
38
- |-- questions
39
- | |-- planning_to_leave_airport.txt
40
- | |-- purpose_of_visit.txt
41
- | |-- staying_for_how_long.txt
42
- | |-- what_passport_do_you_have.txt
43
- `-- check-uk-visa.txt
44
- ```
45
-
46
- ## General node file syntax
47
-
48
- Each file has three parts: front-matter, a model definition, rules/logic. Only the model definition is required.
49
-
50
- * **front-matter** defines metadata in the form `property: value`. Note: this
51
- does not support full YAML syntax.
52
- * the **model definition* is a markdown-like block which defines a flow,
53
- question or outcome.
54
- * **rules/logic** defines 'next node' transition rules or other
55
- logic/predicate definitions
56
-
57
- ## Cover sheet node
58
-
59
- The cover sheet starts the flow off, its filename should match the flow name,
60
- e.g. 'check-uk-visa.txt'.
61
-
62
- It has initial 'front matter' which defines metadata for the flow. It then
63
- defines the copy for the cover sheet in markdown format. The h1 title is
64
- compulsory and used as the title for the smart answer.
65
-
66
- A start button determines which question node is presented first.
67
-
68
- ```
69
- meta_description: You may need a visa to come to the UK to visit, study or work.
70
- satisfies_need: 100982
71
-
72
- # Check if you need a UK visa
73
-
74
- You may need a visa to come to the UK to visit, study or work.
75
-
76
- [start_button: what_passport_do_you_have]
77
- ```
78
-
79
- ## Question nodes
80
-
81
- Question nodes follow the same standard structure outlined above.
82
-
83
- Smartdown currently allows multiple questions to be defined per node, but this
84
- feature has only [recently been introduced](CHANGELOG.md#010) and may still change.
85
-
86
- The next sections define the various question types available.
87
-
88
- Note that at present only the 'choice' question type has been implemented.
89
- Unimplemented question types are marked with **(tbd)** in the heading. For
90
- these question types, consider this documentation to be a proposal of how they
91
- might work.
92
-
93
- ### "Choice" questions (aka. radio buttons)
94
-
95
- A choice question allows the user to select a single option from a list of choices.
96
-
97
- ```markdown
98
- ## Will you pass through UK Border Control?
99
-
100
- You might pass through UK Border Control even if you don't leave the airport -
101
- eg your bags aren't checked through and you need to collect them before transferring
102
- to your outbound flight.
103
-
104
- [choice: uk_border_control]
105
- * yes: Yes
106
- * no: No
107
- ```
108
-
109
- ### 'Country' question
110
-
111
- A 'country' question allows the user to select a country from a drop-down list.
112
-
113
- ```markdown
114
- ## Where do you want to get married?
115
-
116
- [country: marriage_country, countries: all_countries]
117
- ```
118
-
119
- Where `all_countries` is the name of a data-plugin method that will return a hash of
120
- country slugs/names.
121
-
122
- ### Date
123
-
124
- ```markdown
125
- ## What is the baby’s due date?
126
-
127
- [date: baby_due_date]
128
- ```
129
-
130
- To control the range of years selected you can supply 2 optional arguments to date questions: `from` and `to`.
131
- These can take the form of absolute values, eg.
132
-
133
- ```markdown
134
- [date: baby_due_date, from: 2010, to: 2015]
135
- ```
136
-
137
- Or relative values (from the current year), eg.
138
-
139
- ```markdown
140
- [date: baby_due_date, from: -4, to: 1]
141
- ```
142
-
143
- The default values for `from` and `to` are relative years: `-1` and `3` respectively.
144
-
145
- ### Text
146
-
147
- ```markdown
148
- [text: text_value]
149
- ```
150
-
151
- Asks for an arbitrary text input.
152
-
153
- ### Salary
154
-
155
- ```markdown
156
- [salary: salary_value]
157
- ```
158
-
159
- ## Aliases
160
-
161
- An alias lets you referrer to any question identifier by its question intentifer or its alias.
162
-
163
- ```markdown
164
- ## Are you Clark Kent?
165
-
166
- [choice: clark_kent, alias: superman]
167
- * yes: Yes
168
- * no: No
169
- ```
170
-
171
- ## Next steps
172
-
173
- Markdown to be displayed as part of an outcome to direct the users to other information of potential interest to them.
174
-
175
- ```markdown
176
- [next_steps]
177
- * Any kind of markdown
178
- [A link](https://gov.uk/somewhere)
179
- [end_next_steps]
180
- ```
181
-
182
- ## Next node rules
183
-
184
- Logical rules for transitioning to the next node are defined in 'Next node' section. This is declared using a markdown h1 'Next node'.
185
-
186
- There are two constructs for defining rules:
187
-
188
- ```
189
- # Next node
190
-
191
- * predicate => outcome
192
- ```
193
-
194
- defines a conditional transition
195
-
196
- ```
197
- # Next node
198
-
199
- * reddish?
200
- * yellowish? => orange
201
- * blueish? => purple
202
- ```
203
-
204
- defines nested rules.
205
-
206
- In the example above the node `orange` would be selected if both `reddish?` and `yellowish?` were true.
207
-
208
- ## Predicates
209
-
210
- As well as 'named' predicates which might be defined by a plugin or other
211
- mechanism, there's also a basic expression language for predicates.
212
-
213
- The currently supported operations are:
214
-
215
- ```
216
- variable_name is 'string'
217
- variable_name in {this that the-other}
218
- ```
219
-
220
- ### Date comparison predicates (tbd)
221
-
222
- ```
223
- date_variable_name >= '14/07/2014'
224
- date_variable_name < '14/07/2014'
225
- ```
226
-
227
- ### Logical connectives
228
-
229
- There are operators that can be used to combine predicates, or invert
230
- their value. Namely NOT, OR and AND.
231
-
232
- eg.
233
-
234
- ```
235
- variable_name is 'string' OR NOT variable name is 'date'
236
- ```
237
-
238
- `OR` connectives join a sequence of predicates and will return true if
239
- any of them evaluate to true, otherwise false.
240
-
241
- `AND` connectives join a sequence of predicates and will return true if
242
- all of them evaluate to true, otherwise false.
243
-
244
- `NOT` connectives will invert the return value of a predicate. ie turn
245
- true to false and vice versa. They have high precedence so bind to a single
246
- predicate in chain eg in:
247
-
248
- ```
249
- NOT variable_name is 'lovely name' OR variable_name is 'special name'
250
- ```
251
-
252
- The implied parentheses around the experssion are:
253
-
254
- ```
255
- (NOT variable_name is 'lovely name') OR variable_name is 'special name'
256
- ```
257
-
258
- For more information on Logical Connectives see:
259
-
260
- http://en.wikipedia.org/wiki/Logical_connective
261
-
262
-
263
- ## Processing model
264
-
265
- Each response to a question is assigned to a variable which corresponds to the
266
- question name (as determined by the filename).
267
-
268
- ## Conditional blocks in outcomes
269
-
270
- The syntax is:
271
-
272
- ```markdown
273
-
274
- $IF pred?
275
-
276
- Text if true
277
-
278
- more text if you like
279
-
280
- $ENDIF
281
- ```
282
-
283
- You can also have an else clause:
284
-
285
- ```markdown
286
-
287
- $IF pred?
288
-
289
- Text if true
290
-
291
- $ELSE
292
-
293
- Text if false
294
-
295
- $ENDIF
296
- ```
297
-
298
- 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**:
299
-
300
- ```markdown
301
-
302
- $IF pred?
303
- Text if true
304
- $ENDIF
305
- ```
7
+ For example:
306
8
 
307
- Similarly, it is also possible to specify an elseif clause. These can be
308
- chained together indefinitely. It is also possible to keep an else
309
- clause at the end like so:
310
9
 
311
10
  ```markdown
312
- $IF pred1?
313
-
314
- Text if pred1 true
315
-
316
- $ELSEIF pred2?
317
-
318
- Text if pred1 false and pred2 true
319
-
320
- $ELSEIF pred3?
321
-
322
- Text if pred1 and pred2 false, and pred3 true
323
-
324
- $ELSE
325
-
326
- Text if pred1, pred2, pred3 are false
327
-
328
- $ENDIF
329
- ```
330
-
331
- It is also possible to nest if statements: like so.
11
+ # A Formatting and Logic Language
332
12
 
333
- ```markdown
13
+ Smartdown helps GOV.UK users find the information they need without
14
+ having to search through daunting official documentation.
334
15
 
335
- $IF pred1?
16
+ ## Some extra information you need to know before you start
336
17
 
337
- $IF pred2?
18
+ * Like bullet points
19
+ * Can be used
20
+ * Throughout this journey
338
21
 
339
- Text if both true
22
+ [start: step_1]
340
23
 
341
- $ENDIF
24
+ ## Additional context after a start button
342
25
 
343
- $ENDIF
26
+ Any more information down here
344
27
  ```
345
28
 
346
- ## Interpolation
29
+ Will produce:
347
30
 
348
- It's possible to interpolate values from calculations, responses to questions, plugins etc.
31
+ ```html
32
+ <div id="js-replaceable">
349
33
 
350
- Interpolations are currently supported into headings and paragraphs using the following syntax:
34
+ <header class="page-header group">
35
+ <div>
36
+ <h1>
37
+ A formatting and Logic Language
38
+ </h1>
39
+ </div>
40
+ </header>
351
41
 
352
- ```markdown
42
+ <div class="article-container group">
43
+ <article role="article" class="group">
44
+ <div class="inner">
45
+ <div class="intro">
46
+ <p>Smartdown helps GOV.UK users find the information they need
47
+ without having to search through daunting official
48
+ documentation.</p>
353
49
 
354
- # State pension age calculation for %{name}
50
+ <h2 id="some-extra-information-you-need-to-know-before-you-start">
51
+ Some extra information you need to know before you start
52
+ </h2>
355
53
 
356
- Your state pension age is %{state_pension_age}.
357
- ```
54
+ <ul>
55
+ <li>Like bullet points</li>
56
+ <li>Can be used</li>
57
+ <li>Throughout this journey</li>
58
+ </ul>
358
59
 
359
- ## Snippets
60
+ <p class="get-started">
61
+ <a rel="nofollow" href="/step-1/y" class="big button">
62
+ Start now
63
+ </a>
64
+ </p>
65
+ </div>
360
66
 
361
- Snippets work like partials, to re-use common text. They can be block or inline,
362
- can be called recursivelty and can contain interpolation and conditional logic
67
+ <h2 id="additional-context-after-a-start-button">
68
+ Additional context after a start button
69
+ </h2>
70
+ <p>Any more information down here</p>
363
71
 
364
- They're called like so:
72
+ </div>
73
+ </article>
74
+ </div>
365
75
 
76
+ </div>
366
77
  ```
367
- ## My header
368
-
369
- Markdown copy..
370
78
 
371
- {{snippet: my_snippet}}
79
+ Which on GOV.UK will look like:
372
80
 
373
- More copy...
374
- ```
81
+ ![](http://cl.ly/image/1V3e042P0s0h/Screen%20Shot%202014-12-03%20at%2017.50.55.png)
375
82
 
376
- Where `snippet_name` is in a `snippets/` directory in the flow root with a `.txt`
377
- extension, eg `my-flow-name/snippets/my_snippet.txt`.
378
83
 
379
- The contents of `my_snippet` will be inserted into the outcome/question.
380
-
381
- ###Snippet Organisation
382
-
383
- You can organise related snippets into a sub-directory of arbitrary depth
384
-
385
- For example:
386
-
387
- ```
388
- ## My header
84
+ The language is designed to look like [Markdown](http://daringfireball.net/projects/markdown/), but it has been extended to allow you to write in logic rules, questions and conditional blocks of text.
389
85
 
390
- Markdown copy..
391
-
392
- {{snippet: my_sub_directory/my_snippet}}
393
-
394
- More copy...
395
- ```
396
- 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`.
397
-
398
- ## Scenarios
86
+ ## Overview
399
87
 
400
- Scenarios are meant to be run as test to check that a Smartdown flow behaves
401
- in a certain way given some input data.
88
+ A single smartdown flow has a [Start Page](doc/start-pages.md), a set of [Questions](doc/questions.md),
89
+ [Outcomes](doc/outcomes.md) and a set of [Test Scenarios](doc/scenarios.md).
402
90
 
403
- ###Scenario files
91
+ Start Pages, Questions and Outcomes are all a type of 'node'.
92
+ A node represents a single user interaction (normally a web page, but in other media may be presented differently).
404
93
 
405
- There can be as many scenario files as one wishes, with no restriction on name. Each
406
- scenario file should contain scenarios written as documented below.
94
+ Each question and outcome is held in its own file. The name of the files are significant: they are used to identify each question.
407
95
 
408
- ###Format
409
96
 
410
- Each scenario is made of:
411
- * a description (optional)
412
- * list of questions pages (each question page starts with a -), inside which questions to answers are defined
413
- * name of the outcome
97
+ Here's an example of the check-uk-visa flow:
414
98
 
415
99
  ```
416
- # Description
417
- - name_of_q1_p1: answer_to_q1_p1
418
- - name_of_q1_p2: answer_to_q1_p2
419
- name_of_q2_p2: answer_to_q2_p2
420
- outcome_the_result
421
-
422
- # Descriptions
423
- # can have several lines
424
- - name_of_q1_p1: answer_to_q1_p1_2
425
- - name_of_q1_p3: answer_to_q1_p3
426
- name_of_q2_p3: answer_to_q2_p3
427
- outcome_the_other_result
100
+ -- check-uk-visa
101
+ |-- outcomes
102
+ | |-- general_y.txt
103
+ | |-- joining_family_m.txt
104
+ | |-- joining_family_y.txt
105
+ | |-- marriage.txt
106
+ | |-- medical_n.txt
107
+ | |-- medical_y.txt
108
+ | `-- ...
109
+ |-- scenarios
110
+ | |-- 1.txt
111
+ | |-- 2.txt
112
+ | `-- 3.txt
113
+ |-- questions
114
+ | |-- planning_to_leave_airport.txt
115
+ | |-- purpose_of_visit.txt
116
+ | |-- staying_for_how_long.txt
117
+ | |-- what_passport_do_you_have.txt
118
+ `-- check-uk-visa.txt
428
119
  ```
429
120
 
430
- ##Code terminology
431
-
432
- ####Answers vs responses
121
+ ## Wiki
433
122
 
434
- The words 'answers' and 'responses' are used for various variable names and method names throughout the gem.
435
- Both are used to describe an answer to a question, but indicate two different formats:
436
- * ```response``` is used for raw string inputs
437
- * ```answer``` is used for Model::Answer objects
123
+ Additional documentation and a [glossary of terms](https://github.com/alphagov/smartdown/wiki/Glossary) and concepts can be found in the [project wiki](https://github.com/alphagov/smartdown/wiki/) or in the [documentation folder](doc)
438
124
 
439
- ## Software design
125
+ ### Dependencies
440
126
 
441
- The initial plan for software design can be seen in this diagram:
127
+ Currently smartdown relies on the [Smart Answers](https://github.com/alphagov/smart-answers/) application to run.
442
128
 
443
- ![Software design](https://raw.githubusercontent.com/alphagov/smartdown/master/doc/design.png)
129
+ ### Running
444
130
 
131
+ For GOV.UK developers you can `bowl smartanswers` and navigate to an example flow such as http://smartanswers.dev.gov.uk/animal-example-multiple from within the GOV.UK VM.
@@ -0,0 +1,8 @@
1
+ require 'smartdown/api/question'
2
+
3
+ module Smartdown
4
+ module Api
5
+ class PostcodeQuestion < Question
6
+ end
7
+ end
8
+ end
@@ -18,6 +18,8 @@ module Smartdown
18
18
  @question = SalaryQuestion.new(elements)
19
19
  elsif element = elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Text}
20
20
  @question = TextQuestion.new(elements)
21
+ elsif element = elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Postcode}
22
+ @question = PostcodeQuestion.new(elements)
21
23
  end
22
24
  @answer = element.answer_type.new(response, element) if element
23
25
  end
@@ -3,6 +3,7 @@ require 'smartdown/api/date_question'
3
3
  require 'smartdown/api/country_question'
4
4
  require 'smartdown/api/salary_question'
5
5
  require 'smartdown/api/text_question'
6
+ require 'smartdown/api/postcode_question'
6
7
 
7
8
  module Smartdown
8
9
  module Api
@@ -21,6 +22,8 @@ module Smartdown
21
22
  Smartdown::Api::SalaryQuestion.new(question_element_group)
22
23
  elsif question_element_group.find{|element| element.is_a? Smartdown::Model::Element::Question::Text}
23
24
  Smartdown::Api::TextQuestion.new(question_element_group)
25
+ elsif question_element_group.find{|element| element.is_a? Smartdown::Model::Element::Question::Postcode}
26
+ Smartdown::Api::PostcodeQuestion.new(question_element_group)
24
27
  end
25
28
  end
26
29
  end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ require_relative "base"
3
+ require "uk_postcode"
4
+
5
+ module Smartdown
6
+ module Model
7
+ module Answer
8
+ class Postcode < Base
9
+
10
+ def value_type
11
+ ::String
12
+ end
13
+
14
+ def humanize
15
+ value
16
+ end
17
+
18
+ private
19
+ def parse_value(value)
20
+ postcode = UKPostcode.new(value)
21
+ if !postcode.valid?
22
+ @error = "Invalid postcode"
23
+ return
24
+ elsif !postcode.full?
25
+ @error = "Please enter a full postcode"
26
+ return
27
+ end
28
+ value
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ require 'smartdown/model/answer/postcode'
2
+
3
+ module Smartdown
4
+ module Model
5
+ module Element
6
+ module Question
7
+ class Postcode < Struct.new(:name, :alias)
8
+ def answer_type
9
+ Smartdown::Model::Answer::Postcode
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -16,7 +16,7 @@ module Smartdown
16
16
  end
17
17
 
18
18
  def outcomes
19
- read_dir("outcomes")
19
+ recursive_files_relatively_renamed("outcomes")
20
20
  end
21
21
 
22
22
  def scenario_sets
@@ -0,0 +1,20 @@
1
+ require 'smartdown/parser/question'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class PostcodeQuestion < Question
7
+
8
+ rule(:question_type) {
9
+ str("postcode")
10
+ }
11
+
12
+ rule(:postcode_question) {
13
+ question_tag.as(:postcode)
14
+ }
15
+
16
+ root(:postcode_question)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -7,20 +7,21 @@ require 'smartdown/parser/node_transform'
7
7
  module Smartdown
8
8
  module Parser
9
9
  class NodeInterpreter
10
- attr_reader :name, :source, :reporter
10
+ attr_reader :name, :source, :reporter, :data_module
11
11
 
12
12
  def initialize(name, source, options = {})
13
13
  @name = name
14
14
  @source = source
15
- data_module = options.fetch(:data_module, {})
15
+ @data_module = options.fetch(:data_module, {})
16
16
  @parser = options.fetch(:parser, Smartdown::Parser::NodeParser.new)
17
- @transform = options.fetch(:transform, Smartdown::Parser::NodeTransform.new(data_module))
17
+ @transform = options.fetch(:transform, Smartdown::Parser::NodeTransform.new)
18
18
  @reporter = options.fetch(:reporter, Parslet::ErrorReporter::Deepest.new)
19
19
  end
20
20
 
21
21
  def interpret
22
22
  transform.apply(parser.parse(source, reporter: reporter),
23
- node_name: name
23
+ node_name: name,
24
+ data_module: data_module,
24
25
  )
25
26
  end
26
27
 
@@ -7,6 +7,7 @@ require 'smartdown/parser/element/date_question'
7
7
  require 'smartdown/parser/element/salary_question'
8
8
  require 'smartdown/parser/element/text_question'
9
9
  require 'smartdown/parser/element/country_question'
10
+ require 'smartdown/parser/element/postcode_question'
10
11
  require 'smartdown/parser/element/markdown_heading'
11
12
  require 'smartdown/parser/element/markdown_paragraph'
12
13
  require 'smartdown/parser/element/conditional'
@@ -23,6 +24,7 @@ module Smartdown
23
24
  Element::SalaryQuestion.new |
24
25
  Element::TextQuestion.new |
25
26
  Element::CountryQuestion.new |
27
+ Element::PostcodeQuestion.new |
26
28
  Rules.new |
27
29
  Element::StartButton.new |
28
30
  Element::NextSteps.new |
@@ -10,6 +10,7 @@ require 'smartdown/model/element/question/country'
10
10
  require 'smartdown/model/element/question/date'
11
11
  require 'smartdown/model/element/question/salary'
12
12
  require 'smartdown/model/element/question/text'
13
+ require 'smartdown/model/element/question/postcode'
13
14
  require 'smartdown/model/element/start_button'
14
15
  require 'smartdown/model/element/markdown_heading'
15
16
  require 'smartdown/model/element/markdown_paragraph'
@@ -31,46 +32,6 @@ module Smartdown
31
32
  module Parser
32
33
  class NodeTransform < Parslet::Transform
33
34
 
34
- attr_reader :data_module
35
-
36
- def initialize data_module=nil, &block
37
- super(&block)
38
-
39
- @data_module = data_module || {}
40
- end
41
-
42
- # !!ALERT!! MONKEY PATCHING !!ALERT!!
43
- #
44
- # This call_on_match method is used for executing all the rule blocks you see
45
- # below. The only variables that are in scope for these blocks are the contents
46
- # of bindings - which consists of information about bits of the AST that the rule
47
- # matched.
48
- #
49
- # In the country rule: there is a need for accessing an external variable/method
50
- # as the information required to create a country question object is defined in
51
- # the data_module - so cannot be inferred purely from the syntax fed to parselet.
52
- #
53
- # There are 2 options we could have chosen, the first would be to have another
54
- # transformation layer. We would create intermediate elements that were lacking
55
- # information and then recreate them outside of parselet.
56
- #
57
- # A far simpler option is to manually modify the set of bindings available to
58
- # rule blocks, so we can inject our information from the data_module. Unfortunately
59
- # the only way to do this is to Monkey patch the call_on_match method to do the
60
- # to injecting. The drawbacks of this are if the method changes its name or function
61
- # in a newer parselet version; or possibly accidentally overriding some default
62
- # bindings with methods from a data module.
63
- #
64
- # Ideally we would like a way of injecting information into bindings without this
65
- # patch so we have submitted a PR to parselet describing this problem:
66
- #
67
- # https://github.com/kschiess/parslet/pull/119
68
- #
69
- def call_on_match(bindings, block)
70
- bindings.merge! data_module
71
- super(bindings, block)
72
- end
73
-
74
35
  rule(body: subtree(:body)) {
75
36
  Smartdown::Model::Node.new(
76
37
  node_name, body, Smartdown::Model::FrontMatter.new({})
@@ -120,7 +81,7 @@ module Smartdown
120
81
 
121
82
  rule(:country => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs)}) {
122
83
  country_data_method = Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('countries', nil)
123
- country_hash = (eval country_data_method).call
84
+ country_hash = data_module[country_data_method].call
124
85
  Smartdown::Model::Element::Question::Country.new(
125
86
  identifier.to_s,
126
87
  country_hash,
@@ -152,6 +113,13 @@ module Smartdown
152
113
  )
153
114
  }
154
115
 
116
+ rule(:postcode => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs)}) {
117
+ Smartdown::Model::Element::Question::Postcode.new(
118
+ identifier.to_s,
119
+ Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('alias', nil)
120
+ )
121
+ }
122
+
155
123
  rule(:next_steps => { content: simple(:content) }) {
156
124
  Smartdown::Model::Element::NextSteps.new(content.to_s)
157
125
  }
@@ -1,3 +1,3 @@
1
1
  module Smartdown
2
- VERSION = "0.11.2"
2
+ VERSION = "0.11.3"
3
3
  end
@@ -0,0 +1 @@
1
+ nested outcome
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ require 'smartdown/model/answer/postcode'
3
+
4
+ describe Smartdown::Model::Answer::Postcode do
5
+
6
+ let(:answer_string) { "WC2B 6SE" }
7
+ subject(:answer) { described_class.new(answer_string) }
8
+
9
+ describe "#humanize" do
10
+ it "declares itself in the initial format provided" do
11
+ expect(answer.humanize).to eql("WC2B 6SE")
12
+ end
13
+ end
14
+
15
+ describe "errors" do
16
+ context "partial postcodes are not allowed" do
17
+ let(:answer_string) { "WC2B" }
18
+ specify { expect(answer.error).to eql "Please enter a full postcode" }
19
+ end
20
+
21
+ context "invalid postcode" do
22
+ let(:answer_string) { "invalid" }
23
+ specify { expect(answer.error).to eql "Invalid postcode" }
24
+ end
25
+
26
+ context "question not answered" do
27
+ let(:answer_string) { nil }
28
+ specify { expect(answer.error).to eql "Please answer this question" }
29
+ end
30
+ end
31
+ end
@@ -33,10 +33,17 @@ describe Smartdown::Parser::DirectoryInput do
33
33
  end
34
34
 
35
35
  describe "#outcomes" do
36
- it "returns an InputFile for every file in the outcomes folder" do
37
- expect(input.outcomes).to match([instance_of(Smartdown::Parser::InputFile)])
38
- expect(input.outcomes.first.name).to eq("o1")
39
- expect(input.outcomes.first.read).to eq("outcome one\n")
36
+ it "returns an InputFile for every file in the outcomes folder to arbitrary sub directory depth" do
37
+ expect(input.outcomes).to match([
38
+ instance_of(Smartdown::Parser::InputFile),
39
+ instance_of(Smartdown::Parser::InputFile),
40
+ ])
41
+
42
+ expect(input.outcomes.map(&:name)).
43
+ to match_array(["o1", "nested/o1",])
44
+
45
+ expect(input.outcomes.map(&:read)).
46
+ to match_array(["outcome one\n", "nested outcome\n"])
40
47
  end
41
48
  end
42
49
 
@@ -49,16 +56,17 @@ describe Smartdown::Parser::DirectoryInput do
49
56
  end
50
57
 
51
58
  describe "#snippets" do
52
- it "returns an InputFile for every file, ending in .txt, in the snippets folder to arbitrary sub directory depth" do
59
+ it "returns an InputFile for every file in the snippets folder to arbitrary sub directory depth" do
53
60
  expect(input.snippets).to match([
54
61
  instance_of(Smartdown::Parser::InputFile),
55
62
  instance_of(Smartdown::Parser::InputFile),
56
63
  ])
57
- expect(input.snippets.map(&:name)).to include("sn1")
58
- expect(input.snippets.map(&:read)).to include("snippet one\n")
59
64
 
60
- expect(input.snippets.map(&:name)).to include("nested/nested_again/nsn1")
61
- expect(input.snippets.map(&:read)).to include("nested snippet\n")
65
+ expect(input.snippets.map(&:name)).
66
+ to match_array(["sn1", "nested/nested_again/nsn1"])
67
+
68
+ expect(input.snippets.map(&:read)).
69
+ to match_array(["snippet one\n", "nested snippet\n"])
62
70
  end
63
71
  end
64
72
  end
@@ -0,0 +1,57 @@
1
+ require 'smartdown/parser/node_parser'
2
+ require 'smartdown/parser/node_interpreter'
3
+ require 'smartdown/parser/element/postcode_question'
4
+
5
+ describe Smartdown::Parser::Element::PostcodeQuestion do
6
+ subject(:parser) { described_class.new }
7
+
8
+ context "with question tag" do
9
+ let(:source) { "[postcode: home]" }
10
+
11
+ it "parses" do
12
+ should parse(source).as(
13
+ postcode: {
14
+ identifier: "home",
15
+ option_pairs: [],
16
+ },
17
+ )
18
+ end
19
+
20
+ describe "transformed" do
21
+ let(:node_name) { "my_node" }
22
+ subject(:transformed) {
23
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
24
+ }
25
+
26
+ it { should eq(Smartdown::Model::Element::Question::Postcode.new("home")) }
27
+ end
28
+ end
29
+
30
+ context "with question tag and alias" do
31
+ let(:source) { "[postcode: home, alias: sweet_home]" }
32
+
33
+ it "parses" do
34
+ should parse(source).as(
35
+ postcode: {
36
+ identifier: "home",
37
+ option_pairs:[
38
+ {
39
+ key: 'alias',
40
+ value: 'sweet_home',
41
+ }
42
+ ]
43
+ }
44
+ )
45
+ end
46
+
47
+ describe "transformed" do
48
+ let(:node_name) { "my_node" }
49
+ subject(:transformed) {
50
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
51
+ }
52
+
53
+ it { should eq(Smartdown::Model::Element::Question::Postcode.new("home", "sweet_home")) }
54
+ end
55
+ end
56
+
57
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.11.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-04 00:00:00.000000000 Z
12
+ date: 2014-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parslet
16
- requirement: &22904640 !ruby/object:Gem::Requirement
16
+ requirement: &22331140 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,21 @@ dependencies:
21
21
  version: 1.6.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *22904640
24
+ version_requirements: *22331140
25
+ - !ruby/object:Gem::Dependency
26
+ name: uk_postcode
27
+ requirement: &22330540 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *22330540
25
36
  - !ruby/object:Gem::Dependency
26
37
  name: rspec
27
- requirement: &22903900 !ruby/object:Gem::Requirement
38
+ requirement: &22330040 !ruby/object:Gem::Requirement
28
39
  none: false
29
40
  requirements:
30
41
  - - ~>
@@ -32,10 +43,10 @@ dependencies:
32
43
  version: 3.0.0
33
44
  type: :development
34
45
  prerelease: false
35
- version_requirements: *22903900
46
+ version_requirements: *22330040
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: rake
38
- requirement: &22903440 !ruby/object:Gem::Requirement
49
+ requirement: &22329660 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
@@ -43,10 +54,10 @@ dependencies:
43
54
  version: '0'
44
55
  type: :development
45
56
  prerelease: false
46
- version_requirements: *22903440
57
+ version_requirements: *22329660
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: pry
49
- requirement: &22902800 !ruby/object:Gem::Requirement
60
+ requirement: &22329160 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: '0'
55
66
  type: :development
56
67
  prerelease: false
57
- version_requirements: *22902800
68
+ version_requirements: *22329160
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: gem_publisher
60
- requirement: &22902180 !ruby/object:Gem::Requirement
71
+ requirement: &22328660 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,10 +76,10 @@ dependencies:
65
76
  version: '0'
66
77
  type: :development
67
78
  prerelease: false
68
- version_requirements: *22902180
79
+ version_requirements: *22328660
69
80
  - !ruby/object:Gem::Dependency
70
81
  name: timecop
71
- requirement: &22901520 !ruby/object:Gem::Requirement
82
+ requirement: &22328220 !ruby/object:Gem::Requirement
72
83
  none: false
73
84
  requirements:
74
85
  - - ! '>='
@@ -76,7 +87,7 @@ dependencies:
76
87
  version: '0'
77
88
  type: :development
78
89
  prerelease: false
79
- version_requirements: *22901520
90
+ version_requirements: *22328220
80
91
  description:
81
92
  email: david.heath@digital.cabinet-office.gov.uk
82
93
  executables:
@@ -99,6 +110,7 @@ files:
99
110
  - lib/smartdown/parser/scenario_sets_interpreter.rb
100
111
  - lib/smartdown/parser/element/date_question.rb
101
112
  - lib/smartdown/parser/element/salary_question.rb
113
+ - lib/smartdown/parser/element/postcode_question.rb
102
114
  - lib/smartdown/parser/element/conditional.rb
103
115
  - lib/smartdown/parser/element/markdown_paragraph.rb
104
116
  - lib/smartdown/parser/element/start_button.rb
@@ -114,6 +126,7 @@ files:
114
126
  - lib/smartdown/api/coversheet.rb
115
127
  - lib/smartdown/api/question.rb
116
128
  - lib/smartdown/api/salary_question.rb
129
+ - lib/smartdown/api/postcode_question.rb
117
130
  - lib/smartdown/api/flow.rb
118
131
  - lib/smartdown/api/question_page.rb
119
132
  - lib/smartdown/api/state.rb
@@ -154,6 +167,7 @@ files:
154
167
  - lib/smartdown/model/answer/date.rb
155
168
  - lib/smartdown/model/answer/base.rb
156
169
  - lib/smartdown/model/answer/salary.rb
170
+ - lib/smartdown/model/answer/postcode.rb
157
171
  - lib/smartdown/model/answer/country.rb
158
172
  - lib/smartdown/model/answer/money.rb
159
173
  - lib/smartdown/model/answer/multiple_choice.rb
@@ -165,6 +179,7 @@ files:
165
179
  - lib/smartdown/model/element/markdown_heading.rb
166
180
  - lib/smartdown/model/element/question/date.rb
167
181
  - lib/smartdown/model/element/question/salary.rb
182
+ - lib/smartdown/model/element/question/postcode.rb
168
183
  - lib/smartdown/model/element/question/country.rb
169
184
  - lib/smartdown/model/element/question/multiple_choice.rb
170
185
  - lib/smartdown/model/element/question/text.rb
@@ -183,6 +198,7 @@ files:
183
198
  - spec/parser/input_set_spec.rb
184
199
  - spec/parser/rules_spec.rb
185
200
  - spec/parser/predicates_spec.rb
201
+ - spec/parser/element/postcode_question_spec.rb
186
202
  - spec/parser/element/next_steps_spec.rb
187
203
  - spec/parser/element/multiple_choice_question_spec.rb
188
204
  - spec/parser/element/country_question_spec.rb
@@ -211,6 +227,7 @@ files:
211
227
  - spec/engine/transition_spec.rb
212
228
  - spec/engine/conditional_resolver_spec.rb
213
229
  - spec/fixtures/directory_input/scenarios/s1.txt
230
+ - spec/fixtures/directory_input/outcomes/nested/o1.txt
214
231
  - spec/fixtures/directory_input/outcomes/o1.txt
215
232
  - spec/fixtures/directory_input/questions/q1.txt
216
233
  - spec/fixtures/directory_input/cover-sheet.txt
@@ -259,6 +276,7 @@ files:
259
276
  - spec/model/answer/base_spec.rb
260
277
  - spec/model/answer/salary_spec.rb
261
278
  - spec/model/answer/date_spec.rb
279
+ - spec/model/answer/postcode_spec.rb
262
280
  - spec/model/answer/country_spec.rb
263
281
  - spec/model/predicates/not_operation_spec.rb
264
282
  - spec/model/predicates/set_membership_spec.rb
@@ -283,7 +301,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
283
301
  version: '0'
284
302
  segments:
285
303
  - 0
286
- hash: 3333648473832850396
304
+ hash: -2697176231460023822
287
305
  required_rubygems_version: !ruby/object:Gem::Requirement
288
306
  none: false
289
307
  requirements:
@@ -292,7 +310,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
310
  version: '0'
293
311
  segments:
294
312
  - 0
295
- hash: 3333648473832850396
313
+ hash: -2697176231460023822
296
314
  requirements: []
297
315
  rubyforge_project:
298
316
  rubygems_version: 1.8.11
@@ -309,6 +327,7 @@ test_files:
309
327
  - spec/parser/input_set_spec.rb
310
328
  - spec/parser/rules_spec.rb
311
329
  - spec/parser/predicates_spec.rb
330
+ - spec/parser/element/postcode_question_spec.rb
312
331
  - spec/parser/element/next_steps_spec.rb
313
332
  - spec/parser/element/multiple_choice_question_spec.rb
314
333
  - spec/parser/element/country_question_spec.rb
@@ -337,6 +356,7 @@ test_files:
337
356
  - spec/engine/transition_spec.rb
338
357
  - spec/engine/conditional_resolver_spec.rb
339
358
  - spec/fixtures/directory_input/scenarios/s1.txt
359
+ - spec/fixtures/directory_input/outcomes/nested/o1.txt
340
360
  - spec/fixtures/directory_input/outcomes/o1.txt
341
361
  - spec/fixtures/directory_input/questions/q1.txt
342
362
  - spec/fixtures/directory_input/cover-sheet.txt
@@ -385,6 +405,7 @@ test_files:
385
405
  - spec/model/answer/base_spec.rb
386
406
  - spec/model/answer/salary_spec.rb
387
407
  - spec/model/answer/date_spec.rb
408
+ - spec/model/answer/postcode_spec.rb
388
409
  - spec/model/answer/country_spec.rb
389
410
  - spec/model/predicates/not_operation_spec.rb
390
411
  - spec/model/predicates/set_membership_spec.rb