cmxl 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/cmxl.gemspec +2 -0
- data/lib/cmxl.rb +13 -1
- data/lib/cmxl/field.rb +30 -3
- data/lib/cmxl/fields/account_balance.rb +2 -2
- data/lib/cmxl/fields/statement_line.rb +10 -5
- data/lib/cmxl/statement.rb +22 -3
- data/lib/cmxl/version.rb +1 -1
- data/spec/field_spec.rb +1 -1
- data/spec/fields/account_balance_spec.rb +2 -1
- data/spec/fields/reference_spec.rb +1 -1
- data/spec/fields/statment_line_spec.rb +2 -2
- data/spec/transaction_spec.rb +7 -7
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18a7814325172343b3ae6aee190378823aacd8ab
|
4
|
+
data.tar.gz: a87474a066345e76dc8b66a15b83a632c26a5c0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4756b37a6bf9dc970b76d258dd95fef711f86697e64ae9a28c708efc6696ee6df5b076e2d24546bcd977e9fb1481bc1f5a4058eecfe7348b7fb7d9dba2f1446f
|
7
|
+
data.tar.gz: 1baadae4a20e53efc0f5f9a5bdd04c4e7cd7773f7ec04c468a768d36bddb1a86f96800ddcb28c863aeeaacb06829547ee64fd700809adc0a96f2eea0758f604d
|
data/README.md
CHANGED
@@ -131,6 +131,10 @@ other parsers:
|
|
131
131
|
* [betterplace/mt940_parser](https://github.com/betterplace/mt940_parser)
|
132
132
|
* [gmitrev/mt940parser](https://github.com/gmitrev/mt940parser)
|
133
133
|
|
134
|
+
## Stats
|
135
|
+
|
136
|
+
[![Build Status](https://magnum.travis-ci.com/railslove/cmxl.svg?token=e6QUckhTMdWWujkwZNBD&branch=master)](https://magnum.travis-ci.com/railslove/cmxl)
|
137
|
+
|
134
138
|
------------
|
135
139
|
|
136
140
|
2014 - built with love by [Railslove](http://railslove.com). We have built quite a number of FinTech products. If you need support we are happy to help. Please contact us at team@railslove.com.
|
data/cmxl.gemspec
CHANGED
@@ -13,6 +13,8 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/railslove/cmxl"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
+
spec.post_install_message = "Thanks for using Cmxl - your friendly MT940 parser!\nWe hope we can make dealing with MT940 files a bit more fun. :) \nPlease create an issue on github if anything is not as expected.\n\n"
|
17
|
+
|
16
18
|
spec.files = `git ls-files -z`.split("\x0")
|
17
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
data/lib/cmxl.rb
CHANGED
@@ -5,8 +5,20 @@ require 'cmxl/statement'
|
|
5
5
|
require 'cmxl/transaction'
|
6
6
|
Dir[File.join(File.dirname(__FILE__), 'cmxl/fields', '*.rb')].each { |f| require f; }
|
7
7
|
module Cmxl
|
8
|
-
|
9
8
|
STATEMENT_SEPARATOR = "\n-"
|
9
|
+
|
10
|
+
# Public: Parse a MT940 string
|
11
|
+
#
|
12
|
+
# data - The String containing the MT940
|
13
|
+
# options - Hash with encoding options. Accepts the same parameters as String#encode!
|
14
|
+
# It is likely that you want to provide the encoding of your MT940 String
|
15
|
+
#
|
16
|
+
# Examples
|
17
|
+
#
|
18
|
+
# Cmxl.parse(File.read('mt940.txt'), encoding: 'ISO-8859-1')
|
19
|
+
# Cmxl.parse(mt940_string)
|
20
|
+
#
|
21
|
+
# Returns an array of Statement objects
|
10
22
|
def self.parse(data, options={})
|
11
23
|
options[:universal_newline] ||= true
|
12
24
|
if options[:encoding]
|
data/lib/cmxl/field.rb
CHANGED
@@ -11,13 +11,21 @@ module Cmxl
|
|
11
11
|
DATE = /(?<year>\d{0,2})(?<month>\d{2})(?<day>\d{2})/
|
12
12
|
attr_accessor :source, :modifier, :match, :data, :tag
|
13
13
|
|
14
|
+
# The parser class variable is the registry of all available parser.
|
15
|
+
# It is a hash with the tag (MT940 field number/tag) as key and the class as value
|
16
|
+
# When parsing a statment line we look for a matching entry or use the Unknown class as default
|
14
17
|
@@parsers = {}
|
15
18
|
@@parsers.default = Unknown
|
16
19
|
def self.parsers; @@parsers; end
|
17
20
|
|
21
|
+
# Class accessor for the parser
|
22
|
+
# Every sub class should have its own parser (regex to parse a MT940 field/line)
|
23
|
+
# The default parser matches the whole line
|
18
24
|
class << self; attr_accessor :parser; end
|
19
|
-
self.parser = /(?<
|
25
|
+
self.parser = /(?<details>.*)/ # default parser
|
20
26
|
|
27
|
+
# Class accessor for the tag every defines a MT940 tag it can parse
|
28
|
+
# This also adds the class to the parser registry.
|
21
29
|
def self.tag=(tag)
|
22
30
|
@tag = tag.to_s
|
23
31
|
@@parsers[tag.to_s] = self
|
@@ -26,6 +34,15 @@ module Cmxl
|
|
26
34
|
@tag
|
27
35
|
end
|
28
36
|
|
37
|
+
# Public: Parses a statement line and initiates a matching Field class
|
38
|
+
#
|
39
|
+
# Returns an instance of the special field class for the matched line.
|
40
|
+
# Raises and LineFormatError if the line is not well formatted
|
41
|
+
#
|
42
|
+
# Example:
|
43
|
+
#
|
44
|
+
# Cmxl::Field.parse(':60F:C031002PLN40000,00') #=> returns an AccountBalance instance
|
45
|
+
#
|
29
46
|
def self.parse(line)
|
30
47
|
if line.match(/^:(\d{2,2})(\w)?:(.*)$/)
|
31
48
|
tag, modifier, content = $1, $2, $3
|
@@ -49,16 +66,26 @@ module Cmxl
|
|
49
66
|
end
|
50
67
|
|
51
68
|
def to_h
|
52
|
-
self.data
|
69
|
+
self.data.merge('tag' => self.tag)
|
53
70
|
end
|
54
71
|
alias :to_hash :to_h
|
55
72
|
def to_json(*args)
|
56
73
|
to_h.to_json(*args)
|
57
74
|
end
|
58
75
|
|
76
|
+
# Internal: Converts a provided string into a date object
|
77
|
+
# In MT940 documents the date is provided as a 6 char string (YYMMDD) or as a 4 char string (MMDD)
|
78
|
+
# If a 4 char string is provided a second parameter with the year should be provided. If no year is present the current year is assumed.
|
79
|
+
#
|
80
|
+
# Example:
|
81
|
+
#
|
82
|
+
# to_date('140909')
|
83
|
+
# to_date('0909', 2014)
|
84
|
+
#
|
85
|
+
# Retuns a date object or the provided date value if it is not parseable.
|
59
86
|
def to_date(date, year=nil)
|
60
87
|
if match = date.to_s.match(DATE)
|
61
|
-
year ||= "20#{match['year']}"
|
88
|
+
year ||= "20#{match['year'] || Date.today.strftime("%y")}"
|
62
89
|
month = match['month']
|
63
90
|
day = match['day']
|
64
91
|
Date.new(year.to_i, month.to_i, day.to_i)
|
@@ -29,7 +29,7 @@ module Cmxl
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def to_h
|
32
|
-
{
|
32
|
+
super.merge({
|
33
33
|
'date' => date,
|
34
34
|
'funds_code' => funds_code,
|
35
35
|
'credit' => credit?,
|
@@ -38,7 +38,7 @@ module Cmxl
|
|
38
38
|
'amount' => amount,
|
39
39
|
'amount_in_cents' => amount_in_cents,
|
40
40
|
'sign' => sign
|
41
|
-
}
|
41
|
+
})
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -17,11 +17,11 @@ module Cmxl
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def amount
|
20
|
-
self.data['amount']
|
20
|
+
to_amount(self.data['amount'])
|
21
21
|
end
|
22
22
|
|
23
23
|
def amount_in_cents
|
24
|
-
self.data['amount']
|
24
|
+
to_amount_in_cents(self.data['amount'])
|
25
25
|
end
|
26
26
|
|
27
27
|
def date
|
@@ -32,15 +32,20 @@ module Cmxl
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def to_h
|
35
|
-
|
35
|
+
{
|
36
36
|
'date' => date,
|
37
37
|
'entry_date' => entry_date,
|
38
38
|
'amount' => amount,
|
39
39
|
'amount_in_cents' => amount_in_cents,
|
40
40
|
'sign' => sign,
|
41
41
|
'debit' => debit?,
|
42
|
-
'credit' => credit
|
43
|
-
|
42
|
+
'credit' => credit?,
|
43
|
+
'funds_code' => funds_code,
|
44
|
+
'swift_code' => swift_code,
|
45
|
+
'reference' => reference,
|
46
|
+
'bank_reference' => bank_reference,
|
47
|
+
'currency_letter' => currency_letter
|
48
|
+
}
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
data/lib/cmxl/statement.rb
CHANGED
@@ -3,6 +3,12 @@ module Cmxl
|
|
3
3
|
class Statement
|
4
4
|
attr_accessor :source, :collection, :transactions, :fields, :lines
|
5
5
|
|
6
|
+
# Public: Initiate a new Statement and parse a provided single statement string
|
7
|
+
# It directly parses the source and initiates file and transaction objects.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Cmxl::Statement.new(single_statement_string)
|
6
12
|
def initialize(source)
|
7
13
|
self.source = source
|
8
14
|
self.fields = []
|
@@ -11,8 +17,10 @@ module Cmxl
|
|
11
17
|
self.parse!
|
12
18
|
end
|
13
19
|
|
20
|
+
# Internal: Parse a single MT940 statement and extract the line data
|
21
|
+
#
|
14
22
|
def parse!
|
15
|
-
# first we clean uo the source and make concat wraped lines
|
23
|
+
# first we clean uo the source and make concat wraped lines (lines not starting with a ":")
|
16
24
|
self.source.split("\n").each do |line|
|
17
25
|
if line.start_with?(':') || self.lines.last.nil?
|
18
26
|
self.lines << line.strip
|
@@ -20,7 +28,7 @@ module Cmxl
|
|
20
28
|
self.lines.last << line.strip
|
21
29
|
end
|
22
30
|
end
|
23
|
-
#
|
31
|
+
# Now we check each line for its content ans structure it for further use. If it is part of a transaction we initate or update a transaction else we parse the field and add it to the fields collection
|
24
32
|
self.lines.each do |line|
|
25
33
|
if line.match(/\A:61:/)
|
26
34
|
self.transactions << Cmxl::Transaction.new(line)
|
@@ -32,6 +40,10 @@ module Cmxl
|
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
43
|
+
# Public: SHA2 of the provided source
|
44
|
+
# This is an experiment of trying to identify statements. The MT940 itself might not provide a unique identifier
|
45
|
+
#
|
46
|
+
# Retruns the SHA2 of the source
|
35
47
|
def sha
|
36
48
|
Digest::SHA2.new.update(self.source).to_s
|
37
49
|
end
|
@@ -69,7 +81,8 @@ module Cmxl
|
|
69
81
|
'opening_balance' => opening_balance,
|
70
82
|
'closing_balance' => closing_balance,
|
71
83
|
'available_balance' => available_balance,
|
72
|
-
'transactions' => transactions
|
84
|
+
'transactions' => transactions,
|
85
|
+
'fields' => fields
|
73
86
|
}
|
74
87
|
end
|
75
88
|
alias :to_hash :to_h
|
@@ -77,6 +90,12 @@ module Cmxl
|
|
77
90
|
to_h.to_json(*args)
|
78
91
|
end
|
79
92
|
|
93
|
+
# Internal: Field accessor
|
94
|
+
# returns a field object by a given tag
|
95
|
+
#
|
96
|
+
# Example:
|
97
|
+
# field(20)
|
98
|
+
# field(61,'F')
|
80
99
|
def field(tag, modifier=nil)
|
81
100
|
self.fields.detect {|field| field.tag == tag.to_s && (modifier.nil? || field.modifier == modifier) }
|
82
101
|
end
|
data/lib/cmxl/version.rb
CHANGED
data/spec/field_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Cmxl::Field do
|
4
4
|
subject { Cmxl::Field.new('D140829EUR000000000147,64') }
|
5
5
|
|
6
|
-
it { expect(Cmxl::Field.parser).to eql(/(?<
|
6
|
+
it { expect(Cmxl::Field.parser).to eql(/(?<details>.*)/) } # default must be set
|
7
7
|
|
8
8
|
it { expect(subject.to_amount('123.')).to eql(123.00) }
|
9
9
|
it { expect(subject.to_amount('123.1')).to eql(123.10) }
|
@@ -8,5 +8,5 @@ describe Cmxl::Fields::Reference do
|
|
8
8
|
it { expect(subject.date).to eql(Date.new(2014,9,2)) }
|
9
9
|
it { expect(subject.statement_identifier).to eql('D') }
|
10
10
|
it { expect(subject.additional_number).to eql('049') }
|
11
|
-
it { expect(subject.to_h).to eql({"statement_identifier"=>"D", "date"=> Date.new(2014,9,2), "additional_number"=>"049", "reference"=>"D140902049"}) }
|
11
|
+
it { expect(subject.to_h).to eql({"tag" => '20', "statement_identifier"=>"D", "date"=> Date.new(2014,9,2), "additional_number"=>"049", "reference"=>"D140902049"}) }
|
12
12
|
end
|
@@ -8,8 +8,8 @@ describe Cmxl::Fields::StatementLine do
|
|
8
8
|
it { expect(subject.entry_date).to eql(Date.new(2014,9,2)) }
|
9
9
|
it { expect(subject.funds_code).to eql('D') }
|
10
10
|
it { expect(subject.currency_letter).to eql('R') }
|
11
|
-
it { expect(subject.amount).to eql(
|
12
|
-
it { expect(subject.amount_in_cents).to eql(
|
11
|
+
it { expect(subject.amount).to eql(1.62) }
|
12
|
+
it { expect(subject.amount_in_cents).to eql(162) }
|
13
13
|
it { expect(subject.swift_code).to eql('NTRF') }
|
14
14
|
it { expect(subject.reference).to eql('0000549855700010') }
|
15
15
|
it { expect(subject.bank_reference).to eql('025498557/000001') }
|
data/spec/transaction_spec.rb
CHANGED
@@ -9,10 +9,10 @@ describe Cmxl::Transaction do
|
|
9
9
|
it { expect(subject.funds_code).to eql('D') }
|
10
10
|
it { expect(subject.date).to eql(Date.new(2014,9,1))}
|
11
11
|
it { expect(subject.entry_date).to eql(Date.new(2014,9,2))}
|
12
|
-
it { expect(subject.amount).to eql(
|
13
|
-
it { expect(subject.statement_line.amount).to eql(
|
14
|
-
it { expect(subject.amount_in_cents).to eql(
|
15
|
-
it { expect(subject.to_h).to eql({"date"=>Date.new(2014,9,1), "entry_date"=>Date.new(2014,9,2), "funds_code"=>"D", "currency_letter"=>"R", "amount"
|
12
|
+
it { expect(subject.amount).to eql(1.62)}
|
13
|
+
it { expect(subject.statement_line.amount).to eql(1.62)}
|
14
|
+
it { expect(subject.amount_in_cents).to eql(162)}
|
15
|
+
it { expect(subject.to_h).to eql({"date"=>Date.new(2014,9,1), "entry_date"=>Date.new(2014,9,2), "funds_code"=>"D", "currency_letter"=>"R", "amount"=>1.62, "swift_code"=>"NTRF", "reference"=>"0000549855700010", "bank_reference"=>"025498557/000001", "amount_in_cents"=>162, "sign"=>-1, "debit"=>true, "credit"=>false, "bic"=>"HYVEDEMMXXX", "iban"=>"HUkkbbbsssskcccccccccccccccx", "name"=>"Peter Pan", "sepa"=>{"EREF"=>"TRX-0A4A47C3-F846-4729-8A1B-5DF620F", "MREF"=>"CAC97D2144174318AC18D9BF815BD4FB", "CRED"=>"DE98ZZZ09999999999", "SVWZ"=>"FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F"}, "information"=>"EREF+TRX-0A4A47C3-F846-4729-8A1B-5DF620FMREF+CAC97D2144174318AC18D9BF815BD4FBCRED+DE98ZZZ09999999999SVWZ+FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F", "description"=>"SEPA LASTSCHRIFT KUNDE", "sub_fields"=>{"00"=>"SEPA LASTSCHRIFT KUNDE", "10"=>"281", "20"=>"EREF+TRX-0A4A47C3-F846-4729", "21"=>"-8A1B-5DF620F", "22"=>"MREF+CAC97D2144174318AC18D9", "23"=>"BF815BD4FB", "24"=>"CRED+DE98ZZZ09999999999", "25"=>"SVWZ+FOO TRX-0A4A47C3-F84", "26"=>"6-4729-8A1B-5DF620F", "30"=>"HYVEDEMMXXX", "31"=>"HUkkbbbsssskcccccccccccccccx", "32"=>"Peter Pan", "34"=>"171"}, "transaction_code"=>"171", "details"=>"?00SEPA LASTSCHRIFT KUNDE?10281?20EREF+TRX-0A4A47C3-F846-4729?21-8A1B-5DF620F?22MREF+CAC97D2144174318AC18D9?23BF815BD4FB?24CRED+DE98ZZZ09999999999?25SVWZ+FOO TRX-0A4A47C3-F84?266-4729-8A1B-5DF620F?30HYVEDEMMXXX?31HUkkbbbsssskcccccccccccccccx?32Peter Pan?34171"}) }
|
16
16
|
end
|
17
17
|
|
18
18
|
context 'without details' do
|
@@ -21,8 +21,8 @@ describe Cmxl::Transaction do
|
|
21
21
|
it { expect(subject).to_not be_credit }
|
22
22
|
it { expect(subject.date).to eql(Date.new(2014,9,1))}
|
23
23
|
it { expect(subject.entry_date).to eql(Date.new(2014,9,2))}
|
24
|
-
it { expect(subject.amount).to eql(
|
25
|
-
it { expect(subject.amount_in_cents).to eql(
|
26
|
-
it { expect(subject.to_h).to eql({"date"=>Date.new(2014,9,1), "entry_date"=>Date.new(2014,9,2), "funds_code"=>"D", "currency_letter"=>"R", "amount"
|
24
|
+
it { expect(subject.amount).to eql(1.62)}
|
25
|
+
it { expect(subject.amount_in_cents).to eql(162)}
|
26
|
+
it { expect(subject.to_h).to eql({"date"=>Date.new(2014,9,1), "entry_date"=>Date.new(2014,9,2), "funds_code"=>"D", "currency_letter"=>"R", "amount"=>1.62, "swift_code"=>"NTRF", "reference"=>"0000549855700010", "bank_reference"=>"025498557/000001", "amount_in_cents"=>162, "sign"=>-1, "debit"=>true, "credit"=>false}) }
|
27
27
|
end
|
28
28
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cmxl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bumann
|
@@ -109,7 +109,9 @@ homepage: https://github.com/railslove/cmxl
|
|
109
109
|
licenses:
|
110
110
|
- MIT
|
111
111
|
metadata: {}
|
112
|
-
post_install_message:
|
112
|
+
post_install_message: "Thanks for using Cmxl - your friendly MT940 parser!\nWe hope
|
113
|
+
we can make dealing with MT940 files a bit more fun. :) \nPlease create an issue
|
114
|
+
on github if anything is not as expected.\n\n"
|
113
115
|
rdoc_options: []
|
114
116
|
require_paths:
|
115
117
|
- lib
|