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