smartdown 0.10.0 → 0.11.0
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 +50 -0
- data/lib/smartdown/api/node.rb +1 -5
- data/lib/smartdown/api/outcome.rb +1 -2
- data/lib/smartdown/api/question.rb +1 -5
- data/lib/smartdown/model/predicate/{combined.rb → and_operation.rb} +1 -1
- data/lib/smartdown/model/predicate/not_operation.rb +15 -0
- data/lib/smartdown/model/predicate/or_operation.rb +15 -0
- data/lib/smartdown/parser/base.rb +4 -3
- data/lib/smartdown/parser/element/conditional.rb +21 -10
- data/lib/smartdown/parser/node_transform.rb +40 -5
- data/lib/smartdown/parser/predicates.rb +34 -18
- data/lib/smartdown/version.rb +1 -1
- data/spec/model/predicates/{combined_spec.rb → and_operation_spec.rb} +2 -3
- data/spec/model/predicates/not_operation_spec.rb +27 -0
- data/spec/model/predicates/or_operation_spec.rb +46 -0
- data/spec/parser/base_spec.rb +1 -1
- data/spec/parser/element/conditional_spec.rb +64 -0
- data/spec/parser/node_parser_spec.rb +19 -0
- data/spec/parser/predicates_spec.rb +82 -12
- data/spec/support/model_builder.rb +1 -1
- metadata +22 -16
data/README.md
CHANGED
@@ -224,6 +224,42 @@ date_variable_name >= '14/07/2014'
|
|
224
224
|
date_variable_name < '14/07/2014'
|
225
225
|
```
|
226
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
|
+
|
227
263
|
## Processing model
|
228
264
|
|
229
265
|
Each response to a question is assigned to a variable which corresponds to the
|
@@ -292,6 +328,20 @@ Text if pred1, pred2, pred3 are false
|
|
292
328
|
$ENDIF
|
293
329
|
```
|
294
330
|
|
331
|
+
It is also possible to nest if statements: like so.
|
332
|
+
|
333
|
+
```markdown
|
334
|
+
|
335
|
+
$IF pred1?
|
336
|
+
|
337
|
+
$IF pred2?
|
338
|
+
|
339
|
+
Text if both true
|
340
|
+
|
341
|
+
$ENDIF
|
342
|
+
|
343
|
+
$ENDIF
|
344
|
+
```
|
295
345
|
|
296
346
|
## Interpolation
|
297
347
|
|
data/lib/smartdown/api/node.rb
CHANGED
@@ -50,11 +50,7 @@ module Smartdown
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def build_govspeak(elements)
|
53
|
-
|
54
|
-
markdown_element?(element)
|
55
|
-
end
|
56
|
-
govspeak = markdown_elements.map(&:content).join("\n")
|
57
|
-
GovspeakPresenter.new(govspeak).html unless govspeak.empty?
|
53
|
+
elements.select { |element| markdown_element?(element) }.map(&:content).join("\n")
|
58
54
|
end
|
59
55
|
end
|
60
56
|
end
|
@@ -3,8 +3,7 @@ module Smartdown
|
|
3
3
|
class Outcome < Node
|
4
4
|
|
5
5
|
def next_steps
|
6
|
-
|
7
|
-
GovspeakPresenter.new(next_step_element.content).html if next_step_element
|
6
|
+
elements.find{|element| element.is_a? Smartdown::Model::Element::NextSteps}.content
|
8
7
|
end
|
9
8
|
|
10
9
|
end
|
@@ -48,11 +48,7 @@ module Smartdown
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def build_govspeak(elements)
|
51
|
-
|
52
|
-
markdown_element?(element)
|
53
|
-
end
|
54
|
-
govspeak = markdown_elements.map(&:content).join("\n")
|
55
|
-
GovspeakPresenter.new(govspeak).html unless govspeak.empty?
|
51
|
+
elements.select { |element| markdown_element?(element) }.markdown_elements.map(&:content).join("\n")
|
56
52
|
end
|
57
53
|
end
|
58
54
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Smartdown
|
2
|
+
module Model
|
3
|
+
module Predicate
|
4
|
+
OrOperation = Struct.new(:predicates) do
|
5
|
+
def evaluate(state)
|
6
|
+
predicates.map { |predicate| predicate.evaluate(state) }.any?
|
7
|
+
end
|
8
|
+
|
9
|
+
def humanize
|
10
|
+
"(#{predicates.map { |p| p.humanize }.join(' OR ')})"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -4,13 +4,14 @@ module Smartdown
|
|
4
4
|
module Parser
|
5
5
|
class Base < Parslet::Parser
|
6
6
|
rule(:eof) { any.absent? }
|
7
|
-
rule(:ws_char) {
|
7
|
+
rule(:ws_char) { space_char | str("\t") }
|
8
8
|
rule(:space_char) { str(" ") }
|
9
9
|
rule(:non_ws_char) { match('\S') }
|
10
|
-
rule(:
|
10
|
+
rule(:carriage_return) { str("\r\n") | str("\n\r") | str("\n") | str("\r") }
|
11
|
+
rule(:newline) { optional_space >> carriage_return }
|
11
12
|
rule(:line_ending) { eof | newline }
|
12
13
|
|
13
|
-
rule(:optional_space) {
|
14
|
+
rule(:optional_space) { ws_char.repeat }
|
14
15
|
rule(:some_space) { space_char.repeat(1) }
|
15
16
|
rule(:ws) { ws_char.repeat }
|
16
17
|
rule(:non_ws) { non_ws.repeat }
|
@@ -6,34 +6,45 @@ module Smartdown
|
|
6
6
|
module Parser
|
7
7
|
module Element
|
8
8
|
class Conditional < Base
|
9
|
+
|
10
|
+
rule(:dollar_if) { str("$IF ") }
|
11
|
+
rule(:dollar_else) { str("$ELSE") }
|
12
|
+
rule(:dollar_elseif) { str("$ELSEIF ") }
|
13
|
+
rule(:dollar_endif) { str("$ENDIF") }
|
14
|
+
|
9
15
|
rule(:markdown_block_inside_conditional) {
|
10
|
-
|
16
|
+
dollar_keywords = [dollar_if, dollar_else, dollar_elseif, dollar_endif]
|
17
|
+
dollar_keywords.map(&:absent?).reduce(:>>) >> NodeParser.new.markdown_block
|
18
|
+
}
|
19
|
+
|
20
|
+
rule(:conditional_body_block) {
|
21
|
+
markdown_block_inside_conditional | conditional_clause
|
11
22
|
}
|
12
23
|
|
13
|
-
rule(:
|
14
|
-
|
24
|
+
rule(:blocks_inside_conditional) {
|
25
|
+
conditional_body_block.repeat(1,1) >> (newline.repeat(1) >> conditional_body_block).repeat
|
15
26
|
}
|
16
27
|
|
17
28
|
rule(:else_clause) {
|
18
|
-
|
19
|
-
(
|
29
|
+
dollar_else >> optional_space >> newline.repeat(2) >>
|
30
|
+
(blocks_inside_conditional.as(:false_case) >> newline).maybe
|
20
31
|
}
|
21
32
|
|
22
33
|
rule(:elseif_clause) {
|
23
|
-
|
34
|
+
dollar_elseif >> (Predicates.new.as(:predicate) >>
|
24
35
|
optional_space >> newline.repeat(2) >>
|
25
|
-
(
|
36
|
+
(blocks_inside_conditional.as(:true_case) >> newline).maybe >>
|
26
37
|
((elseif_clause | else_clause).maybe)).as(:conditional).repeat(1,1).as(:false_case)
|
27
38
|
}
|
28
39
|
|
29
40
|
rule(:conditional_clause) {
|
30
41
|
(
|
31
|
-
|
42
|
+
dollar_if >>
|
32
43
|
Predicates.new.as(:predicate) >>
|
33
44
|
optional_space >> newline.repeat(2) >>
|
34
|
-
(
|
45
|
+
(blocks_inside_conditional.as(:true_case) >> newline).maybe >>
|
35
46
|
(else_clause | elseif_clause).maybe >>
|
36
|
-
|
47
|
+
dollar_endif >> optional_space >> line_ending
|
37
48
|
).as(:conditional)
|
38
49
|
}
|
39
50
|
|
@@ -18,7 +18,9 @@ require 'smartdown/model/element/next_steps'
|
|
18
18
|
require 'smartdown/model/predicate/equality'
|
19
19
|
require 'smartdown/model/predicate/set_membership'
|
20
20
|
require 'smartdown/model/predicate/named'
|
21
|
-
require 'smartdown/model/predicate/
|
21
|
+
require 'smartdown/model/predicate/not_operation'
|
22
|
+
require 'smartdown/model/predicate/and_operation'
|
23
|
+
require 'smartdown/model/predicate/or_operation'
|
22
24
|
require 'smartdown/model/predicate/function'
|
23
25
|
require 'smartdown/model/predicate/comparison/greater_or_equal'
|
24
26
|
require 'smartdown/model/predicate/comparison/greater'
|
@@ -37,8 +39,33 @@ module Smartdown
|
|
37
39
|
@data_module = data_module || {}
|
38
40
|
end
|
39
41
|
|
40
|
-
#
|
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
|
+
#
|
42
69
|
def call_on_match(bindings, block)
|
43
70
|
bindings.merge! data_module
|
44
71
|
super(bindings, block)
|
@@ -169,8 +196,16 @@ module Smartdown
|
|
169
196
|
Smartdown::Model::Predicate::Otherwise.new
|
170
197
|
}
|
171
198
|
|
172
|
-
rule(:
|
173
|
-
Smartdown::Model::Predicate::
|
199
|
+
rule(:and_operation => {first_predicate: subtree(:first_predicate), and_predicates: subtree(:and_predicates) }) {
|
200
|
+
Smartdown::Model::Predicate::AndOperation.new([first_predicate]+and_predicates)
|
201
|
+
}
|
202
|
+
|
203
|
+
rule(:or_operation => {first_predicate: subtree(:first_predicate), or_predicates: subtree(:or_predicates) }) {
|
204
|
+
Smartdown::Model::Predicate::OrOperation.new([first_predicate]+or_predicates)
|
205
|
+
}
|
206
|
+
|
207
|
+
rule(:not_operation => {predicate: subtree(:predicate)}) {
|
208
|
+
Smartdown::Model::Predicate::NotOperation.new(predicate)
|
174
209
|
}
|
175
210
|
|
176
211
|
rule(:function_argument => simple(:argument)) { argument.to_s }
|
@@ -4,9 +4,11 @@ module Smartdown
|
|
4
4
|
module Parser
|
5
5
|
class Predicates < Base
|
6
6
|
rule(:equality_predicate) {
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
(
|
8
|
+
identifier.as(:varname) >> some_space >>
|
9
|
+
str('is') >> some_space >>
|
10
|
+
str("'") >> match("[^']").repeat.as(:expected_value) >> str("'")
|
11
|
+
).as(:equality_predicate)
|
10
12
|
}
|
11
13
|
|
12
14
|
rule(:comparison_operator) {
|
@@ -14,9 +16,9 @@ module Smartdown
|
|
14
16
|
}
|
15
17
|
|
16
18
|
rule(:comparison_predicate) {
|
17
|
-
identifier.as(:varname) >> some_space >>
|
19
|
+
(identifier.as(:varname) >> some_space >>
|
18
20
|
comparison_operator.as(:operator) >> some_space >>
|
19
|
-
str("'") >> match("[^']").repeat.as(:value) >> str("'")
|
21
|
+
str("'") >> match("[^']").repeat.as(:value) >> str("'")).as(:comparison_predicate)
|
20
22
|
}
|
21
23
|
|
22
24
|
rule(:set_value) {
|
@@ -28,9 +30,11 @@ module Smartdown
|
|
28
30
|
}
|
29
31
|
|
30
32
|
rule(:set_membership_predicate) {
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
(
|
34
|
+
identifier.as(:varname) >> some_space >>
|
35
|
+
str('in') >> some_space >>
|
36
|
+
str("{") >> optional_space >> set_values.maybe.as(:values) >> optional_space >> str("}")
|
37
|
+
).as(:set_membership_predicate)
|
34
38
|
}
|
35
39
|
|
36
40
|
rule(:named_predicate) {
|
@@ -42,18 +46,29 @@ module Smartdown
|
|
42
46
|
}
|
43
47
|
|
44
48
|
rule(:predicate) {
|
45
|
-
equality_predicate
|
46
|
-
set_membership_predicate
|
47
|
-
comparison_predicate
|
48
|
-
function_predicate
|
49
|
+
equality_predicate |
|
50
|
+
set_membership_predicate |
|
51
|
+
comparison_predicate |
|
52
|
+
function_predicate |
|
53
|
+
not_operation |
|
49
54
|
otherwise_predicate |
|
50
55
|
named_predicate
|
51
56
|
}
|
52
57
|
|
53
|
-
rule (:
|
54
|
-
predicate.as(:first_predicate) >>
|
58
|
+
rule (:and_operation) {
|
59
|
+
(predicate.as(:first_predicate) >>
|
55
60
|
(some_space >> str('AND') >> some_space >>
|
56
|
-
|
61
|
+
predicate).repeat(1).as(:and_predicates)).as(:and_operation)
|
62
|
+
}
|
63
|
+
|
64
|
+
rule (:or_operation) {
|
65
|
+
(predicate.as(:first_predicate) >>
|
66
|
+
(some_space >> str('OR') >> some_space >>
|
67
|
+
predicate).repeat(1).as(:or_predicates)).as(:or_operation)
|
68
|
+
}
|
69
|
+
|
70
|
+
rule (:not_operation) {
|
71
|
+
(str('NOT') >> some_space >> predicate.as(:predicate)).as(:not_operation)
|
57
72
|
}
|
58
73
|
|
59
74
|
rule(:function_arguments) {
|
@@ -61,14 +76,15 @@ module Smartdown
|
|
61
76
|
}
|
62
77
|
|
63
78
|
rule (:function_predicate) {
|
64
|
-
(identifier >> str('?').maybe).as(:name) >>
|
79
|
+
((identifier >> str('?').maybe).as(:name) >>
|
65
80
|
str('(') >>
|
66
81
|
function_arguments.as(:arguments).maybe >>
|
67
|
-
str(')')
|
82
|
+
str(')')).as(:function_predicate)
|
68
83
|
}
|
69
84
|
|
70
85
|
rule (:predicates) {
|
71
|
-
|
86
|
+
and_operation |
|
87
|
+
or_operation |
|
72
88
|
predicate
|
73
89
|
}
|
74
90
|
|
data/lib/smartdown/version.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
require 'smartdown/model/predicate/
|
2
|
-
require 'smartdown/model/predicate/combined'
|
1
|
+
require 'smartdown/model/predicate/and_operation'
|
3
2
|
require 'smartdown/model/predicate/named'
|
4
3
|
require 'smartdown/engine/state'
|
5
4
|
|
6
|
-
describe Smartdown::Model::Predicate::
|
5
|
+
describe Smartdown::Model::Predicate::AndOperation do
|
7
6
|
let(:predicate_1) { Smartdown::Model::Predicate::Named.new("my_pred?") }
|
8
7
|
let(:predicate_2) { Smartdown::Model::Predicate::Named.new("my_other_pred?") }
|
9
8
|
subject(:predicate) { described_class.new([predicate_1, predicate_2])}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'smartdown/model/predicate/not_operation'
|
2
|
+
require 'smartdown/model/predicate/named'
|
3
|
+
require 'smartdown/engine/state'
|
4
|
+
|
5
|
+
describe Smartdown::Model::Predicate::NotOperation do
|
6
|
+
subject(:negated_predicate) { described_class.new(inner_predicate) }
|
7
|
+
let(:inner_predicate_name) { "my_pred?" }
|
8
|
+
let(:inner_predicate) { Smartdown::Model::Predicate::Named.new inner_predicate_name }
|
9
|
+
|
10
|
+
describe "#evaluate" do
|
11
|
+
context "state has predicate definition" do
|
12
|
+
let(:state) {
|
13
|
+
Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true )
|
14
|
+
}
|
15
|
+
|
16
|
+
it "returns the negation of the inner predicate" do
|
17
|
+
expect(negated_predicate.evaluate(state)).to eq(false)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#humanize" do
|
23
|
+
it "prepends the inner predicate's humanize with NOT" do
|
24
|
+
expect(negated_predicate.humanize).to eq("NOT my_pred?")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'smartdown/model/predicate/or_operation'
|
2
|
+
require 'smartdown/model/predicate/named'
|
3
|
+
require 'smartdown/engine/state'
|
4
|
+
|
5
|
+
describe Smartdown::Model::Predicate::OrOperation do
|
6
|
+
let(:predicate_1) { Smartdown::Model::Predicate::Named.new("my_pred?") }
|
7
|
+
let(:predicate_2) { Smartdown::Model::Predicate::Named.new("my_other_pred?") }
|
8
|
+
subject(:predicate) { described_class.new([predicate_1, predicate_2])}
|
9
|
+
|
10
|
+
describe "#evaluate" do
|
11
|
+
|
12
|
+
context "both states are true" do
|
13
|
+
let(:state) {
|
14
|
+
Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => true )
|
15
|
+
}
|
16
|
+
|
17
|
+
it "evaluates as true" do
|
18
|
+
expect(predicate.evaluate(state)).to eq(true)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "both states are false" do
|
23
|
+
let(:state) {
|
24
|
+
Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => false, "my_other_pred?" => false )
|
25
|
+
}
|
26
|
+
|
27
|
+
it "evaluates as false" do
|
28
|
+
expect(predicate.evaluate(state)).to eq(false)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "one of the states is false" do
|
33
|
+
let(:state) {
|
34
|
+
Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => false )
|
35
|
+
}
|
36
|
+
|
37
|
+
it "evaluates as true" do
|
38
|
+
expect(predicate.evaluate(state)).to eq(true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#humanize" do
|
44
|
+
it { expect(predicate.humanize).to eq("(my_pred? OR my_other_pred?)") }
|
45
|
+
end
|
46
|
+
end
|
data/spec/parser/base_spec.rb
CHANGED
@@ -329,5 +329,69 @@ SOURCE
|
|
329
329
|
|
330
330
|
end
|
331
331
|
end
|
332
|
+
|
333
|
+
context "Nested IF statement" do
|
334
|
+
let(:source) { <<-SOURCE
|
335
|
+
$IF pred1?
|
336
|
+
|
337
|
+
#{first_true_body}
|
338
|
+
|
339
|
+
$IF pred2?
|
340
|
+
|
341
|
+
#{both_true_body}
|
342
|
+
|
343
|
+
$ENDIF
|
344
|
+
|
345
|
+
#{first_true_body}
|
346
|
+
|
347
|
+
$ENDIF
|
348
|
+
SOURCE
|
349
|
+
}
|
350
|
+
context "With a body for double true case" do
|
351
|
+
let(:first_true_body) { "Text if first predicates is true" }
|
352
|
+
let(:both_true_body) { "Text if both predicates are true" }
|
353
|
+
it { should parse(source).as(
|
354
|
+
conditional: {
|
355
|
+
predicate: {named_predicate: "pred1?"},
|
356
|
+
true_case: [
|
357
|
+
{p: "#{first_true_body}\n"},
|
358
|
+
{conditional: {
|
359
|
+
predicate: {named_predicate: "pred2?"},
|
360
|
+
true_case: [{p: "#{both_true_body}\n"}],
|
361
|
+
}},
|
362
|
+
{p: "#{first_true_body}\n"}
|
363
|
+
]
|
364
|
+
}
|
365
|
+
)
|
366
|
+
}
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
context "with custom markdown tags inside" do
|
371
|
+
|
372
|
+
let (:source) { <<-SOURCE
|
373
|
+
$IF pred?
|
374
|
+
|
375
|
+
#{true_body.chomp}
|
376
|
+
|
377
|
+
$ENDIF
|
378
|
+
SOURCE
|
379
|
+
}
|
380
|
+
let(:true_body) { <<-TAG
|
381
|
+
$E
|
382
|
+
Bit of content in lovely custom tag
|
383
|
+
$E
|
384
|
+
TAG
|
385
|
+
}
|
386
|
+
|
387
|
+
it { should parse(source).as(
|
388
|
+
conditional: {
|
389
|
+
predicate: {named_predicate: "pred?"},
|
390
|
+
true_case: [
|
391
|
+
{p: "#{true_body}"}
|
392
|
+
]},
|
393
|
+
)
|
394
|
+
}
|
395
|
+
end
|
332
396
|
end
|
333
397
|
|
@@ -194,4 +194,23 @@ SOURCE
|
|
194
194
|
}
|
195
195
|
end
|
196
196
|
|
197
|
+
context "blank line with a tab on it" do
|
198
|
+
|
199
|
+
let(:source) {
|
200
|
+
<<SOURCE
|
201
|
+
# Lovely title
|
202
|
+
|
203
|
+
line of content
|
204
|
+
SOURCE
|
205
|
+
}
|
206
|
+
|
207
|
+
it "doesn't blow up" do
|
208
|
+
should parse(source).as({
|
209
|
+
body: [
|
210
|
+
{ h1: "Lovely title" },
|
211
|
+
{ p: "line of content\n" },
|
212
|
+
]
|
213
|
+
})
|
214
|
+
end
|
215
|
+
end
|
197
216
|
end
|
@@ -2,9 +2,9 @@ require 'smartdown/parser/predicates'
|
|
2
2
|
require 'smartdown/parser/node_interpreter'
|
3
3
|
|
4
4
|
describe Smartdown::Parser::Predicates do
|
5
|
+
subject(:parser) { described_class.new }
|
5
6
|
|
6
7
|
describe "equality predicate" do
|
7
|
-
subject(:parser) { described_class.new }
|
8
8
|
let(:source) { "varname is 'expected_value'" }
|
9
9
|
|
10
10
|
it { should parse(source).as(equality_predicate: {varname: "varname", expected_value: "expected_value"}) }
|
@@ -24,7 +24,6 @@ describe Smartdown::Parser::Predicates do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
describe "set membership predicate" do
|
27
|
-
subject(:parser) { described_class.new }
|
28
27
|
let(:source) { "varname in {a b c}" }
|
29
28
|
|
30
29
|
it { should parse(source).as(set_membership_predicate: {varname: "varname", values: [{set_value: "a"}, {set_value: "b"}, {set_value: "c"}]}) }
|
@@ -45,8 +44,6 @@ describe Smartdown::Parser::Predicates do
|
|
45
44
|
end
|
46
45
|
|
47
46
|
describe "named predicate" do
|
48
|
-
subject(:parser) { described_class.new }
|
49
|
-
|
50
47
|
it { should parse("my_pred?").as(named_predicate: "my_pred?") }
|
51
48
|
it { should_not parse("my_pred") }
|
52
49
|
it { should_not parse("my pred") }
|
@@ -63,8 +60,6 @@ describe Smartdown::Parser::Predicates do
|
|
63
60
|
end
|
64
61
|
|
65
62
|
describe "otherwise predicate" do
|
66
|
-
subject(:parser) { described_class.new }
|
67
|
-
|
68
63
|
it { should parse("otherwise").as(otherwise_predicate: "otherwise") }
|
69
64
|
it { should_not parse("other") }
|
70
65
|
|
@@ -80,10 +75,9 @@ describe Smartdown::Parser::Predicates do
|
|
80
75
|
end
|
81
76
|
|
82
77
|
describe "predicate AND predicate" do
|
83
|
-
subject(:parser) { described_class.new }
|
84
78
|
|
85
79
|
it { should parse("my_pred() AND my_other_pred?").as(
|
86
|
-
{
|
80
|
+
{ and_operation: {
|
87
81
|
first_predicate: { function_predicate: { name: "my_pred" } },
|
88
82
|
and_predicates:
|
89
83
|
[
|
@@ -92,7 +86,7 @@ describe Smartdown::Parser::Predicates do
|
|
92
86
|
} }
|
93
87
|
) }
|
94
88
|
it { should parse("my_pred? AND my_other_pred? AND varname in {a b c}").as(
|
95
|
-
{
|
89
|
+
{ and_operation: {
|
96
90
|
first_predicate: { named_predicate: "my_pred?" },
|
97
91
|
and_predicates:
|
98
92
|
[
|
@@ -104,6 +98,7 @@ describe Smartdown::Parser::Predicates do
|
|
104
98
|
} }
|
105
99
|
) }
|
106
100
|
it { should_not parse("my_pred AND ") }
|
101
|
+
it { should_not parse("my_pred AND my_other_pred OR special_pred") }
|
107
102
|
|
108
103
|
describe "transformed" do
|
109
104
|
let(:node_name) { "my_node" }
|
@@ -112,7 +107,7 @@ describe Smartdown::Parser::Predicates do
|
|
112
107
|
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
113
108
|
}
|
114
109
|
|
115
|
-
it { should eq(Smartdown::Model::Predicate::
|
110
|
+
it { should eq(Smartdown::Model::Predicate::AndOperation.new(
|
116
111
|
[
|
117
112
|
Smartdown::Model::Predicate::Function.new("my_pred", []),
|
118
113
|
Smartdown::Model::Predicate::Named.new("my_other_pred?")
|
@@ -121,8 +116,84 @@ describe Smartdown::Parser::Predicates do
|
|
121
116
|
end
|
122
117
|
end
|
123
118
|
|
119
|
+
describe "predicate OR predicate" do
|
120
|
+
it {should parse("my_pred() OR my_other_pred?").as(
|
121
|
+
{ or_operation: {
|
122
|
+
first_predicate: { function_predicate: { name: "my_pred" } },
|
123
|
+
or_predicates:
|
124
|
+
[
|
125
|
+
{named_predicate: "my_other_pred?"},
|
126
|
+
]
|
127
|
+
} }
|
128
|
+
) }
|
129
|
+
it { should parse("my_pred? OR my_other_pred? OR varname in {a b c}").as(
|
130
|
+
{ or_operation: {
|
131
|
+
first_predicate: { named_predicate: "my_pred?" },
|
132
|
+
or_predicates:
|
133
|
+
[
|
134
|
+
{named_predicate: "my_other_pred?"},
|
135
|
+
{set_membership_predicate:
|
136
|
+
{varname: "varname", values: [{set_value: "a"}, {set_value: "b"}, {set_value: "c"}]}
|
137
|
+
}
|
138
|
+
]
|
139
|
+
} }
|
140
|
+
) }
|
141
|
+
it { should_not parse("my_pred OR ") }
|
142
|
+
it { should_not parse("my_pred OR my_other_pred AND special_pred") }
|
143
|
+
|
144
|
+
describe "transformed" do
|
145
|
+
let(:node_name) { "my_node" }
|
146
|
+
let(:source) { "my_pred() OR my_other_pred?" }
|
147
|
+
subject(:transformed) {
|
148
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
149
|
+
}
|
150
|
+
|
151
|
+
it { should eq(Smartdown::Model::Predicate::OrOperation.new(
|
152
|
+
[
|
153
|
+
Smartdown::Model::Predicate::Function.new("my_pred", []),
|
154
|
+
Smartdown::Model::Predicate::Named.new("my_other_pred?")
|
155
|
+
]
|
156
|
+
)) }
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "NOT predicate" do
|
162
|
+
it { should parse("NOT my_pred?").as(
|
163
|
+
{ not_operation: {
|
164
|
+
predicate: { named_predicate: "my_pred?" }
|
165
|
+
} }
|
166
|
+
) }
|
167
|
+
|
168
|
+
it { should parse("NOT my_pred? AND my_other_pred?").as (
|
169
|
+
{ and_operation:
|
170
|
+
{
|
171
|
+
first_predicate:
|
172
|
+
{
|
173
|
+
not_operation: { predicate: { named_predicate: 'my_pred?' } }
|
174
|
+
},
|
175
|
+
and_predicates:
|
176
|
+
[
|
177
|
+
{ named_predicate: 'my_other_pred?' }
|
178
|
+
]
|
179
|
+
}
|
180
|
+
}
|
181
|
+
)}
|
182
|
+
|
183
|
+
describe "transformed" do
|
184
|
+
let(:node_name) { "my_node" }
|
185
|
+
let(:source) { "NOT my_pred?" }
|
186
|
+
subject(:transformed) {
|
187
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
188
|
+
}
|
189
|
+
|
190
|
+
it { should eq(Smartdown::Model::Predicate::NotOperation.new(
|
191
|
+
Smartdown::Model::Predicate::Named.new("my_pred?"),
|
192
|
+
)) }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
124
196
|
describe "function predicate" do
|
125
|
-
subject(:parser) { described_class.new }
|
126
197
|
|
127
198
|
context "no arguments" do
|
128
199
|
let(:source) { "function_name()" }
|
@@ -202,7 +273,6 @@ describe Smartdown::Parser::Predicates do
|
|
202
273
|
end
|
203
274
|
|
204
275
|
describe "comparison predicate" do
|
205
|
-
subject(:parser) { described_class.new }
|
206
276
|
let(:greater_equal_source) { "varname >= 'value'" }
|
207
277
|
let(:greater_source) { "varname > 'value'" }
|
208
278
|
let(:less_equal_source) { "varname <= 'value'" }
|
@@ -13,7 +13,7 @@ require 'smartdown/model/rule'
|
|
13
13
|
require 'smartdown/model/predicate/named'
|
14
14
|
require 'smartdown/model/predicate/equality'
|
15
15
|
require 'smartdown/model/predicate/set_membership'
|
16
|
-
require 'smartdown/model/predicate/
|
16
|
+
require 'smartdown/model/predicate/and_operation'
|
17
17
|
require 'smartdown/model/predicate/comparison/greater_or_equal'
|
18
18
|
require 'smartdown/model/predicate/comparison/less_or_equal'
|
19
19
|
require 'smartdown/model/predicate/comparison/greater'
|
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.
|
4
|
+
version: 0.11.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2014-07-09 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: parslet
|
16
|
-
requirement: &
|
16
|
+
requirement: &8759180 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.6.1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *8759180
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &8758660 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.0.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *8758660
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &8758220 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *8758220
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: gem_publisher
|
49
|
-
requirement: &
|
49
|
+
requirement: &8757720 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *8757720
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: timecop
|
60
|
-
requirement: &
|
60
|
+
requirement: &8757300 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *8757300
|
69
69
|
description:
|
70
70
|
email: david.heath@digital.cabinet-office.gov.uk
|
71
71
|
executables:
|
@@ -127,6 +127,7 @@ files:
|
|
127
127
|
- lib/smartdown/model/flow.rb
|
128
128
|
- lib/smartdown/model/predicate/named.rb
|
129
129
|
- lib/smartdown/model/predicate/set_membership.rb
|
130
|
+
- lib/smartdown/model/predicate/and_operation.rb
|
130
131
|
- lib/smartdown/model/predicate/function.rb
|
131
132
|
- lib/smartdown/model/predicate/equality.rb
|
132
133
|
- lib/smartdown/model/predicate/comparison/less_or_equal.rb
|
@@ -134,8 +135,9 @@ files:
|
|
134
135
|
- lib/smartdown/model/predicate/comparison/base.rb
|
135
136
|
- lib/smartdown/model/predicate/comparison/greater.rb
|
136
137
|
- lib/smartdown/model/predicate/comparison/less.rb
|
137
|
-
- lib/smartdown/model/predicate/
|
138
|
+
- lib/smartdown/model/predicate/not_operation.rb
|
138
139
|
- lib/smartdown/model/predicate/otherwise.rb
|
140
|
+
- lib/smartdown/model/predicate/or_operation.rb
|
139
141
|
- lib/smartdown/model/node.rb
|
140
142
|
- lib/smartdown/model/front_matter.rb
|
141
143
|
- lib/smartdown/model/answer/date.rb
|
@@ -243,11 +245,13 @@ files:
|
|
243
245
|
- spec/model/answer/salary_spec.rb
|
244
246
|
- spec/model/answer/date_spec.rb
|
245
247
|
- spec/model/answer/country_spec.rb
|
248
|
+
- spec/model/predicates/not_operation_spec.rb
|
246
249
|
- spec/model/predicates/set_membership_spec.rb
|
247
|
-
- spec/model/predicates/
|
250
|
+
- spec/model/predicates/or_operation_spec.rb
|
248
251
|
- spec/model/predicates/comparison_spec.rb
|
249
252
|
- spec/model/predicates/equality_spec.rb
|
250
253
|
- spec/model/predicates/named_spec.rb
|
254
|
+
- spec/model/predicates/and_operation_spec.rb
|
251
255
|
- spec/model/predicates/function_spec.rb
|
252
256
|
homepage: https://github.com/alphagov/smartdown
|
253
257
|
licenses:
|
@@ -264,7 +268,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
264
268
|
version: '0'
|
265
269
|
segments:
|
266
270
|
- 0
|
267
|
-
hash:
|
271
|
+
hash: -3164786129793908873
|
268
272
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
269
273
|
none: false
|
270
274
|
requirements:
|
@@ -273,7 +277,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
273
277
|
version: '0'
|
274
278
|
segments:
|
275
279
|
- 0
|
276
|
-
hash:
|
280
|
+
hash: -3164786129793908873
|
277
281
|
requirements: []
|
278
282
|
rubyforge_project:
|
279
283
|
rubygems_version: 1.8.11
|
@@ -363,9 +367,11 @@ test_files:
|
|
363
367
|
- spec/model/answer/salary_spec.rb
|
364
368
|
- spec/model/answer/date_spec.rb
|
365
369
|
- spec/model/answer/country_spec.rb
|
370
|
+
- spec/model/predicates/not_operation_spec.rb
|
366
371
|
- spec/model/predicates/set_membership_spec.rb
|
367
|
-
- spec/model/predicates/
|
372
|
+
- spec/model/predicates/or_operation_spec.rb
|
368
373
|
- spec/model/predicates/comparison_spec.rb
|
369
374
|
- spec/model/predicates/equality_spec.rb
|
370
375
|
- spec/model/predicates/named_spec.rb
|
376
|
+
- spec/model/predicates/and_operation_spec.rb
|
371
377
|
- spec/model/predicates/function_spec.rb
|