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,61 @@
1
+ require 'pathname'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ class DirectoryInput
6
+ def initialize(coversheet_path)
7
+ @coversheet_path = Pathname.new(coversheet_path.to_s)
8
+ end
9
+
10
+ def coversheet
11
+ InputFile.new(@coversheet_path)
12
+ end
13
+
14
+ def questions
15
+ read_dir("questions")
16
+ end
17
+
18
+ def outcomes
19
+ read_dir("outcomes")
20
+ end
21
+
22
+ def scenarios
23
+ read_dir("scenarios")
24
+ end
25
+
26
+ def filenames_hash
27
+ {
28
+ coversheet: coversheet.to_s,
29
+ questions: questions.map(&:to_s),
30
+ outcomes: outcomes.map(&:to_s),
31
+ scenarios: scenarios.map(&:to_s)
32
+ }
33
+ end
34
+
35
+ private
36
+ def read_dir(dir)
37
+ Dir[@coversheet_path.dirname + dir + "*.txt"].map do |filename|
38
+ InputFile.new(filename)
39
+ end
40
+ end
41
+ end
42
+
43
+ class InputFile
44
+ def initialize(path)
45
+ @path = Pathname.new(path.to_s)
46
+ end
47
+
48
+ def name
49
+ @path.basename.to_s.split(".").first
50
+ end
51
+
52
+ def read
53
+ File.read(@path)
54
+ end
55
+
56
+ def to_s
57
+ @path.to_s
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,17 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class FrontMatter < Base
7
+ rule(:front_matter_line) {
8
+ identifier.as(:name) >> str(":") >> ws >> whitespace_terminated_string.as(:value) >> line_ending
9
+ }
10
+ rule(:front_matter) {
11
+ front_matter_line.repeat(1).as(:front_matter)
12
+ }
13
+ root(:front_matter)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class MarkdownHeading < Base
7
+ rule(:markdown_heading) {
8
+ str('# ') >> (whitespace_terminated_string).as(:h1) >> optional_space >> line_ending
9
+ }
10
+ root(:markdown_heading)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class MarkdownParagraph < Base
7
+ rule(:markdown_line) {
8
+ optional_space >> whitespace_terminated_string >> optional_space
9
+ }
10
+
11
+ rule(:markdown_paragraph) {
12
+ (markdown_line >> line_ending).repeat(1).as(:p)
13
+ }
14
+
15
+ root(:markdown_paragraph)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class MultipleChoiceQuestion < Base
7
+ rule(:option_definition_line) {
8
+ bullet >>
9
+ optional_space >>
10
+ identifier.as(:value) >>
11
+ str(":") >>
12
+ optional_space >>
13
+ whitespace_terminated_string.as(:label) >>
14
+ optional_space
15
+ }
16
+
17
+ rule(:multiple_choice_question) {
18
+ (option_definition_line >> line_ending).repeat(1).as(:multiple_choice)
19
+ }
20
+ root(:multiple_choice_question)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class StartButton < Base
7
+ rule(:start_button) {
8
+ str('[start: ') >> question_identifier.as(:start_button) >> str(']') >> line_ending
9
+ }
10
+
11
+ root(:start_button)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ require 'smartdown/model/flow'
2
+ require 'smartdown/parser/node_interpreter'
3
+
4
+ module Smartdown
5
+ module Parser
6
+ class ParseError < StandardError
7
+ attr_reader :filename, :parse_error
8
+
9
+ def initialize(filename, parse_error)
10
+ @filename = filename
11
+ @parse_error = parse_error
12
+ end
13
+
14
+ def to_s(full = true)
15
+ "Parse error in '#{filename}':\n\n" + @parse_error.cause.ascii_tree
16
+ end
17
+ end
18
+
19
+ class FlowInterpreter
20
+ attr_reader :flow_input
21
+
22
+ def initialize(flow_input)
23
+ @flow_input = flow_input
24
+ end
25
+
26
+ def interpret
27
+ Smartdown::Model::Flow.new(coversheet.name, [coversheet] + questions + outcomes)
28
+ end
29
+
30
+ private
31
+ def coversheet
32
+ interpret_node(flow_input.coversheet)
33
+ end
34
+
35
+ def questions
36
+ flow_input.questions.map { |i| interpret_node(i) }
37
+ end
38
+
39
+ def outcomes
40
+ flow_input.outcomes.map { |i| interpret_node(i) }
41
+ end
42
+
43
+ def interpret_node(input_data)
44
+ Smartdown::Parser::NodeInterpreter.new(input_data.name, input_data.read).interpret
45
+ rescue Parslet::ParseFailed => error
46
+ raise ParseError.new(input_data.to_s, error)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ require 'pathname'
2
+ require 'smartdown/model/flow'
3
+ require 'smartdown/model/node'
4
+ require 'smartdown/parser/node_parser'
5
+ require 'smartdown/parser/node_transform'
6
+
7
+ module Smartdown
8
+ module Parser
9
+ class NodeInterpreter
10
+ attr_reader :name, :source
11
+
12
+ def initialize(name, source, options = {})
13
+ @name = name
14
+ @source = source
15
+ @parser = options.fetch(:parser, Smartdown::Parser::NodeParser.new)
16
+ @transform = options.fetch(:transform, Smartdown::Parser::NodeTransform.new)
17
+ end
18
+
19
+ def interpret
20
+ transform.apply(parser.parse(source),
21
+ node_name: name
22
+ )
23
+ end
24
+
25
+ private
26
+ attr_reader :parser, :transform
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ require 'smartdown/parser/base'
2
+ require 'smartdown/parser/rules'
3
+ require 'smartdown/parser/element/front_matter'
4
+ require 'smartdown/parser/element/start_button'
5
+ require 'smartdown/parser/element/multiple_choice_question'
6
+ require 'smartdown/parser/element/markdown_heading'
7
+ require 'smartdown/parser/element/markdown_paragraph'
8
+
9
+ module Smartdown
10
+ module Parser
11
+ class NodeParser < Base
12
+ rule(:markdown_block) {
13
+ Element::MarkdownHeading.new |
14
+ Element::MultipleChoiceQuestion.new |
15
+ Rules.new |
16
+ Element::StartButton.new |
17
+ Element::MarkdownParagraph.new
18
+ }
19
+
20
+ rule(:markdown_blocks) {
21
+ markdown_block >> (newline.repeat(1) >> markdown_block).repeat
22
+ }
23
+
24
+ rule(:body) {
25
+ markdown_blocks.as(:body)
26
+ }
27
+
28
+ rule(:flow) {
29
+ Element::FrontMatter.new >> newline.repeat(1) >> body |
30
+ Element::FrontMatter.new |
31
+ ws >> body
32
+ }
33
+
34
+ root(:flow)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,83 @@
1
+ require 'parslet/transform'
2
+ require 'smartdown/model/node'
3
+ require 'smartdown/model/front_matter'
4
+ require 'smartdown/model/rule'
5
+ require 'smartdown/model/nested_rule'
6
+ require 'smartdown/model/next_node_rules'
7
+ require 'smartdown/model/element/multiple_choice'
8
+ require 'smartdown/model/element/start_button'
9
+ require 'smartdown/model/element/markdown_heading'
10
+ require 'smartdown/model/element/markdown_paragraph'
11
+ require 'smartdown/model/predicate/equality'
12
+ require 'smartdown/model/predicate/set_membership'
13
+ require 'smartdown/model/predicate/named'
14
+
15
+ module Smartdown
16
+ module Parser
17
+ class NodeTransform < Parslet::Transform
18
+ rule(body: subtree(:body)) {
19
+ Smartdown::Model::Node.new(
20
+ node_name, body, Smartdown::Model::FrontMatter.new({})
21
+ )
22
+ }
23
+
24
+ rule(h1: simple(:content)) {
25
+ Smartdown::Model::Element::MarkdownHeading.new(content)
26
+ }
27
+
28
+ rule(p: simple(:content)) {
29
+ Smartdown::Model::Element::MarkdownParagraph.new(content)
30
+ }
31
+
32
+ rule(:start_button => simple(:start_node)) {
33
+ Smartdown::Model::Element::StartButton.new(start_node)
34
+ }
35
+
36
+ rule(:front_matter => subtree(:attrs), body: subtree(:body)) {
37
+ Smartdown::Model::Node.new(
38
+ node_name, body, Smartdown::Model::FrontMatter.new(Hash[attrs])
39
+ )
40
+ }
41
+ rule(:front_matter => subtree(:attrs)) {
42
+ [Smartdown::Model::FrontMatter.new(Hash[attrs])]
43
+ }
44
+ rule(:name => simple(:name), :value => simple(:value)) {
45
+ [name.to_s, value.to_s]
46
+ }
47
+
48
+ rule(:value => simple(:value), :label => simple(:label)) {
49
+ [value.to_s, label.to_s]
50
+ }
51
+
52
+ rule(:multiple_choice => subtree(:choices)) {
53
+ Smartdown::Model::Element::MultipleChoice.new(
54
+ node_name, Hash[choices]
55
+ )
56
+ }
57
+
58
+ rule(:equality_predicate => { varname: simple(:varname), expected_value: simple(:expected_value) }) {
59
+ Smartdown::Model::Predicate::Equality.new(varname, expected_value)
60
+ }
61
+
62
+ rule(:set_value => simple(:value)) { value }
63
+ rule(:set_membership_predicate => { varname: simple(:varname), values: subtree(:values) }) {
64
+ Smartdown::Model::Predicate::SetMembership.new(varname, values)
65
+ }
66
+
67
+ rule(:named_predicate => simple(:name) ) {
68
+ Smartdown::Model::Predicate::Named.new(name)
69
+ }
70
+
71
+ rule(:rule => {predicate: subtree(:predicate), outcome: simple(:outcome_name) } ) {
72
+ Smartdown::Model::Rule.new(predicate, outcome_name)
73
+ }
74
+ rule(:nested_rule => {predicate: subtree(:predicate), child_rules: subtree(:child_rules) } ) {
75
+ Smartdown::Model::NestedRule.new(predicate, child_rules)
76
+ }
77
+ rule(:next_node_rules => subtree(:rules)) {
78
+ Smartdown::Model::NextNodeRules.new(rules)
79
+ }
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,36 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ class Predicates < Base
6
+ rule(:equality_predicate) {
7
+ identifier.as(:varname) >> some_space >>
8
+ str('is') >> some_space >>
9
+ str("'") >> match("[^']").repeat.as(:expected_value) >> str("'")
10
+ }
11
+
12
+ rule(:set_value) {
13
+ match('[^\s}]').repeat(1).as(:set_value)
14
+ }
15
+
16
+ rule(:set_values) {
17
+ (set_value >> some_space).repeat >> set_value
18
+ }
19
+
20
+ rule(:set_membership_predicate) {
21
+ identifier.as(:varname) >> some_space >>
22
+ str('in') >> some_space >>
23
+ str("{") >> optional_space >> set_values.maybe.as(:values) >> optional_space >> str("}")
24
+ }
25
+
26
+ rule(:named_predicate) {
27
+ question_identifier.as(:named_predicate)
28
+ }
29
+ rule(:predicates) {
30
+ equality_predicate.as(:equality_predicate) | set_membership_predicate.as(:set_membership_predicate) | named_predicate
31
+ }
32
+
33
+ root(:predicates)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,51 @@
1
+ require 'smartdown/parser/base'
2
+ require 'smartdown/parser/predicates'
3
+
4
+ module Smartdown
5
+ module Parser
6
+ class Rules < Base
7
+ def indent(depth)
8
+ str(' ').repeat(depth).capture(:indent)
9
+ end
10
+
11
+ def conditional_line(depth)
12
+ indent(depth) >> bullet >> optional_space >> Smartdown::Parser::Predicates.new.as(:predicate) >> optional_space >> line_ending
13
+ end
14
+
15
+ def children
16
+ dynamic do |s,c|
17
+ current_indent = c.captures[:indent].size
18
+ condition_with_children_or_rule(current_indent + 1).repeat(1)
19
+ end.as(:child_rules)
20
+ end
21
+
22
+ def condition_with_children(depth)
23
+ conditional_line(depth) >> children
24
+ end
25
+
26
+ def rule(depth)
27
+ (
28
+ indent(depth) >> bullet >> optional_space >>
29
+ Smartdown::Parser::Predicates.new.as(:predicate) >> optional_space >>
30
+ str("=>") >> optional_space >>
31
+ question_identifier.as(:outcome) >>
32
+ optional_space >> line_ending
33
+ ).as(:rule)
34
+ end
35
+
36
+ def condition_with_children_or_rule(depth)
37
+ condition_with_children(depth).as(:nested_rule) | rule(depth)
38
+ end
39
+
40
+ rule(:one_top_level_rule) {
41
+ condition_with_children_or_rule(0)
42
+ }
43
+
44
+ rule(:rules) {
45
+ one_top_level_rule.repeat(1).as(:next_node_rules)
46
+ }
47
+
48
+ root(:rules)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module Smartdown
2
+ VERSION = "0.0.1"
3
+ end
data/lib/smartdown.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'smartdown/parser/flow_interpreter'
2
+ require 'smartdown/parser/directory_input'
3
+
4
+ module Smartdown
5
+ def self.parse(coversheet_file)
6
+ input = Smartdown::Parser::DirectoryInput.new(coversheet_file)
7
+ Smartdown::Parser::FlowInterpreter.new(input).interpret
8
+ end
9
+ end
@@ -0,0 +1,109 @@
1
+ require 'smartdown.rb'
2
+
3
+ describe "Smartdown.parse" do
4
+
5
+ def fixture(name)
6
+ File.dirname(__FILE__) + "/../fixtures/acceptance/#{name}/#{name}.txt"
7
+ end
8
+
9
+ context "flow with only a cover-sheet" do
10
+ subject(:flow) { Smartdown.parse(fixture("cover-sheet")) }
11
+
12
+ it "builds a Flow model" do
13
+ expect(flow).to be_a(Smartdown::Model::Flow)
14
+ end
15
+
16
+ it "derives the name from the filename" do
17
+ expect(flow.name).to eq("cover-sheet")
18
+ end
19
+
20
+ it "has a single coversheet node" do
21
+ expect(flow.nodes).to match([instance_of(Smartdown::Model::Node)])
22
+ end
23
+
24
+ describe "#coversheet" do
25
+ subject(:coversheet) { flow.coversheet }
26
+
27
+ it "returns the coversheet node" do
28
+ expect(flow.coversheet).to be_a(Smartdown::Model::Node)
29
+ end
30
+
31
+ it "has front matter" do
32
+ expect(coversheet.front_matter).to be_a(Smartdown::Model::FrontMatter)
33
+ end
34
+
35
+ describe "front matter" do
36
+ it "has satisfies_need" do
37
+ expect(coversheet.front_matter.satisfies_need).to eq("1234")
38
+ end
39
+
40
+ it "has meta_description" do
41
+ expect(coversheet.front_matter.meta_description).to eq("Blah blah")
42
+ end
43
+ end
44
+
45
+ it "has a title extracted from the first markdown H1" do
46
+ expect(coversheet.title).to eq("My coversheet")
47
+ end
48
+
49
+ it "has a body" do
50
+ expect(coversheet.body).to eq(<<-EXPECTED)
51
+ This is the body markdown.
52
+
53
+ It has many paragraphs
54
+ of text.
55
+
56
+ * it
57
+ * can
58
+ * have
59
+ * lists
60
+ EXPECTED
61
+ end
62
+ end
63
+ end
64
+
65
+ context "flow with a cover-sheet and a question" do
66
+ subject(:flow) { Smartdown.parse(fixture("one-question")) }
67
+
68
+ it "has two nodes" do
69
+ expect(flow.nodes.size).to eq(2)
70
+ expect(flow.nodes[0]).to eq(flow.coversheet)
71
+ expect(flow.nodes[1]).to be_a(Smartdown::Model::Node)
72
+ end
73
+
74
+ describe "the question node" do
75
+ subject(:question_node) { flow.nodes[1] }
76
+
77
+ it "has a title" do
78
+ expect(question_node.title).to eq("Question one")
79
+ end
80
+
81
+ it "has two body paras" do
82
+ expect(question_node.body).to eq(<<-EXPECTED)
83
+ Body text line 1.
84
+
85
+ Body text
86
+ para 2.
87
+ EXPECTED
88
+ end
89
+
90
+ it "has a multiple choice question" do
91
+ expect(question_node.questions).to match([instance_of(Smartdown::Model::Element::MultipleChoice)])
92
+ end
93
+ end
94
+ end
95
+
96
+ context "flow coversheet, question and outcome" do
97
+ subject(:flow) { Smartdown.parse(fixture("question-and-outcome")) }
98
+
99
+ it "has three nodes" do
100
+ expect(flow.nodes.size).to eq(3)
101
+ end
102
+
103
+ it "has node names" do
104
+ expect(flow.nodes.map(&:name)).to eq(["question-and-outcome", "q1", "o1"])
105
+ end
106
+ end
107
+ end
108
+
109
+
@@ -0,0 +1,16 @@
1
+ describe "bin/smartdown" do
2
+ subject(:the_executable) { "bin/smartdown" }
3
+
4
+ it "is executable" do
5
+ expect(File.executable?(the_executable)).to eq(true)
6
+ end
7
+
8
+ describe "invocation with no arguments" do
9
+ subject(:output) { `#{the_executable} 2>&1` }
10
+
11
+ it "reports usage" do
12
+ expect(output).to match(/usage/i)
13
+ expect($?.exitstatus).to eq(1)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,98 @@
1
+ require 'smartdown/engine/predicate_evaluator'
2
+ require 'smartdown/model/predicate/equality'
3
+ require 'smartdown/model/predicate/set_membership'
4
+ require 'smartdown/model/predicate/named'
5
+ require 'smartdown/engine/state'
6
+
7
+ describe Smartdown::Engine::PredicateEvaluator do
8
+ subject(:evalutator) { described_class.new }
9
+
10
+ context "equality predicate" do
11
+ let(:predicate) { Smartdown::Model::Predicate::Equality.new("my_var", "some value") }
12
+
13
+ describe "#evaluate" do
14
+ context "state missing expected variable" do
15
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
16
+
17
+ it "raises an UndefinedValue error" do
18
+ expect { evalutator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
19
+ end
20
+ end
21
+
22
+ context "state has expected variable with same value" do
23
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some value") }
24
+
25
+ it "evalutes to true" do
26
+ expect(evalutator.evaluate(predicate, state)).to eq(true)
27
+ end
28
+ end
29
+
30
+ context "state has expected variable with a different value" do
31
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some other value") }
32
+
33
+ it "evalutes to false" do
34
+ expect(evalutator.evaluate(predicate, state)).to eq(false)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ context "set membership predicate" do
41
+ let(:expected_values) { ["v1", "v2", "v3"] }
42
+ let(:varname) { "my_var" }
43
+ let(:predicate) { Smartdown::Model::Predicate::SetMembership.new(varname, expected_values) }
44
+
45
+ describe "#evaluate" do
46
+ context "state missing expected variable" do
47
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
48
+
49
+ it "raises an UndefinedValue error" do
50
+ expect { evalutator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
51
+ end
52
+ end
53
+
54
+ context "state has expected variable with one of the expected values" do
55
+ it "evalutes to true" do
56
+ expected_values.each do |value|
57
+ state = Smartdown::Engine::State.new(current_node: "n", varname => value)
58
+ expect(evalutator.evaluate(predicate, state)).to eq(true)
59
+ end
60
+ end
61
+ end
62
+
63
+ context "state has expected variable with a value not in the list of expected values" do
64
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", varname => "some other value") }
65
+
66
+ it "evalutes to false" do
67
+ expect(evalutator.evaluate(predicate, state)).to eq(false)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ context "named predicate" do
74
+ let(:predicate_name) { "my_pred?" }
75
+ let(:predicate) { Smartdown::Model::Predicate::Named.new(predicate_name) }
76
+
77
+ describe "#evaluate" do
78
+ context "state missing predicate definition" do
79
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
80
+
81
+ it "raises an UndefinedValue error" do
82
+ expect { evalutator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
83
+ end
84
+ end
85
+
86
+ context "state has predicate definition" do
87
+ let(:state) {
88
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true )
89
+ }
90
+
91
+ it "fetches the predicate value from the state" do
92
+ expect(evalutator.evaluate(predicate, state)).to eq(true)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ end