stamp 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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