smartdown 0.9.0 → 0.10.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
@@ -127,6 +127,21 @@ country slugs/names.
127
127
  [date: baby_due_date]
128
128
  ```
129
129
 
130
+ To control the range of years selected you can supply 2 optional arguments to date questions: `from` and `to`.
131
+ These can take the form of absolute values, eg.
132
+
133
+ ```markdown
134
+ [date: baby_due_date, from: 2010, to: 2015]
135
+ ```
136
+
137
+ Or relative values (from the current year), eg.
138
+
139
+ ```markdown
140
+ [date: baby_due_date, from: -4, to: 1]
141
+ ```
142
+
143
+ The default values for `from` and `to` are relative years: `-1` and `3` respectively.
144
+
130
145
  ### Text
131
146
 
132
147
  ```markdown
@@ -3,6 +3,39 @@ require 'smartdown/api/question'
3
3
  module Smartdown
4
4
  module Api
5
5
  class DateQuestion < Question
6
+ extend Forwardable
7
+ DEFAULT_START_YEAR = Time.now.year - 1
8
+ DEFAULT_END_YEAR = Time.now.year + 3
9
+
10
+ def start_year
11
+ parse_date(from) || DEFAULT_START_YEAR
12
+ end
13
+
14
+ def end_year
15
+ parse_date(to) || DEFAULT_END_YEAR
16
+ end
17
+
18
+ private
19
+
20
+ delegate [:to, :from] => :question_element
21
+
22
+ def question_element
23
+ @question_element ||= elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Date}
24
+ end
25
+
26
+ def parse_date(date_string)
27
+ return nil unless date_string
28
+ year = date_string.to_i
29
+ if is_fixed_year?(year)
30
+ year
31
+ else
32
+ Time.now.year + year
33
+ end
34
+ end
35
+
36
+ def is_fixed_year?(int)
37
+ int.abs >= 1000
38
+ end
6
39
  end
7
40
  end
8
41
  end
@@ -41,7 +41,10 @@ module Smartdown
41
41
  def interpolate(text, state)
42
42
  text.to_s.gsub(/%{([^}]+)}/) do |_|
43
43
  term = resolve_term($1, state)
44
- term.respond_to?(:humanize) ? term.humanize : term
44
+ if term.is_a?(Smartdown::Model::Answer::Base)
45
+ term = term.humanize
46
+ end
47
+ term
45
48
  end
46
49
  end
47
50
 
@@ -4,7 +4,7 @@ module Smartdown
4
4
  module Model
5
5
  module Element
6
6
  module Question
7
- class Date < Struct.new(:name, :alias)
7
+ class Date < Struct.new(:name, :from, :to, :alias)
8
8
  def answer_type
9
9
  Smartdown::Model::Answer::Date
10
10
  end
@@ -13,7 +13,11 @@ module Smartdown
13
13
  end
14
14
 
15
15
  def to_s(full = true)
16
- "Parse error in '#{filename}':\n\n" + @parse_error.cause.ascii_tree
16
+ position = parse_error.cause.pos
17
+ line, column = parse_error.cause.source.line_and_column(position)
18
+
19
+ "Parse error in file:'#{filename}' line:'#{line}' column:'#{column}'" +
20
+ "\n\n" + parse_error.cause.ascii_tree
17
21
  end
18
22
  end
19
23
 
@@ -7,7 +7,7 @@ require 'smartdown/parser/node_transform'
7
7
  module Smartdown
8
8
  module Parser
9
9
  class NodeInterpreter
10
- attr_reader :name, :source
10
+ attr_reader :name, :source, :reporter
11
11
 
12
12
  def initialize(name, source, options = {})
13
13
  @name = name
@@ -15,10 +15,11 @@ module Smartdown
15
15
  data_module = options.fetch(:data_module, {})
16
16
  @parser = options.fetch(:parser, Smartdown::Parser::NodeParser.new)
17
17
  @transform = options.fetch(:transform, Smartdown::Parser::NodeTransform.new(data_module))
18
+ @reporter = options.fetch(:reporter, Parslet::ErrorReporter::Deepest.new)
18
19
  end
19
20
 
20
21
  def interpret
21
- transform.apply(parser.parse(source),
22
+ transform.apply(parser.parse(source, reporter: reporter),
22
23
  node_name: name
23
24
  )
24
25
  end
@@ -102,9 +102,12 @@ module Smartdown
102
102
  }
103
103
 
104
104
  rule(:date => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs)}) {
105
+ transformed_option_pairs = Smartdown::Parser::OptionPairs.transform(option_pairs)
105
106
  Smartdown::Model::Element::Question::Date.new(
106
107
  identifier.to_s,
107
- Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('alias', nil)
108
+ transformed_option_pairs.fetch('from', nil),
109
+ transformed_option_pairs.fetch('to', nil),
110
+ transformed_option_pairs.fetch('alias', nil),
108
111
  )
109
112
  }
110
113
 
@@ -45,6 +45,7 @@ module Smartdown
45
45
  equality_predicate.as(:equality_predicate) |
46
46
  set_membership_predicate.as(:set_membership_predicate) |
47
47
  comparison_predicate.as(:comparison_predicate) |
48
+ function_predicate.as(:function_predicate) |
48
49
  otherwise_predicate |
49
50
  named_predicate
50
51
  }
@@ -68,7 +69,6 @@ module Smartdown
68
69
 
69
70
  rule (:predicates) {
70
71
  combined_predicate.as(:combined_predicate) |
71
- function_predicate.as(:function_predicate) |
72
72
  predicate
73
73
  }
74
74
 
@@ -1,3 +1,3 @@
1
1
  module Smartdown
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -0,0 +1,71 @@
1
+ require 'smartdown/api/date_question'
2
+ require 'smartdown/model/element/question/date'
3
+
4
+ describe Smartdown::Api::DateQuestion do
5
+ before do
6
+ Timecop.freeze(Time.local(2014))
7
+ end
8
+
9
+ after do
10
+ Timecop.return
11
+ end
12
+
13
+ subject(:date_question) { Smartdown::Api::DateQuestion.new(elements) }
14
+ let(:elements) { [ date_question_element ] }
15
+ let(:date_question_element) {
16
+ Smartdown::Model::Element::Question::Date.new(name, *args)
17
+ }
18
+ let(:aliaz) { nil }
19
+ let(:args) {[start_year, end_year, aliaz]}
20
+
21
+
22
+ context "no to/from provided" do
23
+ let(:name) { 'year_of_emancipation' }
24
+ let(:start_year) { nil }
25
+ let(:end_year) { nil }
26
+
27
+ describe "#start_year" do
28
+ it 'returns good default' do
29
+ expect(date_question.start_year).to eq(Time.now.year - 1)
30
+ end
31
+ end
32
+
33
+ describe "#end_year" do
34
+ it 'returns good default' do
35
+ expect(date_question.end_year).to eq(Time.now.year + 3)
36
+ end
37
+ end
38
+ end
39
+
40
+ context "with to/from provided" do
41
+ context "Fixed years" do
42
+ let(:name) { 'year_of_incarceration_for_possession_of_alcohol' }
43
+ let(:start_year) { '1920' }
44
+ let(:end_year) { '1933' }
45
+
46
+ describe "start_year" do
47
+ specify { expect(date_question.start_year).to eq(start_year.to_i) }
48
+ end
49
+
50
+ describe "end_year" do
51
+ specify { expect(date_question.end_year).to eq(end_year.to_i) }
52
+ end
53
+ end
54
+
55
+ context "Relative years" do
56
+ let(:name) { 'year_of_incarceration_for_practicing_witchcraft' }
57
+ let(:start_year) { '-279' }
58
+ let(:end_year) { '-63' }
59
+
60
+ describe "start_year" do
61
+ specify { expect(date_question.start_year).to eq(1735) }
62
+ end
63
+
64
+ describe "end_year" do
65
+ specify { expect(date_question.end_year).to eq(1951) }
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ end
@@ -27,6 +27,38 @@ describe Smartdown::Parser::Element::DateQuestion do
27
27
  end
28
28
  end
29
29
 
30
+ context "with question tag and a date range with years" do
31
+ let(:source) { "[date: date_of_birth, from: 1980, to: -2]" }
32
+
33
+ it "parses" do
34
+ should parse(source).as(
35
+ date: {
36
+ identifier: "date_of_birth",
37
+ option_pairs: [
38
+ {
39
+ key: 'from',
40
+ value: '1980',
41
+ },
42
+ {
43
+ key: 'to',
44
+ value: '-2',
45
+ }
46
+ ]
47
+
48
+ }
49
+ )
50
+ end
51
+
52
+ describe "transformed" do
53
+ let(:node_name) { "my_node" }
54
+ subject(:transformed) {
55
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
56
+ }
57
+
58
+ it { should eq(Smartdown::Model::Element::Question::Date.new("date_of_birth", '1980', '-2')) }
59
+ end
60
+ end
61
+
30
62
  context "with question tag and an alias" do
31
63
  let(:source) { "[date: date_of_birth, alias: date_for_adoption_or_birth]" }
32
64
 
@@ -50,7 +82,7 @@ describe Smartdown::Parser::Element::DateQuestion do
50
82
  Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
51
83
  }
52
84
 
53
- it { should eq(Smartdown::Model::Element::Question::Date.new("date_of_birth", "date_for_adoption_or_birth")) }
85
+ it { should eq(Smartdown::Model::Element::Question::Date.new("date_of_birth", nil, nil, "date_for_adoption_or_birth")) }
54
86
  end
55
87
  end
56
88
  end
@@ -82,9 +82,9 @@ describe Smartdown::Parser::Predicates do
82
82
  describe "predicate AND predicate" do
83
83
  subject(:parser) { described_class.new }
84
84
 
85
- it { should parse("my_pred? AND my_other_pred?").as(
85
+ it { should parse("my_pred() AND my_other_pred?").as(
86
86
  { combined_predicate: {
87
- first_predicate: { named_predicate: "my_pred?" },
87
+ first_predicate: { function_predicate: { name: "my_pred" } },
88
88
  and_predicates:
89
89
  [
90
90
  {named_predicate: "my_other_pred?"},
@@ -107,16 +107,16 @@ describe Smartdown::Parser::Predicates do
107
107
 
108
108
  describe "transformed" do
109
109
  let(:node_name) { "my_node" }
110
- let(:source) { "my_pred? AND my_other_pred?" }
110
+ let(:source) { "my_pred() AND my_other_pred?" }
111
111
  subject(:transformed) {
112
112
  Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
113
113
  }
114
114
 
115
115
  it { should eq(Smartdown::Model::Predicate::Combined.new(
116
116
  [
117
- Smartdown::Model::Predicate::Named.new("my_pred?"),
117
+ Smartdown::Model::Predicate::Function.new("my_pred", []),
118
118
  Smartdown::Model::Predicate::Named.new("my_other_pred?")
119
- ]
119
+ ]
120
120
  )) }
121
121
  end
122
122
  end
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,7 @@ $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
3
3
  require 'pathname'
4
4
  require 'parslet/rig/rspec'
5
5
  require 'support/model_builder'
6
+ require 'timecop'
6
7
 
7
8
  RSpec.configure do |config|
8
9
  if config.files_to_run.one?
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.9.0
4
+ version: 0.10.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: &9477660 !ruby/object:Gem::Requirement
16
+ requirement: &18204100 !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: *9477660
24
+ version_requirements: *18204100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &9476740 !ruby/object:Gem::Requirement
27
+ requirement: &18232520 !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: *9476740
35
+ version_requirements: *18232520
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &9475460 !ruby/object:Gem::Requirement
38
+ requirement: &18231800 !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: *9475460
46
+ version_requirements: *18231800
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: gem_publisher
49
- requirement: &9474100 !ruby/object:Gem::Requirement
49
+ requirement: &18230980 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,18 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *9474100
57
+ version_requirements: *18230980
58
+ - !ruby/object:Gem::Dependency
59
+ name: timecop
60
+ requirement: &18230000 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *18230000
58
69
  description:
59
70
  email: david.heath@digital.cabinet-office.gov.uk
60
71
  executables:
@@ -176,6 +187,7 @@ files:
176
187
  - spec/support/flow_input_interface.rb
177
188
  - spec/support/model_builder.rb
178
189
  - spec/api/state_spec.rb
190
+ - spec/api/date_question_spec.rb
179
191
  - spec/api/previous_question_spec.rb
180
192
  - spec/spec_helper.rb
181
193
  - spec/engine/state_spec.rb
@@ -252,7 +264,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
252
264
  version: '0'
253
265
  segments:
254
266
  - 0
255
- hash: -2036017641587620978
267
+ hash: 508907290184423365
256
268
  required_rubygems_version: !ruby/object:Gem::Requirement
257
269
  none: false
258
270
  requirements:
@@ -261,7 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
273
  version: '0'
262
274
  segments:
263
275
  - 0
264
- hash: -2036017641587620978
276
+ hash: 508907290184423365
265
277
  requirements: []
266
278
  rubyforge_project:
267
279
  rubygems_version: 1.8.11
@@ -295,6 +307,7 @@ test_files:
295
307
  - spec/support/flow_input_interface.rb
296
308
  - spec/support/model_builder.rb
297
309
  - spec/api/state_spec.rb
310
+ - spec/api/date_question_spec.rb
298
311
  - spec/api/previous_question_spec.rb
299
312
  - spec/spec_helper.rb
300
313
  - spec/engine/state_spec.rb