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