jinx-migrate 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +38 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +33 -0
- data/Rakefile +40 -0
- data/bin/csvjoin +24 -0
- data/examples/family/README.md +24 -0
- data/examples/family/conf/children/fields.yaml +2 -0
- data/examples/family/conf/parents/defaults.yaml +3 -0
- data/examples/family/conf/parents/fields.yaml +6 -0
- data/examples/family/conf/parents/values.yaml +4 -0
- data/examples/family/data/children.csv +1 -0
- data/examples/family/data/parents.csv +1 -0
- data/examples/family/lib/shims.rb +17 -0
- data/jinx-migrate.gemspec +26 -0
- data/lib/jinx/csv/csvio.rb +214 -0
- data/lib/jinx/csv/joiner.rb +196 -0
- data/lib/jinx/migration/filter.rb +167 -0
- data/lib/jinx/migration/migratable.rb +244 -0
- data/lib/jinx/migration/migrator.rb +1029 -0
- data/lib/jinx/migration/reader.rb +16 -0
- data/lib/jinx/migration/version.rb +5 -0
- data/spec/bad/bad_spec.rb +25 -0
- data/spec/bad/fields.yaml +1 -0
- data/spec/bad/parents.csv +1 -0
- data/spec/bad/shims.rb +16 -0
- data/spec/csv/join/join_helper.rb +35 -0
- data/spec/csv/join/join_spec.rb +100 -0
- data/spec/csv/join/jumbled_src.csv +7 -0
- data/spec/csv/join/jumbled_tgt.csv +7 -0
- data/spec/csv/join/source.csv +7 -0
- data/spec/csv/join/target.csv +7 -0
- data/spec/extract/extract.rb +13 -0
- data/spec/extract/extract_spec.rb +33 -0
- data/spec/extract/fields.yaml +1 -0
- data/spec/extract/parents.csv +1 -0
- data/spec/family/child_spec.rb +27 -0
- data/spec/family/family.rb +13 -0
- data/spec/family/parent_spec.rb +57 -0
- data/spec/filter/fields.yaml +1 -0
- data/spec/filter/filter_spec.rb +20 -0
- data/spec/filter/parents.csv +1 -0
- data/spec/filter/values.yaml +4 -0
- data/spec/primitive/children.csv +1 -0
- data/spec/primitive/fields.yaml +4 -0
- data/spec/primitive/primitive_spec.rb +24 -0
- data/spec/skip/fields.yaml +1 -0
- data/spec/skip/parents.csv +1 -0
- data/spec/skip/skip_spec.rb +17 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/model.rb +7 -0
- data/spec/unique/fields.yaml +1 -0
- data/spec/unique/parent.rb +6 -0
- data/spec/unique/parents.csv +1 -0
- data/spec/unique/shims.rb +10 -0
- data/spec/unique/unique_spec.rb +20 -0
- data/test/fixtures/csv/data/empty.csv +1 -0
- data/test/fixtures/csv/data/variety.csv +1 -0
- data/test/lib/csv/csvio_test.rb +74 -0
- metadata +206 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-o doc/api --private --protected - History.md LEGAL LICENSE
|
data/Gemfile
ADDED
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
data/LEGAL
ADDED
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 @@
|
|
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
|