smartdown 0.11.2 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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