cmxl 1.5.0 → 2.2

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
  SHA256:
3
- metadata.gz: 661a3296bdc88ce9874f07cb4a1cf4220bf1a60ea559e289edc5c5c34774764b
4
- data.tar.gz: 4f0217f656646bd14540e5993f6419354641f30d901e8b7afc884b46fc5ac69e
3
+ metadata.gz: f7d3b16a51915ab4abdd146b5f213b4957392ace7f27be151fbc253ea820dd6c
4
+ data.tar.gz: cc398d5bd6fb2e112abfa815de950fd34a300107ba573b50ea8fb9ed98703f23
5
5
  SHA512:
6
- metadata.gz: 407ecb157601f8cc767ddb7699edbadc66582041cdcd8d1de3b3e169a41865d3f81ffdb794b7b9f8add8a55ecea1e16e4048f30a117cfe2401b05b487d2e1bcf
7
- data.tar.gz: 4cbfe3e46cc9a00527bfa1a4d465f5e0ad4311d915449b45a81b172e1a653ee3ec2605778bb1241aca752016651b8f54dad0daf00769349a6a80cb679cff0dcf
6
+ metadata.gz: 9c9d4c881bc2f1f644471bf24a5c2f0c0fdc0543f828ba82e2960f1a015d397f10cdbc74d5fece4f79f2638625000ba8f8e80d0bbfa7ead01aec2b09992ff940
7
+ data.tar.gz: 2ba29e61b1060f43ddc7699211ff109064256366e18089a1d46b41b513906fabbb60ca12a74d322595f6ef726ea9068150829d0e0c5c4c3d2c7c690a0aeef309
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ ruby-version:
16
+ - 3.4
17
+ - 3.3
18
+ - 3.2
19
+ - 3.1
20
+ - "3.0"
21
+ - jruby-9.4
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - name: Set up Ruby ${{ matrix.ruby-version }}
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby-version }}
29
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
30
+ - name: Run tests
31
+ run: bundle exec rake
@@ -0,0 +1,26 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ push:
9
+ name: Push gem to RubyGems.org
10
+ runs-on: ubuntu-latest
11
+
12
+ permissions:
13
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
14
+ contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
15
+
16
+ steps:
17
+ # Set up
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ bundler-cache: true
23
+ ruby-version: ruby
24
+
25
+ # Release
26
+ - uses: rubygems/release-gem@v1
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  --color
2
+ --require 'pry'
data/CHANGELOG.mdown CHANGED
@@ -1,5 +1,21 @@
1
1
  # NEXT release
2
2
 
3
+ # 2.2
4
+ - `[ENHANCEMENT]` enable `strip_headers` option per default.
5
+ - `[BUGFIX]` fix `strip_header` remove greedy regex logic
6
+ - `[BUGFIX]` support details parsing from files on windows os
7
+
8
+ # 2.1
9
+ - `[REFACTOR]` improve file parser to work with windows line breaks and any known header formats
10
+ - `[BUGFIX]` fix `strip_header` making it work on statements without headers
11
+ - `[HOUSEKEEPING]` use `main` instead of `master` as default branch
12
+ - `[ENHANCEMENT]` add support for ruby `3.4`
13
+
14
+ # 2.0
15
+ - `[REFACTOR]` **DEPRECATED:** `storno?` and related methods `storno_credit`, `storno_debit`. Use `reversal?` and related methods `reversal_credit?`, `reversal_debit?` instead
16
+ - `[BUGFIX]` **DEPRECATED:** `funds_code` method returns the `credit_debit_indicator` from the SWIFT definition. Therefore the method is deprecated in favor of `credit_debit_indicator` method
17
+ - `[HOUSEKEEPING]` [Replace Travis CI with github actions](https://github.com/railslove/cmxl/pull/57)
18
+
3
19
  # 1.5.0
4
20
 
5
21
  - `[BUGFIX]` fix potential bug when generation_date is not provided in field 20 and 13 (issue: [#35](https://github.com/railslove/cmxl/issues/35) PR: [#36](https://github.com/railslove/cmxl/pull/36))
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
- [![Build Status](https://travis-ci.org/railslove/cmxl.svg?branch=master)](https://travis-ci.org/railslove/cmxl)
2
- [![Gem Version](https://badge.fury.io/rb/cmxl.svg)](http://badge.fury.io/rb/cmxl)
1
+ [![CI](https://github.com/railslove/cmxl/actions/workflows/ci.yml/badge.svg)](https://github.com/railslove/cmxl/actions/workflows/ci.yml)
2
+ [![Gem Version](https://badge.fury.io/rb/cmxl.svg)](https://rubygems.org/gems/cmxl)
3
3
 
4
4
  # Cmxl - your friendly ruby MT940 parser
5
5
 
@@ -16,7 +16,7 @@ For more information have a look at the different [SWIFT message types](http://e
16
16
 
17
17
  At some point in the future MT940 file should be exchanged with newer XML documents - but banking institutions are slow, so MT940 will stick around for a while.
18
18
 
19
- ## Reqirements
19
+ ## Requirements
20
20
 
21
21
  Cmxl is a pure ruby parser and has no dependency on native extensions.
22
22
 
@@ -38,37 +38,23 @@ Or install it yourself as:
38
38
 
39
39
  ## Usage
40
40
 
41
- Simple usage:
41
+ ### Simple usage:
42
42
 
43
43
  ```ruby
44
+ statements = Cmxl.parse(File.read('mt940.txt'))
44
45
 
45
- # Configuration:
46
-
47
- # statement divider regex to split the individual statements in one file - the default is standard and should be good for most files
48
- Cmxl.config[:statement_separator] = /\n-.\n/m
49
-
50
- # do you want an error to be raised when a line can not be parsed? default is true
51
- Cmxl.config[:raise_line_format_errors] = true
52
-
53
- # try to stip the SWIFT header data. This strips everything until the actual first MT940 field. (if parsing fails, try this!)
54
- Cmxl.config[:strip_headers] = true
55
-
56
-
57
- # Statment parsing:
58
-
59
- statements = Cmxl.parse(File.read('mt940.txt'), :encoding => 'ISO-8859-1') # parses the file and returns an array of statement objects. Please note: if no encoding is given Cmxl tries to guess the encoding from the content and converts it to UTF-8.
60
46
  statements.each do |s|
61
47
  puts s.reference
62
48
  puts s.generation_date
63
49
  puts s.opening_balance.amount
64
50
  puts s.closing_balance.amount
65
- puts s.sha # SHA of the statement source - could be used as an identifier (see: https://github.com/railslove/cmxl/blob/master/lib/cmxl/statement.rb#L49-L55)
51
+ puts s.sha # SHA of the statement source - could be used as an identifier (see: https://github.com/railslove/cmxl/blob/main/lib/cmxl/statement.rb#L49-L55)
66
52
 
67
53
  s.transactions.each do |t|
68
54
  puts t.information
69
55
  puts t.description
70
56
  puts t.entry_date
71
- puts t.funds_code
57
+ puts t.credit_debit_indicator
72
58
  puts t.credit?
73
59
  puts t.debit?
74
60
  puts t.sign # -1 if it's a debit; 1 if it's a credit
@@ -81,25 +67,58 @@ statements.each do |s|
81
67
  # ...
82
68
  end
83
69
  end
84
-
85
70
  ```
86
71
 
87
72
  Every object responds to `to_h` and let's you easily convert the data to a hash. Also every object responds to `to_json` which lets you easily represent the statements as JSON with your favorite JSON library.
88
73
 
89
- #### A note about encoding and file weirdnesses
74
+ ### File encoding options
90
75
 
91
76
  You probably will encounter encoding issues (hey, you are building banking applications!).
92
77
  We try to handle encoding and format weirdnesses as much as possible. If no encoding is passed we try to guess the encoding of the data and convert it to UTF8.
93
78
  In the likely case that you encounter encoding issues you can pass encoding options to `Cmxl.parse(<string>, <options hash>)`. It accepts the same options as [String#encode](http://ruby-doc.org/core-2.1.3/String.html#method-i-encode)
94
79
  If that fails, try to modify the file before you pass it to the parser - and please create an issue.
95
80
 
96
- ### MT940 SWIFT header data
81
+ ```ruby
82
+ Cmxl.parse(File.read('mt940.txt'), :encoding => 'ISO-8859-1')
83
+ ```
84
+
85
+ ## Global configurations:
86
+ The gem offers option to adjust behavior for the gem
87
+
88
+ ### `statement_separator`
89
+ statement divider regex to split the individual statements in one file
90
+
91
+ |type|default|
92
+ |----|-------|
93
+ |regex|[`/\R+-[^\n\r]*\R*/m`](https://github.com/railslove/cmxl/blob/main/lib/cmxl.rb#L18)|
97
94
 
95
+ ```ruby
96
+ Cmxl.config[:statement_separator] = ...
97
+ Cmxl.parse(...)
98
+ ```
99
+
100
+ ### `raise_line_format_errors`
101
+ do you want an error to be raised when a line can not be parsed?
102
+
103
+ |type|default|
104
+ |----|-------|
105
+ |boolean|[`true`](https://github.com/railslove/cmxl/blob/main/lib/cmxl.rb#L19)|
106
+
107
+ ```ruby
108
+ Cmxl.config[:raise_line_format_errors] = ...
109
+ Cmxl.parse(...)
110
+ ```
111
+
112
+ ### `strip_headers`
98
113
  Cmxl currently does not support parsing of the SWIFT headers (like {1:F01AXISINBBA ....)
99
- If your file comes with these headers try the `strip_headers` configuration option to strip data execpt the actual MT940 fields.
114
+ If your file comes with these headers try the `strip_headers` configuration option to strip data except the actual MT940 fields.
115
+
116
+ |type|default|
117
+ |----|-------|
118
+ |boolean|[`false`](https://github.com/railslove/cmxl/blob/main/lib/cmxl.rb#L20)|
100
119
 
101
120
  ```ruby
102
- Cmxl.config[:strip_headers] = true
121
+ Cmxl.config[:strip_headers] = ...
103
122
  Cmxl.parse(...)
104
123
  ```
105
124
 
@@ -187,6 +206,6 @@ other parsers:
187
206
 
188
207
  ---
189
208
  built with love by [Railslove](http://railslove.com) and some [amazing people](https://github.com/railslove/cmxl/graphs/contributors).
190
- Released under the MIT-Licence.
209
+ Released under the MIT-License.
191
210
 
192
211
  Railslove builds FinTech products, if you need support for your project we are happy to help. Please contact us at team@railslove.com.
data/cmxl.gemspec CHANGED
@@ -19,11 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ['lib']
21
21
 
22
+ spec.add_dependency 'rchardet'
23
+
22
24
  spec.add_development_dependency 'bundler'
23
25
  spec.add_development_dependency 'pry'
24
26
  spec.add_development_dependency 'rake'
25
27
  spec.add_development_dependency 'rspec', '~>3.0'
26
28
  spec.add_development_dependency 'simplecov'
27
-
28
- spec.add_dependency 'rchardet19'
29
29
  end
data/lib/cmxl/field.rb CHANGED
@@ -13,7 +13,7 @@ module Cmxl
13
13
 
14
14
  # The parser class variable is the registry of all available parser.
15
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
16
+ # When parsing a statement line we look for a matching entry or use the Unknown class as default
17
17
  @@parsers = {}
18
18
  @@parsers.default = Unknown
19
19
  def self.parsers
@@ -7,7 +7,7 @@ module Cmxl
7
7
  class << self
8
8
  def parse(line)
9
9
  # remove line breaks as they are allowed via documentation but not needed for data-parsing
10
- super line.delete("\n")
10
+ super line.gsub(/\R?/, '')
11
11
  end
12
12
  end
13
13
 
@@ -2,7 +2,7 @@ module Cmxl
2
2
  module Fields
3
3
  class Transaction < Field
4
4
  self.tag = 61
5
- self.parser = %r{^(?<date>\d{6})(?<entry_date>\d{4})?(?<storno_flag>R?)(?<funds_code>[CD]{1})(?<currency_letter>[a-zA-Z])?(?<amount>\d{1,12},\d{0,2})(?<swift_code>(?:N|F|S).{3})(?<reference>NONREF|(.(?!\/\/)){,16}([^\/]){,1})((?:\/\/)(?<bank_reference>[^\n]{,16}))?((?:\n)(?<supplementary>.{,34}))?$}
5
+ self.parser = %r{^(?<date>\d{6})(?<entry_date>\d{4})?(?<credit_debit_indicator>D|C|RD|RC|ED|EC)(?<currency_letter>[a-zA-Z])?(?<amount>\d{1,12},\d{0,2})(?<swift_code>(?:N|F|S).{3})(?<reference>NONREF|(.(?!\/\/)){,16}([^\/]){,1})((?:\/\/)(?<bank_reference>[^\n]{,16}))?((?:\n)(?<supplementary>.{,34}))?$}
6
6
 
7
7
  attr_accessor :details
8
8
 
@@ -15,31 +15,63 @@ module Cmxl
15
15
  end
16
16
 
17
17
  def credit?
18
- data['funds_code'].to_s.casecmp('C').zero?
18
+ credit_debit_indicator.include?('C')
19
19
  end
20
20
 
21
21
  def debit?
22
- data['funds_code'].to_s.casecmp('D').zero?
22
+ credit_debit_indicator.include?('D')
23
23
  end
24
24
 
25
25
  def storno_credit?
26
+ warn "[DEPRECATION] `storno_credit?` is deprecated. Please use `reversal_credit?` instead. It will be removed in version 3.0."
27
+ reversal_credit?
28
+ end
29
+
30
+ def reversal_credit?
26
31
  credit? && storno?
27
32
  end
28
33
 
29
34
  def storno_debit?
35
+ warn "[DEPRECATION] `storno_debit?` is deprecated. Please use `reversal_debit?` instead. It will be removed in version 3.0."
36
+ reversal_debit?
37
+ end
38
+
39
+ def reversal_debit?
30
40
  debit? && storno?
31
41
  end
32
42
 
33
43
  def storno?
34
- !storno_flag.empty?
44
+ warn "[DEPRECATION] `storno?` is deprecated. Please use `reversal?` instead. It will be removed in version 3.0."
45
+ reversal?
46
+ end
47
+
48
+ def reversal?
49
+ credit_debit_indicator.include?('R')
50
+ end
51
+
52
+ def expected_credit?
53
+ credit? && expected?
54
+ end
55
+
56
+ def expected_debit?
57
+ debit? && expected?
58
+ end
59
+
60
+ def expected?
61
+ credit_debit_indicator.include?('E')
62
+ end
63
+
64
+ def credit_debit_indicator
65
+ data['credit_debit_indicator'].to_s
35
66
  end
36
67
 
37
68
  def funds_code
38
- data.values_at('storno_flag', 'funds_code').join
69
+ warn "[DEPRECATION] `funds_code` is deprecated. Please use `credit_debit_indicator` instead. It will be removed in version 3.0."
70
+ data['credit_debit_indicator'].to_s
39
71
  end
40
72
 
41
73
  def storno_flag
42
- data['storno_flag']
74
+ reversal? ? 'R' : ''
43
75
  end
44
76
 
45
77
  def sign
@@ -140,8 +172,11 @@ module Cmxl
140
172
  'sign' => sign,
141
173
  'debit' => debit?,
142
174
  'credit' => credit?,
143
- 'storno' => storno?,
144
- 'funds_code' => funds_code,
175
+ 'storno' => reversal?,
176
+ 'reversal' => reversal?,
177
+ 'expected' => expected?,
178
+ 'funds_code' => credit_debit_indicator,
179
+ 'credit_debit_indicator' => credit_debit_indicator,
145
180
  'swift_code' => swift_code,
146
181
  'reference' => reference,
147
182
  'bank_reference' => bank_reference,
@@ -41,8 +41,7 @@ module Cmxl
41
41
  end
42
42
 
43
43
  def strip_headers!
44
- source.gsub!(/\A.+?(?=^:)/m, '') # beginning: strip every line in the beginning that does not start with a :
45
- source.gsub!(/^[^:]+\z/, '') # end: strip every line in the end that does not start with a :
44
+ source.gsub!(/\A.*?(?=^:)/m, '') # beginning: strip every line in the beginning that does not start with a :
46
45
  source.strip!
47
46
  end
48
47
 
data/lib/cmxl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cmxl
2
- VERSION = '1.5.0'.freeze
2
+ VERSION = '2.2'.freeze
3
3
  end
data/lib/cmxl.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'cmxl/version'
2
2
 
3
- require 'rchardet19'
3
+ require 'rchardet'
4
4
 
5
5
  require 'cmxl/field'
6
6
  require 'cmxl/statement'
@@ -11,9 +11,13 @@ module Cmxl
11
11
  @config
12
12
  end
13
13
  @config = {
14
- statement_separator: /\n-\s*\n/m,
14
+ # One or more newlines
15
+ # followed by `-` at the beginning of a line.
16
+ # "Eats up" but does not require characters until the end of the line and more newlines.
17
+ # \R is a platform independent newline but in the negated group `[^\n\r]` that did not seem to work.
18
+ statement_separator: /\R+-[^\n\r]*\R*/m,
15
19
  raise_line_format_errors: true,
16
- strip_headers: false
20
+ strip_headers: true
17
21
  }
18
22
 
19
23
  # Public: Parse a MT940 string
@@ -31,8 +35,8 @@ module Cmxl
31
35
  def self.parse(data, options = {})
32
36
  options[:statement_separator] ||= config[:statement_separator]
33
37
  # if no encoding is provided we try to guess using CharDet
34
- if options[:encoding].nil? && cd = CharDet.detect(data, silent: true)
35
- options[:encoding] = cd.encoding
38
+ if options[:encoding].nil? && cd = CharDet.detect(data)
39
+ options[:encoding] = cd['encoding']
36
40
  end
37
41
 
38
42
  if options[:encoding]
@@ -75,6 +75,47 @@ describe Cmxl::Fields::StatementDetails do
75
75
  )
76
76
  }
77
77
  it { expect(subject.to_hash).to eql(subject.to_h) }
78
+
79
+ context 'with newlines in windows format' do
80
+ it 'removes any newlines' do
81
+ data = ":86:171?00SEPA LASTSCHRIFT KUNDE?10281?\r\n20KREF+EREF+TRX-0A4A47C3-F846-4729?21-8A1B-5DF620F?22MREF+CAC97D2144174318AC18D9?23BF815BD4FB?24CRED+DE98ZZZ09999999999?25SVWZ+FOO TRX-0A4A47C3-F84?266-4729-8A1B-5DF620F?30HYVEDEMMXXX?31HUkkbbbsssskcccccccccccccccx?\r\n32Peter Pan?99?34171"
82
+ result = Cmxl::Fields::StatementDetails.parse(data)
83
+
84
+ expect(result.to_h).to eql(
85
+ 'bic' => 'HYVEDEMMXXX',
86
+ 'iban' => 'HUkkbbbsssskcccccccccccccccx',
87
+ 'name' => 'Peter Pan',
88
+ 'sepa' => {
89
+ 'KREF' => '',
90
+ 'EREF' => 'TRX-0A4A47C3-F846-4729-8A1B-5DF620F',
91
+ 'MREF' => 'CAC97D2144174318AC18D9BF815BD4FB',
92
+ 'CRED' => 'DE98ZZZ09999999999',
93
+ 'SVWZ' => 'FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F'
94
+ },
95
+ 'information' => 'KREF+EREF+TRX-0A4A47C3-F846-4729-8A1B-5DF620FMREF+CAC97D2144174318AC18D9BF815BD4FBCRED+DE98ZZZ09999999999SVWZ+FOO TRX-0A4A47C3-F846-4729-8A1B-5DF620F',
96
+ 'description' => 'SEPA LASTSCHRIFT KUNDE',
97
+ 'sub_fields' => {
98
+ '00' => 'SEPA LASTSCHRIFT KUNDE',
99
+ '10' => '281',
100
+ '20' => 'KREF+EREF+TRX-0A4A47C3-F846-4729',
101
+ '21' => '-8A1B-5DF620F',
102
+ '22' => 'MREF+CAC97D2144174318AC18D9',
103
+ '23' => 'BF815BD4FB',
104
+ '24' => 'CRED+DE98ZZZ09999999999',
105
+ '25' => 'SVWZ+FOO TRX-0A4A47C3-F84',
106
+ '26' => '6-4729-8A1B-5DF620F',
107
+ '30' => 'HYVEDEMMXXX',
108
+ '31' => 'HUkkbbbsssskcccccccccccccccx',
109
+ '32' => 'Peter Pan',
110
+ '34' => '171',
111
+ '99' => ''
112
+ },
113
+ 'transaction_code' => '171',
114
+ 'primanota' => '281',
115
+ 'details' => '?00SEPA LASTSCHRIFT KUNDE?10281?20KREF+EREF+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?99?34171'
116
+ )
117
+ end
118
+ end
78
119
  end
79
120
 
80
121
  describe 'information parsing with empty fields on the end' do
@@ -7,7 +7,7 @@ describe Cmxl::Fields::Transaction do
7
7
  subject(:ocmt_cghs_transaction) { Cmxl::Fields::Transaction.parse(fixture_line(:statement_ocmt_chgs)) }
8
8
  subject(:supplementary_transaction) { Cmxl::Fields::Transaction.parse(fixture_line(:statement_supplementary_plain)) }
9
9
  subject(:complex_supplementary_transaction) { Cmxl::Fields::Transaction.parse(fixture_line(:statement_supplementary_complex)) }
10
- subject(:valuta_after_enty_date) { Cmxl::Fields::Transaction.parse(fixture[3]) }
10
+ subject(:valuta_after_entry_date) { Cmxl::Fields::Transaction.parse(fixture[3]) }
11
11
  subject(:entry_before_valuta_transaction) { Cmxl::Fields::Transaction.parse(fixture[2]) }
12
12
  subject(:transaction_type_swift) { Cmxl::Fields::Transaction.parse(fixture[4]) }
13
13
 
@@ -70,6 +70,11 @@ describe Cmxl::Fields::Transaction do
70
70
  end
71
71
 
72
72
  context 'statement with complex supplementary' do
73
+ it 'future reference' do
74
+ result = Cmxl::Fields::Transaction.parse(':61:2412121212ED162,57NDDTNONREF//950\n')
75
+ expect(result.amount).to eql(162.57)
76
+ end
77
+
73
78
  it { expect(complex_supplementary_transaction.initial_amount_in_cents).to eql(nil) }
74
79
  it { expect(complex_supplementary_transaction.initial_currency).to eql(nil) }
75
80
 
@@ -82,14 +87,14 @@ describe Cmxl::Fields::Transaction do
82
87
  end
83
88
 
84
89
  context 'valuta and entry-date assumptions' do
85
- it 'entry_date before valuta is recognized correclty when including year-change' do
90
+ it 'entry_date before valuta is recognized correctly when including year-change' do
86
91
  expect(entry_before_valuta_transaction.date).to eql(Date.new(2014, 1, 10))
87
92
  expect(entry_before_valuta_transaction.entry_date).to eql(Date.new(2013, 12, 24))
88
93
  end
89
94
 
90
95
  it 'entry_date after valuta is recognized correctly when including year-change' do
91
- expect(valuta_after_enty_date.date).to eql(Date.new(2014, 12, 24))
92
- expect(valuta_after_enty_date.entry_date).to eql(Date.new(2015, 1, 2))
96
+ expect(valuta_after_entry_date.date).to eql(Date.new(2014, 12, 24))
97
+ expect(valuta_after_entry_date.entry_date).to eql(Date.new(2015, 1, 2))
93
98
  end
94
99
  end
95
100
 
@@ -107,4 +112,77 @@ describe Cmxl::Fields::Transaction do
107
112
  it { expect(transaction_type_swift).not_to be_storno }
108
113
  it { expect(transaction_type_swift.sign).to eql(1) }
109
114
  end
115
+
116
+ describe '#credit_debit_indicator' do
117
+ it 'returns the credit_debit_indicator as debit' do
118
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902DR000000000001,62NTRF0000549855700010//025498557/000001')
119
+ expect(result.credit_debit_indicator).to eql('D')
120
+ end
121
+
122
+ it 'returns the credit_debit_indicator as credit' do
123
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902CR000000000001,62NTRF0000549855700010//025498557/000001')
124
+ expect(result.credit_debit_indicator).to eql('C')
125
+ end
126
+
127
+ it 'returns the credit_debit_indicator as reversal credit' do
128
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902RC000000000001,62NTRF0000549855700010//025498557/000001')
129
+ expect(result.credit_debit_indicator).to eql('RC')
130
+ end
131
+
132
+ it 'returns the credit_debit_indicator as reversal debit' do
133
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902RD000000000001,62NTRF0000549855700010//025498557/000001')
134
+ expect(result.credit_debit_indicator).to eql('RD')
135
+ end
136
+
137
+ it 'returns the credit_debit_indicator as expected credit' do
138
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
139
+ expect(result.credit_debit_indicator).to eql('EC')
140
+ end
141
+ end
142
+
143
+ describe '#expected?' do
144
+ it 'returns true if the transaction is expected' do
145
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
146
+ expect(result).to be_expected
147
+ end
148
+
149
+ it 'returns false if the transaction is not expected' do
150
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902RD000000000001,62NTRF0000549855700010//025498557/000001')
151
+ expect(result).not_to be_expected
152
+ end
153
+ end
154
+
155
+ describe '#expected_credit?' do
156
+ it 'returns true if the transaction is expected and credit' do
157
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
158
+ expect(result).to be_expected_credit
159
+ end
160
+
161
+ it 'returns false if the transaction is not expected and credit' do
162
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902RC000000000001,62NTRF0000549855700010//025498557/000001')
163
+ expect(result).not_to be_expected_credit
164
+ end
165
+
166
+ it 'returns false if the transaction is expected and debit' do
167
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902ED000000000001,62NTRF0000549855700010//025498557/000001')
168
+ expect(result).not_to be_expected_credit
169
+ end
170
+ end
171
+
172
+ describe '#expected_debit?' do
173
+ it 'returns true if the transaction is expected and debit' do
174
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902ED000000000001,62NTRF0000549855700010//025498557/000001')
175
+ expect(result).to be_expected_debit
176
+ end
177
+
178
+ it 'returns false if the transaction is not expected and debit' do
179
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902RD000000000001,62NTRF0000549855700010//025498557/000001')
180
+ expect(result).not_to be_expected_debit
181
+ end
182
+
183
+ it 'returns false if the transaction is expected and credit' do
184
+ result = Cmxl::Fields::Transaction.parse(':61:1409010902EC000000000001,62NTRF0000549855700010//025498557/000001')
185
+ expect(result).not_to be_expected_debit
186
+ end
187
+ end
110
188
  end
@@ -0,0 +1,44 @@
1
+ ABNANL2A
2
+ 940
3
+ ABNANL2A
4
+ :20:ABN AMRO BANK NV
5
+ :25:517852257
6
+ :28:19321/1
7
+ :60F:C110522EUR3236,28
8
+ :61:1105240524D9,N192NONREF
9
+ :86:GIRO 428428 KPN - DIGITENNE BETALINGSKENM. 000000042188659
10
+ 5314606715 BETREFT FACTUUR D.D. 20-05-2011
11
+ INCL. 1,44 BTW
12
+ :61:1105210523D11,59N426NONREF
13
+ :86:BEA NR:XXX1234 21.05.11/12.54 DIRCKIII FIL2500 KATWIJK,PAS999
14
+ :61:1105230523D11,63N426NONREF
15
+ :86:BEA NR:XXX1234 23.05.11/09.08 DIGROS FIL1015 KATWIJK Z,PAS999
16
+ :61:1105220523D11,8N426NONREF
17
+ :86:BEA NR:XXX1234 22.05.11/14.25 MC DONALDS A44 LEIDEN,PAS999
18
+ :61:1105210523D13,45N426NONREF
19
+ :86:BEA NR:XXX1234 21.05.11/12.09 PRINCE FIL. 55 KATWIJK Z,PAS999
20
+ :61:1105210523D15,49N426NONREF
21
+ :86:BEA NR:XXX1234 21.05.11/12.55 DIRX FIL6017 KATWIJK ZH ,PAS999
22
+
23
+ :61:1105210523D107,N426NONREF
24
+ :86:BEA NR:XXX1234 21.05.11/12.04 HANS ANDERS OPT./056 KAT,PAS999
25
+ :61:1105220523D141,48N426NONREF
26
+ :86:BEA NR:XXX1234 22.05.11/13.45 MYCOM DEN HAAG S-GRAVEN,PAS999
27
+ :62F:C110523EUR876,84
28
+
29
+ -
30
+
31
+ ABNANL2A
32
+ 940
33
+ ABNANL2A
34
+ :20:ABN AMRO BANK NV
35
+ :25:517852257
36
+ :28:19322/1
37
+ :60F:C110523EUR2876,84
38
+ :61:1105240524D9,49N426NONREF
39
+ :86:BEA NR:XXX1234 24.05.11/09.18 PETS PLACE KATWIJK KATWI,PAS999
40
+ :61:1105240524D15,N426NONREF
41
+ :86:52.89.39.882 MYCOM DEN HAAG S-GRAVEN,PAS999
42
+ :62F:C110524EUR1849,75
43
+
44
+ -
@@ -0,0 +1,16 @@
1
+ ä4:
2
+ :20:5566778899100112
3
+ :25:10020030/1234567
4
+ :28C:188/1
5
+ :60F:C130928SEK0,
6
+ :62F:C130930SEK0,
7
+ :64:C130930SEK0,
8
+
9
+ ä4:
10
+ :20:5566778899100169
11
+ :25:10020030/1234567
12
+ :28C:188/1
13
+ :60F:C130928SEK0,
14
+ :62F:C130930SEK0,
15
+ :64:C130930SEK0,
16
+
@@ -0,0 +1,16 @@
1
+ {1:D02AASDISLNETAXXXXXXXXXXXXX}
2
+ {2:E623XXXXXXXXAXXXN}
3
+ {4:
4
+ :20:1234567
5
+ :21:9876543210
6
+ :25:10020030/1234567
7
+ :28C:5/1
8
+ :60F:C160314EUR2187,95
9
+ :61:0211011102DR800,NSTONONREF//55555
10
+ :86:008?00DAUERAUFTRAG?100599?20Miete November?3010020030?31234567?32MUELLER?34339
11
+ :61:0211021102CR3000,NTRFNONREF//55555
12
+ :86:051?00UEBERWEISUNG?100599?20Gehalt Oktober?21Firma
13
+ Mustermann GmbH?3050060400?310847564700?32MUELLER?34339
14
+ :62F:C160315EUR4387,95
15
+ :86:Some random data
16
+ -}
@@ -95,4 +95,115 @@ describe 'parsing a statement' do
95
95
  expect(subject[0].transactions.count).to eql(1)
96
96
  end
97
97
  end
98
+
99
+ describe "MT940 abnamro" do
100
+ it "splits the file into on statement with the headers included" do
101
+ expected_data = <<~MT940.chomp
102
+ ABNANL2A
103
+ 940
104
+ ABNANL2A
105
+ :20:ABN AMRO BANK NV
106
+ :25:517852257
107
+ :28:19321/1
108
+ :60F:C110522EUR3236,28
109
+ :61:1105240524D9,N192NONREF
110
+ :86:GIRO 428428 KPN - DIGITENNE BETALINGSKENM. 000000042188659
111
+ 5314606715 BETREFT FACTUUR D.D. 20-05-2011
112
+ INCL. 1,44 BTW
113
+ :61:1105210523D11,59N426NONREF
114
+ :86:BEA NR:XXX1234 21.05.11/12.54 DIRCKIII FIL2500 KATWIJK,PAS999
115
+ :61:1105230523D11,63N426NONREF
116
+ :86:BEA NR:XXX1234 23.05.11/09.08 DIGROS FIL1015 KATWIJK Z,PAS999
117
+ :61:1105220523D11,8N426NONREF
118
+ :86:BEA NR:XXX1234 22.05.11/14.25 MC DONALDS A44 LEIDEN,PAS999
119
+ :61:1105210523D13,45N426NONREF
120
+ :86:BEA NR:XXX1234 21.05.11/12.09 PRINCE FIL. 55 KATWIJK Z,PAS999
121
+ :61:1105210523D15,49N426NONREF
122
+ :86:BEA NR:XXX1234 21.05.11/12.55 DIRX FIL6017 KATWIJK ZH ,PAS999
123
+
124
+ :61:1105210523D107,N426NONREF
125
+ :86:BEA NR:XXX1234 21.05.11/12.04 HANS ANDERS OPT./056 KAT,PAS999
126
+ :61:1105220523D141,48N426NONREF
127
+ :86:BEA NR:XXX1234 22.05.11/13.45 MYCOM DEN HAAG S-GRAVEN,PAS999
128
+ :62F:C110523EUR876,84
129
+ MT940
130
+ stub = instance_double(Cmxl::Statement)
131
+ allow(Cmxl::Statement).to receive(:new).and_return(stub)
132
+
133
+ Cmxl.parse(mt940_file('mt940-abnamro'))
134
+
135
+ expect(Cmxl::Statement).to have_received(:new).with(expected_data)
136
+ end
137
+
138
+ it 'splits the file into two statements' do
139
+ allow(Cmxl::Statement).to receive(:new)
140
+
141
+ Cmxl.parse(mt940_file('mt940-abnamro'))
142
+
143
+ expect(Cmxl::Statement).to have_received(:new).twice
144
+ end
145
+ end
146
+
147
+ describe 'MT940 handelsbank' do
148
+ it 'splits the file with the special characters correctly' do
149
+ expected_data = <<~MT940.chomp
150
+ 채4:
151
+ :20:5566778899100112
152
+ :25:10020030/1234567
153
+ :28C:188/1
154
+ :60F:C130928SEK0,
155
+ :62F:C130930SEK0,
156
+ :64:C130930SEK0,
157
+ MT940
158
+ stub = instance_double(Cmxl::Statement)
159
+ allow(Cmxl::Statement).to receive(:new).and_return(stub)
160
+
161
+ Cmxl.parse(mt940_file('mt940-handelsbank'))
162
+
163
+ expect(Cmxl::Statement).to have_received(:new).with(expected_data)
164
+ end
165
+
166
+ it 'splits the file into two statements' do
167
+ allow(Cmxl::Statement).to receive(:new)
168
+
169
+ Cmxl.parse(mt940_file('mt940-handelsbank'))
170
+
171
+ expect(Cmxl::Statement).to have_received(:new).twice
172
+ end
173
+ end
174
+
175
+ describe "MT940 windows line breaks" do
176
+ it 'splits the file with the special characters correctly' do
177
+ expected_data =
178
+ "{1:D02AASDISLNETAXXXXXXXXXXXXX}\r\n"\
179
+ "{2:E623XXXXXXXXAXXXN}\r\n"\
180
+ "{4:\r\n"\
181
+ ":20:1234567\r\n"\
182
+ ":21:9876543210\r\n"\
183
+ ":25:10020030/1234567\r\n"\
184
+ ":28C:5/1\r\n"\
185
+ ":60F:C160314EUR2187,95\r\n"\
186
+ ":61:0211011102DR800,NSTONONREF//55555\r\n"\
187
+ ":86:008?00DAUERAUFTRAG?100599?20Miete November?3010020030?31234567?32MUELLER?34339\r\n"\
188
+ ":61:0211021102CR3000,NTRFNONREF//55555\r\n"\
189
+ ":86:051?00UEBERWEISUNG?100599?20Gehalt Oktober?21Firma\r\n"\
190
+ "Mustermann GmbH?3050060400?310847564700?32MUELLER?34339\r\n"\
191
+ ":62F:C160315EUR4387,95\r\n"\
192
+ ":86:Some random data"
193
+ stub = instance_double(Cmxl::Statement)
194
+ allow(Cmxl::Statement).to receive(:new).and_return(stub)
195
+
196
+ Cmxl.parse(mt940_file('mt940-windows-line-breaks'))
197
+
198
+ expect(Cmxl::Statement).to have_received(:new).with(expected_data)
199
+ end
200
+
201
+ it 'splits the file into two statements' do
202
+ allow(Cmxl::Statement).to receive(:new)
203
+
204
+ Cmxl.parse(mt940_file('mt940-windows-line-breaks'))
205
+
206
+ expect(Cmxl::Statement).to have_received(:new).once
207
+ end
208
+ end
98
209
  end
@@ -27,9 +27,12 @@ describe Cmxl do
27
27
  'bank_reference' => '025498557/000001',
28
28
  'amount_in_cents' => 162,
29
29
  'sign' => -1,
30
+ 'credit_debit_indicator' => 'D',
30
31
  'debit' => true,
31
32
  'credit' => false,
32
33
  'storno' => false,
34
+ 'reversal' => false,
35
+ 'expected' => false,
33
36
  'bic' => 'HYVEDEMMXXX',
34
37
  'iban' => 'HUkkbbbsssskcccccccccccccccx',
35
38
  'name' => 'Peter Pan',
@@ -81,6 +84,7 @@ describe Cmxl do
81
84
  'sha' => '3c5e65aa3d3878b06b58b6f1ae2f3693004dfb04e3ab7119a1c1244e612293da',
82
85
  'entry_date' => Date.new(2014, 9, 2),
83
86
  'funds_code' => 'D',
87
+ 'credit_debit_indicator' => 'D',
84
88
  'currency_letter' => 'R',
85
89
  'amount' => 1.62,
86
90
  'swift_code' => 'NTRF',
@@ -90,12 +94,14 @@ describe Cmxl do
90
94
  'sign' => -1,
91
95
  'debit' => true,
92
96
  'credit' => false,
93
- 'storno' => false
97
+ 'storno' => false,
98
+ 'reversal' => false,
99
+ 'expected' => false,
94
100
  )
95
101
  end
96
102
  end
97
103
 
98
- describe 'statement issued over a years boudary' do
104
+ describe 'statement issued over a years boundary' do
99
105
  subject { Cmxl.parse(mt940_file('statement-mt940')).first.transactions.last }
100
106
 
101
107
  it { expect(subject.mt942?).to be_falsey }
@@ -161,4 +167,95 @@ describe Cmxl do
161
167
  it { expect(statement.generation_date).to eql(Date.new(2019, 1, 9)) }
162
168
  end
163
169
 
170
+ describe "header parsing" do
171
+ context "when strip_headers is enabled" do
172
+ around do |example|
173
+ existing_value = Cmxl.config[:strip_headers]
174
+ Cmxl.config[:strip_headers] = true
175
+ example.run
176
+ Cmxl.config[:strip_headers] = existing_value
177
+ end
178
+
179
+ it "removes any headers" do
180
+ data = <<~MT940.chomp
181
+ {1:D02AASDISLNETAXXXXXXXXXXXXX}
182
+ {2:E623XXXXXXXXAXXXN}
183
+ {4:
184
+ :20:MT940/78374
185
+ :25:xxxxxxxxxxxxxx
186
+ :28C:3/1
187
+ :60F:C160201INR0,00
188
+ :61:3622687806CR1368378,92NMSC37935531
189
+ :86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
190
+ :62F:C141387INR11 27421,94
191
+ -}
192
+ MT940
193
+
194
+ result = Cmxl::Statement.new(data)
195
+
196
+ expect(result.fields.count).to eq(6)
197
+ end
198
+
199
+ it "does nothing if there are no headers" do
200
+ data = <<~MT940.chomp
201
+ :20:MT940/78374
202
+ :25:xxxxxxxxxxxxxx
203
+ :28C:3/1
204
+ :60F:C160201INR0,00
205
+ :61:3622687806CR1368378,92NMSC37935531
206
+ :86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
207
+ :62F:C141387INR11 27421,94
208
+ MT940
209
+
210
+ result = Cmxl::Statement.new(data)
211
+
212
+ expect(result.fields.count).to eq(6)
213
+ end
214
+ end
215
+
216
+ context "when strip_headers is disabled" do
217
+ around do |example|
218
+ existing_value = Cmxl.config[:strip_headers]
219
+ Cmxl.config[:strip_headers] = false
220
+ example.run
221
+ Cmxl.config[:strip_headers] = existing_value
222
+ end
223
+
224
+ it "raise an parsing error exception if headers are present" do
225
+ data = <<~MT940.chomp
226
+ {1:D02AASDISLNETAXXXXXXXXXXXXX}
227
+ {2:E623XXXXXXXXAXXXN}
228
+ {4:
229
+ :20:MT940/78374
230
+ :25:xxxxxxxxxxxxxx
231
+ :28C:3/1
232
+ :60F:C160201INR0,00
233
+ :61:3622687806CR1368378,92NMSC37935531
234
+ :86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
235
+ :62F:C141387INR11 27421,94
236
+ -}
237
+ MT940
238
+
239
+ expect{
240
+ Cmxl::Statement.new(data)
241
+ }.to raise_error(Cmxl::Field::LineFormatError)
242
+ end
243
+
244
+ it "extracts the field" do
245
+ data = <<~MT940.chomp
246
+ :20:MT940/78374
247
+ :25:xxxxxxxxxxxxxx
248
+ :28C:3/1
249
+ :60F:C160201INR0,00
250
+ :61:3622687806CR1368378,92NMSC37935531
251
+ :86:-TX TRN-REF NO.1156ADS5601187 EUR 13456/TSV
252
+ :62F:C141387INR11 27421,94
253
+ MT940
254
+
255
+ result = Cmxl::Statement.new(data)
256
+
257
+ expect(result.fields.count).to eq(6)
258
+ end
259
+ end
260
+ end
164
261
  end
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmxl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: '2.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bumann
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2020-07-10 00:00:00.000000000 Z
10
+ date: 2025-02-04 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rchardet
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: bundler
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +93,6 @@ dependencies:
80
93
  - - ">="
81
94
  - !ruby/object:Gem::Version
82
95
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rchardet19
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
96
  description: Cmxl provides an friendly, extensible and customizable parser for the
98
97
  MT940 bank statement format.
99
98
  email:
@@ -102,9 +101,10 @@ executables: []
102
101
  extensions: []
103
102
  extra_rdoc_files: []
104
103
  files:
104
+ - ".github/workflows/ci.yml"
105
+ - ".github/workflows/release.yml"
105
106
  - ".gitignore"
106
107
  - ".rspec"
107
- - ".travis.yml"
108
108
  - CHANGELOG.mdown
109
109
  - Gemfile
110
110
  - LICENSE.txt
@@ -163,9 +163,12 @@ files:
163
163
  - spec/fixtures/lines/statement_supplementary_plain.txt
164
164
  - spec/fixtures/lines/vmk_summary_credit.txt
165
165
  - spec/fixtures/lines/vmk_summary_debit.txt
166
+ - spec/fixtures/mt940-abnamro.txt
166
167
  - spec/fixtures/mt940-deutsche_bank.txt
168
+ - spec/fixtures/mt940-handelsbank.txt
167
169
  - spec/fixtures/mt940-headers.txt
168
170
  - spec/fixtures/mt940-iso8859-1.txt
171
+ - spec/fixtures/mt940-windows-line-breaks.txt
169
172
  - spec/fixtures/mt940-with-colon-after-line-break.txt
170
173
  - spec/fixtures/mt940-with-detailed-end-balance.txt
171
174
  - spec/fixtures/mt940.txt
@@ -198,8 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
201
  - !ruby/object:Gem::Version
199
202
  version: '0'
200
203
  requirements: []
201
- rubygems_version: 3.1.2
202
- signing_key:
204
+ rubygems_version: 3.6.2
203
205
  specification_version: 4
204
206
  summary: Cmxl is your friendly MT940 bank statement parser
205
207
  test_files:
@@ -239,9 +241,12 @@ test_files:
239
241
  - spec/fixtures/lines/statement_supplementary_plain.txt
240
242
  - spec/fixtures/lines/vmk_summary_credit.txt
241
243
  - spec/fixtures/lines/vmk_summary_debit.txt
244
+ - spec/fixtures/mt940-abnamro.txt
242
245
  - spec/fixtures/mt940-deutsche_bank.txt
246
+ - spec/fixtures/mt940-handelsbank.txt
243
247
  - spec/fixtures/mt940-headers.txt
244
248
  - spec/fixtures/mt940-iso8859-1.txt
249
+ - spec/fixtures/mt940-windows-line-breaks.txt
245
250
  - spec/fixtures/mt940-with-colon-after-line-break.txt
246
251
  - spec/fixtures/mt940-with-detailed-end-balance.txt
247
252
  - spec/fixtures/mt940.txt
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.4.10
5
- - 2.5.8
6
- - 2.6.6
7
- - 2.7.1
8
- - jruby-9.1.17.0
9
- - jruby-9.2.11.1