smartdown 0.0.1

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