jinx-migrate 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +38 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +33 -0
  10. data/Rakefile +40 -0
  11. data/bin/csvjoin +24 -0
  12. data/examples/family/README.md +24 -0
  13. data/examples/family/conf/children/fields.yaml +2 -0
  14. data/examples/family/conf/parents/defaults.yaml +3 -0
  15. data/examples/family/conf/parents/fields.yaml +6 -0
  16. data/examples/family/conf/parents/values.yaml +4 -0
  17. data/examples/family/data/children.csv +1 -0
  18. data/examples/family/data/parents.csv +1 -0
  19. data/examples/family/lib/shims.rb +17 -0
  20. data/jinx-migrate.gemspec +26 -0
  21. data/lib/jinx/csv/csvio.rb +214 -0
  22. data/lib/jinx/csv/joiner.rb +196 -0
  23. data/lib/jinx/migration/filter.rb +167 -0
  24. data/lib/jinx/migration/migratable.rb +244 -0
  25. data/lib/jinx/migration/migrator.rb +1029 -0
  26. data/lib/jinx/migration/reader.rb +16 -0
  27. data/lib/jinx/migration/version.rb +5 -0
  28. data/spec/bad/bad_spec.rb +25 -0
  29. data/spec/bad/fields.yaml +1 -0
  30. data/spec/bad/parents.csv +1 -0
  31. data/spec/bad/shims.rb +16 -0
  32. data/spec/csv/join/join_helper.rb +35 -0
  33. data/spec/csv/join/join_spec.rb +100 -0
  34. data/spec/csv/join/jumbled_src.csv +7 -0
  35. data/spec/csv/join/jumbled_tgt.csv +7 -0
  36. data/spec/csv/join/source.csv +7 -0
  37. data/spec/csv/join/target.csv +7 -0
  38. data/spec/extract/extract.rb +13 -0
  39. data/spec/extract/extract_spec.rb +33 -0
  40. data/spec/extract/fields.yaml +1 -0
  41. data/spec/extract/parents.csv +1 -0
  42. data/spec/family/child_spec.rb +27 -0
  43. data/spec/family/family.rb +13 -0
  44. data/spec/family/parent_spec.rb +57 -0
  45. data/spec/filter/fields.yaml +1 -0
  46. data/spec/filter/filter_spec.rb +20 -0
  47. data/spec/filter/parents.csv +1 -0
  48. data/spec/filter/values.yaml +4 -0
  49. data/spec/primitive/children.csv +1 -0
  50. data/spec/primitive/fields.yaml +4 -0
  51. data/spec/primitive/primitive_spec.rb +24 -0
  52. data/spec/skip/fields.yaml +1 -0
  53. data/spec/skip/parents.csv +1 -0
  54. data/spec/skip/skip_spec.rb +17 -0
  55. data/spec/spec_helper.rb +17 -0
  56. data/spec/support/model.rb +7 -0
  57. data/spec/unique/fields.yaml +1 -0
  58. data/spec/unique/parent.rb +6 -0
  59. data/spec/unique/parents.csv +1 -0
  60. data/spec/unique/shims.rb +10 -0
  61. data/spec/unique/unique_spec.rb +20 -0
  62. data/test/fixtures/csv/data/empty.csv +1 -0
  63. data/test/fixtures/csv/data/variety.csv +1 -0
  64. data/test/lib/csv/csvio_test.rb +74 -0
  65. metadata +206 -0
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ .DS_Store
2
+ *~
3
+ *.pdf
4
+ .project
5
+ .loadpath
6
+ .yardoc
7
+ *.gem
8
+ *.tar*
9
+ **/ext/bin
10
+ **/classes
11
+ /doc/api
12
+ **log
13
+ /test/results
14
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --backtrace
2
+ --format Fuubar
3
+ --color
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ -o doc/api --private --protected - History.md LEGAL LICENSE
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ group :development do
5
+ # Uncomment to use the local development project.
6
+ gem 'jinx', :path => File.dirname(__FILE__) + '/../core'
7
+ gem 'jinx-migrate', :path => File.dirname(__FILE__)
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jinx-migrate (2.1.1)
5
+ bundler
6
+ fastercsv
7
+ rack
8
+
9
+ PATH
10
+ remote: /Users/loneyf/workspace/jinx/core
11
+ specs:
12
+ jinx (2.1.1)
13
+ bundler
14
+
15
+ GEM
16
+ remote: http://rubygems.org/
17
+ specs:
18
+ diff-lcs (1.1.3)
19
+ fastercsv (1.5.4)
20
+ rack (1.4.1)
21
+ rake (0.9.2.2)
22
+ rspec (2.9.0)
23
+ rspec-core (~> 2.9.0)
24
+ rspec-expectations (~> 2.9.0)
25
+ rspec-mocks (~> 2.9.0)
26
+ rspec-core (2.9.0)
27
+ rspec-expectations (2.9.1)
28
+ diff-lcs (~> 1.1.3)
29
+ rspec-mocks (2.9.0)
30
+
31
+ PLATFORMS
32
+ java
33
+
34
+ DEPENDENCIES
35
+ jinx!
36
+ jinx-migrate!
37
+ rake
38
+ rspec (>= 2.6)
data/History.md ADDED
@@ -0,0 +1,6 @@
1
+ This history lists major release themes. See the GitHub commits (https://github.com/jinx/migrate)
2
+ for change details.
3
+
4
+ 2.1.1 / 2012-04-13
5
+ ------------------
6
+ * Initial public release spun off from caruby/core.
data/LEGAL ADDED
@@ -0,0 +1,5 @@
1
+ LEGAL NOTICE INFORMATION
2
+ ------------------------
3
+
4
+ All the files in this distribution are covered under either the MIT
5
+ license (see the file LICENSE).
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Oregon Health & Science University
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ Jinx Migrator
2
+ =============
3
+
4
+ **Home**: [http://github.com/jinx/migrate](http://github.com/jinx/migrate)
5
+ **Git**: [http://github.com/jinx/migrate](http://github.com/jinx/migrate)
6
+ **Author**: OHSU Knight Cancer Institute
7
+ **Copyright**: 2012
8
+ **License**: MIT License
9
+
10
+ Synopsis
11
+ --------
12
+ The Jinx Migrator migrates input data into a [Jinx](http://github.com/jinx/core) target.
13
+
14
+ Installing
15
+ ----------
16
+ The Jinx Migrator is installed as a JRuby gem:
17
+
18
+ [sudo] jgem install jinx-migrate
19
+
20
+ Usage
21
+ -----
22
+ 1. Enable Jinx for a Java package, as described in the [Jinx](http://github.com/jinx/core) Usage.
23
+
24
+ 2. Configure the input -> target mapping.
25
+
26
+ 3. Run the migrator.
27
+
28
+ See the [Family](http://github.com/jinx/migrate/tree/master/examples/family) example for a sample migration.
29
+
30
+ Copyright
31
+ ---------
32
+ Jinx © 2012 by [Oregon Health & Science University](http://www.ohsu.edu/xd/health/services/cancer/index.cfm).
33
+ Jinx is licensed under the MIT license. Please see the LICENSE and LEGAL files for more information.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/lib/jinx/migration/version'
2
+
3
+ # the gem name
4
+ GEM = 'jinx-migrate'
5
+ GEM_VERSION = Jinx::Migrate::VERSION
6
+
7
+ WINDOWS = (Config::CONFIG['host_os'] =~ /mingw|win32|cygwin/ ? true : false) rescue false
8
+ SUDO = WINDOWS ? '' : 'sudo'
9
+
10
+ desc 'Default: run the specs'
11
+ task :default => :spec
12
+
13
+ desc "Builds the gem"
14
+ task :gem do
15
+ sh "jgem build #{GEM}.gemspec"
16
+ end
17
+
18
+ desc "Installs the gem"
19
+ task :install => :gem do
20
+ sh "#{SUDO} jgem install #{GEM}-#{GEM_VERSION}.gem"
21
+ end
22
+
23
+ desc 'Documents the API'
24
+ task :doc do
25
+ FileUtils.rm_rf 'doc/api'
26
+ sh 'yardoc'
27
+ end
28
+
29
+ desc 'Runs the spec tests'
30
+ task :spec do
31
+ Dir['spec/**/*_spec.rb'].each { |f| sh "rspec #{f}" rescue nil }
32
+ end
33
+
34
+ desc 'Runs the unit tests'
35
+ task :unit do
36
+ Dir['test/**/*_test.rb'].each { |f| sh "jruby #{f}" rescue nil }
37
+ end
38
+
39
+ desc 'Runs all tests'
40
+ task :test => [:spec, :unit]
data/bin/csvjoin ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # csvjoin: joins two CSV files on their common fields
4
+ #
5
+
6
+ # Add the migrate lib to the path.
7
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
8
+
9
+ require 'rubygems'
10
+ require 'jinx'
11
+ require 'jinx/csv/csvio'
12
+ require 'jinx/cli/command'
13
+
14
+ specs = [
15
+ [:to, '--to TARGET', 'The join target input file (default stdin)'],
16
+ [:as, '--as OUTPUT', 'The joined output file (default stdout)'],
17
+ [:source, 'SOURCE', 'The join source input file']
18
+ ]
19
+
20
+ Jinx::CLI::Command.new(specs).start do |opts|
21
+ Jinx::CsvIO.join(opts.delete(:source), opts)
22
+ end
23
+
24
+ exit 0
@@ -0,0 +1,24 @@
1
+ Family migration example
2
+ ========================
3
+
4
+ Synopsis
5
+ --------
6
+ This directory contains the Jinx migration Family example.
7
+
8
+ The Family example demonstrates how to load the content of a source CSV file into
9
+ a Family data store. The use cases illustrate several common migration impediments:
10
+
11
+ * Different source-destination terminology
12
+ * Different source-destination associations
13
+ * Incomplete input
14
+ * Denormalized input
15
+ * Inconsistent input
16
+ * Input data scrubbing
17
+
18
+ Migration
19
+ ---------
20
+ The example migration input data resides in the `data` directory.
21
+ Each `parents` CSV input file holds one row for each parent.
22
+ Each `childs` CSV input file holds one row for each parent.
23
+
24
+ Each input file has a corresponding migration mapping configuration in the `conf` directory.
@@ -0,0 +1,2 @@
1
+ Parent: Parent.name
2
+ Child: Child.name
@@ -0,0 +1,3 @@
1
+ # This defaults configuration file demonstrates how to set a default property value in
2
+ # the migrated record.
3
+ Household.address.state: IL
@@ -0,0 +1,6 @@
1
+ Name: Parent.name
2
+ Street: Household.address.street1
3
+ City: Household.address.city
4
+ Zip: Household.address.postal_code
5
+ Spouse: Parent.spouse.name
6
+
@@ -0,0 +1,4 @@
1
+ # This value filter configuration file demonstrates how to transform an input field value
2
+ # to a migrated value. 'Street' is abbreviated to 'St'.
3
+ Address.street1:
4
+ /^(.* St)reet(.*)$/ : "$1$2"
@@ -0,0 +1 @@
1
+ Parent,Child
@@ -0,0 +1 @@
1
+ Name,Street,City,Zip,Spouse
@@ -0,0 +1,17 @@
1
+ module Family
2
+ # Declares the classes modified for migration.
3
+ shims Parent
4
+
5
+ class Parent
6
+ # Augments the migration by setting the spouse household.
7
+ #
8
+ # @param [{Symbol => Object}] row the input row field => value hash
9
+ # @param [<Resource>] migrated the migrated instances
10
+ def migrate(row, migrated)
11
+ super
12
+ if spouse then
13
+ spouse.household = migrated.detect { |m| Household === m }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/lib/jinx/migration/version'
2
+ require 'date'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'jinx-migrate'
6
+ s.summary = 'Jinx JSON plug-in.'
7
+ s.description = s.summary + '. See github.com/jinx/migrate for more information.'
8
+ s.version = Jinx::Migrate::VERSION
9
+ s.date = Date.today
10
+ s.author = 'OHSU'
11
+ s.email = "jinx.ruby@gmail.com"
12
+ s.homepage = 'http://github.com/jinx/migrate'
13
+ s.require_path = 'lib'
14
+ s.bindir = 'bin'
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files spec`.split("\n")
17
+ s.executables = `git ls-files bin`.split("\n").map{ |f| File.basename(f) }
18
+ s.add_runtime_dependency 'rack'
19
+ s.add_runtime_dependency 'bundler'
20
+ s.add_runtime_dependency 'fastercsv'
21
+ s.add_development_dependency 'rake'
22
+ s.add_development_dependency 'rspec', '>= 2.6'
23
+ s.has_rdoc = 'yard'
24
+ s.license = 'MIT'
25
+ s.rubyforge_project = 'jinx'
26
+ end
@@ -0,0 +1,214 @@
1
+ require 'fileutils'
2
+ require 'faster_csv'
3
+ require 'jinx/helpers/options'
4
+ require 'jinx/helpers/collections'
5
+ require 'jinx/csv/joiner'
6
+
7
+ module Jinx
8
+ # CsvIO reads or writes CSV records.
9
+ # This class wraps a FasterCSV with the following modifications:
10
+ # * relax the date parser to allow dd/mm/yyyy dates
11
+ # * don't convert integer text with a leading zero to an octal number
12
+ # * allow one custom converter with different semantics: if the converter block
13
+ # call returns nil, then continue conversion, otherwise return the converter
14
+ # result. This differs from FasterCSV converter semantics which calls converters
15
+ # as long the result equals the input field value. The CsvIO converter semantics
16
+ # supports converters that intend a String result to be the converted result.
17
+ #
18
+ # CsvIO is Enumerable, but does not implement the complete Ruby IO interface.
19
+ class CsvIO
20
+ include Enumerable
21
+
22
+ # @return [<String>] the CSV field names
23
+ attr_reader :field_names
24
+
25
+ # @return [<Symbol>] the CSV field value accessor
26
+ attr_reader :accessors
27
+ alias :headers :accessors
28
+
29
+ # Opens the CSV file and calls the given block with this CsvIO as the argument.
30
+ #
31
+ # @param (see #initialize)
32
+ # @option (see #initialize)
33
+ # @yield [csvio] the optional block to execute
34
+ # @yieldparam [CsvIO] csvio the open CSVIO instance
35
+ def self.open(dev, opts=nil)
36
+ csvio = new(dev, opts)
37
+ if block_given? then
38
+ begin
39
+ yield csvio
40
+ ensure
41
+ csvio.close
42
+ end
43
+ end
44
+ end
45
+
46
+ # Opens the given CSV file and calls {#each} with the given block.
47
+ #
48
+ # @param (see #initialize)
49
+ # @option (see #initialize)
50
+ # @yield [row] the block to execute on the row
51
+ # @yieldparam [{Symbol => Object}] row the field symbol => value hash
52
+ def self.foreach(file, opts=nil, &block)
53
+ open(file, opts) { |csvio| csvio.each(&block) }
54
+ end
55
+
56
+ # Joins the source to the target and writes the output. The match is on all fields
57
+ # held in common. If there is more than one match, then all but the first match has
58
+ # empty values for the merged fields. Both files must be sorted in order of the
59
+ # common fields, sequenced by their occurence in the source header.
60
+ #
61
+ # @param [String, IO] source the join source file
62
+ # @param [{Symbol => String, IO, <String>}] opts the join options
63
+ # @option opts [String, IO] :to the join target file name or device (default stdin)
64
+ # @option opts [<String>] :for the target field names (default all target fields)
65
+ # @option opts [String, IO] :as the output file name or device (default stdout)
66
+ # @yield (see Csv::Joiner#join)
67
+ # @yieldparam (see Csv::Joiner#join)
68
+ def self.join(source, opts, &block)
69
+ flds = opts[:for] || Array::EMPTY_ARRAY
70
+ Csv::Joiner.new(source, opts[:to], opts[:as]).join(*flds, &block)
71
+ end
72
+
73
+ # Creates a new CsvIO for the specified source file.
74
+ # If a converter block is given, then it is added to the CSV converters list.
75
+ #
76
+ # @param [String, IO] dev the CSV file or stream to open
77
+ # @param [Hash] opts the open options
78
+ # @option opts [String] :mode the input mode (default +r+)
79
+ # @option opts [String] :headers the input field headers
80
+ # @yield [value, info] converts the input value
81
+ # @yieldparam [String] value the input value
82
+ # @yieldparam info the current field's FasterCSV FieldInfo metadata
83
+ # @raise [ArgumentError] if the input is nil
84
+ def initialize(dev, opts=nil, &converter)
85
+ raise ArgumentError.new("CSV input argument is missing") if dev.nil?
86
+ # the CSV file open mode
87
+ mode = Options.get(:mode, opts, 'r')
88
+ # the CSV headers option; can be boolean or array
89
+ hdr_opt = Options.get(:headers, opts)
90
+ # there is a header record by default for an input CSV file
91
+ hdr_opt ||= true if mode =~ /^r/
92
+ # make parent directories if necessary for an output CSV file
93
+ File.makedirs(File.dirname(dev)) if String == dev and mode =~ /^w/
94
+ # if headers aren't given, then convert the input CSV header record names to underscore symbols
95
+ hdr_cvtr = :symbol unless Enumerable === hdr_opt
96
+ # make a custom converter
97
+ custom = Proc.new { |value, info| convert(value, info, &converter) }
98
+ # collect the options
99
+ csv_opts = {:headers => hdr_opt, :header_converters => hdr_cvtr, :return_headers => true, :write_headers => true, :converters => custom}
100
+ # Make the parent directory if necessary.
101
+ FileUtils.mkdir_p(File.dirname(dev)) if String === dev and mode !~ /^r/
102
+ # open the CSV file
103
+ @csv = String === dev ? FasterCSV.open(dev, mode, csv_opts) : FasterCSV.new(dev, csv_opts)
104
+ # the header => field name hash:
105
+ # if the header option is set to true, then read the input header line.
106
+ # otherwise, parse an empty string which mimics an input header line.
107
+ hdr_row = case hdr_opt
108
+ when true then
109
+ @csv.shift
110
+ when Enumerable then
111
+ ''.parse_csv(:headers => hdr_opt, :header_converters => :symbol, :return_headers => true)
112
+ else
113
+ Jinx.fail(ArgumentError, "CSV headers option value not supported: #{hdr_opt}")
114
+ end
115
+ # The field value accessors consist of the header row headers converted to a symbol.
116
+ @accessors = hdr_row.headers
117
+ # The field names consist of the header row values.
118
+ @field_names = @accessors.map { |sym| hdr_row[sym] }
119
+ # the header name => symbol map
120
+ @hdr_sym_hash = hdr_row.to_hash.invert
121
+ end
122
+
123
+ # Closes the CSV file.
124
+ def close
125
+ @csv.close
126
+ end
127
+
128
+ # @param [String] header the CSV field header name
129
+ # @param [Symbol] the header accessor method
130
+ def accessor(name)
131
+ @hdr_sym_hash[name]
132
+ end
133
+
134
+ # Iterates over each CSV row, yielding a row for each iteration.
135
+ #
136
+ # @yield [row] processes the CSV row
137
+ # @yieldparam [FasterCSV::Row] row the CSV row
138
+ def each(&block)
139
+ @csv.each(&block)
140
+ end
141
+
142
+ # Reads the next CSV row.
143
+ #
144
+ # @return the next CSV row
145
+ # @see #each
146
+ def readline
147
+ @csv.shift
148
+ end
149
+
150
+ alias :shift :readline
151
+
152
+ alias :next :readline
153
+
154
+ # Writes the given row to the CSV file.
155
+ #
156
+ #@param [{Symbol => Object}] row the input row
157
+ def write(row)
158
+ @csv << row
159
+ @csv.flush
160
+ end
161
+
162
+ alias :<< :write
163
+
164
+ private
165
+
166
+ # 3-letter months => month sequence hash.
167
+ MMM_MM_MAP = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].to_compact_hash_with_index do |mmm, index|
168
+ index < 9 ? ('0' + index.succ.to_s) : index.succ.to_s
169
+ end
170
+
171
+ # DateMatcher relaxes the FasterCSV DateMatcher to allow dd/mm/yyyy dates.
172
+ DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} | \d{1,2}-\w{3}-\d{2,4} | \d{4}[-\/]\d{1,2}[-\/]\d{1,2} | \d{1,2}[-\/]\d{1,2}[-\/]\d{2,4} )\z /x
173
+
174
+ DD_MMM_YYYY_RE = /^(\d{1,2})-([[:alpha:]]{3})-(\d{2,4})$/
175
+
176
+ # @param f the input field value to convert
177
+ # @param info the CSV field info
178
+ # @return the converted value
179
+ def convert(f, info)
180
+ return if f.nil?
181
+ # the block has precedence
182
+ value = yield(f, info) if block_given?
183
+ # integer conversion
184
+ value ||= Integer(f) if f =~ /^[1-9]\d*$/
185
+ # date conversion
186
+ value ||= convert_date(f) if f =~ CsvIO::DateMatcher
187
+ # float conversion
188
+ value ||= (Float(f) rescue f) if f =~ /^\d+\.\d*$/ or f =~ /^\d*\.\d+$/
189
+ # return converted value or the input field if there was no conversion
190
+ value || f
191
+ end
192
+
193
+ # @param [String] the input field value
194
+ # @return [Date] the converted date
195
+ def convert_date(f)
196
+ # If input value is in dd-mmm-yy format, then reformat.
197
+ # Otherwise, parse as a Date if possible.
198
+ if f =~ DD_MMM_YYYY_RE then
199
+ ddmmyy = reformat_dd_mmm_yy_date(f) || return
200
+ convert_date(ddmmyy)
201
+ else
202
+ Date.parse(f, true) rescue nil
203
+ end
204
+ end
205
+
206
+ # @param [String] the input field value in dd-mmm-yy format
207
+ # @return [String] the reformatted date String in mm/dd/yy format
208
+ def reformat_dd_mmm_yy_date(f)
209
+ dd, mmm, yy = DD_MMM_YYYY_RE.match(f).captures
210
+ mm = MMM_MM_MAP[mmm.downcase] || return
211
+ "#{mm}/#{dd}/#{yy}"
212
+ end
213
+ end
214
+ end