cmxl 0.0.4 → 0.0.5
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.
- 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
|
+
[](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
|