smartdown 0.4.0 → 0.5.0

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.
data/README.md CHANGED
@@ -285,6 +285,28 @@ Interpolations are currently supported into headings and paragraphs using the fo
285
285
  Your state pension age is %{state_pension_age}.
286
286
  ```
287
287
 
288
+ ## Snippets
289
+
290
+ Snippets work like partials, to re-use common text. They can be block or inline,
291
+ can be called recursivelty and can contain interpolation and conditional logic
292
+
293
+ They're called like so:
294
+
295
+ ```
296
+ ## My header
297
+
298
+ Markdown copy..
299
+
300
+ {{snippet: my_snippet}}
301
+
302
+ More copy...
303
+ ```
304
+
305
+ Where `snippet_name` is in a `snippets/` directory in the flow root with a `.txt`
306
+ extension, eg `my-flow-name/snippets/my_snippet.txt`.
307
+
308
+ The contents of `my_snippet` will be inseted into the outcome/question.
309
+
288
310
  ## Named predicates (tbd)
289
311
 
290
312
  Named predicates
@@ -23,6 +23,10 @@ module Smartdown
23
23
  read_dir("scenarios")
24
24
  end
25
25
 
26
+ def snippets
27
+ read_dir("snippets")
28
+ end
29
+
26
30
  def filenames_hash
27
31
  {
28
32
  coversheet: coversheet.to_s,
@@ -1,5 +1,6 @@
1
1
  require 'smartdown/model/flow'
2
2
  require 'smartdown/parser/node_interpreter'
3
+ require 'smartdown/parser/snippet_pre_parser'
3
4
 
4
5
  module Smartdown
5
6
  module Parser
@@ -20,7 +21,7 @@ module Smartdown
20
21
  attr_reader :flow_input
21
22
 
22
23
  def initialize(flow_input)
23
- @flow_input = flow_input
24
+ @flow_input = pre_parse(flow_input)
24
25
  end
25
26
 
26
27
  def interpret
@@ -33,11 +34,11 @@ module Smartdown
33
34
  end
34
35
 
35
36
  def questions
36
- flow_input.questions.map { |i| interpret_node(i) }
37
+ flow_input.questions.map { |question_data| interpret_node(question_data) }
37
38
  end
38
39
 
39
40
  def outcomes
40
- flow_input.outcomes.map { |i| interpret_node(i) }
41
+ flow_input.outcomes.map { |outcome_data| interpret_node(outcome_data) }
41
42
  end
42
43
 
43
44
  def interpret_node(input_data)
@@ -45,6 +46,10 @@ module Smartdown
45
46
  rescue Parslet::ParseFailed => error
46
47
  raise ParseError.new(input_data.to_s, error)
47
48
  end
49
+
50
+ def pre_parse(flow_input)
51
+ SnippetPreParser.parse(flow_input)
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -0,0 +1,28 @@
1
+ module Smartdown
2
+ module Parser
3
+ class InputSet
4
+ attr_reader :coversheet, :questions, :outcomes, :snippets, :scenarios
5
+
6
+ def initialize(params = {})
7
+ @coversheet = params[:coversheet]
8
+ @questions = params[:questions]
9
+ @outcomes = params[:outcomes]
10
+ @snippets = params[:snippets]
11
+ @scenarios = params[:scenarios]
12
+ end
13
+ end
14
+
15
+ class InputData
16
+ attr_reader :name, :content
17
+
18
+ def initialize(name, content)
19
+ @name = name
20
+ @content = content
21
+ end
22
+
23
+ def read
24
+ content
25
+ end
26
+ end
27
+ end
28
+ end
@@ -30,7 +30,7 @@ module Smartdown
30
30
  }
31
31
 
32
32
  rule(:body) {
33
- markdown_blocks.as(:body)
33
+ markdown_blocks.as(:body) >> newline.repeat
34
34
  }
35
35
 
36
36
  rule(:flow) {
@@ -0,0 +1,44 @@
1
+ require 'smartdown/parser/input_set'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ class SnippetPreParser
6
+ class SnippetNotFound < StandardError; end
7
+
8
+ attr_reader :input_data
9
+
10
+ def initialize(input_data)
11
+ @input_data = input_data
12
+ end
13
+
14
+ def parse
15
+ InputSet.new({
16
+ coversheet: parse_node_input(input_data.coversheet),
17
+ questions: input_data.questions.map { |question_data| parse_node_input(question_data) },
18
+ outcomes: input_data.outcomes.map { |outcome_data| parse_node_input(outcome_data) },
19
+ snippets: input_data.snippets,
20
+ scenarios: input_data.scenarios,
21
+ })
22
+ end
23
+
24
+ def self.parse(input_data)
25
+ self.new(input_data).parse
26
+ end
27
+
28
+ private
29
+ def parse_node_input(node_input)
30
+ InputData.new(node_input.name, parse_content(node_input.read))
31
+ end
32
+
33
+ def parse_content(content)
34
+ content.gsub(/\{\{snippet:\W?(.*)\}\}/i) { |_|
35
+ parse_content(get_snippet($1).read.strip)
36
+ }
37
+ end
38
+
39
+ def get_snippet(snippet_name)
40
+ input_data.snippets.find { |snippet| snippet.name == snippet_name } or raise SnippetNotFound.new(snippet_name)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module Smartdown
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -112,6 +112,14 @@ EXPECTED
112
112
  expect(flow.instance_of? Smartdown::Model::Flow).to eq(true)
113
113
  end
114
114
  end
115
+
116
+ context "snippets" do
117
+ subject(:flow) { Smartdown.parse(fixture("snippet")) }
118
+
119
+ it "has replaced the snippet tag with the snippet contents" do
120
+ expect(flow.nodes.first.elements.any? { |element| element.content.to_s.include? "snippet body" })
121
+ end
122
+ end
115
123
  end
116
124
 
117
125
 
@@ -0,0 +1,7 @@
1
+ meta_description: Snippets
2
+ satisfies_need: 999999
3
+ status: draft
4
+
5
+ # A snippet in use
6
+
7
+ {{snippet: the_snippet}}
@@ -0,0 +1 @@
1
+ snippet one
@@ -1,11 +1,5 @@
1
1
  require 'smartdown/parser/directory_input'
2
-
3
- shared_examples "flow input interface" do
4
- it { should respond_to(:coversheet) }
5
- it { should respond_to(:questions) }
6
- it { should respond_to(:outcomes) }
7
- it { should respond_to(:scenarios) }
8
- end
2
+ require 'support/flow_input_interface'
9
3
 
10
4
  describe Smartdown::Parser::DirectoryInput do
11
5
  it_should_behave_like "flow input interface"
@@ -53,4 +47,12 @@ describe Smartdown::Parser::DirectoryInput do
53
47
  expect(input.scenarios.first.read).to eq("scenario one\n")
54
48
  end
55
49
  end
50
+
51
+ describe "#snippets" do
52
+ it "returns an InputFile for every file in the snippets folder" do
53
+ expect(input.snippets).to match([instance_of(Smartdown::Parser::InputFile)])
54
+ expect(input.snippets.first.name).to eq("sn1")
55
+ expect(input.snippets.first.read).to eq("snippet one\n")
56
+ end
57
+ end
56
58
  end
@@ -0,0 +1,15 @@
1
+ require 'smartdown/parser/input_set'
2
+ require 'support/flow_input_interface'
3
+
4
+ describe Smartdown::Parser::InputSet do
5
+ it_should_behave_like "flow input interface"
6
+ end
7
+
8
+ describe Smartdown::Parser::InputData do
9
+ let(:name) { 'a name' }
10
+ let(:data) { 'some smartdown' }
11
+ subject { Smartdown::Parser::InputData.new(name, data) }
12
+
13
+ specify { expect(subject.name).to eql name }
14
+ specify { expect(subject.read).to eql data }
15
+ end
@@ -166,4 +166,30 @@ SOURCE
166
166
  })
167
167
  }
168
168
  end
169
+
170
+ context "body with extra trailing newlines" do
171
+ let(:source) {
172
+ <<SOURCE
173
+ # This is my title
174
+
175
+ This is a paragraph of text with stuff
176
+ that flows along
177
+
178
+ Another paragraph of text
179
+
180
+
181
+ SOURCE
182
+ }
183
+
184
+ it {
185
+ should parse(source).as({
186
+ body: [
187
+ {h1: "This is my title"},
188
+ {p: "This is a paragraph of text with stuff\nthat flows along\n"},
189
+ {p: "Another paragraph of text\n"}
190
+ ]
191
+ })
192
+ }
193
+ end
194
+
169
195
  end
@@ -0,0 +1,116 @@
1
+ require 'smartdown/parser/snippet_pre_parser'
2
+ require 'smartdown/parser/directory_input'
3
+ require 'ostruct'
4
+
5
+ describe Smartdown::Parser::SnippetPreParser do
6
+ let(:input_data) {
7
+ Smartdown::Parser::InputSet.new({
8
+ coversheet: Smartdown::Parser::InputData.new("coversheet_1", "some smartdown {{snippet: coversheet_snippet}}"),
9
+ questions: [
10
+ Smartdown::Parser::InputData.new("question_1", "some {{snippet: question_snippet}} smartdown"),
11
+ ],
12
+ outcomes: [
13
+ Smartdown::Parser::InputData.new("outcome_1", "some smartdown\n\n{{snippet: outcome_snippet}}\n\nmore smartdown"),
14
+ ],
15
+ snippets: [
16
+ Smartdown::Parser::InputData.new("question_snippet", "question snippet"),
17
+ Smartdown::Parser::InputData.new("outcome_snippet", "outcome snippet"),
18
+ Smartdown::Parser::InputData.new("coversheet_snippet", "coversheet snippet"),
19
+ ],
20
+ scenarios: [:some_scenario]
21
+ })
22
+ }
23
+
24
+ subject(:parsed_output) {
25
+ described_class.parse(input_data)
26
+ }
27
+
28
+ it "should replace the snippet tag with the snippet content for questions" do
29
+ expect(parsed_output.questions[0].read).to eql "some question snippet smartdown"
30
+ end
31
+
32
+ it "should replace the snippet tag with the snippet content for outcomes" do
33
+ expect(parsed_output.outcomes[0].read).to eql "some smartdown\n\noutcome snippet\n\nmore smartdown"
34
+ end
35
+
36
+ it "should replace the snippet tag with the snippet content for the coversheet" do
37
+ expect(parsed_output.coversheet.read).to eql "some smartdown coversheet snippet"
38
+ end
39
+
40
+ context "with nested snippets" do
41
+ let(:input_data) {
42
+ Smartdown::Parser::InputSet.new({
43
+ coversheet: Smartdown::Parser::InputData.new("coversheet_1", "some smartdown {{snippet: top_level_snippet}}"),
44
+ questions: [],
45
+ outcomes: [],
46
+ snippets: [
47
+ Smartdown::Parser::InputData.new("top_level_snippet", "top level snippet {{snippet: nested_snippet}}"),
48
+ Smartdown::Parser::InputData.new("nested_snippet", "nested snippet"),
49
+ ],
50
+ scenarios: []
51
+ })
52
+ }
53
+
54
+ it "shoud recursively process nested snippets" do
55
+ expect(parsed_output.coversheet.read).to eql "some smartdown top level snippet nested snippet"
56
+ end
57
+ end
58
+
59
+ context "when referencing a non-existent snippet" do
60
+ let(:input_data) {
61
+ Smartdown::Parser::InputSet.new({
62
+ coversheet: Smartdown::Parser::InputData.new("coversheet_1", "some smartdown {{snippet: non_existent_snippet}}"),
63
+ questions: [],
64
+ outcomes: [],
65
+ snippets: [],
66
+ scenarios: []
67
+ })
68
+ }
69
+
70
+ specify { expect { parsed_output }.to raise_exception(Smartdown::Parser::SnippetPreParser::SnippetNotFound) }
71
+ end
72
+
73
+ describe "whitespace handling" do
74
+ let(:snippet_smartdown) { "A snippet" }
75
+ let(:input_data) {
76
+ Smartdown::Parser::InputSet.new({
77
+ coversheet: Smartdown::Parser::InputData.new("coversheet_1", "Smartdown {{snippet: snippet}} more smartdown"),
78
+ questions: [],
79
+ outcomes: [],
80
+ snippets: [ Smartdown::Parser::InputData.new("snippet", snippet_smartdown) ],
81
+ scenarios: []
82
+ })
83
+ }
84
+
85
+ context "when snippet smartdown has leading / trailing whitespace" do
86
+ let(:snippet_smartdown) { "\n\n Snippet text\n\n with whitespace \n\n" }
87
+
88
+ it "should strip off the snippet content's leading and trailing whitespace" do
89
+ expect(parsed_output.coversheet.read).to eql "Smartdown Snippet text\n\n with whitespace more smartdown"
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "alternative tag definitions" do
95
+ let(:snippet_tag) { "{{snippet: snippet_name}}" }
96
+ let(:input_data) {
97
+ Smartdown::Parser::InputSet.new({
98
+ coversheet: Smartdown::Parser::InputData.new("coversheet_1", snippet_tag),
99
+ questions: [],
100
+ outcomes: [],
101
+ snippets: [ Smartdown::Parser::InputData.new("snippet_name", "the snippet") ],
102
+ scenarios: []
103
+ })
104
+ }
105
+
106
+ context "with {{SNIPPET: snippet_name}}" do
107
+ let(:snippet_tag) { "{{SNIPPET: snippet_name}}" }
108
+ specify { expect(parsed_output.coversheet.read).to eql "the snippet" }
109
+ end
110
+
111
+ context "with {{snippet:snippet_name}}" do
112
+ let(:snippet_tag) { "{{snippet:snippet_name}}" }
113
+ specify { expect(parsed_output.coversheet.read).to eql "the snippet" }
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,7 @@
1
+ shared_examples "flow input interface" do
2
+ it { should respond_to(:coversheet) }
3
+ it { should respond_to(:questions) }
4
+ it { should respond_to(:outcomes) }
5
+ it { should respond_to(:scenarios) }
6
+ it { should respond_to(:snippets) }
7
+ end
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.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2014-07-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parslet
16
- requirement: &10821840 !ruby/object:Gem::Requirement
16
+ requirement: &7733220 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.6.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *10821840
24
+ version_requirements: *7733220
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &10820700 !ruby/object:Gem::Requirement
27
+ requirement: &7731020 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 3.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *10820700
35
+ version_requirements: *7731020
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &10818660 !ruby/object:Gem::Requirement
38
+ requirement: &7744660 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *10818660
46
+ version_requirements: *7744660
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: gem_publisher
49
- requirement: &10832280 !ruby/object:Gem::Requirement
49
+ requirement: &7742640 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *10832280
57
+ version_requirements: *7742640
58
58
  description:
59
59
  email: david.heath@digital.cabinet-office.gov.uk
60
60
  executables:
@@ -67,7 +67,9 @@ files:
67
67
  - lib/smartdown/parser/predicates.rb
68
68
  - lib/smartdown/parser/node_transform.rb
69
69
  - lib/smartdown/parser/node_interpreter.rb
70
+ - lib/smartdown/parser/snippet_pre_parser.rb
70
71
  - lib/smartdown/parser/base.rb
72
+ - lib/smartdown/parser/input_set.rb
71
73
  - lib/smartdown/parser/directory_input.rb
72
74
  - lib/smartdown/parser/rules.rb
73
75
  - lib/smartdown/parser/node_parser.rb
@@ -134,6 +136,7 @@ files:
134
136
  - spec/parser/directory_input_spec.rb
135
137
  - spec/parser/integration/cover_sheet_spec.rb
136
138
  - spec/parser/base_spec.rb
139
+ - spec/parser/input_set_spec.rb
137
140
  - spec/parser/rules_spec.rb
138
141
  - spec/parser/predicates_spec.rb
139
142
  - spec/parser/element/next_steps_spec.rb
@@ -146,6 +149,8 @@ files:
146
149
  - spec/parser/element/front_matter_spec.rb
147
150
  - spec/parser/element/salary_question_spec.rb
148
151
  - spec/parser/node_parser_spec.rb
152
+ - spec/parser/snippet_pre_parser_spec.rb
153
+ - spec/support/flow_input_interface.rb
149
154
  - spec/support/model_builder.rb
150
155
  - spec/spec_helper.rb
151
156
  - spec/engine/state_spec.rb
@@ -156,6 +161,7 @@ files:
156
161
  - spec/fixtures/directory_input/outcomes/o1.txt
157
162
  - spec/fixtures/directory_input/questions/q1.txt
158
163
  - spec/fixtures/directory_input/cover-sheet.txt
164
+ - spec/fixtures/directory_input/snippets/sn1.txt
159
165
  - spec/fixtures/acceptance/question-and-outcome/question-and-outcome.txt
160
166
  - spec/fixtures/acceptance/question-and-outcome/outcomes/o1.txt
161
167
  - spec/fixtures/acceptance/question-and-outcome/questions/q1.txt
@@ -169,6 +175,8 @@ files:
169
175
  - spec/fixtures/acceptance/cover-sheet/cover-sheet.txt
170
176
  - spec/fixtures/acceptance/one-question/one-question.txt
171
177
  - spec/fixtures/acceptance/one-question/questions/q1.txt
178
+ - spec/fixtures/acceptance/snippet/snippet.txt
179
+ - spec/fixtures/acceptance/snippet/snippets/the_snippet.txt
172
180
  - spec/fixtures/example.sd
173
181
  - spec/support_specs/model_builder_spec.rb
174
182
  - spec/engine_spec.rb
@@ -195,7 +203,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
195
203
  version: '0'
196
204
  segments:
197
205
  - 0
198
- hash: -1672361924519016433
206
+ hash: 2695022002413728177
199
207
  required_rubygems_version: !ruby/object:Gem::Requirement
200
208
  none: false
201
209
  requirements:
@@ -204,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
212
  version: '0'
205
213
  segments:
206
214
  - 0
207
- hash: -1672361924519016433
215
+ hash: 2695022002413728177
208
216
  requirements: []
209
217
  rubyforge_project:
210
218
  rubygems_version: 1.8.11
@@ -217,6 +225,7 @@ test_files:
217
225
  - spec/parser/directory_input_spec.rb
218
226
  - spec/parser/integration/cover_sheet_spec.rb
219
227
  - spec/parser/base_spec.rb
228
+ - spec/parser/input_set_spec.rb
220
229
  - spec/parser/rules_spec.rb
221
230
  - spec/parser/predicates_spec.rb
222
231
  - spec/parser/element/next_steps_spec.rb
@@ -229,6 +238,8 @@ test_files:
229
238
  - spec/parser/element/front_matter_spec.rb
230
239
  - spec/parser/element/salary_question_spec.rb
231
240
  - spec/parser/node_parser_spec.rb
241
+ - spec/parser/snippet_pre_parser_spec.rb
242
+ - spec/support/flow_input_interface.rb
232
243
  - spec/support/model_builder.rb
233
244
  - spec/spec_helper.rb
234
245
  - spec/engine/state_spec.rb
@@ -239,6 +250,7 @@ test_files:
239
250
  - spec/fixtures/directory_input/outcomes/o1.txt
240
251
  - spec/fixtures/directory_input/questions/q1.txt
241
252
  - spec/fixtures/directory_input/cover-sheet.txt
253
+ - spec/fixtures/directory_input/snippets/sn1.txt
242
254
  - spec/fixtures/acceptance/question-and-outcome/question-and-outcome.txt
243
255
  - spec/fixtures/acceptance/question-and-outcome/outcomes/o1.txt
244
256
  - spec/fixtures/acceptance/question-and-outcome/questions/q1.txt
@@ -252,6 +264,8 @@ test_files:
252
264
  - spec/fixtures/acceptance/cover-sheet/cover-sheet.txt
253
265
  - spec/fixtures/acceptance/one-question/one-question.txt
254
266
  - spec/fixtures/acceptance/one-question/questions/q1.txt
267
+ - spec/fixtures/acceptance/snippet/snippet.txt
268
+ - spec/fixtures/acceptance/snippet/snippets/the_snippet.txt
255
269
  - spec/fixtures/example.sd
256
270
  - spec/support_specs/model_builder_spec.rb
257
271
  - spec/engine_spec.rb