timeliness 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/CHANGELOG.rdoc +6 -0
- data/README.rdoc +30 -4
- data/Rakefile +2 -32
- data/benchmark.rb +161 -0
- data/lib/timeliness.rb +5 -4
- data/lib/timeliness/{formats.rb → definitions.rb} +19 -5
- data/lib/timeliness/format.rb +64 -0
- data/lib/timeliness/format_set.rb +19 -74
- data/lib/timeliness/helpers.rb +1 -1
- data/lib/timeliness/parser.rb +59 -17
- data/lib/timeliness/version.rb +1 -1
- data/spec/spec_helper.rb +2 -2
- data/spec/timeliness/{formats_spec.rb → definitions_spec.rb} +23 -23
- data/spec/timeliness/format_set_spec.rb +20 -33
- data/spec/timeliness/format_spec.rb +41 -0
- data/spec/timeliness/parser_spec.rb +134 -61
- data/timeliness.gemspec +14 -22
- metadata +22 -13
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/*
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
= 0.3.0 - 2010-11-27
|
2
|
+
* Support for parsed timezone offset or abbreviation being used in creating time value
|
3
|
+
* Added timezone abbreviation mapping config option
|
4
|
+
* Allow 2nd argument for parse method to be the type, :now value, or options hash.
|
5
|
+
* Refactoring
|
6
|
+
|
1
7
|
= 0.2.0 - 2010-10-27
|
2
8
|
* Allow a lambda for date_for_time_type which is evaluated on parse
|
3
9
|
* Return the offset or zone in array from _parse
|
data/README.rdoc
CHANGED
@@ -14,9 +14,9 @@ Date/time parser for Ruby with the following features:
|
|
14
14
|
* I18n support (for months), if I18n gem loaded.
|
15
15
|
* Fewer WTFs than Time/Date parse method.
|
16
16
|
* Has no dependencies.
|
17
|
-
* Works with Ruby MRI 1.8.*, 1.9.2, Rubinius
|
17
|
+
* Works with Ruby MRI 1.8.*, 1.9.2, Rubinius and JRuby.
|
18
18
|
|
19
|
-
Extracted from
|
19
|
+
Extracted from the {validates_timeliness gem}[http://github.com/adzap/validates_timeliness], it has been rewritten cleaner and much faster. It's most suitable for when
|
20
20
|
you need to control the parsing behaviour. It's faster than the Time/Date class parse methods, so it
|
21
21
|
has general appeal.
|
22
22
|
|
@@ -64,6 +64,9 @@ It can also be specified with :now option:
|
|
64
64
|
|
65
65
|
Timeliness.parse('12:13:14', :now => Time.mktime(2010,9,8)) #=> Wed Sep 08 12:13:14 1000 2010
|
66
66
|
|
67
|
+
As well conforming to the Ruby Time class style.
|
68
|
+
|
69
|
+
Timeliness.parse('12:13:14', Time.mktime(2010,9,8)) #=> Wed Sep 08 12:13:14 1000 2010
|
67
70
|
|
68
71
|
=== Timezone
|
69
72
|
|
@@ -95,13 +98,36 @@ To get super finicky, you can restrict the parsing to a single format with the :
|
|
95
98
|
Timeliness.parse('08/09/2010 12:13:14', :format => 'yyyy-mm-dd hh:nn:ss') #=> nil
|
96
99
|
|
97
100
|
|
101
|
+
=== String with Offset or Zone Abbreviations
|
102
|
+
|
103
|
+
Sometimes you may want to parse a string with a zone abbreviation (e.g. MST) or the zone offset (e.g. +1000).
|
104
|
+
These values are supported by the parser and will be used when creating the time object. The return value
|
105
|
+
will be in the default timezone or the zone specified with the :zone option.
|
106
|
+
|
107
|
+
Timeliness.parse('Wed, 08 Sep 2010 12:13:14 MST') => Thu, 09 Sep 2010 05:13:14 EST 10:00
|
108
|
+
|
109
|
+
Timeliness.parse('2010-09-08T12:13:14-06:00') => Thu, 09 Sep 2010 05:13:14 EST 10:00
|
110
|
+
|
111
|
+
To enable zone abbreviations to work you must have loaded ActiveSupport.
|
112
|
+
|
113
|
+
The zone abbreviations supported are those defined in the TzInfo gem, used by ActiveSupport. If you find some
|
114
|
+
that are missing you can add more:
|
115
|
+
|
116
|
+
Timeliness.timezone_mapping.update(
|
117
|
+
'ZZZ' => 'Sleepy Town'
|
118
|
+
)
|
119
|
+
|
120
|
+
Where 'Sleepy Town' is a valid zone name supported by ActiveSupport/TzInfo.
|
121
|
+
|
122
|
+
|
98
123
|
=== Raw Parsed Values
|
99
124
|
|
100
125
|
If you would like to get the raw array of values before the time object is created, you can with
|
101
126
|
|
102
|
-
Timeliness._parse('2010-09-08 12:13:14') # => [2010, 9, 8, 12, 13, 14,
|
127
|
+
Timeliness._parse('2010-09-08 12:13:14.123456 MST') # => [2010, 9, 8, 12, 13, 14, 123456, 'MST']
|
103
128
|
|
104
|
-
The last two
|
129
|
+
The last two value are the microseconds, and zone abbreviation or offset.
|
130
|
+
Note: The format for this value is not defined. You can add it yourself, easily.
|
105
131
|
|
106
132
|
|
107
133
|
== Formats
|
data/Rakefile
CHANGED
@@ -1,28 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake/rdoctask'
|
3
|
-
require 'rake/gempackagetask'
|
4
3
|
require 'rubygems/specification'
|
5
4
|
require 'rspec/core/rake_task'
|
6
|
-
require 'lib/timeliness/version'
|
7
5
|
|
8
6
|
GEM_NAME = "timeliness"
|
9
|
-
GEM_VERSION = Timeliness::VERSION
|
10
|
-
|
11
|
-
spec = Gem::Specification.new do |s|
|
12
|
-
s.name = GEM_NAME
|
13
|
-
s.version = GEM_VERSION
|
14
|
-
s.platform = Gem::Platform::RUBY
|
15
|
-
s.rubyforge_project = "timeliness"
|
16
|
-
s.has_rdoc = true
|
17
|
-
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG.rdoc"]
|
18
|
-
s.summary = %q{Control time (parsing), quickly.}
|
19
|
-
s.description = %q{Fast date/time parser with customisable formats and I18n support.}
|
20
|
-
s.author = "Adam Meehan"
|
21
|
-
s.email = "adam.meehan@gmail.com"
|
22
|
-
s.homepage = "http://github.com/adzap/timeliness"
|
23
|
-
s.require_path = 'lib'
|
24
|
-
s.files = %w(timeliness.gemspec LICENSE CHANGELOG.rdoc README.rdoc Rakefile) + Dir.glob("{lib,spec}/**/*")
|
25
|
-
end
|
26
7
|
|
27
8
|
desc 'Default: run specs.'
|
28
9
|
task :default => :spec
|
@@ -47,18 +28,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
47
28
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
29
|
end
|
49
30
|
|
50
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
51
|
-
pkg.gem_spec = spec
|
52
|
-
end
|
53
|
-
|
54
|
-
desc "Install the gem locally"
|
55
|
-
task :install => [:package] do
|
56
|
-
sh %{gem install pkg/#{GEM_NAME}-#{GEM_VERSION}}
|
57
|
-
end
|
58
|
-
|
59
31
|
desc "Create a gemspec file"
|
60
|
-
task :
|
61
|
-
|
62
|
-
file.puts spec.to_ruby
|
63
|
-
end
|
32
|
+
task :build do
|
33
|
+
`gem build #{GEM_NAME}.gemspec`
|
64
34
|
end
|
data/benchmark.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
$:.unshift(File.expand_path('lib'))
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
require 'time'
|
5
|
+
require 'parsedate'
|
6
|
+
require 'timeliness'
|
7
|
+
|
8
|
+
if defined?(JRUBY_VERSION)
|
9
|
+
# Warm up JRuby
|
10
|
+
20000.times do
|
11
|
+
Time.parse("2000-01-04 12:12:12")
|
12
|
+
Timeliness::Parser.parse("2000-01-04 12:12:12", :datetime)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
n = 10000
|
17
|
+
Benchmark.bm do |x|
|
18
|
+
x.report('timeliness - datetime') {
|
19
|
+
n.times do
|
20
|
+
Timeliness::Parser.parse("2000-01-04 12:12:12", :datetime)
|
21
|
+
end
|
22
|
+
}
|
23
|
+
|
24
|
+
x.report('timeliness - datetime with :format') {
|
25
|
+
n.times do
|
26
|
+
Timeliness::Parser.parse("2000-01-04 12:12:12", :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
x.report('timeliness - date') {
|
31
|
+
n.times do
|
32
|
+
Timeliness::Parser.parse("2000-01-04", :date)
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
x.report('timeliness - date as datetime') {
|
37
|
+
n.times do
|
38
|
+
Timeliness::Parser.parse("2000-01-04", :datetime)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
x.report('timeliness - time') {
|
43
|
+
n.times do
|
44
|
+
Timeliness::Parser.parse("12:01:02", :time)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
x.report('timeliness - no type with datetime value') {
|
49
|
+
n.times do
|
50
|
+
Timeliness::Parser.parse("2000-01-04 12:12:12")
|
51
|
+
end
|
52
|
+
}
|
53
|
+
|
54
|
+
x.report('timeliness - no type with date value') {
|
55
|
+
n.times do
|
56
|
+
Timeliness::Parser.parse("2000-01-04")
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
60
|
+
x.report('timeliness - no type with time value') {
|
61
|
+
n.times do
|
62
|
+
Timeliness::Parser.parse("12:01:02")
|
63
|
+
end
|
64
|
+
}
|
65
|
+
|
66
|
+
x.report('timeliness - invalid format datetime') {
|
67
|
+
n.times do
|
68
|
+
Timeliness::Parser.parse("20xx-01-04 12:12:12", :datetime)
|
69
|
+
end
|
70
|
+
}
|
71
|
+
|
72
|
+
x.report('timeliness - invalid format date') {
|
73
|
+
n.times do
|
74
|
+
Timeliness::Parser.parse("20xx-01-04", :date)
|
75
|
+
end
|
76
|
+
}
|
77
|
+
|
78
|
+
x.report('timeliness - invalid format time') {
|
79
|
+
n.times do
|
80
|
+
Timeliness::Parser.parse("12:xx:02", :time)
|
81
|
+
end
|
82
|
+
}
|
83
|
+
|
84
|
+
x.report('timeliness - invalid value datetime') {
|
85
|
+
n.times do
|
86
|
+
Timeliness::Parser.parse("2000-01-32 12:12:12", :datetime)
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
x.report('timeliness - invalid value date') {
|
91
|
+
n.times do
|
92
|
+
Timeliness::Parser.parse("2000-01-32", :date)
|
93
|
+
end
|
94
|
+
}
|
95
|
+
|
96
|
+
x.report('timeliness - invalid value time') {
|
97
|
+
n.times do
|
98
|
+
Timeliness::Parser.parse("12:61:02", :time)
|
99
|
+
end
|
100
|
+
}
|
101
|
+
|
102
|
+
x.report('ISO regexp for datetime') {
|
103
|
+
n.times do
|
104
|
+
"2000-01-04 12:12:12" =~ /\A(\d{4})-(\d{2})-(\d{2}) (\d{2})[\. :](\d{2})([\. :](\d{2}))?\Z/
|
105
|
+
microsec = ($7.to_f * 1_000_000).to_i
|
106
|
+
Time.mktime($1.to_i, $2.to_i, $3.to_i, $3.to_i, $5.to_i, $6.to_i, microsec)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
|
110
|
+
x.report('Time.parse - valid') {
|
111
|
+
n.times do
|
112
|
+
Time.parse("2000-01-04 12:12:12")
|
113
|
+
end
|
114
|
+
}
|
115
|
+
|
116
|
+
x.report('Time.parse - invalid ') {
|
117
|
+
n.times do
|
118
|
+
Time.parse("2000-01-32 12:12:12") rescue nil
|
119
|
+
end
|
120
|
+
}
|
121
|
+
|
122
|
+
x.report('Date._parse - valid') {
|
123
|
+
n.times do
|
124
|
+
hash = Date._parse("2000-01-04 12:12:12")
|
125
|
+
Time.mktime(hash[:year], hash[:mon], hash[:mday], hash[:hour], hash[:min], hash[:sec])
|
126
|
+
end
|
127
|
+
}
|
128
|
+
|
129
|
+
x.report('Date._parse - invalid ') {
|
130
|
+
n.times do
|
131
|
+
hash = Date._parse("2000-01-32 12:12:12")
|
132
|
+
Time.mktime(hash[:year], hash[:mon], hash[:mday], hash[:hour], hash[:min], hash[:sex]) rescue nil
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
x.report('parsedate - valid') {
|
137
|
+
n.times do
|
138
|
+
arr = ParseDate.parsedate("2000-01-04 12:12:12")
|
139
|
+
Date.new(*arr[0..2])
|
140
|
+
Time.mktime(*arr)
|
141
|
+
end
|
142
|
+
}
|
143
|
+
|
144
|
+
x.report('parsedate - invalid ') {
|
145
|
+
n.times do
|
146
|
+
arr = ParseDate.parsedate("2000-00-04 12:12:12")
|
147
|
+
end
|
148
|
+
}
|
149
|
+
|
150
|
+
x.report('strptime - valid') {
|
151
|
+
n.times do
|
152
|
+
DateTime.strptime("2000-01-04 12:12:12", '%Y-%m-%d %H:%M:%s')
|
153
|
+
end
|
154
|
+
}
|
155
|
+
|
156
|
+
x.report('strptime - invalid') {
|
157
|
+
n.times do
|
158
|
+
DateTime.strptime("2000-00-04 12:12:12", '%Y-%m-%d %H:%M:%s') rescue nil
|
159
|
+
end
|
160
|
+
}
|
161
|
+
end
|
data/lib/timeliness.rb
CHANGED
@@ -2,7 +2,8 @@ require 'date'
|
|
2
2
|
require 'forwardable'
|
3
3
|
|
4
4
|
require 'timeliness/helpers'
|
5
|
-
require 'timeliness/
|
5
|
+
require 'timeliness/definitions'
|
6
|
+
require 'timeliness/format'
|
6
7
|
require 'timeliness/format_set'
|
7
8
|
require 'timeliness/parser'
|
8
9
|
require 'timeliness/version'
|
@@ -11,7 +12,7 @@ module Timeliness
|
|
11
12
|
class << self
|
12
13
|
extend Forwardable
|
13
14
|
def_delegators Parser, :parse, :_parse
|
14
|
-
def_delegators
|
15
|
+
def_delegators Definitions, :add_formats, :remove_formats, :use_us_formats, :use_euro_formats
|
15
16
|
attr_accessor :default_timezone, :date_for_time_type, :ambiguous_year_threshold
|
16
17
|
end
|
17
18
|
|
@@ -26,7 +27,7 @@ module Timeliness
|
|
26
27
|
@default_timezone = :local
|
27
28
|
|
28
29
|
# Set the default date part for a time type values.
|
29
|
-
@date_for_time_type =
|
30
|
+
@date_for_time_type = lambda { Time.now }
|
30
31
|
|
31
32
|
def self.date_for_time_type
|
32
33
|
case @date_for_time_type
|
@@ -49,4 +50,4 @@ module Timeliness
|
|
49
50
|
@ambiguous_year_threshold = 30
|
50
51
|
end
|
51
52
|
|
52
|
-
Timeliness::
|
53
|
+
Timeliness::Definitions.compile_formats
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Timeliness
|
2
|
-
module
|
2
|
+
module Definitions
|
3
3
|
|
4
4
|
# Format tokens:
|
5
5
|
# y = year
|
@@ -100,7 +100,7 @@ module Timeliness
|
|
100
100
|
'u' => [ '\d{1,6}', :usec ],
|
101
101
|
'ampm' => [ '[aApP]\.?[mM]\.?', :meridian ],
|
102
102
|
'zo' => [ '[+-]\d{2}:?\d{2}', :offset ],
|
103
|
-
'tz' => [ '[A-Z]{1,
|
103
|
+
'tz' => [ '[A-Z]{1,5}', :zone ],
|
104
104
|
'_' => [ '\s?' ]
|
105
105
|
}
|
106
106
|
|
@@ -126,10 +126,25 @@ module Timeliness
|
|
126
126
|
:meridian => [ nil ]
|
127
127
|
}
|
128
128
|
|
129
|
+
# Mapping some common timezone abbreviations which are not mapped or
|
130
|
+
# mapped inconsistenly in ActiveSupport (TzInfo).
|
131
|
+
@timezone_mapping = {
|
132
|
+
'AEST' => 'Australia/Sydney',
|
133
|
+
'AEDT' => 'Australia/Sydney',
|
134
|
+
'ACST' => 'Australia/Adelaide',
|
135
|
+
'ACDT' => 'Australia/Adelaide',
|
136
|
+
'PST' => 'PST8PDT',
|
137
|
+
'PDT' => 'PST8PDT',
|
138
|
+
'CST' => 'CST6CDT',
|
139
|
+
'CDT' => 'CST6CDT',
|
140
|
+
'EDT' => 'EST5EDT',
|
141
|
+
'MDT' => 'MST7MDT'
|
142
|
+
}
|
143
|
+
|
129
144
|
US_FORMAT_REGEXP = /\Am{1,2}[^m]/
|
130
145
|
|
131
146
|
class << self
|
132
|
-
attr_accessor :time_formats, :date_formats, :datetime_formats, :format_tokens, :format_components
|
147
|
+
attr_accessor :time_formats, :date_formats, :datetime_formats, :format_tokens, :format_components, :timezone_mapping
|
133
148
|
attr_reader :date_format_set, :time_format_set, :datetime_format_set
|
134
149
|
|
135
150
|
# Adds new formats. Must specify format type and can specify a :before
|
@@ -191,7 +206,7 @@ module Timeliness
|
|
191
206
|
|
192
207
|
# Returns format for type and other possible matching format set based on type
|
193
208
|
# and value length. Gives minor speed-up by checking string length.
|
194
|
-
def
|
209
|
+
def format_sets(type, string)
|
195
210
|
case type
|
196
211
|
when :date
|
197
212
|
[ @date_format_set, @datetime_format_set ]
|
@@ -217,6 +232,5 @@ module Timeliness
|
|
217
232
|
end
|
218
233
|
|
219
234
|
end
|
220
|
-
|
221
235
|
end
|
222
236
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Timeliness
|
2
|
+
class Format
|
3
|
+
include Helpers
|
4
|
+
|
5
|
+
attr_reader :format_string, :regexp, :regexp_string, :token_count
|
6
|
+
|
7
|
+
def initialize(format_string)
|
8
|
+
@format_string = format_string
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile!
|
12
|
+
@token_count = 0
|
13
|
+
format = format_string.dup
|
14
|
+
format.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
|
15
|
+
found_tokens, token_order = [], []
|
16
|
+
|
17
|
+
# Substitute tokens with numbered placeholder
|
18
|
+
Definitions.sorted_token_keys.each do |token|
|
19
|
+
token_regexp_str, arg_key = Definitions.format_tokens[token]
|
20
|
+
if format.gsub!(/#{token}/, "%<#{found_tokens.size}>")
|
21
|
+
if arg_key
|
22
|
+
token_regexp_str = "(#{token_regexp_str})"
|
23
|
+
@token_count += 1
|
24
|
+
end
|
25
|
+
found_tokens << [token_regexp_str, arg_key]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Replace placeholders with token regexps
|
30
|
+
format.scan(/%<(\d)>/).each {|token_index|
|
31
|
+
token_index = token_index.first
|
32
|
+
token_regexp_str, arg_key = found_tokens[token_index.to_i]
|
33
|
+
format.gsub!("%<#{token_index}>", token_regexp_str)
|
34
|
+
token_order << arg_key
|
35
|
+
}
|
36
|
+
|
37
|
+
define_process_method(token_order.compact)
|
38
|
+
@regexp_string = format
|
39
|
+
@regexp = Regexp.new("^(#{format})$")
|
40
|
+
self
|
41
|
+
rescue
|
42
|
+
raise "The format '#{format_string}' failed to compile using regexp string #{format}."
|
43
|
+
end
|
44
|
+
|
45
|
+
# Redefined on compile
|
46
|
+
def process(*args); end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def define_process_method(components)
|
51
|
+
values = [nil] * 8
|
52
|
+
components.each do |component|
|
53
|
+
position, code = Definitions.format_components[component]
|
54
|
+
values[position] = code || "#{component}.to_i" if position
|
55
|
+
end
|
56
|
+
instance_eval <<-DEF
|
57
|
+
def process(#{components.join(',')})
|
58
|
+
[#{values.map {|i| i || 'nil' }.join(',')}]
|
59
|
+
end
|
60
|
+
DEF
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -1,97 +1,42 @@
|
|
1
1
|
module Timeliness
|
2
2
|
class FormatSet
|
3
|
-
include Helpers
|
4
|
-
|
5
3
|
attr_reader :formats, :regexp
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
def compile(formats)
|
10
|
-
set = new(formats)
|
11
|
-
set.compile!
|
12
|
-
set
|
13
|
-
end
|
14
|
-
|
15
|
-
def compile_format(string_format)
|
16
|
-
format = string_format.dup
|
17
|
-
format.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
|
18
|
-
found_tokens, token_order, value_token_count = [], [], 0
|
19
|
-
|
20
|
-
# Substitute tokens with numbered placeholder
|
21
|
-
Formats.sorted_token_keys.each do |token|
|
22
|
-
regexp_str, arg_key = *Formats.format_tokens[token]
|
23
|
-
if format.gsub!(/#{token}/, "%<#{found_tokens.size}>")
|
24
|
-
if arg_key
|
25
|
-
regexp_str = "(#{regexp_str})"
|
26
|
-
value_token_count += 1
|
27
|
-
end
|
28
|
-
found_tokens << [regexp_str, arg_key]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Replace placeholders with token regexps
|
33
|
-
format.scan(/%<(\d)>/).each {|token_index|
|
34
|
-
token_index = token_index.first
|
35
|
-
regexp_str, arg_key = found_tokens[token_index.to_i]
|
36
|
-
format.gsub!("%<#{token_index}>", regexp_str)
|
37
|
-
token_order << arg_key
|
38
|
-
}
|
39
|
-
|
40
|
-
define_format_method(string_format, token_order.compact)
|
41
|
-
return format, value_token_count
|
42
|
-
rescue
|
43
|
-
raise "The following format regular expression failed to compile: #{format}\n from format #{string_format}."
|
44
|
-
end
|
45
|
-
|
46
|
-
# Compiles a format method which maps the regexp capture groups to method
|
47
|
-
# arguments based on order captured. A time array is built using the argument
|
48
|
-
# values placed in the position defined by the component.
|
49
|
-
#
|
50
|
-
def define_format_method(name, components)
|
51
|
-
values = [nil] * 8
|
52
|
-
components.each do |component|
|
53
|
-
position, code = *Formats.format_components[component]
|
54
|
-
values[position] = code || "#{component}.to_i" if position
|
55
|
-
end
|
56
|
-
class_eval <<-DEF
|
57
|
-
define_method(:"format_#{name}") do |#{components.join(',')}|
|
58
|
-
[#{values.map {|i| i || 'nil' }.join(',')}]
|
59
|
-
end
|
60
|
-
DEF
|
61
|
-
end
|
62
|
-
|
5
|
+
def self.compile(formats)
|
6
|
+
new(formats).compile!
|
63
7
|
end
|
64
8
|
|
65
9
|
def initialize(formats)
|
66
|
-
@formats
|
10
|
+
@formats = formats
|
11
|
+
@formats_hash = {}
|
12
|
+
@match_indexes = {}
|
67
13
|
end
|
68
14
|
|
69
15
|
# Compiles the formats into one big regexp. Stores the index of where
|
70
|
-
# each format's capture values begin in the
|
71
|
-
# format regpexp is also stored for use with the parse :format option.
|
72
|
-
#
|
16
|
+
# each format's capture values begin in the matchdata.
|
73
17
|
def compile!
|
74
|
-
regexp_string
|
75
|
-
@
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
regexp_string = "#{regexp_string}(#{format_regexp})|"
|
82
|
-
index + token_count + 1 # add one for wrapper capture
|
18
|
+
regexp_string = ''
|
19
|
+
@formats.inject(0) { |index, format_string|
|
20
|
+
format = Format.new(format_string).compile!
|
21
|
+
@formats_hash[format_string] = format
|
22
|
+
@match_indexes[index] = format
|
23
|
+
regexp_string = "#{regexp_string}(#{format.regexp_string})|"
|
24
|
+
index + format.token_count + 1 # add one for wrapper capture
|
83
25
|
}
|
84
26
|
@regexp = Regexp.new("^(?:#{regexp_string.chop})$")
|
27
|
+
self
|
85
28
|
end
|
86
29
|
|
87
|
-
def match(string,
|
88
|
-
|
30
|
+
def match(string, format_string=nil)
|
31
|
+
format = @formats_hash[format_string] if format_string
|
32
|
+
match_regexp = format && format.regexp || @regexp
|
33
|
+
|
89
34
|
if match_data = match_regexp.match(string)
|
90
35
|
index = match_data.captures.index(string)
|
91
36
|
start = index + 1
|
92
37
|
values = match_data.captures[start..(start+7)].compact
|
93
38
|
format ||= @match_indexes[index]
|
94
|
-
|
39
|
+
format.process(*values)
|
95
40
|
end
|
96
41
|
end
|
97
42
|
|