Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to parse data in memory #8

Merged
merged 4 commits into from Jun 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .ruby-version
@@ -0,0 +1 @@
2.4.1
1 change: 1 addition & 0 deletions .travis.yml
@@ -1,6 +1,7 @@
language: ruby

rvm:
- 2.4.1
- 2.3.3
- 2.2.6
- 2.1.10
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,8 @@
# 2.0.0
* Allow loading content from a string as well as from a file.
* Introduce new public interface for parsing and loading, eg. Paxmex.epraw.parse(...).
* Upgrade to rspec 3.0.

# 1.2.1
* General cleanup
* Fixed issue with numeric fields containing float values.
Expand Down
41 changes: 26 additions & 15 deletions README.md
Expand Up @@ -12,14 +12,23 @@ This gem parses your Amex data files into human readable data.
% gem install paxmex
```

## Available Methods
## Available Formats

* parse_eptrn(file_path)
* parse_epraw(file_path)
* parse_cbnot(file_path)
* parse_epa(file_path)
* eptrn
* epraw
* cbnot
* epa

The first three methods return a readable hash in the following format:
## Usage

Each report format has a corresponding method on the `Paxmex` module. Call `parse` or `load_file` on the return value to either parse data in memory or from a file:

```ruby
Paxmex.eptrn.parse("...")
Paxmex.eptrn.load_file('/path/to/file.cbnot')
```

The first three report formats (i.e. eptrn, epraw, and cbnot) return a hash in the following format:

```ruby
{
Expand All @@ -43,7 +52,7 @@ The first three methods return a readable hash in the following format:
}
```

The last method (parse_epa) returns nearly the same, but it contains nested records (according to the schema definition):
The last format (epa) returns nearly the same thing, but contains nested records as indicated by the epa schema definition:

```ruby
{
Expand Down Expand Up @@ -89,15 +98,16 @@ Values are parsed from their representation into a corresponding native Ruby typ
If you'd like the raw values to be returned instead, you can set the ```raw_values``` option to true, e.g.:

```ruby
Paxmex.parse_eptrn(path_to_file, raw_values: true)
Paxmex.parse_epraw(path_to_file, raw_values: true)
Paxmex.parse_cbnot(path_to_file, raw_values: true)
Paxmex.parse_epa(path_to_file, raw_values: true)
Paxmex.eptrn.parse(path_to_file, raw_values: true)
Paxmex.epraw.parse(path_to_file, raw_values: true)
Paxmex.cbnot.parse(path_to_file, raw_values: true)
Paxmex.epa.parse(path_to_file, raw_values: true)
```

## User-defined schema

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:

```ruby
parser = Parser.new(path_to_raw_file, path_to_schema_file)
result = parser.parse
Expand All @@ -109,17 +119,18 @@ result = parser.parse
require 'paxmex'

# Use default schema definitions
Paxmex.parse_eptrn('/path/to/amex/eptrn/raw/file')
Paxmex.parse_epraw('/path/to/amex/epraw/raw/file')
Paxmex.parse_cbnot('/path/to/amex/cbnot/raw/file')
Paxmex.parse_epa('/path/to/amex/epa/raw/file')
Paxmex.eptrn.parse('/path/to/amex/eptrn/raw/file')
Paxmex.epraw.parse('/path/to/amex/epraw/raw/file')
Paxmex.cbnot.parse('/path/to/amex/cbnot/raw/file')
Paxmex.epa.parse('/path/to/amex/epa/raw/file')

# Use your own schema definition
parser = Parser.new('/path/to/raw/file', '/path/to/your/schema.yml')
result = parser.parse
```

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.

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.

## Contributing
Expand Down
34 changes: 22 additions & 12 deletions lib/paxmex.rb
@@ -1,27 +1,37 @@
require 'paxmex/parser'

module Paxmex
class << self
def parse_cbnot(*args)
parse('cbnot', *args)
class SchemaProxy
attr_reader :schema_name

def initialize(schema_name)
@schema_name = schema_name
end

def parse_epa(*args)
parse('epa', *args)
def parse(data, opts = {})
Parser.new(data, schema_name).parse(opts)
end

def parse_epraw(*args)
parse('epraw', *args)
def load_file(file, opts = {})
parse(File.read(file), opts)
end
end

def parse_eptrn(*args)
parse('eptrn', *args)
class << self
def cbnot
@cbnot ||= SchemaProxy.new('cbnot')
end

private
def epa
@epa ||= SchemaProxy.new('epa')
end

def epraw
@epraw ||= SchemaProxy.new('epraw')
end

def parse(schema, *args)
Parser.new(args.shift, schema).parse(*args)
def eptrn
@eptrn ||= SchemaProxy.new('eptrn')
end
end
end
8 changes: 4 additions & 4 deletions lib/paxmex/parser.rb
Expand Up @@ -10,8 +10,8 @@ class Paxmex::Parser

attr_reader :schema, :path

def initialize(path, schema)
@path = path
def initialize(data, schema)
@data = data.chomp
@parent_chain = []

if File.file?(schema)
Expand All @@ -22,13 +22,13 @@ def initialize(path, schema)
end

def raw
@raw ||= File.read(@path).chomp
@data
end

def parse(opts = {})
return @parsed if @parsed

content = raw.split("\n")
content = @data.split("\n")

# Parse the trailing section first so that we don't need
# to consider it when parsing recurring sections
Expand Down
2 changes: 2 additions & 0 deletions lib/paxmex/schema/field.rb
@@ -1,3 +1,5 @@
require 'date'
require 'time'
require 'bigdecimal'
require 'paxmex/schema'

Expand Down
2 changes: 1 addition & 1 deletion lib/paxmex/version.rb
@@ -1,3 +1,3 @@
module Paxmex
VERSION = '1.2.1'
VERSION = '2.0.0'
end
2 changes: 1 addition & 1 deletion paxmex.gemspec
Expand Up @@ -15,6 +15,6 @@ Gem::Specification.new do |s|

s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]

s.add_development_dependency "rspec", "~> 2.12"
s.add_development_dependency "rspec", "~> 3.0"
s.add_development_dependency "rake", "~> 10.0"
end
7 changes: 3 additions & 4 deletions spec/parsed_section_spec.rb
@@ -1,13 +1,12 @@
require 'paxmex/parsed_section'
require 'spec_helper'

describe Paxmex::ParsedSection do
let(:section) { Paxmex::Schema::Section.new('BABA', {}) }
subject(:parsed_section) { Paxmex::ParsedSection.new(section, {}) }

it { should be_a_kind_of(Hash) }
it { is_expected.to be_a_kind_of(Hash) }

it 'has a key' do
parsed_section.key.should == 'BABA'
expect(parsed_section.key).to eq('BABA')
end

end
54 changes: 27 additions & 27 deletions spec/parser_spec.rb
Expand Up @@ -3,62 +3,62 @@
describe Paxmex::Parser do
let(:eptrn_file) { File.join(File.dirname(__FILE__), 'support/dummy_eptrn_raw') }
let(:schema_key_eptrn) { 'eptrn' }
let(:parser_eptrn) { Paxmex::Parser.new(eptrn_file, schema_key_eptrn) }
let(:parser_eptrn) { Paxmex::Parser.new(File.read(eptrn_file), schema_key_eptrn) }

let(:epraw_file) { File.join(File.dirname(__FILE__), 'support/dummy_epraw_raw') }
let(:schema_key_epraw) { 'epraw' }
let(:parser_epraw) { Paxmex::Parser.new(epraw_file, schema_key_epraw) }
let(:parser_epraw) { Paxmex::Parser.new(File.read(epraw_file), schema_key_epraw) }

let(:epa_file) { File.join(File.dirname(__FILE__), 'support/dummy_epa_raw') }
let(:schema_key_epa) { 'epa' }
let(:schema_file_epa) { File.expand_path('../config/epa.yml', File.dirname(__FILE__)) }
let(:parser_epa) { Paxmex::Parser.new(epa_file, schema_key_epa) }
let(:parser_epa) { Paxmex::Parser.new(File.read(epa_file), schema_key_epa) }

let(:cbnot_file) { File.join(File.dirname(__FILE__), 'support/dummy_cbnot_raw') }
let(:schema_key_cbnot) { 'cbnot' }
let(:schema_file_cbnot) { File.expand_path('../config/cbnot.yml', File.dirname(__FILE__)) }
let(:parser_cbnot) { Paxmex::Parser.new(cbnot_file, schema_key_cbnot) }
let(:parser_cbnot) { Paxmex::Parser.new(File.read(cbnot_file), schema_key_cbnot) }

describe '.new' do
it 'accepts a schema name as well as a schema file' do
schema_1 = Paxmex::Parser.new(eptrn_file, schema_key_epa).schema
schema_2 = Paxmex::Parser.new(eptrn_file, schema_file_epa).schema

schema_1.to_h.should == schema_2.to_h
expect(schema_1.to_h).to eq(schema_2.to_h)
end
end

describe '#raw' do
it 'returns the raw text for the eptrn file' do
parser_eptrn.raw.should == File.read(eptrn_file).chomp
expect(parser_eptrn.raw).to eq(File.read(eptrn_file).chomp)
end

it 'returns the raw text for the epraw file' do
parser_epraw.raw.should == File.read(epraw_file).chomp
expect(parser_epraw.raw).to eq(File.read(epraw_file).chomp)
end

it 'returns the raw text for the epa file' do
parser_epa.raw.should == File.read(epa_file).chomp
expect(parser_epa.raw).to eq(File.read(epa_file).chomp)
end
end

describe '#schema' do
it 'returns a schema object for the eptrn file' do
parser_eptrn.schema.should be_instance_of(Paxmex::Schema)
expect(parser_eptrn.schema).to be_instance_of(Paxmex::Schema)
end

it 'returns a schema object for the epraw file' do
parser_epraw.schema.should be_instance_of(Paxmex::Schema)
expect(parser_epraw.schema).to be_instance_of(Paxmex::Schema)
end

it 'returns a schema object for the epa file' do
parser_epa.schema.should be_instance_of(Paxmex::Schema)
expect(parser_epa.schema).to be_instance_of(Paxmex::Schema)
end
end

describe '#parse' do
it 'returns a hash with parsed values for the eptrn file' do
parser_eptrn.parse.should == {
expect(parser_eptrn.parse).to eq({
"DATA_FILE_TRAILER_RECORD" => {
"DF_TRL_RECORD_TYPE" => "DFTRL",
"DF_TRL_DATE" => Date.parse('2013-03-08'),
Expand Down Expand Up @@ -145,11 +145,11 @@
"NON_SWIPED_INDICATOR" => ""
}
]
}
})
end

it 'returns a hash with parsed values for the epraw file' do
parser_epraw.parse.should == {
expect(parser_epraw.parse).to eq({
"DATA_FILE_TRAILER_RECORD" => {
"DF_TRL_RECORD_TYPE" => "DFTRL",
"DF_TRL_DATE" => Date.parse('2013-03-08'),
Expand Down Expand Up @@ -210,11 +210,11 @@
"CPC_INDICATOR" => ""
}
]
}
})
end

it 'returns a hash with parsed values for the epa file' do
parser_epa.parse.should == {
expect(parser_epa.parse).to eq({
"TRAILER_RECORD" => {
"TRAILER_RECORD_TYPE" => "DFTLR",
"TRAILER_TIME" => Time.new(2014, 7, 22, 5, 36),
Expand Down Expand Up @@ -294,11 +294,11 @@
}
}
]
}
})
end

it 'returns a hash with parsed values for the cbnot file' do
parser_cbnot.parse.should == {
expect(parser_cbnot.parse).to eq({
"DATA_FILE_TRAILER_RECORD" => {
"REC_TYPE" => "T",
"AMEX_APPL_AREA" => "010120170316 0000000000000007000000007",
Expand Down Expand Up @@ -434,12 +434,12 @@
"CCYYDDD" => Date.new(2017, 3, 16),
"0HHMMSS" => 231306
}
}
})
end

context 'with raw set to true' do
it 'returns a hash with raw values for the eptrn file' do
parser_eptrn.parse(raw_values: true).should == {
expect(parser_eptrn.parse(raw_values: true)).to eq({
"DATA_FILE_TRAILER_RECORD" => {
"DF_TRL_RECORD_TYPE" => "DFTRL",
"DF_TRL_DATE" => "03082013",
Expand Down Expand Up @@ -526,11 +526,11 @@
"NON_SWIPED_INDICATOR" => " "
}
]
}
})
end

it 'returns a hash with raw values for the epraw file' do
parser_epraw.parse(raw_values: true).should == {
expect(parser_epraw.parse(raw_values: true)).to eq({
"DATA_FILE_TRAILER_RECORD" => {
"DF_TRL_RECORD_TYPE" => "DFTRL",
"DF_TRL_DATE" => "03082013",
Expand Down Expand Up @@ -591,11 +591,11 @@
"CPC_INDICATOR" => " "
}
]
}
})
end

it 'returns a hash with raw values for the cbnot file' do
parser_cbnot.parse(raw_values: true).should == {
expect(parser_cbnot.parse(raw_values: true)).to eq({
"DATA_FILE_HEADER_RECORD" => {
"REC_TYPE" => "H",
"AMEX_APPL_AREA" => "010120170316 00 ",
Expand Down Expand Up @@ -730,11 +730,11 @@
"0HHMMSS" => "0231306",
"STARS_FILESEQ_NB" => "001"
},
}
})
end

it 'returns a hash with raw values for the epa file' do
parser_epa.parse(raw_values: true).should == {
expect(parser_epa.parse(raw_values: true)).to eq({
"TRAILER_RECORD" => {
"TRAILER_RECORD_TYPE" => "DFTLR ",
"TRAILER_TIME" => "201407220536",
Expand Down Expand Up @@ -814,7 +814,7 @@
}
}
]
}
})
end
end
end
Expand Down