jinx-migrate 2.1.1

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.
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