smartdown 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/LICENSE.md +21 -0
  2. data/README.md +204 -0
  3. data/bin/smartdown +41 -0
  4. data/lib/smartdown/engine/errors.rb +5 -0
  5. data/lib/smartdown/engine/predicate_evaluator.rb +23 -0
  6. data/lib/smartdown/engine/state.rb +63 -0
  7. data/lib/smartdown/engine/transition.rb +65 -0
  8. data/lib/smartdown/engine.rb +33 -0
  9. data/lib/smartdown/model/element/markdown_heading.rb +7 -0
  10. data/lib/smartdown/model/element/markdown_paragraph.rb +7 -0
  11. data/lib/smartdown/model/element/multiple_choice.rb +7 -0
  12. data/lib/smartdown/model/element/start_button.rb +7 -0
  13. data/lib/smartdown/model/flow.rb +24 -0
  14. data/lib/smartdown/model/front_matter.rb +37 -0
  15. data/lib/smartdown/model/nested_rule.rb +5 -0
  16. data/lib/smartdown/model/next_node_rules.rb +5 -0
  17. data/lib/smartdown/model/node.rb +47 -0
  18. data/lib/smartdown/model/predicate/equality.rb +7 -0
  19. data/lib/smartdown/model/predicate/named.rb +7 -0
  20. data/lib/smartdown/model/predicate/set_membership.rb +7 -0
  21. data/lib/smartdown/model/rule.rb +5 -0
  22. data/lib/smartdown/parser/base.rb +35 -0
  23. data/lib/smartdown/parser/directory_input.rb +61 -0
  24. data/lib/smartdown/parser/element/front_matter.rb +17 -0
  25. data/lib/smartdown/parser/element/markdown_heading.rb +14 -0
  26. data/lib/smartdown/parser/element/markdown_paragraph.rb +19 -0
  27. data/lib/smartdown/parser/element/multiple_choice_question.rb +24 -0
  28. data/lib/smartdown/parser/element/start_button.rb +15 -0
  29. data/lib/smartdown/parser/flow_interpreter.rb +50 -0
  30. data/lib/smartdown/parser/node_interpreter.rb +29 -0
  31. data/lib/smartdown/parser/node_parser.rb +37 -0
  32. data/lib/smartdown/parser/node_transform.rb +83 -0
  33. data/lib/smartdown/parser/predicates.rb +36 -0
  34. data/lib/smartdown/parser/rules.rb +51 -0
  35. data/lib/smartdown/version.rb +3 -0
  36. data/lib/smartdown.rb +9 -0
  37. data/spec/acceptance/parsing_spec.rb +109 -0
  38. data/spec/acceptance/smartdown_cli_spec.rb +16 -0
  39. data/spec/engine/predicate_evaluator_spec.rb +98 -0
  40. data/spec/engine/state_spec.rb +106 -0
  41. data/spec/engine/transition_spec.rb +150 -0
  42. data/spec/engine_spec.rb +79 -0
  43. data/spec/fixtures/acceptance/cover-sheet/cover-sheet.txt +14 -0
  44. data/spec/fixtures/acceptance/one-question/one-question.txt +3 -0
  45. data/spec/fixtures/acceptance/one-question/questions/q1.txt +9 -0
  46. data/spec/fixtures/acceptance/question-and-outcome/outcomes/o1.txt +3 -0
  47. data/spec/fixtures/acceptance/question-and-outcome/question-and-outcome.txt +3 -0
  48. data/spec/fixtures/acceptance/question-and-outcome/questions/q1.txt +9 -0
  49. data/spec/fixtures/directory_input/cover-sheet.txt +1 -0
  50. data/spec/fixtures/directory_input/outcomes/o1.txt +1 -0
  51. data/spec/fixtures/directory_input/questions/q1.txt +1 -0
  52. data/spec/fixtures/directory_input/scenarios/s1.txt +1 -0
  53. data/spec/fixtures/example.sd +17 -0
  54. data/spec/model/flow_spec.rb +42 -0
  55. data/spec/model/node_spec.rb +32 -0
  56. data/spec/parser/base_spec.rb +49 -0
  57. data/spec/parser/directory_input_spec.rb +56 -0
  58. data/spec/parser/element/front_matter_spec.rb +21 -0
  59. data/spec/parser/element/markdown_heading_spec.rb +24 -0
  60. data/spec/parser/element/markdown_paragraph_spec.rb +28 -0
  61. data/spec/parser/element/multiple_choice_question_spec.rb +31 -0
  62. data/spec/parser/element/start_button_parser_spec.rb +30 -0
  63. data/spec/parser/integration/cover_sheet_spec.rb +30 -0
  64. data/spec/parser/node_parser_spec.rb +133 -0
  65. data/spec/parser/predicates_spec.rb +65 -0
  66. data/spec/parser/rules_spec.rb +244 -0
  67. data/spec/spec_helper.rb +27 -0
  68. data/spec/support/model_builder.rb +83 -0
  69. data/spec/support_specs/model_builder_spec.rb +90 -0
  70. metadata +218 -0
@@ -0,0 +1,31 @@
1
+ require 'smartdown/parser/element/start_button'
2
+ require 'smartdown/parser/node_parser'
3
+ require 'smartdown/parser/node_interpreter'
4
+
5
+ describe Smartdown::Parser::Element::MultipleChoiceQuestion do
6
+ subject(:parser) { described_class.new }
7
+ let(:source) {
8
+ [
9
+ "* yes: Yes",
10
+ "* no: No"
11
+ ].join("\n")
12
+ }
13
+
14
+ it "parses" do
15
+ should parse(source).as(
16
+ multiple_choice: [
17
+ {value: "yes", label: "Yes"},
18
+ {value: "no", label: "No"}
19
+ ]
20
+ )
21
+ end
22
+
23
+ describe "transformed" do
24
+ let(:node_name) { "my_node" }
25
+ subject(:transformed) {
26
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
27
+ }
28
+
29
+ it { should eq(Smartdown::Model::Element::MultipleChoice.new(node_name, {"yes"=>"Yes", "no"=>"No"})) }
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ require 'smartdown/parser/element/start_button'
2
+ require 'smartdown/parser/node_parser'
3
+ require 'smartdown/parser/node_interpreter'
4
+
5
+ describe Smartdown::Parser::Element::StartButton do
6
+ subject(:parser) { described_class.new }
7
+
8
+ it "parses a start indicator with a question identifier" do
9
+ should parse("[start: first_question?]").as({start_button: "first_question?"})
10
+ should parse("[start: first_question?]\n").as({start_button: "first_question?"})
11
+ end
12
+
13
+ it "requires a question identifier" do
14
+ should_not parse("[start]")
15
+ end
16
+
17
+ it "does not allow spaces in question identifier" do
18
+ should_not parse("[start: first question]")
19
+ end
20
+
21
+ describe "transformed" do
22
+ let(:node_name) { "my_node" }
23
+ let(:source) { "[start: first_question?]\n" }
24
+ subject(:transformed) {
25
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
26
+ }
27
+
28
+ it { should eq(Smartdown::Model::Element::StartButton.new("first_question?")) }
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'smartdown/parser/node_interpreter'
2
+ require 'smartdown/model/element/start_button'
3
+
4
+ describe "interpreting a cover sheet containing a start button" do
5
+ let(:source) { <<-SOURCE
6
+ meta_description: My coversheet
7
+
8
+ # Cover sheet
9
+
10
+ This is the cover sheet
11
+
12
+ [start: first_question?]
13
+ SOURCE
14
+ }
15
+ let(:interpreter) { Smartdown::Parser::NodeInterpreter.new("my_node", source) }
16
+ subject(:node) { interpreter.interpret }
17
+
18
+ it "has front_matter" do
19
+ expect(node.front_matter.meta_description).to eq("My coversheet")
20
+ end
21
+
22
+ let(:start_button_element) do
23
+ Smartdown::Model::Element::StartButton.new("first_question?")
24
+ end
25
+
26
+ it "has start button element" do
27
+ expect(node.elements).to include(start_button_element)
28
+ end
29
+
30
+ end
@@ -0,0 +1,133 @@
1
+ require 'smartdown/parser/node_parser'
2
+ require 'smartdown/parser/node_interpreter'
3
+
4
+ describe Smartdown::Parser::NodeParser do
5
+ subject(:parser) { described_class.new }
6
+
7
+ describe "body only" do
8
+ let(:source) {
9
+ <<SOURCE
10
+ # This is my title
11
+
12
+ This is a paragraph of text with stuff
13
+ that flows along
14
+
15
+ Another paragraph of text
16
+ SOURCE
17
+ }
18
+
19
+ it {
20
+ should parse(source).as({
21
+ body: [
22
+ {h1: "This is my title"},
23
+ {p: "This is a paragraph of text with stuff\nthat flows along\n"},
24
+ {p: "Another paragraph of text\n"}
25
+ ]
26
+ })
27
+ }
28
+
29
+ it "parses markdown blocks separated by multiple newlines" do
30
+ expect(parser).to parse("a\n\n\nb\n")
31
+ end
32
+ end
33
+
34
+ describe "front matter and body" do
35
+ let(:source) {
36
+ <<SOURCE
37
+ name: My node
38
+
39
+ # This is my title
40
+
41
+ A paragraph
42
+ SOURCE
43
+ }
44
+
45
+ it do
46
+ should parse(source).as({
47
+ front_matter: [
48
+ {name: "name", value: "My node"}
49
+ ],
50
+ body: [
51
+ {h1: "This is my title"},
52
+ {p: "A paragraph\n"}
53
+ ]
54
+ })
55
+ end
56
+ end
57
+
58
+ describe "body with multiple choice options" do
59
+ let(:source) {
60
+ <<SOURCE
61
+ # This is my title
62
+
63
+ * yes: Yes
64
+ * no: No
65
+ SOURCE
66
+ }
67
+
68
+ it do
69
+ should parse(source).as({
70
+ body: [
71
+ {h1: "This is my title"},
72
+ {multiple_choice: [
73
+ {value: "yes", label: "Yes"},
74
+ {value: "no", label: "No"}
75
+ ]}
76
+ ]
77
+ })
78
+ end
79
+ end
80
+
81
+ describe "body and next_node rules" do
82
+ let(:source) {
83
+ <<SOURCE
84
+ # This is my title
85
+
86
+ * yes: Yes
87
+ * no: No
88
+
89
+ # Next node rules
90
+
91
+ * pred1? => outcome
92
+ SOURCE
93
+ }
94
+
95
+ it {
96
+ should parse(source).as({
97
+ body: [
98
+ {h1: "This is my title"},
99
+ {multiple_choice: [
100
+ {value: "yes", label: "Yes"},
101
+ {value: "no", label: "No"}
102
+ ]},
103
+ {h1: "Next node rules"},
104
+ {next_node_rules: [{rule: {predicate: {named_predicate: "pred1?"}, outcome: "outcome"}}]}
105
+ ]
106
+ })
107
+ }
108
+
109
+ describe "transformed" do
110
+ let(:node_name) { "my_node" }
111
+ subject(:transformed) {
112
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
113
+ }
114
+
115
+ let(:expected_elements) {
116
+ [
117
+ Smartdown::Model::Element::MarkdownHeading.new("This is my title"),
118
+ Smartdown::Model::Element::MultipleChoice.new(node_name, "yes"=>"Yes", "no"=>"No"),
119
+ Smartdown::Model::Element::MarkdownHeading.new("Next node rules"),
120
+ Smartdown::Model::NextNodeRules.new([
121
+ Smartdown::Model::Rule.new(Smartdown::Model::Predicate::Named.new("pred1?"), "outcome")
122
+ ])
123
+ ]
124
+ }
125
+
126
+ let(:expected_node_model) {
127
+ Smartdown::Model::Node.new(node_name, expected_elements)
128
+ }
129
+
130
+ it { should eq(expected_node_model) }
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,65 @@
1
+ require 'smartdown/parser/predicates'
2
+ require 'smartdown/parser/node_interpreter'
3
+
4
+ describe Smartdown::Parser::Predicates do
5
+
6
+ describe "equality predicate" do
7
+ subject(:parser) { described_class.new }
8
+ let(:source) { "varname is 'expected_value'" }
9
+
10
+ it { should parse(source).as(equality_predicate: {varname: "varname", expected_value: "expected_value"}) }
11
+ it { should_not parse("v is value") }
12
+ it { should_not parse("v is 'a thing's thing'") }
13
+ it { should_not parse("v is 'a thing\\'s thing'") }
14
+ it { should_not parse(%q{v is "a thing"}) }
15
+
16
+ describe "transformed" do
17
+ let(:node_name) { "my_node" }
18
+ subject(:transformed) {
19
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
20
+ }
21
+
22
+ it { should eq(Smartdown::Model::Predicate::Equality.new("varname", "expected_value")) }
23
+ end
24
+ end
25
+
26
+ describe "set membership predicate" do
27
+ subject(:parser) { described_class.new }
28
+ let(:source) { "varname in {a b c}" }
29
+
30
+ it { should parse(source).as(set_membership_predicate: {varname: "varname", values: [{set_value: "a"}, {set_value: "b"}, {set_value: "c"}]}) }
31
+ it { should parse('v in {}').as(set_membership_predicate: {varname: "v", values: nil}) }
32
+ it { should parse('v in { }').as(set_membership_predicate: {varname: "v", values: nil}) }
33
+ it { should parse('v in { }').as(set_membership_predicate: {varname: "v", values: nil}) }
34
+ it { should_not parse('vin {}') }
35
+ it { should_not parse("v in {\n}") }
36
+
37
+ describe "transformed" do
38
+ let(:node_name) { "my_node" }
39
+ subject(:transformed) {
40
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
41
+ }
42
+
43
+ it { should eq(Smartdown::Model::Predicate::SetMembership.new("varname", ["a", "b", "c"])) }
44
+ end
45
+ end
46
+
47
+ describe "named predicate" do
48
+ subject(:parser) { described_class.new }
49
+
50
+ it { should parse("my_pred").as(named_predicate: "my_pred") }
51
+ it { should parse("my_pred?").as(named_predicate: "my_pred?") }
52
+ it { should_not parse("my pred") }
53
+
54
+ describe "transformed" do
55
+ let(:node_name) { "my_node" }
56
+ let(:source) { "my_pred" }
57
+ subject(:transformed) {
58
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
59
+ }
60
+
61
+ it { should eq(Smartdown::Model::Predicate::Named.new("my_pred")) }
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,244 @@
1
+ require 'smartdown/parser/rules'
2
+ require 'smartdown/parser/node_interpreter'
3
+
4
+ describe Smartdown::Parser::Rules do
5
+ subject(:parser) { described_class.new }
6
+ let(:node_name) { "my_node" }
7
+
8
+ describe "simple rule" do
9
+ let(:source) { "* my_pred? => outcome" }
10
+
11
+ it { should parse(source).as({next_node_rules: [{rule: {predicate: {named_predicate: "my_pred?"}, outcome: "outcome"}}]}) }
12
+ it { should parse("* p? => q?").as({next_node_rules: [{rule: {predicate: {named_predicate: "p?"}, outcome: "q?"}}]}) }
13
+
14
+ describe "transformed" do
15
+ subject(:transformed) {
16
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
17
+ }
18
+
19
+ it { should eq(
20
+ Smartdown::Model::NextNodeRules.new(
21
+ [
22
+ Smartdown::Model::Rule.new(
23
+ Smartdown::Model::Predicate::Named.new("my_pred?"),
24
+ "outcome"
25
+ )
26
+ ]
27
+ )
28
+ ) }
29
+ end
30
+ end
31
+
32
+ describe "nested rules" do
33
+ describe "one inner rule" do
34
+ let(:child_rule) { "* pred2? => outcome"}
35
+ let(:source) {
36
+ [
37
+ "* pred1?",
38
+ " " + child_rule
39
+ ].join("\n")
40
+ }
41
+ let(:parsed_child_rule) { parser.one_top_level_rule.parse(child_rule) }
42
+
43
+ it { should parse(source).as(next_node_rules: [{nested_rule: {predicate: {named_predicate: "pred1?"}, child_rules: [parsed_child_rule]}}]) }
44
+
45
+ describe "transformed" do
46
+ subject(:transformed) {
47
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
48
+ }
49
+
50
+ it {
51
+ should eq(
52
+ Smartdown::Model::NextNodeRules.new(
53
+ [
54
+ Smartdown::Model::NestedRule.new(
55
+ Smartdown::Model::Predicate::Named.new("pred1?"),
56
+ [
57
+ Smartdown::Model::Rule.new(
58
+ Smartdown::Model::Predicate::Named.new("pred2?"),
59
+ "outcome"
60
+ )
61
+ ]
62
+ )
63
+ ]
64
+ )
65
+ )
66
+ }
67
+ end
68
+ end
69
+
70
+ describe "two inner rules" do
71
+ let(:source) {
72
+ [
73
+ "* pred1?",
74
+ " * pred2? => outcome1",
75
+ " * pred3? => outcome2"
76
+ ].join("\n")
77
+ }
78
+ let(:parsed_child1) { parser.one_top_level_rule.parse("* pred2? => outcome1") }
79
+ let(:parsed_child2) { parser.one_top_level_rule.parse("* pred3? => outcome2") }
80
+
81
+ it {
82
+ should parse(source).as(
83
+ next_node_rules: [
84
+ {
85
+ nested_rule: {
86
+ predicate: { named_predicate: "pred1?" },
87
+ child_rules: [parsed_child1, parsed_child2]
88
+ }
89
+ }
90
+ ]
91
+ )
92
+ }
93
+
94
+ describe "transformed" do
95
+ subject(:transformed) {
96
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
97
+ }
98
+
99
+ it {
100
+ should eq(
101
+ Smartdown::Model::NextNodeRules.new(
102
+ [
103
+ Smartdown::Model::NestedRule.new(
104
+ Smartdown::Model::Predicate::Named.new("pred1?"),
105
+ [
106
+ Smartdown::Model::Rule.new(
107
+ Smartdown::Model::Predicate::Named.new("pred2?"),
108
+ "outcome1"
109
+ ),
110
+ Smartdown::Model::Rule.new(
111
+ Smartdown::Model::Predicate::Named.new("pred3?"),
112
+ "outcome2"
113
+ )
114
+ ]
115
+ )
116
+ ]
117
+ )
118
+ )
119
+ }
120
+ end
121
+ end
122
+
123
+ describe "combination of nested and non-nested rules" do
124
+ let(:child_rule) { "* pred2? => outcome"}
125
+ let(:source) {
126
+ [
127
+ "* pred1?",
128
+ " * pred2? => outcome1_2",
129
+ "* pred3? => outcome3"
130
+ ].join("\n")
131
+ }
132
+ let(:parsed_child_1) { parser.one_top_level_rule.parse("* pred2? => outcome1_2") }
133
+ let(:parsed_child_2) { parser.one_top_level_rule.parse("* pred3? => outcome3") }
134
+
135
+ it {
136
+ should parse(source).as(
137
+ next_node_rules: [
138
+ {
139
+ nested_rule: {
140
+ predicate: { named_predicate: "pred1?" },
141
+ child_rules: [parsed_child_1]
142
+ }
143
+ },
144
+ parsed_child_2
145
+ ]
146
+ )
147
+ }
148
+
149
+ describe "transformed" do
150
+ subject(:transformed) {
151
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
152
+ }
153
+
154
+ it {
155
+ should eq(
156
+ Smartdown::Model::NextNodeRules.new(
157
+ [
158
+ Smartdown::Model::NestedRule.new(
159
+ Smartdown::Model::Predicate::Named.new("pred1?"),
160
+ [
161
+ Smartdown::Model::Rule.new(
162
+ Smartdown::Model::Predicate::Named.new("pred2?"),
163
+ "outcome1_2"
164
+ )
165
+ ]
166
+ ),
167
+ Smartdown::Model::Rule.new(
168
+ Smartdown::Model::Predicate::Named.new("pred3?"),
169
+ "outcome3"
170
+ )
171
+ ]
172
+ )
173
+ )
174
+ }
175
+ end
176
+ end
177
+
178
+ describe "nested rule in a nested rule" do
179
+ let(:child_rule) { "* pred2? => outcome"}
180
+ let(:source) {
181
+ [
182
+ "* pred1?",
183
+ " * pred2?",
184
+ " " + child_rule
185
+ ].join("\n")
186
+ }
187
+ let(:parsed_child_rule) { parser.one_top_level_rule.parse(child_rule) }
188
+
189
+ it {
190
+ should parse(source, trace: true).as(
191
+ next_node_rules: [
192
+ {
193
+ nested_rule: {
194
+ predicate: {named_predicate: "pred1?"},
195
+ child_rules: [
196
+ nested_rule: {
197
+ predicate: {named_predicate: "pred2?"},
198
+ child_rules: [parsed_child_rule]
199
+ }
200
+ ]
201
+ }
202
+ }
203
+ ]
204
+ )
205
+ }
206
+
207
+ describe "transformed" do
208
+ subject(:transformed) {
209
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
210
+ }
211
+
212
+ it {
213
+ should eq(
214
+ Smartdown::Model::NextNodeRules.new(
215
+ [
216
+ Smartdown::Model::NestedRule.new(
217
+ Smartdown::Model::Predicate::Named.new("pred1?"),
218
+ [
219
+ Smartdown::Model::NestedRule.new(
220
+ Smartdown::Model::Predicate::Named.new("pred2?"),
221
+ [
222
+ Smartdown::Model::Rule.new(
223
+ Smartdown::Model::Predicate::Named.new("pred2?"),
224
+ "outcome"
225
+ )
226
+ ]
227
+ )
228
+ ]
229
+ )
230
+ ]
231
+ )
232
+ )
233
+ }
234
+ end
235
+ end
236
+
237
+ describe "missing inner rule" do
238
+ let(:source) { "* pred1?" }
239
+
240
+ it { should_not parse(source) }
241
+ end
242
+ end
243
+ end
244
+
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
2
+
3
+ require 'pathname'
4
+ require 'parslet/rig/rspec'
5
+ require 'support/model_builder'
6
+
7
+ RSpec.configure do |config|
8
+ if config.files_to_run.one?
9
+ config.full_backtrace = false
10
+ config.default_formatter = 'doc'
11
+ end
12
+
13
+ config.order = :random
14
+
15
+ Kernel.srand config.seed
16
+
17
+ config.expect_with :rspec do |expectations|
18
+ expectations.syntax = :expect
19
+ end
20
+
21
+ config.mock_with :rspec do |mocks|
22
+ mocks.syntax = :expect
23
+ mocks.verify_partial_doubles = true
24
+ end
25
+
26
+ config.include ModelBuilder::DSL
27
+ end
@@ -0,0 +1,83 @@
1
+ require 'smartdown/model/flow'
2
+ require 'smartdown/model/node'
3
+ require 'smartdown/model/element/markdown_heading'
4
+ require 'smartdown/model/element/markdown_paragraph'
5
+ require 'smartdown/model/element/start_button'
6
+ require 'smartdown/model/element/multiple_choice'
7
+ require 'smartdown/model/next_node_rules'
8
+ require 'smartdown/model/rule'
9
+ require 'smartdown/model/predicate/named'
10
+ require 'smartdown/model/predicate/equality'
11
+ require 'smartdown/model/predicate/set_membership'
12
+
13
+ class ModelBuilder
14
+ def flow(name, &block)
15
+ @nodes = []
16
+ instance_eval(&block) if block_given?
17
+ Smartdown::Model::Flow.new(name, @nodes)
18
+ end
19
+
20
+ def node(name, &block)
21
+ @nodes ||= []
22
+ @elements = []
23
+ @front_matter = nil
24
+ instance_eval(&block) if block_given?
25
+ @nodes << Smartdown::Model::Node.new(name, @elements, @front_matter)
26
+ @nodes.last
27
+ end
28
+
29
+ def heading(content)
30
+ @elements ||= []
31
+ @elements << Smartdown::Model::Element::MarkdownHeading.new(content)
32
+ @elements.last
33
+ end
34
+
35
+ def paragraph(content)
36
+ @elements ||= []
37
+ @elements << Smartdown::Model::Element::MarkdownParagraph.new(content)
38
+ @elements.last
39
+ end
40
+
41
+ def start_button(content)
42
+ @elements ||= []
43
+ @elements << Smartdown::Model::Element::StartButton.new(content)
44
+ @elements.last
45
+ end
46
+
47
+ def multiple_choice(options)
48
+ @elements ||= []
49
+ options_with_string_keys = ::Hash[options.map {|k,v| [k.to_s, v]}]
50
+ @elements << Smartdown::Model::Element::MultipleChoice.new(nil, options_with_string_keys)
51
+ @elements.last
52
+ end
53
+
54
+ def next_node_rules(&block)
55
+ @rules = []
56
+ instance_eval(&block) if block_given?
57
+ @elements << Smartdown::Model::NextNodeRules.new(@rules)
58
+ @elements.last
59
+ end
60
+
61
+ def rule(predicate = nil, outcome = nil, &block)
62
+ @predicate = predicate
63
+ @outcome = outcome
64
+ @rules ||= []
65
+ instance_eval(&block) if block_given?
66
+ @rules << Smartdown::Model::Rule.new(@predicate, @outcome)
67
+ @rules.last
68
+ end
69
+
70
+ def named_predicate(name)
71
+ @predicate = Smartdown::Model::Predicate::Named.new(name)
72
+ end
73
+
74
+ def outcome(name)
75
+ @outcome = name
76
+ end
77
+
78
+ module DSL
79
+ def build_flow(name, &block)
80
+ ModelBuilder.new.flow(name, &block)
81
+ end
82
+ end
83
+ end