stamp 0.5.0 → 0.6.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5c0058768a5169006ec3da166fdb88f082715295
4
+ data.tar.gz: fcf6e1e6fc53cdd3105b1eceb2099b4014b003fc
5
+ SHA512:
6
+ metadata.gz: 5be616540b39f2dac3e8bef2e886e809b908446f70e4d18d291e84dc17d22a376b12ac75332a844611be190bebc6df71a072247ed6d75ff8c8bd4e9da8b1ef6d
7
+ data.tar.gz: c508e4cc1a1821006ced92ae88ca0b2bb7a766cf30d04eb26cdf56f244ed0f41788d08297993a89850bf625860cc38fec416e48448d36739c1d5611a931c2813
@@ -3,6 +3,7 @@ rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
5
  - 2.0.0
6
+ - 2.1.2
6
7
  - ree
7
8
  - rbx
8
- - jruby
9
+ - jruby
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
@@ -19,6 +19,9 @@ Feature: Stamping a date
19
19
  | Jan 10 | Sep 08 |
20
20
  | Jan 1, 1999 | Sep 8, 2011 |
21
21
  | Jan 12, 1999 | Sep 08, 2011 |
22
+ | 1 Jan 1999 | 8 Sep 2011 |
23
+ | 1/1/1999 | 9/8/2011 |
24
+ | 31/01/2013 | 08/09/2011 |
22
25
  | 13 January 1999 | 08 September 2011 |
23
26
  | Monday | Thursday |
24
27
  | Tue, Jan 1 | Thu, Sep 8 |
@@ -32,6 +35,7 @@ Feature: Stamping a date
32
35
  | 31/12 | 08/09 |
33
36
  | 31/12/99 | 08/09/11 |
34
37
  | 31-Jan-1999 | 08-Sep-2011 |
38
+ | 1999-01-01 | 2011-09-08 |
35
39
  | 1999-12-31 | 2011-09-08 |
36
40
  | DOB: 12-31-1999 | DOB: 09-08-2011 |
37
41
 
@@ -117,15 +121,3 @@ Feature: Stamping a date
117
121
  | alias |
118
122
  | stamp_like |
119
123
  | format_like |
120
-
121
- @wip
122
- Scenario Outline: Examples that aren't supported yet
123
- Given the time September 8, 2011 at 13:31:27
124
- When I stamp the example "<example>"
125
- Then I produce "<output>"
126
-
127
- Examples:
128
- | example | output |
129
- | 8 am | 1 pm |
130
- | 8am | 1pm |
131
- | 8AM | 1PM |
@@ -3,20 +3,18 @@ require "time"
3
3
 
4
4
  require "stamp/emitters/modifiable"
5
5
  require "stamp/emitters/am_pm"
6
+ require "stamp/emitters/ambiguous"
6
7
  require "stamp/emitters/composite"
7
8
  require "stamp/emitters/delegate"
8
9
  require "stamp/emitters/lookup"
9
10
  require "stamp/emitters/ordinal"
10
11
  require "stamp/emitters/string"
11
12
  require "stamp/emitters/two_digit"
13
+ require "stamp/disambiguator"
12
14
  require "stamp/translator"
13
15
  require "stamp/version"
14
16
 
15
17
  module Stamp
16
- # Limits the number of formats that we memoize to prevent unbounded
17
- # memory consumption.
18
- MEMOIZATION_CAP = 999
19
-
20
18
  # Formats a date/time using a human-friendly example as a template.
21
19
  #
22
20
  # @param [String] example a human-friendly date/time example
@@ -33,18 +31,20 @@ module Stamp
33
31
  alias :stamp_like :stamp
34
32
  alias :format_like :stamp
35
33
 
34
+ private
35
+
36
36
  # Memoizes the set of emitter objects for the given +example+ in
37
37
  # order to improve performance.
38
38
  def memoize_stamp_emitters(example)
39
39
  @@memoized_stamp_emitters ||= {}
40
- @@memoized_stamp_emitters.clear if @@memoized_stamp_emitters.size > MEMOIZATION_CAP
41
40
  @@memoized_stamp_emitters[example] ||= stamp_emitters(example)
42
41
  end
43
42
 
44
43
  def stamp_emitters(example)
45
- Translator.new.translate(example)
44
+ emitters = Translator.new.translate(example)
45
+ Disambiguator.new(emitters).disambiguate!
46
46
  end
47
47
  end
48
48
 
49
49
  Date.send(:include, ::Stamp)
50
- Time.send(:include, ::Stamp)
50
+ Time.send(:include, ::Stamp)
@@ -0,0 +1,23 @@
1
+ class Disambiguator
2
+ attr_reader :emitters
3
+
4
+ def initialize(emitters)
5
+ @emitters = emitters
6
+ end
7
+
8
+ def disambiguate!
9
+ emitters.replace_each! do |emitter|
10
+ disambiguate(emitter)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def disambiguate(emitter)
17
+ if emitter.respond_to?(:disambiguate)
18
+ emitter.disambiguate(emitters)
19
+ else
20
+ emitter
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Stamp
2
+ module Emitters
3
+ class Ambiguous
4
+ attr_reader :potential_emitters
5
+
6
+ def initialize(*emitters)
7
+ @potential_emitters = emitters
8
+ end
9
+
10
+ def field
11
+ nil
12
+ end
13
+
14
+ def disambiguate(emitters)
15
+ other_emitters = emitters - self
16
+ known_fields = other_emitters.map { |e| e.field }.compact
17
+
18
+ potential_emitters.reject do |potential_emitter|
19
+ known_fields.include?(potential_emitter.field)
20
+ end.first
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,29 +3,40 @@ module Stamp
3
3
  class Composite
4
4
  include Enumerable
5
5
 
6
- def initialize
7
- @emitters = []
6
+ attr_reader :emitters
7
+
8
+ def initialize(emitters=[])
9
+ @emitters = emitters
8
10
  end
9
11
 
10
12
  def format(target)
11
13
  # NOTE using #each to build string because benchmarking shows
12
14
  # that it's ~20% faster than .map.join('')
13
15
  result = ''
14
- @emitters.each { |e| result << e.format(target).to_s }
16
+ emitters.each { |e| result << e.format(target).to_s }
15
17
  result
16
18
  end
17
19
 
18
- def <<(emitter)
19
- if emitter.is_a?(Enumerable)
20
- emitter.each { |e| @emitters << e }
21
- else
22
- @emitters << emitter
23
- end
20
+ def <<(emitter)
21
+ Array(emitter).each { |e| emitters << e }
24
22
  end
25
23
 
26
24
  def each(&block)
27
- @emitters.each(&block)
25
+ emitters.each(&block)
26
+ end
27
+
28
+ def -(others)
29
+ emitters - Array(others)
30
+ end
31
+
32
+ # Replace each element as we iterate with the result of the given block.
33
+ def replace_each!
34
+ emitters.each_with_index do |emitter, index|
35
+ emitters[index] = yield(emitter)
36
+ end
37
+
38
+ self
28
39
  end
29
40
  end
30
41
  end
31
- end
42
+ end
@@ -34,48 +34,30 @@ module Stamp
34
34
  ORDINAL_DAY_REGEXP = /^(\d{1,2})(st|nd|rd|th)$/
35
35
 
36
36
  # Disambiguate based on value
37
- OBVIOUS_YEARS = 60..99
38
- OBVIOUS_MONTHS = 12
39
- OBVIOUS_DAYS = 13..31
40
37
  OBVIOUS_24_HOUR = 13..23
38
+ OBVIOUS_DAY = 13..31
39
+ OBVIOUS_YEAR = 32..99
41
40
 
42
41
  TWO_DIGIT_YEAR_EMITTER = Emitters::TwoDigit.new(:year) { |year| year % 100 }
43
42
  TWO_DIGIT_MONTH_EMITTER = Emitters::TwoDigit.new(:month)
44
43
  TWO_DIGIT_DAY_EMITTER = Emitters::TwoDigit.new(:day)
45
44
  HOUR_TO_12_HOUR = lambda { |h| ((h - 1) % 12) + 1 }
46
45
 
47
- OBVIOUS_DATE_MAP = {
48
- OBVIOUS_YEARS => TWO_DIGIT_YEAR_EMITTER,
49
- OBVIOUS_MONTHS => TWO_DIGIT_MONTH_EMITTER,
50
- OBVIOUS_DAYS => TWO_DIGIT_DAY_EMITTER
51
- }
52
-
53
- TWO_DIGIT_DATE_SUCCESSION = {
54
- :month => TWO_DIGIT_DAY_EMITTER,
55
- :day => TWO_DIGIT_YEAR_EMITTER,
56
- :year => TWO_DIGIT_MONTH_EMITTER
57
- }
58
-
59
- TWO_DIGIT_TIME_SUCCESSION = {
60
- :hour => Emitters::TwoDigit.new(:min),
61
- :min => Emitters::TwoDigit.new(:sec)
62
- }
63
-
64
46
  def translate(example)
65
47
  # extract any substrings that look like times, like "23:59" or "8:37 am"
66
48
  before, time_example, after = example.partition(TIME_REGEXP)
67
49
 
68
50
  # build emitters from the example date
69
51
  emitters = Emitters::Composite.new
70
- emitters << build_emitters(before.split(/\b/)) do |token, previous_part|
71
- date_emitter(token, previous_part)
52
+ emitters << build_emitters(before.split(/\b/)) do |token|
53
+ date_emitter(token)
72
54
  end
73
55
 
74
56
  # build emitters from the example time
75
57
  unless time_example.empty?
76
58
  time_parts = time_example.scan(TIME_REGEXP).first
77
- emitters << build_emitters(time_parts) do |token, previous_part|
78
- time_emitter(token, previous_part)
59
+ emitters << build_emitters(time_parts) do |token|
60
+ time_emitter(token)
79
61
  end
80
62
  end
81
63
 
@@ -86,16 +68,12 @@ module Stamp
86
68
 
87
69
  # Transforms tokens that look like date/time parts to emitter objects.
88
70
  def build_emitters(tokens)
89
- previous_part = nil
90
71
  tokens.map do |token|
91
- emitter = yield(token, previous_part)
92
- previous_part = emitter.field unless emitter.nil?
93
-
94
- emitter || Emitters::String.new(token)
72
+ yield(token) || Emitters::String.new(token)
95
73
  end
96
74
  end
97
75
 
98
- def time_emitter(token, previous_part)
76
+ def time_emitter(token)
99
77
  case token
100
78
  when MERIDIAN_LOWER_REGEXP
101
79
  Emitters::AmPm.new
@@ -104,15 +82,10 @@ module Stamp
104
82
  Emitters::AmPm.new { |v| v.upcase }
105
83
 
106
84
  when TWO_DIGIT_REGEXP
107
- TWO_DIGIT_TIME_SUCCESSION[previous_part] ||
108
- case token.to_i
109
- when OBVIOUS_24_HOUR
110
- # 24-hour clock
111
- Emitters::TwoDigit.new(:hour)
112
- else
113
- # 12-hour clock with leading zero
114
- Emitters::TwoDigit.new(:hour, &HOUR_TO_12_HOUR)
115
- end
85
+ Emitters::Ambiguous.new(
86
+ two_digit_hour_emitter(token),
87
+ Emitters::TwoDigit.new(:min),
88
+ Emitters::TwoDigit.new(:sec))
116
89
 
117
90
  when ONE_DIGIT_REGEXP
118
91
  # 12-hour clock without leading zero
@@ -120,7 +93,18 @@ module Stamp
120
93
  end
121
94
  end
122
95
 
123
- def date_emitter(token, previous_part)
96
+ def two_digit_hour_emitter(token)
97
+ case token.to_i
98
+ when OBVIOUS_24_HOUR
99
+ # 24-hour clock
100
+ Emitters::TwoDigit.new(:hour)
101
+ else
102
+ # 12-hour clock with leading zero
103
+ Emitters::TwoDigit.new(:hour, &HOUR_TO_12_HOUR)
104
+ end
105
+ end
106
+
107
+ def date_emitter(token)
124
108
  case token
125
109
  when MONTHNAMES_REGEXP
126
110
  Emitters::Lookup.new(:month, Date::MONTHNAMES)
@@ -146,21 +130,22 @@ module Stamp
146
130
  when TWO_DIGIT_REGEXP
147
131
  value = token.to_i
148
132
 
149
- obvious_mappings =
150
- OBVIOUS_DATE_MAP.reject { |k,v| v.field == previous_part }
151
-
152
- obvious_directive = obvious_mappings.find do |range, directive|
153
- break directive if range === value
133
+ case value
134
+ when OBVIOUS_DAY
135
+ TWO_DIGIT_DAY_EMITTER
136
+ when OBVIOUS_YEAR
137
+ TWO_DIGIT_YEAR_EMITTER
138
+ else
139
+ Emitters::Ambiguous.new(
140
+ TWO_DIGIT_MONTH_EMITTER,
141
+ TWO_DIGIT_DAY_EMITTER,
142
+ TWO_DIGIT_YEAR_EMITTER)
154
143
  end
155
144
 
156
- # if the intent isn't obvious based on the example value, try to
157
- # disambiguate based on context
158
- obvious_directive ||
159
- TWO_DIGIT_DATE_SUCCESSION[previous_part] ||
160
- TWO_DIGIT_MONTH_EMITTER
161
-
162
145
  when ONE_DIGIT_REGEXP
163
- Emitters::Delegate.new(:day)
146
+ Emitters::Ambiguous.new(
147
+ Emitters::Delegate.new(:month),
148
+ Emitters::Delegate.new(:day))
164
149
  end
165
150
  end
166
151
 
@@ -1,3 +1,3 @@
1
1
  module Stamp
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
6
6
  s.name = "stamp"
7
7
  s.version = Stamp::VERSION
8
8
  s.authors = ["Jeremy Weiskotten"]
9
- s.email = ["jeremy@weiskotten.com"]
9
+ s.email = ["jeremy@terriblelabs.com"]
10
10
  s.homepage = "https://github.com/jeremyw/stamp"
11
11
  s.summary = %Q{Date and time formatting for humans.}
12
12
  s.description = %Q{Format dates and times based on human-friendly examples, not arcane strftime directives.}
metadata CHANGED
@@ -1,59 +1,54 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stamp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
5
- prerelease:
4
+ version: 0.6.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Jeremy Weiskotten
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-12-17 00:00:00.000000000 Z
11
+ date: 2014-08-21 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: cucumber
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rake
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  description: Format dates and times based on human-friendly examples, not arcane strftime
47
42
  directives.
48
43
  email:
49
- - jeremy@weiskotten.com
44
+ - jeremy@terriblelabs.com
50
45
  executables: []
51
46
  extensions: []
52
47
  extra_rdoc_files: []
53
48
  files:
54
- - .gitignore
55
- - .travis.yml
56
- - .yardopts
49
+ - ".gitignore"
50
+ - ".travis.yml"
51
+ - ".yardopts"
57
52
  - CONTRIBUTING.md
58
53
  - Gemfile
59
54
  - LICENSE.txt
@@ -64,7 +59,9 @@ files:
64
59
  - features/step_definitions/stamp_steps.rb
65
60
  - features/support/env.rb
66
61
  - lib/stamp.rb
62
+ - lib/stamp/disambiguator.rb
67
63
  - lib/stamp/emitters/am_pm.rb
64
+ - lib/stamp/emitters/ambiguous.rb
68
65
  - lib/stamp/emitters/composite.rb
69
66
  - lib/stamp/emitters/delegate.rb
70
67
  - lib/stamp/emitters/lookup.rb
@@ -77,33 +74,26 @@ files:
77
74
  - stamp.gemspec
78
75
  homepage: https://github.com/jeremyw/stamp
79
76
  licenses: []
77
+ metadata: {}
80
78
  post_install_message:
81
79
  rdoc_options: []
82
80
  require_paths:
83
81
  - lib
84
82
  required_ruby_version: !ruby/object:Gem::Requirement
85
- none: false
86
83
  requirements:
87
- - - ! '>='
84
+ - - ">="
88
85
  - !ruby/object:Gem::Version
89
86
  version: '0'
90
- segments:
91
- - 0
92
- hash: 167698609462982419
93
87
  required_rubygems_version: !ruby/object:Gem::Requirement
94
- none: false
95
88
  requirements:
96
- - - ! '>='
89
+ - - ">="
97
90
  - !ruby/object:Gem::Version
98
91
  version: '0'
99
- segments:
100
- - 0
101
- hash: 167698609462982419
102
92
  requirements: []
103
93
  rubyforge_project:
104
- rubygems_version: 1.8.23
94
+ rubygems_version: 2.2.2
105
95
  signing_key:
106
- specification_version: 3
96
+ specification_version: 4
107
97
  summary: Date and time formatting for humans.
108
98
  test_files:
109
99
  - features/stamp.feature