smartdown 0.3.0 → 0.4.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.
Files changed (42) hide show
  1. data/lib/smartdown/engine/conditional_resolver.rb +1 -9
  2. data/lib/smartdown/engine/interpolator.rb +12 -1
  3. data/lib/smartdown/engine/state.rb +1 -10
  4. data/lib/smartdown/engine/transition.rb +2 -6
  5. data/lib/smartdown/engine.rb +2 -3
  6. data/lib/smartdown/model/predicate/combined.rb +9 -1
  7. data/lib/smartdown/model/predicate/comparison/base.rb +4 -1
  8. data/lib/smartdown/model/predicate/comparison/greater.rb +6 -1
  9. data/lib/smartdown/model/predicate/comparison/greater_or_equal.rb +6 -1
  10. data/lib/smartdown/model/predicate/comparison/less.rb +6 -1
  11. data/lib/smartdown/model/predicate/comparison/less_or_equal.rb +6 -1
  12. data/lib/smartdown/model/predicate/equality.rb +9 -1
  13. data/lib/smartdown/model/predicate/function.rb +28 -0
  14. data/lib/smartdown/model/predicate/named.rb +9 -1
  15. data/lib/smartdown/model/predicate/otherwise.rb +15 -0
  16. data/lib/smartdown/model/predicate/set_membership.rb +9 -1
  17. data/lib/smartdown/parser/node_transform.rb +14 -0
  18. data/lib/smartdown/parser/predicates.rb +18 -1
  19. data/lib/smartdown/version.rb +1 -1
  20. data/spec/acceptance/parsing_spec.rb +8 -0
  21. data/spec/engine/interpolator_spec.rb +14 -0
  22. data/spec/engine/state_spec.rb +0 -37
  23. data/spec/engine/transition_spec.rb +23 -19
  24. data/spec/engine_spec.rb +8 -11
  25. data/spec/fixtures/acceptance/animal-example-simple/animal-example-simple.txt +25 -0
  26. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_safe_pet.txt +69 -0
  27. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_tigers_are_fine.txt +9 -0
  28. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_trained_with_lions.txt +3 -0
  29. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_untrained_with_lions.txt +3 -0
  30. data/spec/fixtures/acceptance/animal-example-simple/questions/question_1.txt +13 -0
  31. data/spec/fixtures/acceptance/animal-example-simple/questions/question_2.txt +10 -0
  32. data/spec/model/predicates/combined_spec.rb +47 -0
  33. data/spec/model/predicates/comparison_spec.rb +162 -0
  34. data/spec/model/predicates/equality_spec.rb +36 -0
  35. data/spec/model/predicates/function_spec.rb +85 -0
  36. data/spec/model/predicates/named_spec.rb +41 -0
  37. data/spec/model/predicates/set_membership_spec.rb +39 -0
  38. data/spec/parser/predicates_spec.rb +98 -16
  39. data/spec/support/model_builder.rb +4 -0
  40. metadata +39 -14
  41. data/lib/smartdown/engine/predicate_evaluator.rb +0 -27
  42. data/spec/engine/predicate_evaluator_spec.rb +0 -284
@@ -0,0 +1,25 @@
1
+ meta_description: Animals eh?
2
+ satisfies_need: 100982
3
+ status: draft
4
+
5
+ # A simple example of smartdown
6
+
7
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
8
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
9
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
10
+ consequat.
11
+
12
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
13
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
14
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
15
+
16
+ ## Before you start, you will need
17
+
18
+ * To know the kind of feline you have
19
+ * Any animal training certification documents
20
+
21
+ [start: question_1]
22
+
23
+ ## Devolved body
24
+
25
+ Which is really just body content after the start button
@@ -0,0 +1,69 @@
1
+ # You picked a safe pet
2
+
3
+ Good call.
4
+
5
+ # Markdown/Govspeak examples
6
+
7
+ ## H2 example
8
+
9
+ With answers, that are smart. Down with that?
10
+
11
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
12
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
13
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
14
+
15
+ ### H3 example
16
+
17
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
18
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
19
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
20
+
21
+ ## List
22
+
23
+ * one
24
+ * two
25
+ * three
26
+
27
+ ## List 2
28
+
29
+ - A
30
+ - B
31
+ - C
32
+
33
+ ## List 3
34
+
35
+ + Up
36
+ + Down
37
+ + Left
38
+ + Right
39
+
40
+ ^This is an information callout.^
41
+
42
+ %This is a warning callout - use it when there’s a risk of getting a fine or going to prison.%
43
+
44
+ $E
45
+ **Example**
46
+ Your income for the last tax year was £30,000. You think your income will drop to £20,000 this tax year. Enter £22,500 into the calculator.
47
+ $E
48
+
49
+ {::highlight-answer}
50
+ The VAT rate is *20%*
51
+ {:/highlight-answer}
52
+
53
+
54
+ $C
55
+ **Student Finance England**
56
+ Telephone: 0845 300 5090
57
+ Minicom: 0845 604 4434
58
+ [Find out about call charges](http://www.gov.uk/call-charges)
59
+ $C
60
+
61
+ $A
62
+ Hercules House
63
+ Hercules Road
64
+ London SE1 7DU
65
+ $A
66
+
67
+ $D
68
+ [Download the 'Example application form' (PDF, 35KB)](http://example.com/ "Example form")
69
+ $D
@@ -0,0 +1,9 @@
1
+ # Tigers are fine
2
+
3
+ You haven't trained with tigers, but you'll be alright, probably.
4
+
5
+ [next_steps]
6
+ ###You bought a tiger? Seriously?
7
+
8
+ [Your nearest hospital](https://gov.uk/hospital)
9
+ [end_next_steps]
@@ -0,0 +1,3 @@
1
+ # You've trained with lions
2
+
3
+ You'll be alright, I think.
@@ -0,0 +1,3 @@
1
+ # You're not trained with lions
2
+
3
+ Nice to know you.
@@ -0,0 +1,13 @@
1
+ # Amazing page title
2
+
3
+ # What type of feline do you have?
4
+
5
+ Check the serial number by the tail.
6
+
7
+ [choice: question_1]
8
+ * lion: Lion
9
+ * tiger: Tiger
10
+ * cat: Cat
11
+
12
+ * question_1 in {lion tiger} => question_2
13
+ * otherwise => outcome_safe_pet
@@ -0,0 +1,10 @@
1
+ # Are you trained for lions?
2
+
3
+ [choice: trained_for_lions]
4
+ * yes: Yes
5
+ * no: No
6
+
7
+ * question_1 is 'lion'
8
+ * trained_for_lions is 'yes' => outcome_trained_with_lions
9
+ * otherwise => outcome_untrained_with_lions
10
+ * otherwise => outcome_tigers_are_fine
@@ -0,0 +1,47 @@
1
+ require 'smartdown/model/predicate/combined'
2
+ require 'smartdown/model/predicate/combined'
3
+ require 'smartdown/model/predicate/named'
4
+ require 'smartdown/engine/state'
5
+
6
+ describe Smartdown::Model::Predicate::Combined do
7
+ let(:predicate_1) { Smartdown::Model::Predicate::Named.new("my_pred?") }
8
+ let(:predicate_2) { Smartdown::Model::Predicate::Named.new("my_other_pred?") }
9
+ subject(:predicate) { described_class.new([predicate_1, predicate_2])}
10
+
11
+ describe "#evaluate" do
12
+
13
+ context "both states are true" do
14
+ let(:state) {
15
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => true )
16
+ }
17
+
18
+ it "evaluates as true" do
19
+ expect(predicate.evaluate(state)).to eq(true)
20
+ end
21
+ end
22
+
23
+ context "both states are false" do
24
+ let(:state) {
25
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => false, "my_other_pred?" => false )
26
+ }
27
+
28
+ it "evaluates as false" do
29
+ expect(predicate.evaluate(state)).to eq(false)
30
+ end
31
+ end
32
+
33
+ context "one of the states is false" do
34
+ let(:state) {
35
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => false )
36
+ }
37
+
38
+ it "evaluates as false" do
39
+ expect(predicate.evaluate(state)).to eq(false)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#humanize" do
45
+ it { expect(predicate.humanize).to eq("(my_pred? AND my_other_pred?)") }
46
+ end
47
+ end
@@ -0,0 +1,162 @@
1
+ require 'smartdown/model/predicate/comparison/greater_or_equal'
2
+ require 'smartdown/model/predicate/comparison/greater'
3
+ require 'smartdown/model/predicate/comparison/less_or_equal'
4
+ require 'smartdown/model/predicate/comparison/less'
5
+ require 'smartdown/engine/state'
6
+
7
+ describe "comparison predicates" do
8
+ context "comparison predicates with integers" do
9
+ let(:greater_predicate) { Smartdown::Model::Predicate::Comparison::Greater.new("my_var", "5") }
10
+ let(:greater_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("my_var", "5") }
11
+ let(:less_predicate) { Smartdown::Model::Predicate::Comparison::Less.new("my_var", "5") }
12
+ let(:less_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::LessOrEqual.new("my_var", "5") }
13
+ let(:predicates) { {
14
+ :greater => greater_predicate,
15
+ :greater_or_equal => greater_or_equal_predicate,
16
+ :less => less_predicate,
17
+ :less_or_equal => less_or_equal_predicate
18
+ } }
19
+
20
+ describe "#evaluate" do
21
+ context "state missing expected variable" do
22
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
23
+
24
+ it "raises an UndefinedValue error" do
25
+ predicates.values.each do |predicate|
26
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
27
+ end
28
+ end
29
+ end
30
+
31
+ context "state has lower value" do
32
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "4") }
33
+ let(:results) { {
34
+ :greater => false,
35
+ :greater_or_equal => false,
36
+ :less => true,
37
+ :less_or_equal => true
38
+ } }
39
+ it "evaluates correctly" do
40
+ predicates.each do |predicate_key, predicate|
41
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
42
+ end
43
+ end
44
+ end
45
+
46
+ context "state has identical value" do
47
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "5") }
48
+ let(:results) { {
49
+ :greater => false,
50
+ :greater_or_equal => true,
51
+ :less => false,
52
+ :less_or_equal => true
53
+ } }
54
+ it "evaluates correctly" do
55
+ predicates.each do |predicate_key, predicate|
56
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
57
+ end
58
+ end
59
+ end
60
+
61
+ context "state has higher value" do
62
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "6") }
63
+ let(:results) { {
64
+ :greater => true,
65
+ :greater_or_equal => true,
66
+ :less => false,
67
+ :less_or_equal => false
68
+ } }
69
+ it "evaluates correctly" do
70
+ predicates.each do |predicate_key, predicate|
71
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "#humanize" do
78
+ let(:results) { {
79
+ :greater => "my_var > 5",
80
+ :greater_or_equal => "my_var >= 5",
81
+ :less => "my_var < 5",
82
+ :less_or_equal => "my_var <= 5"
83
+ } }
84
+
85
+ it "has the right human representation" do
86
+ predicates.each do |predicate_key, predicate|
87
+ expect(predicate.humanize).to eq(results[predicate_key])
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ context "comparison predicates with dates" do
94
+ let(:greater_predicate) { Smartdown::Model::Predicate::Comparison::Greater.new("my_var", "2014-1-31") }
95
+ let(:greater_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("my_var", "2014-1-31") }
96
+ let(:less_predicate) { Smartdown::Model::Predicate::Comparison::Less.new("my_var", "2014-1-31") }
97
+ let(:less_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::LessOrEqual.new("my_var", "2014-1-31") }
98
+ let(:predicates) { {
99
+ :greater => greater_predicate,
100
+ :greater_or_equal => greater_or_equal_predicate,
101
+ :less => less_predicate,
102
+ :less_or_equal => less_or_equal_predicate
103
+ } }
104
+
105
+ describe "#evaluate" do
106
+ context "state missing expected variable" do
107
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
108
+
109
+ it "raises an UndefinedValue error" do
110
+ predicates.values.each do |predicate|
111
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
112
+ end
113
+ end
114
+ end
115
+
116
+ context "state has lower value" do
117
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-1-30") }
118
+ let(:results) { {
119
+ :greater => false,
120
+ :greater_or_equal => false,
121
+ :less => true,
122
+ :less_or_equal => true
123
+ } }
124
+ it "evaluates correctly" do
125
+ predicates.each do |predicate_key, predicate|
126
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
127
+ end
128
+ end
129
+ end
130
+
131
+ context "state has identical value" do
132
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-1-31") }
133
+ let(:results) { {
134
+ :greater => false,
135
+ :greater_or_equal => true,
136
+ :less => false,
137
+ :less_or_equal => true
138
+ } }
139
+ it "evaluates correctly" do
140
+ predicates.each do |predicate_key, predicate|
141
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
142
+ end
143
+ end
144
+ end
145
+
146
+ context "state has higher value" do
147
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-2-1") }
148
+ let(:results) { {
149
+ :greater => true,
150
+ :greater_or_equal => true,
151
+ :less => false,
152
+ :less_or_equal => false
153
+ } }
154
+ it "evaluates correctly" do
155
+ predicates.each do |predicate_key, predicate|
156
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,36 @@
1
+ require 'smartdown/model/predicate/equality'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::Equality do
5
+ subject(:predicate) { described_class.new("my_var", "some value") }
6
+
7
+ describe "#evaluate" do
8
+ context "state missing expected variable" do
9
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
10
+
11
+ it "raises an UndefinedValue error" do
12
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
13
+ end
14
+ end
15
+
16
+ context "state has expected variable with same value" do
17
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some value") }
18
+
19
+ it "evaluates to true" do
20
+ expect(predicate.evaluate(state)).to eq(true)
21
+ end
22
+ end
23
+
24
+ context "state has expected variable with a different value" do
25
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some other value") }
26
+
27
+ it "evaluates to false" do
28
+ expect(predicate.evaluate(state)).to eq(false)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#humanize" do
34
+ it { expect(predicate.humanize).to eq("my_var == 'some value'") }
35
+ end
36
+ end
@@ -0,0 +1,85 @@
1
+ require 'smartdown/model/predicate/function'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::Function do
5
+ let(:function_name) { "my_function" }
6
+ subject(:predicate) { described_class.new(function_name, []) }
7
+
8
+ describe "#evaluate" do
9
+ context "state missing expected function definition" do
10
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
11
+
12
+ it "raises an UndefinedValue error" do
13
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
14
+ end
15
+ end
16
+
17
+ context "with no arguments" do
18
+ let(:function_return) { "hello" }
19
+ let(:my_function) { ->() { "hello" } }
20
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", function_name => my_function) }
21
+
22
+ it "gets the return value of the function" do
23
+ expect(predicate.evaluate(state)).to eq(function_return)
24
+ end
25
+ end
26
+
27
+ context "with arguments" do
28
+ let(:my_function) { ->(x) { x * 100 } }
29
+ subject(:predicate) { described_class.new(function_name, ["number"]) }
30
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", "number" => 3, function_name => my_function) }
31
+
32
+ it "gets the return value of the function" do
33
+ expect(predicate.evaluate(state)).to eq(300)
34
+ end
35
+ end
36
+
37
+ context "with many arguments" do
38
+ let(:function_return) { "hello" }
39
+ let(:my_function) { ->(name, age) { "Hi #{name}, you were #{age-20}, 20 years ago" } }
40
+ subject(:predicate) { described_class.new(function_name, ["name", "age"]) }
41
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", "name" => "David", "age" => 30, function_name => my_function) }
42
+
43
+ it "gets the return value of the function" do
44
+ expect(predicate.evaluate(state)).to eq("Hi David, you were 10, 20 years ago")
45
+ end
46
+ end
47
+
48
+ context "with nested functions" do
49
+ # nesting looks like: function_1(function_2(5))
50
+ let(:function_1) { ->(x) { x - 1 } }
51
+ let(:function_2) { ->(x) { x * 100 } }
52
+ subject(:predicate) {
53
+ described_class.new("function_1", [
54
+ described_class.new("function_2", ['number'])
55
+ ])
56
+ }
57
+
58
+ let(:state) { Smartdown::Engine::State.new(
59
+ current_node: "n",
60
+ "function_1" => function_1,
61
+ "function_2" => function_2,
62
+ "number" => 5
63
+ ) }
64
+
65
+ it "gets the return value of the function" do
66
+ expect(predicate.evaluate(state)).to eq(499)
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#humanize" do
72
+ context "with no arguments" do
73
+ subject(:predicate) { described_class.new(function_name, []) }
74
+
75
+ it { expect(predicate.humanize).to eq("my_function()") }
76
+ end
77
+
78
+ context "with some arguments" do
79
+ let(:function_name) { "my_function" }
80
+ subject(:predicate) { described_class.new(function_name, ['foo', 'bar']) }
81
+
82
+ it { expect(predicate.humanize).to eq("my_function(foo bar)") }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,41 @@
1
+ require 'smartdown/model/predicate/named'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::Named do
5
+ let(:predicate_name) { "my_pred?" }
6
+ subject(:predicate) { described_class.new(predicate_name) }
7
+
8
+ describe "#evaluate" do
9
+ context "state missing predicate definition" do
10
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
11
+
12
+ it "raises an UndefinedValue error" do
13
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
14
+ end
15
+ end
16
+
17
+ context "state has predicate definition" do
18
+ let(:state) {
19
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true )
20
+ }
21
+
22
+ it "fetches the predicate value from the state" do
23
+ expect(predicate.evaluate(state)).to eq(true)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#humanize" do
29
+ context "predicate name without question mark" do
30
+ subject(:predicate) { described_class.new("my_pred") }
31
+
32
+ it { expect(predicate.humanize).to eq("my_pred?") }
33
+ end
34
+
35
+ context "predicate name with question mark" do
36
+ subject(:predicate) { described_class.new("my_pred?") }
37
+
38
+ it { expect(predicate.humanize).to eq("my_pred?") }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ require 'smartdown/model/predicate/set_membership'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::SetMembership do
5
+ let(:expected_values) { ["v1", "v2", "v3"] }
6
+ let(:varname) { "my_var" }
7
+ subject(:predicate) { described_class.new(varname, expected_values) }
8
+
9
+ describe "#evaluate" do
10
+ context "state missing expected variable" do
11
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
12
+
13
+ it "raises an UndefinedValue error" do
14
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
15
+ end
16
+ end
17
+
18
+ context "state has expected variable with one of the expected values" do
19
+ it "evaluates to true" do
20
+ expected_values.each do |value|
21
+ state = Smartdown::Engine::State.new(current_node: "n", varname => value)
22
+ expect(predicate.evaluate(state)).to eq(true)
23
+ end
24
+ end
25
+ end
26
+
27
+ context "state has expected variable with a value not in the list of expected values" do
28
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", varname => "some other value") }
29
+
30
+ it "evaluates to false" do
31
+ expect(predicate.evaluate(state)).to eq(false)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "#humanize" do
37
+ it { expect(predicate.humanize).to eq("my_var in [v1, v2, v3]") }
38
+ end
39
+ end