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 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
 
@@ -50,11 +50,7 @@ module Smartdown
50
50
  end
51
51
 
52
52
  def build_govspeak(elements)
53
- markdown_elements = elements.select do |element|
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
- next_step_element = elements.find{|element| element.is_a? Smartdown::Model::Element::NextSteps}
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
- markdown_elements = elements.select do |element|
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
@@ -1,7 +1,7 @@
1
1
  module Smartdown
2
2
  module Model
3
3
  module Predicate
4
- Combined = Struct.new(:predicates) do
4
+ AndOperation = Struct.new(:predicates) do
5
5
  def evaluate(state)
6
6
  predicates.map { |predicate| predicate.evaluate(state) }.all?
7
7
  end
@@ -0,0 +1,15 @@
1
+ module Smartdown
2
+ module Model
3
+ module Predicate
4
+ NotOperation = Struct.new(:predicate) do
5
+ def evaluate(state)
6
+ !predicate.evaluate(state)
7
+ end
8
+
9
+ def humanize
10
+ "NOT #{predicate.humanize}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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) { match('\s') }
7
+ rule(:ws_char) { space_char | str("\t") }
8
8
  rule(:space_char) { str(" ") }
9
9
  rule(:non_ws_char) { match('\S') }
10
- rule(:newline) { str("\r\n") | str("\n\r") | str("\n") | str("\r") }
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) { space_char.repeat }
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
- str("$").absent? >> NodeParser.new.markdown_block
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(:markdown_blocks_inside_conditional) {
14
- markdown_block_inside_conditional.repeat(1,1) >> (newline.repeat(1) >> markdown_block_inside_conditional).repeat
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
- str("$ELSE") >> optional_space >> newline.repeat(2) >>
19
- (markdown_blocks_inside_conditional.as(:false_case) >> newline).maybe
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
- str("$ELSEIF ") >> (Predicates.new.as(:predicate) >>
34
+ dollar_elseif >> (Predicates.new.as(:predicate) >>
24
35
  optional_space >> newline.repeat(2) >>
25
- (markdown_blocks_inside_conditional.as(:true_case) >> newline).maybe >>
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
- str("$IF ") >>
42
+ dollar_if >>
32
43
  Predicates.new.as(:predicate) >>
33
44
  optional_space >> newline.repeat(2) >>
34
- (markdown_blocks_inside_conditional.as(:true_case) >> newline).maybe >>
45
+ (blocks_inside_conditional.as(:true_case) >> newline).maybe >>
35
46
  (else_clause | elseif_clause).maybe >>
36
- str("$ENDIF") >> optional_space >> line_ending
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/combined'
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
- #TODO: Horrible monkey patching, should try to submit a PR to parselet
41
- #to allow modification of bindings?
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(:combined_predicate => {first_predicate: subtree(:first_predicate), and_predicates: subtree(:and_predicates) }) {
173
- Smartdown::Model::Predicate::Combined.new([first_predicate]+and_predicates)
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
- identifier.as(:varname) >> some_space >>
8
- str('is') >> some_space >>
9
- str("'") >> match("[^']").repeat.as(:expected_value) >> str("'")
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
- identifier.as(:varname) >> some_space >>
32
- str('in') >> some_space >>
33
- str("{") >> optional_space >> set_values.maybe.as(:values) >> optional_space >> str("}")
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.as(:equality_predicate) |
46
- set_membership_predicate.as(:set_membership_predicate) |
47
- comparison_predicate.as(:comparison_predicate) |
48
- function_predicate.as(: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 (:combined_predicate) {
54
- predicate.as(:first_predicate) >>
58
+ rule (:and_operation) {
59
+ (predicate.as(:first_predicate) >>
55
60
  (some_space >> str('AND') >> some_space >>
56
- predicate).repeat(1).as(:and_predicates)
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
- combined_predicate.as(:combined_predicate) |
86
+ and_operation |
87
+ or_operation |
72
88
  predicate
73
89
  }
74
90
 
@@ -1,3 +1,3 @@
1
1
  module Smartdown
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -1,9 +1,8 @@
1
- require 'smartdown/model/predicate/combined'
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::Combined do
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
@@ -6,9 +6,9 @@ describe Smartdown::Parser::Base do
6
6
 
7
7
  describe "#ws" do
8
8
  subject { parser.ws }
9
- it { should parse("\n") }
10
9
  it { should parse(" ") }
11
10
  it { should parse(" ") }
11
+ it { should parse(" ") }
12
12
  end
13
13
 
14
14
  describe "#eof" do
@@ -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
- { combined_predicate: {
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
- { combined_predicate: {
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::Combined.new(
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/combined'
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.10.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: &18204100 !ruby/object:Gem::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: *18204100
24
+ version_requirements: *8759180
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &18232520 !ruby/object:Gem::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: *18232520
35
+ version_requirements: *8758660
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &18231800 !ruby/object:Gem::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: *18231800
46
+ version_requirements: *8758220
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: gem_publisher
49
- requirement: &18230980 !ruby/object:Gem::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: *18230980
57
+ version_requirements: *8757720
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: timecop
60
- requirement: &18230000 !ruby/object:Gem::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: *18230000
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/combined.rb
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/combined_spec.rb
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: 508907290184423365
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: 508907290184423365
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/combined_spec.rb
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