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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 099dfa2701305989d3afe020ee6fee473d75a50e
4
- data.tar.gz: 74e6f6ee642e4d3680b17b977ac470ef6ed1b1a5
3
+ metadata.gz: 18a7814325172343b3ae6aee190378823aacd8ab
4
+ data.tar.gz: a87474a066345e76dc8b66a15b83a632c26a5c0b
5
5
  SHA512:
6
- metadata.gz: 3635643d3f1966515e350da68bff1c46b0960508b085953338ad2f275eccb87e80fad2b8e7723ddbc526c64b4e2e036a9771e9c0343300e9fe5f613c34333364
7
- data.tar.gz: 74761935463086b4464f94d64bf77a06d6ec5b5914f5def5ce1840ec491a01ee67a544643096454a15998977184ad174090fd959a1c26007dd857cfd27acb478
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.
@@ -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)/})
@@ -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]
@@ -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 = /(?<source>.*)/ # default 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'].gsub(',','.').to_f * sign
20
+ to_amount(self.data['amount'])
21
21
  end
22
22
 
23
23
  def amount_in_cents
24
- self.data['amount'].gsub(',', '').gsub('.','').to_i * sign
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
- super.merge({
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
@@ -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
- # now we check each line. 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
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
@@ -1,3 +1,3 @@
1
1
  module Cmxl
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -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(/(?<source>.*)/) } # default must be set
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) }
@@ -21,7 +21,8 @@ describe Cmxl::Fields::AccountBalance do
21
21
  'currency' =>"EUR",
22
22
  'amount' =>147.64,
23
23
  'amount_in_cents' =>14764,
24
- 'sign' =>1
24
+ 'sign' =>1,
25
+ 'tag' => '60'
25
26
  }) }
26
27
  end
27
28
 
@@ -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(-1.62) }
12
- it { expect(subject.amount_in_cents).to eql(-162) }
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') }
@@ -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(-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"}) }
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(-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}) }
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
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