timeliness 0.2.0 → 0.3.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/.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
|
|