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 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