paxmex 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/lib/paxmex/schema/field.rb +54 -44
- data/lib/paxmex/schema/section.rb +51 -47
- data/lib/paxmex/schema.rb +35 -33
- data/lib/paxmex/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 328b6d41aeeeccde646c599fbc62a8e08b5250aa
|
4
|
+
data.tar.gz: f21beec92244d5731a3643b7dd07b0cc4e7b0c94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6204b8480df620b96e07967dfe18b8c64c2d6f9b3822391ab64651f7f873af2e5f8dbc4c760b7b6d80c12b4f97e778a4eec6e7adfe3152930909d0c9cdaa2362
|
7
|
+
data.tar.gz: b5a7ba48f0ca141a4ee034a5ec1f97f3d62116f9f021f9468af15e7519d953f438ae6e608414d7ef025e5763aaa9d54e48b67a489a8335e2251b8aaa73f91bc1
|
data/README.md
CHANGED
@@ -16,9 +16,10 @@ This gem parses your Amex data files into human readable data.
|
|
16
16
|
|
17
17
|
* parse_eptrn(file_path)
|
18
18
|
* parse_epraw(file_path)
|
19
|
+
* parse_cbnot(file_path)
|
19
20
|
* parse_epa(file_path)
|
20
21
|
|
21
|
-
The first
|
22
|
+
The first three methods return a readable hash in the following format:
|
22
23
|
|
23
24
|
```ruby
|
24
25
|
{
|
@@ -90,12 +91,13 @@ If you'd like the raw values to be returned instead, you can set the ```raw_valu
|
|
90
91
|
```ruby
|
91
92
|
Paxmex.parse_eptrn(path_to_file, raw_values: true)
|
92
93
|
Paxmex.parse_epraw(path_to_file, raw_values: true)
|
94
|
+
Paxmex.parse_cbnot(path_to_file, raw_values: true)
|
93
95
|
Paxmex.parse_epa(path_to_file, raw_values: true)
|
94
96
|
```
|
95
97
|
|
96
98
|
## User-defined schema
|
97
99
|
|
98
|
-
If you need to parse a different format (i.e. not EPRAW, EPTRN or EPA), write your own schema definition and use it like this:
|
100
|
+
If you need to parse a different format (i.e. not EPRAW, EPTRN, CBNOT, or EPA), write your own schema definition and use it like this:
|
99
101
|
```ruby
|
100
102
|
parser = Parser.new(path_to_raw_file, path_to_schema_file)
|
101
103
|
result = parser.parse
|
@@ -109,6 +111,7 @@ require 'paxmex'
|
|
109
111
|
# Use default schema definitions
|
110
112
|
Paxmex.parse_eptrn('/path/to/amex/eptrn/raw/file')
|
111
113
|
Paxmex.parse_epraw('/path/to/amex/epraw/raw/file')
|
114
|
+
Paxmex.parse_cbnot('/path/to/amex/cbnot/raw/file')
|
112
115
|
Paxmex.parse_epa('/path/to/amex/epa/raw/file')
|
113
116
|
|
114
117
|
# Use your own schema definition
|
@@ -116,7 +119,7 @@ parser = Parser.new('/path/to/raw/file', '/path/to/your/schema.yml')
|
|
116
119
|
result = parser.parse
|
117
120
|
```
|
118
121
|
|
119
|
-
The raw input files for either methods are data report files provided by American Express. These files are in either EPRAW, EPTRN or EPA format so use the relevant method to parse them. We have provided dummy EPRAW, EPTRN and EPA files in `spec/support`. Output and key-value pairs vary depending on whether you choose to parse an EPTRN, EPRAW or EPA file.
|
122
|
+
The raw input files for either methods are data report files provided by American Express. These files are in either EPRAW, EPTRN, CBNOT, or EPA format so use the relevant method to parse them. We have provided dummy EPRAW, EPTRN, CBNOT, and EPA files in `spec/support`. Output and key-value pairs vary depending on whether you choose to parse an EPTRN, EPRAW, CBNOT, or EPA file.
|
120
123
|
If you need to parse a file in another format, you can write your own YAML schema for this purpose. We would be happy if you help us improving this project by sharing your schemas.
|
121
124
|
|
122
125
|
## Contributing
|
data/lib/paxmex/schema/field.rb
CHANGED
@@ -1,59 +1,69 @@
|
|
1
1
|
require 'bigdecimal'
|
2
2
|
require 'paxmex/schema'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module Paxmex
|
5
|
+
class Schema
|
6
|
+
class Field
|
7
|
+
DATE_PATTERN = /^date\((.+)\)$/
|
8
|
+
TIME_PATTERN = /^time\((.+)\)$/
|
7
9
|
|
8
|
-
|
10
|
+
attr_reader :name, :start, :final, :type
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def initialize(opts = {})
|
13
|
+
@name = opts[:name]
|
14
|
+
@start = opts[:start]
|
15
|
+
@final = opts[:final]
|
16
|
+
@type = opts[:type] || 'string'
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
19
|
+
def parse(raw_value)
|
20
|
+
case type
|
21
|
+
when 'string' then raw_value.rstrip
|
22
|
+
when 'julian' then parse_julian_date(raw_value) rescue nil
|
23
|
+
when 'date' then Date.strptime(raw_value, '%m%d%Y') rescue nil
|
24
|
+
when 'numeric' then parse_numeric(raw_value)
|
25
|
+
when 'decimal' then parse_decimal(raw_value)
|
26
|
+
when DATE_PATTERN then parse_date_pattern(raw_value) rescue nil
|
27
|
+
when TIME_PATTERN then parse_time_pattern(raw_value) rescue nil
|
28
|
+
else fail "Could not parse field type #{type}. Supported types: string, julian, date, numeric, decimal, date(format), time(format)"
|
29
|
+
end
|
30
|
+
end
|
29
31
|
|
30
|
-
|
32
|
+
private
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
def parse_date_pattern(value)
|
35
|
+
Date.strptime(value, DATE_PATTERN.match(type).captures.first)
|
36
|
+
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
def parse_time_pattern(value)
|
39
|
+
Time.strptime(value, TIME_PATTERN.match(type).captures.first)
|
40
|
+
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
def parse_numeric(value)
|
43
|
+
value.strip!
|
44
|
+
return value.to_f if value.include?('.')
|
45
|
+
value.to_i
|
46
|
+
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
def parse_decimal(value)
|
49
|
+
# fields _may_ end with a letter
|
50
|
+
unless value.match(/^[0-9]*[0-9A-R{}]$/)
|
51
|
+
fail "Unexpected value '#{value}' for field '#{name}'"
|
52
|
+
end
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
is_credit = !!(value =~ /[JKLMNOPQR}]/)
|
55
|
+
value = value.gsub(/[ABCDEFGHIJKLMNOPQR{}]/,
|
56
|
+
'A'=>1, 'B'=>2, 'C'=>3, 'D'=>4, 'E'=>5, 'F'=>6, 'G'=>7, 'H'=>8, 'I'=>9,
|
57
|
+
'J'=>1, 'K'=>2, 'L'=>3, 'M'=>4, 'N'=>5, 'O'=>6, 'P'=>7, 'Q'=>8, 'R'=>9,
|
58
|
+
'{'=>0, '}'=>0)
|
55
59
|
|
56
|
-
|
57
|
-
|
60
|
+
parsed_value = value.to_i * (is_credit ? -1 : 1) / 100.0
|
61
|
+
BigDecimal.new(parsed_value.to_s, 7)
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_julian_date(date_string)
|
65
|
+
Date.ordinal(date_string[0..3].to_i, date_string[4..6].to_i)
|
66
|
+
end
|
67
|
+
end
|
58
68
|
end
|
59
69
|
end
|
@@ -1,64 +1,68 @@
|
|
1
1
|
require 'paxmex/schema/field'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Paxmex
|
4
|
+
class Schema
|
5
|
+
class Section
|
6
|
+
BLOCK_LENGTH = 450
|
5
7
|
|
6
|
-
|
8
|
+
attr_reader :key, :data, :parent_key
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def initialize(key, data)
|
11
|
+
@parent_key = data.delete('PARENT') { nil }
|
12
|
+
@key = key
|
13
|
+
@data = data
|
14
|
+
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
def recurring?
|
17
|
+
!!data['RECURRING']
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
def abstract?
|
21
|
+
!!data['ABSTRACT']
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
def trailer?
|
25
|
+
!!data['TRAILER']
|
26
|
+
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
def child?
|
29
|
+
!!@parent_key
|
30
|
+
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
def fields
|
33
|
+
@fields ||= data['FIELDS'].map do |field|
|
34
|
+
Paxmex::Schema::Field.new(
|
35
|
+
name: field['NAME'],
|
36
|
+
start: field['RANGE'].first,
|
37
|
+
final: field['RANGE'].last,
|
38
|
+
type: field['TYPE']
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
def types
|
44
|
+
data['TYPES']
|
45
|
+
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
def type_field
|
48
|
+
data['TYPE_FIELD']
|
49
|
+
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
51
|
+
def type_mapping
|
52
|
+
data['TYPE_MAPPING']
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
def section_for_type(type)
|
56
|
+
sections_for_types.detect { |t| t.key == type_mapping[type] }
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
def length
|
60
|
+
BLOCK_LENGTH
|
61
|
+
end
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
+
def sections_for_types
|
64
|
+
@sections_for_types ||= types.map { |k, v| self.class.new(k, v) }
|
65
|
+
end
|
66
|
+
end
|
63
67
|
end
|
64
68
|
end
|
data/lib/paxmex/schema.rb
CHANGED
@@ -1,46 +1,48 @@
|
|
1
|
-
|
2
|
-
require 'paxmex/schema/section'
|
1
|
+
require 'paxmex/schema/section'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Paxmex
|
4
|
+
class Schema
|
5
|
+
def initialize(schema_hash)
|
6
|
+
@schema_hash = schema_hash
|
7
|
+
@parents = []
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def sections
|
11
|
+
@sections ||= @schema_hash.map { |k, v| build_section(k,v) }
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def to_h
|
15
|
+
@schema_hash
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
def parent_section?(key)
|
19
|
+
sections.any? do |s|
|
20
|
+
if s.abstract?
|
21
|
+
s.sections_for_types.any? { |ss| ss.parent_key == key }
|
22
|
+
else
|
23
|
+
s.parent_key == key
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
25
|
-
end
|
26
27
|
|
27
|
-
|
28
|
+
private
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def build_section(key, data)
|
31
|
+
section = Section.new(key, data)
|
32
|
+
mark_abstract if section.abstract?
|
33
|
+
add_parent(section) if section.child?
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
section
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
def mark_abstract
|
39
|
+
fail 'Cannot have more than one abstract section' if @have_abstract
|
40
|
+
@have_abstract = true
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
def add_parent(section)
|
44
|
+
fail 'Recursive parent definition' if @parents.include?(section.key)
|
45
|
+
@parents << parent
|
46
|
+
end
|
45
47
|
end
|
46
48
|
end
|
data/lib/paxmex/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paxmex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daryl Yeo
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-05-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|