smartdown 0.0.2 → 0.0.3
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 +26 -12
- data/bin/smartdown +2 -1
- data/lib/smartdown/engine/node_presenter.rb +38 -0
- data/lib/smartdown/engine.rb +6 -0
- data/lib/smartdown/model/element/conditional.rb +7 -0
- data/lib/smartdown/parser/element/conditional.rb +37 -0
- data/lib/smartdown/parser/element/multiple_choice_question.rb +14 -1
- data/lib/smartdown/parser/node_parser.rb +4 -2
- data/lib/smartdown/parser/node_transform.rb +25 -2
- data/lib/smartdown/version.rb +1 -1
- data/spec/engine/node_presenter_spec.rb +60 -0
- data/spec/engine/state_spec.rb +2 -2
- data/spec/engine_spec.rb +36 -3
- data/spec/fixtures/acceptance/one-question/questions/q1.txt +1 -0
- data/spec/fixtures/acceptance/question-and-outcome/questions/q1.txt +1 -0
- data/spec/parser/element/conditional_spec.rb +154 -0
- data/spec/parser/element/multiple_choice_question_spec.rb +38 -18
- data/spec/parser/node_parser_spec.rb +45 -9
- data/spec/support/model_builder.rb +34 -1
- data/spec/support_specs/model_builder_spec.rb +26 -0
- metadata +10 -3
data/README.md
CHANGED
@@ -78,15 +78,16 @@ question type.
|
|
78
78
|
|
79
79
|
The next sections define the various question types
|
80
80
|
|
81
|
-
###
|
81
|
+
### Radio buttons
|
82
82
|
|
83
83
|
```markdown
|
84
|
-
|
84
|
+
## Will you pass through UK Border Control?
|
85
85
|
|
86
86
|
You might pass through UK Border Control even if you don't leave the airport -
|
87
87
|
eg your bags aren't checked through and you need to collect them before transferring
|
88
88
|
to your outbound flight.
|
89
89
|
|
90
|
+
[choice: uk_border_control]
|
90
91
|
* yes: Yes
|
91
92
|
* no: No
|
92
93
|
```
|
@@ -94,28 +95,33 @@ to your outbound flight.
|
|
94
95
|
### Country (tbd)
|
95
96
|
|
96
97
|
```markdown
|
97
|
-
|
98
|
+
## What passport do you have?
|
98
99
|
|
99
|
-
[country]
|
100
|
+
[country: passport_country]
|
100
101
|
```
|
101
102
|
|
102
103
|
Presents a drop-down list of countries from the built-in list.
|
103
104
|
|
104
105
|
Use front matter to exclude/include countries
|
105
106
|
|
106
|
-
```
|
107
|
-
|
108
|
-
|
107
|
+
```markdown
|
108
|
+
---
|
109
|
+
passport_country:
|
110
|
+
exclude_countries: country1, country2
|
111
|
+
include_countries: {country3: "Country 3", country4: "Country 4"}
|
112
|
+
---
|
109
113
|
|
110
|
-
|
114
|
+
## What passport do you have?
|
115
|
+
|
116
|
+
[country: passport_country]
|
111
117
|
```
|
112
118
|
|
113
119
|
### Date (tbd)
|
114
120
|
|
115
121
|
```markdown
|
116
|
-
|
122
|
+
## What is the baby’s due date?
|
117
123
|
|
118
|
-
[
|
124
|
+
[date: baby_due_date]
|
119
125
|
```
|
120
126
|
|
121
127
|
Asks for a specific date in the given range. Ranges can be expressed as a relative number of years or absolute dates in YYYY-MM-DD format.
|
@@ -139,7 +145,7 @@ Asks for a numerical input which can have decimals and optional thousand-separat
|
|
139
145
|
### Salary (tbd)
|
140
146
|
|
141
147
|
```markdown
|
142
|
-
[salary]
|
148
|
+
[salary: salary_value]
|
143
149
|
```
|
144
150
|
|
145
151
|
Asks for salary which can be expressed as either a weekly or monthly money amount. The user chooses between weekly/monthly
|
@@ -147,8 +153,9 @@ Asks for salary which can be expressed as either a weekly or monthly money amoun
|
|
147
153
|
### Checkbox (tbd)
|
148
154
|
|
149
155
|
```markdown
|
150
|
-
|
156
|
+
## Will you pass through UK Border Control?
|
151
157
|
|
158
|
+
[checkbox: uk_border_control]
|
152
159
|
* [ ] yes: Yes
|
153
160
|
* [ ] no: No
|
154
161
|
```
|
@@ -184,6 +191,13 @@ variable_name is 'string'
|
|
184
191
|
variable_name in {this that the-other}
|
185
192
|
```
|
186
193
|
|
194
|
+
### Date comparison predicates
|
195
|
+
|
196
|
+
```
|
197
|
+
date_variable_name >= '14/07/2014'
|
198
|
+
date_variable_name < '14/07/2014'
|
199
|
+
```
|
200
|
+
|
187
201
|
## Conditional blocks in outcomes (tbd)
|
188
202
|
|
189
203
|
## Processing model
|
data/bin/smartdown
CHANGED
@@ -17,6 +17,7 @@ if ARGV.size == 0
|
|
17
17
|
else
|
18
18
|
coversheet_path, *responses = ARGV
|
19
19
|
begin
|
20
|
+
#TODO: use same object as smartdown-frontend will be using here
|
20
21
|
input = Smartdown::Parser::DirectoryInput.new(coversheet_path)
|
21
22
|
flow = Smartdown::Parser::FlowInterpreter.new(input).interpret
|
22
23
|
engine = Smartdown::Engine.new(flow)
|
@@ -24,7 +25,7 @@ else
|
|
24
25
|
|
25
26
|
puts "RESPONSES: " + end_state.get(:responses).join(" / ")
|
26
27
|
puts "PATH: " + (end_state.get(:path) + [end_state.get(:current_node)]).join(" -> ")
|
27
|
-
node =
|
28
|
+
node = engine.evaluate_node(end_state)
|
28
29
|
puts "# #{node.title}\n\n"
|
29
30
|
node.questions.each do |q|
|
30
31
|
pp q.choices
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'smartdown/engine/predicate_evaluator'
|
2
|
+
|
3
|
+
module Smartdown
|
4
|
+
class Engine
|
5
|
+
class NodePresenter
|
6
|
+
def initialize(predicate_evaluator = nil)
|
7
|
+
@predicate_evaluator = predicate_evaluator || PredicateEvaluator.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def present(node, state)
|
11
|
+
node.dup.tap do |new_node|
|
12
|
+
new_node.elements = resolve_conditionals(node.elements, state)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_accessor :predicate_evaluator
|
18
|
+
|
19
|
+
def evaluate(conditional, state)
|
20
|
+
if predicate_evaluator.evaluate(conditional.predicate, state)
|
21
|
+
conditional.true_case
|
22
|
+
else
|
23
|
+
conditional.false_case
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def resolve_conditionals(elements, state)
|
28
|
+
elements.map do |element|
|
29
|
+
if element.is_a?(Smartdown::Model::Element::Conditional)
|
30
|
+
evaluate(element, state)
|
31
|
+
else
|
32
|
+
element
|
33
|
+
end
|
34
|
+
end.flatten
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/smartdown/engine.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'smartdown/engine/transition'
|
2
2
|
require 'smartdown/engine/state'
|
3
|
+
require 'smartdown/engine/node_presenter'
|
3
4
|
|
4
5
|
module Smartdown
|
5
6
|
class Engine
|
@@ -29,5 +30,10 @@ module Smartdown
|
|
29
30
|
Transition.new(state, current_node, input).next_state
|
30
31
|
end
|
31
32
|
end
|
33
|
+
|
34
|
+
def evaluate_node(state)
|
35
|
+
current_node = flow.node(state.get(:current_node))
|
36
|
+
NodePresenter.new.present(current_node, state)
|
37
|
+
end
|
32
38
|
end
|
33
39
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'smartdown/parser/base'
|
2
|
+
require 'smartdown/parser/node_parser'
|
3
|
+
require 'smartdown/parser/predicates'
|
4
|
+
|
5
|
+
module Smartdown
|
6
|
+
module Parser
|
7
|
+
module Element
|
8
|
+
class Conditional < Base
|
9
|
+
rule(:markdown_block_inside_conditional) {
|
10
|
+
str("$").absent? >> NodeParser.new.markdown_block
|
11
|
+
}
|
12
|
+
|
13
|
+
rule(:markdown_blocks_inside_conditional) {
|
14
|
+
markdown_block_inside_conditional.repeat(1,1) >> (newline.repeat(1) >> markdown_block_inside_conditional).repeat
|
15
|
+
}
|
16
|
+
|
17
|
+
rule(:else_clause) {
|
18
|
+
str("$ELSE") >> optional_space >> newline.repeat(2) >>
|
19
|
+
(markdown_blocks_inside_conditional.as(:false_case) >> newline).maybe
|
20
|
+
}
|
21
|
+
|
22
|
+
rule(:conditional_clause) {
|
23
|
+
(
|
24
|
+
str("$IF ") >>
|
25
|
+
Predicates.new.as(:predicate) >>
|
26
|
+
optional_space >> newline.repeat(2) >>
|
27
|
+
(markdown_blocks_inside_conditional.as(:true_case) >> newline).maybe >>
|
28
|
+
else_clause.maybe >>
|
29
|
+
str("$ENDIF") >> optional_space >> line_ending
|
30
|
+
).as(:conditional)
|
31
|
+
}
|
32
|
+
|
33
|
+
root(:conditional_clause)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -4,6 +4,16 @@ module Smartdown
|
|
4
4
|
module Parser
|
5
5
|
module Element
|
6
6
|
class MultipleChoiceQuestion < Base
|
7
|
+
rule(:multiple_choice_question_tag) {
|
8
|
+
str("[choice:") >>
|
9
|
+
optional_space >>
|
10
|
+
question_identifier.as(:identifier) >>
|
11
|
+
optional_space >>
|
12
|
+
str("]") >>
|
13
|
+
optional_space >>
|
14
|
+
line_ending
|
15
|
+
}
|
16
|
+
|
7
17
|
rule(:option_definition_line) {
|
8
18
|
bullet >>
|
9
19
|
optional_space >>
|
@@ -15,7 +25,10 @@ module Smartdown
|
|
15
25
|
}
|
16
26
|
|
17
27
|
rule(:multiple_choice_question) {
|
18
|
-
(
|
28
|
+
(
|
29
|
+
multiple_choice_question_tag >>
|
30
|
+
(option_definition_line >> line_ending).repeat(1).as(:options)
|
31
|
+
).as(:multiple_choice)
|
19
32
|
}
|
20
33
|
root(:multiple_choice_question)
|
21
34
|
end
|
@@ -5,12 +5,14 @@ require 'smartdown/parser/element/start_button'
|
|
5
5
|
require 'smartdown/parser/element/multiple_choice_question'
|
6
6
|
require 'smartdown/parser/element/markdown_heading'
|
7
7
|
require 'smartdown/parser/element/markdown_paragraph'
|
8
|
+
require 'smartdown/parser/element/conditional'
|
8
9
|
|
9
10
|
module Smartdown
|
10
11
|
module Parser
|
11
12
|
class NodeParser < Base
|
12
13
|
rule(:markdown_block) {
|
13
|
-
Element::
|
14
|
+
Element::Conditional.new |
|
15
|
+
Element::MarkdownHeading.new |
|
14
16
|
Element::MultipleChoiceQuestion.new |
|
15
17
|
Rules.new |
|
16
18
|
Element::StartButton.new |
|
@@ -18,7 +20,7 @@ module Smartdown
|
|
18
20
|
}
|
19
21
|
|
20
22
|
rule(:markdown_blocks) {
|
21
|
-
markdown_block >> (newline.repeat(1) >> markdown_block).repeat
|
23
|
+
markdown_block.repeat(1, 1) >> (newline.repeat(1) >> markdown_block).repeat
|
22
24
|
}
|
23
25
|
|
24
26
|
rule(:body) {
|
@@ -8,6 +8,7 @@ require 'smartdown/model/element/multiple_choice'
|
|
8
8
|
require 'smartdown/model/element/start_button'
|
9
9
|
require 'smartdown/model/element/markdown_heading'
|
10
10
|
require 'smartdown/model/element/markdown_paragraph'
|
11
|
+
require 'smartdown/model/element/conditional'
|
11
12
|
require 'smartdown/model/predicate/equality'
|
12
13
|
require 'smartdown/model/predicate/set_membership'
|
13
14
|
require 'smartdown/model/predicate/named'
|
@@ -49,12 +50,34 @@ module Smartdown
|
|
49
50
|
[value.to_s, label.to_s]
|
50
51
|
}
|
51
52
|
|
52
|
-
rule(:multiple_choice => subtree(:choices)) {
|
53
|
+
rule(:multiple_choice => {identifier: simple(:identifier), options: subtree(:choices)}) {
|
53
54
|
Smartdown::Model::Element::MultipleChoice.new(
|
54
|
-
|
55
|
+
identifier, Hash[choices]
|
55
56
|
)
|
56
57
|
}
|
57
58
|
|
59
|
+
# Conditional with no content in true-case
|
60
|
+
rule(:conditional => {:predicate => subtree(:predicate)}) {
|
61
|
+
Smartdown::Model::Element::Conditional.new(predicate)
|
62
|
+
}
|
63
|
+
|
64
|
+
# Conditional with content in true-case
|
65
|
+
rule(:conditional => {
|
66
|
+
:predicate => subtree(:predicate),
|
67
|
+
:true_case => subtree(:true_case)
|
68
|
+
}) {
|
69
|
+
Smartdown::Model::Element::Conditional.new(predicate, true_case)
|
70
|
+
}
|
71
|
+
|
72
|
+
# Conditional with content in both true-case and false-case
|
73
|
+
rule(:conditional => {
|
74
|
+
:predicate => subtree(:predicate),
|
75
|
+
:true_case => subtree(:true_case),
|
76
|
+
:false_case => subtree(:false_case)
|
77
|
+
}) {
|
78
|
+
Smartdown::Model::Element::Conditional.new(predicate, true_case, false_case)
|
79
|
+
}
|
80
|
+
|
58
81
|
rule(:equality_predicate => { varname: simple(:varname), expected_value: simple(:expected_value) }) {
|
59
82
|
Smartdown::Model::Predicate::Equality.new(varname, expected_value)
|
60
83
|
}
|
data/lib/smartdown/version.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'smartdown/engine/node_presenter'
|
2
|
+
require 'smartdown/engine/state'
|
3
|
+
|
4
|
+
describe Smartdown::Engine::NodePresenter do
|
5
|
+
subject(:node_presenter) { described_class.new }
|
6
|
+
|
7
|
+
context "a node with a conditional" do
|
8
|
+
let(:node) {
|
9
|
+
model_builder.node("outcome_no_visa_needed") do
|
10
|
+
conditional do
|
11
|
+
named_predicate "pred?"
|
12
|
+
true_case do
|
13
|
+
paragraph("True case")
|
14
|
+
end
|
15
|
+
false_case do
|
16
|
+
paragraph("False case")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
}
|
21
|
+
|
22
|
+
context "pred is true" do
|
23
|
+
let(:state) {
|
24
|
+
Smartdown::Engine::State.new(
|
25
|
+
current_node: node.name,
|
26
|
+
pred?: true
|
27
|
+
)
|
28
|
+
}
|
29
|
+
|
30
|
+
let(:expected_node_after_presentation) {
|
31
|
+
model_builder.node("outcome_no_visa_needed") do
|
32
|
+
paragraph("True case")
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
it "should resolve the conditional and preserve the 'True case' paragraph block" do
|
37
|
+
expect(node_presenter.present(node, state)).to eq(expected_node_after_presentation)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "pred is false" do
|
42
|
+
let(:state) {
|
43
|
+
Smartdown::Engine::State.new(
|
44
|
+
current_node: node.name,
|
45
|
+
pred?: false
|
46
|
+
)
|
47
|
+
}
|
48
|
+
|
49
|
+
let(:expected_node_after_presentation) {
|
50
|
+
model_builder.node("outcome_no_visa_needed") do
|
51
|
+
paragraph("False case")
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
it "should resolve the conditional and preserve the 'False case' paragraph block" do
|
56
|
+
expect(node_presenter.present(node, state)).to eq(expected_node_after_presentation)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/engine/state_spec.rb
CHANGED
@@ -19,7 +19,7 @@ describe Smartdown::Engine::State do
|
|
19
19
|
expect { subject.get(:a) }.to raise_error(Smartdown::Engine::UndefinedValue)
|
20
20
|
end
|
21
21
|
|
22
|
-
it "
|
22
|
+
it "is indifferent to symbols and strings" do
|
23
23
|
expect(subject.get(:current_node)).to eq(:start_state)
|
24
24
|
expect(subject.get("current_node")).to eq(:start_state)
|
25
25
|
end
|
@@ -38,7 +38,7 @@ describe Smartdown::Engine::State do
|
|
38
38
|
expect(new_state.get(:a)).to eq 1
|
39
39
|
end
|
40
40
|
|
41
|
-
it "
|
41
|
+
it "is indifferent to symbols and strings" do
|
42
42
|
s2 = subject.put(:b, 1)
|
43
43
|
expect(s2.get(:b)).to eq(1)
|
44
44
|
expect(s2.get("b")).to eq(1)
|
data/spec/engine_spec.rb
CHANGED
@@ -29,6 +29,18 @@ describe Smartdown::Engine do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
node("outcome_no_visa_needed") do
|
34
|
+
conditional do
|
35
|
+
named_predicate "pred?"
|
36
|
+
true_case do
|
37
|
+
paragraph("True case")
|
38
|
+
end
|
39
|
+
false_case do
|
40
|
+
paragraph("False case")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
32
44
|
end
|
33
45
|
}
|
34
46
|
|
@@ -47,11 +59,13 @@ describe Smartdown::Engine do
|
|
47
59
|
context "start button response only" do
|
48
60
|
let(:responses) { %w{yes} }
|
49
61
|
|
50
|
-
it
|
62
|
+
it { should be_a(Smartdown::Engine::State) }
|
63
|
+
|
64
|
+
it "current_node of 'what_passport_do_you_have?'" do
|
51
65
|
expect(subject.get(:current_node)).to eq("what_passport_do_you_have?")
|
52
66
|
end
|
53
67
|
|
54
|
-
it "
|
68
|
+
it "input recorded in state variable corresponding to node name" do
|
55
69
|
expect(subject.get("check-uk-visa")).to eq("yes")
|
56
70
|
end
|
57
71
|
end
|
@@ -59,7 +73,7 @@ describe Smartdown::Engine do
|
|
59
73
|
context "greek passport" do
|
60
74
|
let(:responses) { %w{yes greek} }
|
61
75
|
|
62
|
-
it "is on
|
76
|
+
it "is on outcome_no_visa_needed" do
|
63
77
|
expect(subject.get(:current_node)).to eq("outcome_no_visa_needed")
|
64
78
|
end
|
65
79
|
|
@@ -76,4 +90,23 @@ describe Smartdown::Engine do
|
|
76
90
|
end
|
77
91
|
end
|
78
92
|
end
|
93
|
+
|
94
|
+
describe "#evaluate_node" do
|
95
|
+
let(:current_state) {
|
96
|
+
start_state
|
97
|
+
.put(:current_node, "outcome_no_visa_needed")
|
98
|
+
.put(:pred?, true)
|
99
|
+
}
|
100
|
+
|
101
|
+
let(:expected_node_after_conditional_resolution) {
|
102
|
+
model_builder.node("outcome_no_visa_needed") do
|
103
|
+
paragraph("True case")
|
104
|
+
end
|
105
|
+
}
|
106
|
+
|
107
|
+
it "evaluates the current node of the given state, resolving any conditionals" do
|
108
|
+
expect(engine.evaluate_node(current_state)).to eq(expected_node_after_conditional_resolution)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
79
112
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'smartdown/parser/element/conditional'
|
2
|
+
require 'smartdown/parser/node_interpreter'
|
3
|
+
|
4
|
+
describe Smartdown::Parser::Element::Conditional do
|
5
|
+
|
6
|
+
subject(:parser) { described_class.new }
|
7
|
+
let(:node_name) { "my_node" }
|
8
|
+
|
9
|
+
context "simple IF" do
|
10
|
+
let(:source) { <<-SOURCE
|
11
|
+
$IF pred1?
|
12
|
+
|
13
|
+
#{true_body}
|
14
|
+
|
15
|
+
$ENDIF
|
16
|
+
SOURCE
|
17
|
+
}
|
18
|
+
|
19
|
+
context "with one-line true case" do
|
20
|
+
let(:true_body) { "Text if true" }
|
21
|
+
|
22
|
+
it {
|
23
|
+
should parse(source).as(
|
24
|
+
conditional: {
|
25
|
+
predicate: {named_predicate: "pred1?"},
|
26
|
+
true_case: [{p: "#{true_body}\n"}]
|
27
|
+
}
|
28
|
+
)
|
29
|
+
}
|
30
|
+
|
31
|
+
describe "transformed" do
|
32
|
+
subject(:transformed) {
|
33
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
34
|
+
}
|
35
|
+
|
36
|
+
it {
|
37
|
+
should eq(
|
38
|
+
Smartdown::Model::Element::Conditional.new(
|
39
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
40
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true_body + "\n")]
|
41
|
+
)
|
42
|
+
)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with multi-line true case" do
|
49
|
+
let(:one_line) { "Text if true" }
|
50
|
+
let(:true_body) { "#{one_line}\n\n#{one_line}" }
|
51
|
+
|
52
|
+
it {
|
53
|
+
should parse(source).as(
|
54
|
+
conditional: {
|
55
|
+
predicate: {named_predicate: "pred1?"},
|
56
|
+
true_case: [{p: "#{one_line}\n"}, {p: "#{one_line}\n"}]
|
57
|
+
}
|
58
|
+
)
|
59
|
+
}
|
60
|
+
|
61
|
+
describe "transformed" do
|
62
|
+
subject(:transformed) {
|
63
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
64
|
+
}
|
65
|
+
|
66
|
+
it {
|
67
|
+
should eq(
|
68
|
+
Smartdown::Model::Element::Conditional.new(
|
69
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
70
|
+
[
|
71
|
+
Smartdown::Model::Element::MarkdownParagraph.new(one_line + "\n"),
|
72
|
+
Smartdown::Model::Element::MarkdownParagraph.new(one_line + "\n")
|
73
|
+
]
|
74
|
+
)
|
75
|
+
)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with empty true case case" do
|
81
|
+
let(:true_body) { "" }
|
82
|
+
|
83
|
+
it {
|
84
|
+
should parse(source).as(
|
85
|
+
conditional: {
|
86
|
+
predicate: {named_predicate: "pred1?"}
|
87
|
+
}
|
88
|
+
)
|
89
|
+
}
|
90
|
+
|
91
|
+
describe "transformed" do
|
92
|
+
subject(:transformed) {
|
93
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
94
|
+
}
|
95
|
+
|
96
|
+
it {
|
97
|
+
should eq(
|
98
|
+
Smartdown::Model::Element::Conditional.new(
|
99
|
+
Smartdown::Model::Predicate::Named.new("pred1?")
|
100
|
+
)
|
101
|
+
)
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "simple IF-ELSE" do
|
108
|
+
let(:source) { <<-SOURCE
|
109
|
+
$IF pred1?
|
110
|
+
|
111
|
+
#{true_body}
|
112
|
+
|
113
|
+
$ELSE
|
114
|
+
|
115
|
+
#{false_body}
|
116
|
+
|
117
|
+
$ENDIF
|
118
|
+
SOURCE
|
119
|
+
}
|
120
|
+
|
121
|
+
context "with one-line true case" do
|
122
|
+
let(:true_body) { "Text if true" }
|
123
|
+
let(:false_body) { "Text if false" }
|
124
|
+
|
125
|
+
it {
|
126
|
+
should parse(source).as(
|
127
|
+
conditional: {
|
128
|
+
predicate: {named_predicate: "pred1?"},
|
129
|
+
true_case: [{p: "#{true_body}\n"}],
|
130
|
+
false_case: [{p: "#{false_body}\n"}]
|
131
|
+
}
|
132
|
+
)
|
133
|
+
}
|
134
|
+
|
135
|
+
describe "transformed" do
|
136
|
+
subject(:transformed) {
|
137
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
138
|
+
}
|
139
|
+
|
140
|
+
it {
|
141
|
+
should eq(
|
142
|
+
Smartdown::Model::Element::Conditional.new(
|
143
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
144
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true_body + "\n")],
|
145
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(false_body + "\n")]
|
146
|
+
)
|
147
|
+
)
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
@@ -4,28 +4,48 @@ require 'smartdown/parser/node_interpreter'
|
|
4
4
|
|
5
5
|
describe Smartdown::Parser::Element::MultipleChoiceQuestion do
|
6
6
|
subject(:parser) { described_class.new }
|
7
|
-
let(:source) {
|
8
|
-
[
|
9
|
-
"* yes: Yes",
|
10
|
-
"* no: No"
|
11
|
-
].join("\n")
|
12
|
-
}
|
13
7
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
8
|
+
context "with question tag" do
|
9
|
+
let(:source) {
|
10
|
+
[
|
11
|
+
"[choice: yes_or_no]",
|
12
|
+
"* yes: Yes",
|
13
|
+
"* no: No"
|
14
|
+
].join("\n")
|
15
|
+
}
|
16
|
+
|
17
|
+
it "parses" do
|
18
|
+
should parse(source).as(
|
19
|
+
multiple_choice: {
|
20
|
+
identifier: "yes_or_no",
|
21
|
+
options: [
|
22
|
+
{value: "yes", label: "Yes"},
|
23
|
+
{value: "no", label: "No"}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "transformed" do
|
30
|
+
let(:node_name) { "my_node" }
|
31
|
+
subject(:transformed) {
|
32
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
33
|
+
}
|
34
|
+
|
35
|
+
it { should eq(Smartdown::Model::Element::MultipleChoice.new("yes_or_no", {"yes"=>"Yes", "no"=>"No"})) }
|
36
|
+
end
|
21
37
|
end
|
22
38
|
|
23
|
-
|
24
|
-
let(:
|
25
|
-
|
26
|
-
|
39
|
+
context "without question tag" do
|
40
|
+
let(:source) {
|
41
|
+
[
|
42
|
+
"* yes: Yes",
|
43
|
+
"* no: No"
|
44
|
+
].join("\n")
|
27
45
|
}
|
28
46
|
|
29
|
-
it
|
47
|
+
it "is not parsable" do
|
48
|
+
should_not parse(source)
|
49
|
+
end
|
30
50
|
end
|
31
51
|
end
|
@@ -60,6 +60,7 @@ SOURCE
|
|
60
60
|
<<SOURCE
|
61
61
|
# This is my title
|
62
62
|
|
63
|
+
[choice: my_question]
|
63
64
|
* yes: Yes
|
64
65
|
* no: No
|
65
66
|
SOURCE
|
@@ -69,10 +70,13 @@ SOURCE
|
|
69
70
|
should parse(source).as({
|
70
71
|
body: [
|
71
72
|
{h1: "This is my title"},
|
72
|
-
{multiple_choice:
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
{multiple_choice: {
|
74
|
+
identifier: "my_question",
|
75
|
+
options: [
|
76
|
+
{value: "yes", label: "Yes"},
|
77
|
+
{value: "no", label: "No"}
|
78
|
+
]}
|
79
|
+
}
|
76
80
|
]
|
77
81
|
})
|
78
82
|
end
|
@@ -83,6 +87,7 @@ SOURCE
|
|
83
87
|
<<SOURCE
|
84
88
|
# This is my title
|
85
89
|
|
90
|
+
[choice: my_question]
|
86
91
|
* yes: Yes
|
87
92
|
* no: No
|
88
93
|
|
@@ -96,10 +101,13 @@ SOURCE
|
|
96
101
|
should parse(source).as({
|
97
102
|
body: [
|
98
103
|
{h1: "This is my title"},
|
99
|
-
{multiple_choice:
|
100
|
-
|
101
|
-
|
102
|
-
|
104
|
+
{multiple_choice: {
|
105
|
+
identifier: "my_question",
|
106
|
+
options: [
|
107
|
+
{value: "yes", label: "Yes"},
|
108
|
+
{value: "no", label: "No"}
|
109
|
+
]}
|
110
|
+
},
|
103
111
|
{h1: "Next node rules"},
|
104
112
|
{next_node_rules: [{rule: {predicate: {named_predicate: "pred1?"}, outcome: "outcome"}}]}
|
105
113
|
]
|
@@ -115,7 +123,7 @@ SOURCE
|
|
115
123
|
let(:expected_elements) {
|
116
124
|
[
|
117
125
|
Smartdown::Model::Element::MarkdownHeading.new("This is my title"),
|
118
|
-
Smartdown::Model::Element::MultipleChoice.new(
|
126
|
+
Smartdown::Model::Element::MultipleChoice.new("my_question", "yes"=>"Yes", "no"=>"No"),
|
119
127
|
Smartdown::Model::Element::MarkdownHeading.new("Next node rules"),
|
120
128
|
Smartdown::Model::NextNodeRules.new([
|
121
129
|
Smartdown::Model::Rule.new(Smartdown::Model::Predicate::Named.new("pred1?"), "outcome")
|
@@ -130,4 +138,32 @@ SOURCE
|
|
130
138
|
it { should eq(expected_node_model) }
|
131
139
|
end
|
132
140
|
end
|
141
|
+
|
142
|
+
describe "body with conditional" do
|
143
|
+
let(:source) {
|
144
|
+
<<SOURCE
|
145
|
+
$IF pred1?
|
146
|
+
|
147
|
+
Text when true
|
148
|
+
|
149
|
+
$ELSE
|
150
|
+
|
151
|
+
Text when false
|
152
|
+
|
153
|
+
$ENDIF
|
154
|
+
SOURCE
|
155
|
+
}
|
156
|
+
|
157
|
+
it {
|
158
|
+
should parse(source).as({
|
159
|
+
body: [
|
160
|
+
{conditional: {
|
161
|
+
predicate: {named_predicate: "pred1?"},
|
162
|
+
true_case: [{p: "Text when true\n"}],
|
163
|
+
false_case: [{p: "Text when false\n"}]
|
164
|
+
}}
|
165
|
+
]
|
166
|
+
})
|
167
|
+
}
|
168
|
+
end
|
133
169
|
end
|
@@ -4,6 +4,7 @@ require 'smartdown/model/element/markdown_heading'
|
|
4
4
|
require 'smartdown/model/element/markdown_paragraph'
|
5
5
|
require 'smartdown/model/element/start_button'
|
6
6
|
require 'smartdown/model/element/multiple_choice'
|
7
|
+
require 'smartdown/model/element/conditional'
|
7
8
|
require 'smartdown/model/next_node_rules'
|
8
9
|
require 'smartdown/model/rule'
|
9
10
|
require 'smartdown/model/predicate/named'
|
@@ -67,6 +68,34 @@ class ModelBuilder
|
|
67
68
|
@rules.last
|
68
69
|
end
|
69
70
|
|
71
|
+
def conditional(&block)
|
72
|
+
@predicate = nil
|
73
|
+
@true_case = nil
|
74
|
+
@false_case = nil
|
75
|
+
@elements ||= []
|
76
|
+
instance_eval(&block) if block_given?
|
77
|
+
@elements << Smartdown::Model::Element::Conditional.new(@predicate, @true_case, @false_case)
|
78
|
+
@elements.last
|
79
|
+
end
|
80
|
+
|
81
|
+
def true_case(&block)
|
82
|
+
@outer_elements = @elements
|
83
|
+
@elements = []
|
84
|
+
instance_eval(&block) if block_given?
|
85
|
+
@true_case = @elements
|
86
|
+
@elements = @outer_elements
|
87
|
+
@true_case
|
88
|
+
end
|
89
|
+
|
90
|
+
def false_case(&block)
|
91
|
+
@outer_elements = @elements
|
92
|
+
@elements = []
|
93
|
+
instance_eval(&block) if block_given?
|
94
|
+
@false_case = @elements
|
95
|
+
@elements = @outer_elements
|
96
|
+
@false_case
|
97
|
+
end
|
98
|
+
|
70
99
|
def named_predicate(name)
|
71
100
|
@predicate = Smartdown::Model::Predicate::Named.new(name)
|
72
101
|
end
|
@@ -76,8 +105,12 @@ class ModelBuilder
|
|
76
105
|
end
|
77
106
|
|
78
107
|
module DSL
|
108
|
+
def model_builder
|
109
|
+
ModelBuilder.new
|
110
|
+
end
|
111
|
+
|
79
112
|
def build_flow(name, &block)
|
80
|
-
|
113
|
+
model_builder.flow(name, &block)
|
81
114
|
end
|
82
115
|
end
|
83
116
|
end
|
@@ -62,6 +62,32 @@ describe ModelBuilder do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
describe "#conditional" do
|
66
|
+
let(:expected) {
|
67
|
+
Smartdown::Model::Element::Conditional.new(
|
68
|
+
Smartdown::Model::Predicate::Named.new("pred?"),
|
69
|
+
[Smartdown::Model::Element::MarkdownParagraph.new("True case")],
|
70
|
+
[Smartdown::Model::Element::MarkdownParagraph.new("False case")]
|
71
|
+
)
|
72
|
+
}
|
73
|
+
|
74
|
+
subject(:model) {
|
75
|
+
builder.conditional do
|
76
|
+
named_predicate "pred?"
|
77
|
+
true_case do
|
78
|
+
paragraph("True case")
|
79
|
+
end
|
80
|
+
false_case do
|
81
|
+
paragraph("False case")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
}
|
85
|
+
|
86
|
+
it "builds a conditional" do
|
87
|
+
should eq(expected)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
65
91
|
describe "#rule" do
|
66
92
|
let(:predicate) { Smartdown::Model::Predicate::Named.new("my_pred") }
|
67
93
|
|
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.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- lib/smartdown/parser/directory_input.rb
|
92
92
|
- lib/smartdown/parser/rules.rb
|
93
93
|
- lib/smartdown/parser/node_parser.rb
|
94
|
+
- lib/smartdown/parser/element/conditional.rb
|
94
95
|
- lib/smartdown/parser/element/markdown_paragraph.rb
|
95
96
|
- lib/smartdown/parser/element/start_button.rb
|
96
97
|
- lib/smartdown/parser/element/front_matter.rb
|
@@ -98,6 +99,7 @@ files:
|
|
98
99
|
- lib/smartdown/parser/element/markdown_heading.rb
|
99
100
|
- lib/smartdown/parser/flow_interpreter.rb
|
100
101
|
- lib/smartdown/version.rb
|
102
|
+
- lib/smartdown/engine/node_presenter.rb
|
101
103
|
- lib/smartdown/engine/state.rb
|
102
104
|
- lib/smartdown/engine/transition.rb
|
103
105
|
- lib/smartdown/engine/predicate_evaluator.rb
|
@@ -108,6 +110,7 @@ files:
|
|
108
110
|
- lib/smartdown/model/predicate/equality.rb
|
109
111
|
- lib/smartdown/model/node.rb
|
110
112
|
- lib/smartdown/model/front_matter.rb
|
113
|
+
- lib/smartdown/model/element/conditional.rb
|
111
114
|
- lib/smartdown/model/element/markdown_paragraph.rb
|
112
115
|
- lib/smartdown/model/element/start_button.rb
|
113
116
|
- lib/smartdown/model/element/markdown_heading.rb
|
@@ -126,6 +129,7 @@ files:
|
|
126
129
|
- spec/parser/rules_spec.rb
|
127
130
|
- spec/parser/predicates_spec.rb
|
128
131
|
- spec/parser/element/multiple_choice_question_spec.rb
|
132
|
+
- spec/parser/element/conditional_spec.rb
|
129
133
|
- spec/parser/element/start_button_parser_spec.rb
|
130
134
|
- spec/parser/element/markdown_paragraph_spec.rb
|
131
135
|
- spec/parser/element/markdown_heading_spec.rb
|
@@ -134,6 +138,7 @@ files:
|
|
134
138
|
- spec/support/model_builder.rb
|
135
139
|
- spec/spec_helper.rb
|
136
140
|
- spec/engine/state_spec.rb
|
141
|
+
- spec/engine/node_presenter_spec.rb
|
137
142
|
- spec/engine/predicate_evaluator_spec.rb
|
138
143
|
- spec/engine/transition_spec.rb
|
139
144
|
- spec/fixtures/directory_input/scenarios/s1.txt
|
@@ -166,7 +171,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
166
171
|
version: '0'
|
167
172
|
segments:
|
168
173
|
- 0
|
169
|
-
hash:
|
174
|
+
hash: 1408391630094207002
|
170
175
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
176
|
none: false
|
172
177
|
requirements:
|
@@ -175,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
180
|
version: '0'
|
176
181
|
segments:
|
177
182
|
- 0
|
178
|
-
hash:
|
183
|
+
hash: 1408391630094207002
|
179
184
|
requirements: []
|
180
185
|
rubyforge_project:
|
181
186
|
rubygems_version: 1.8.23
|
@@ -191,6 +196,7 @@ test_files:
|
|
191
196
|
- spec/parser/rules_spec.rb
|
192
197
|
- spec/parser/predicates_spec.rb
|
193
198
|
- spec/parser/element/multiple_choice_question_spec.rb
|
199
|
+
- spec/parser/element/conditional_spec.rb
|
194
200
|
- spec/parser/element/start_button_parser_spec.rb
|
195
201
|
- spec/parser/element/markdown_paragraph_spec.rb
|
196
202
|
- spec/parser/element/markdown_heading_spec.rb
|
@@ -199,6 +205,7 @@ test_files:
|
|
199
205
|
- spec/support/model_builder.rb
|
200
206
|
- spec/spec_helper.rb
|
201
207
|
- spec/engine/state_spec.rb
|
208
|
+
- spec/engine/node_presenter_spec.rb
|
202
209
|
- spec/engine/predicate_evaluator_spec.rb
|
203
210
|
- spec/engine/transition_spec.rb
|
204
211
|
- spec/fixtures/directory_input/scenarios/s1.txt
|