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