smartdown 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|