norma43_parser 1.0.1 → 2.0.0
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 +5 -5
- data/.github/ISSUE_TEMPLATE.md +13 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
- data/.rubocop.yml +234 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +73 -0
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/lib/norma43.rb +9 -3
- data/lib/norma43/line_handlers.rb +2 -3
- data/lib/norma43/line_parsers/account_end.rb +18 -0
- data/lib/norma43/line_parsers/account_start.rb +18 -0
- data/lib/norma43/line_parsers/additional_currency.rb +12 -0
- data/lib/norma43/line_parsers/additional_item.rb +11 -0
- data/lib/norma43/line_parsers/document_end.rb +9 -0
- data/lib/norma43/line_parsers/document_start.rb +13 -0
- data/lib/norma43/line_parsers/file_format_validator.rb +15 -12
- data/lib/norma43/line_parsers/line_parser.rb +29 -28
- data/lib/norma43/line_parsers/transaction.rb +18 -0
- data/lib/norma43/line_processors.rb +2 -2
- data/lib/norma43/models.rb +5 -4
- data/lib/norma43/parser.rb +38 -42
- data/lib/norma43/utils/contexts.rb +40 -37
- data/lib/norma43/utils/string_helpers.rb +10 -6
- data/lib/norma43/utils/typecaster.rb +16 -12
- data/lib/norma43/version.rb +3 -1
- data/norma43_parser.gemspec +11 -9
- data/spec/example1_parse_spec.rb +12 -11
- data/spec/norma43/line_parsers/account_end_spec.rb +1 -1
- data/spec/norma43/line_parsers/account_start_spec.rb +1 -2
- data/spec/norma43/line_parsers/additional_currency_spec.rb +1 -1
- data/spec/norma43/line_parsers/additional_items_spec.rb +1 -1
- data/spec/norma43/line_parsers/document_end_spec.rb +1 -2
- data/spec/norma43/line_parsers/document_start_spec.rb +1 -1
- data/spec/norma43/line_parsers/transaction_spec.rb +1 -3
- data/spec/norma43/line_processors/account_end_spec.rb +6 -8
- data/spec/norma43/line_processors/account_start_spec.rb +8 -9
- data/spec/norma43/line_processors/additional_currency_spec.rb +6 -7
- data/spec/norma43/line_processors/additional_items_spec.rb +6 -7
- data/spec/norma43/line_processors/document_end_spec.rb +5 -6
- data/spec/norma43/line_processors/document_start_spec.rb +3 -4
- data/spec/norma43/line_processors/transaction_spec.rb +6 -7
- data/spec/norma43/parser_spec.rb +5 -4
- data/spec/norma43_spec.rb +2 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/support/shared_examples_for_values_line_parsers.rb +2 -0
- metadata +41 -32
- data/lib/norma43/line_parsers/line_parsers.rb +0 -69
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Norma43
|
4
|
+
module LineParsers
|
5
|
+
class AdditionalCurrency < LineParser
|
6
|
+
field :data_code, 2..3, :integer
|
7
|
+
field :currency_code, 4..6, :integer
|
8
|
+
field :amount, 7..20, :integer
|
9
|
+
field :free, 21..79
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Norma43
|
4
|
+
module LineParsers
|
5
|
+
class DocumentStart < LineParser
|
6
|
+
field :id, 2..13
|
7
|
+
field :created_at, 14..33, :time
|
8
|
+
field :delivery_number, 34..35, :integer
|
9
|
+
field :file_type, 36..38
|
10
|
+
field :name, 39..48
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,19 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Norma43
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
module LineParsers
|
5
|
+
class FileFormatValidator < LineParser
|
6
|
+
field :record_type, 0..1, :raw
|
7
|
+
field :file_type, 36..38
|
6
8
|
|
7
|
-
|
9
|
+
def has_document?; file_type == "00" end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
def valid?
|
12
|
+
errors.empty?
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def errors
|
16
|
+
errors = []
|
17
|
+
%w(11 00).include?(record_type) || errors << ("Must start with 00 (was ”#{record_type}”)")
|
18
|
+
errors
|
19
|
+
end
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -1,42 +1,43 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Norma43
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module LineParsers
|
5
|
+
class LineParser
|
6
|
+
attr_reader :line
|
7
|
+
def initialize(line)
|
8
|
+
@line = line
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def attributes
|
12
|
+
self.class.field_names.each_with_object({}) do |field, attrs|
|
13
|
+
attrs[field] = self.public_send(field)
|
14
|
+
end
|
13
15
|
end
|
14
|
-
end
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
def self.field(name, range, type = :string)
|
18
|
+
self.field_names.push name
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
define_method name do
|
21
|
+
if range.is_a?(Array) # let multivalued attribute
|
22
|
+
range.map { |r| value_at_position(r, type) }.compact
|
23
|
+
else
|
24
|
+
value_at_position range, type
|
25
|
+
end
|
24
26
|
end
|
25
27
|
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
private
|
30
|
+
def self.field_names
|
31
|
+
@field_names ||= []
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
def value_at_position(range, type)
|
35
|
+
typecast line[range].to_s.strip, type
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
def typecast(value, type)
|
39
|
+
Norma43::Utils::Typecaster.cast value, type
|
40
|
+
end
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Norma43
|
4
|
+
module LineParsers
|
5
|
+
class Transaction < LineParser
|
6
|
+
field :origin_branch_code, 6..9, :integer
|
7
|
+
field :transaction_date, 10..15, :date
|
8
|
+
field :value_date, 16..21, :date
|
9
|
+
field :shared_item, 22..23, :integer
|
10
|
+
field :own_item, 24..26, :integer
|
11
|
+
field :amount_code, 27, :integer
|
12
|
+
field :amount, 28..41, :integer
|
13
|
+
field :document_number, 42..51, :integer
|
14
|
+
field :reference_1, 52..63, :integer
|
15
|
+
field :reference_2, 64..79
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Norma43
|
3
4
|
module LineProcessors
|
4
5
|
DocumentStart = ->(line, contexts) {
|
@@ -55,6 +56,5 @@ module Norma43
|
|
55
56
|
contexts.current.additional_currency = additional_currency
|
56
57
|
contexts
|
57
58
|
}
|
58
|
-
|
59
59
|
end
|
60
60
|
end
|
data/lib/norma43/models.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "virtus"
|
3
4
|
|
4
5
|
module Norma43
|
5
6
|
module Models
|
6
|
-
#forward declarations
|
7
|
+
# forward declarations
|
7
8
|
class Account; end
|
8
9
|
class Transaction; end
|
9
10
|
class AdditionalItem; end
|
@@ -62,7 +63,7 @@ module Norma43
|
|
62
63
|
attribute :reference_2
|
63
64
|
attribute :additional_items, Array[AdditionalItem]
|
64
65
|
attribute :additional_currency, AdditionalCurrency
|
65
|
-
def debit?; self.amount_code==DEBIT_CODE end
|
66
|
+
def debit?; self.amount_code == DEBIT_CODE end
|
66
67
|
end
|
67
68
|
|
68
69
|
class AdditionalItem
|
data/lib/norma43/parser.rb
CHANGED
@@ -1,25 +1,23 @@
|
|
1
|
-
|
2
|
-
require "norma43/line_handlers"
|
3
|
-
require "norma43/utils/contexts"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
3
|
module Norma43
|
6
|
-
class InvalidFileFormatError < ArgumentError; end
|
4
|
+
class InvalidFileFormatError < ArgumentError; end
|
7
5
|
|
8
6
|
class Parser
|
9
7
|
attr_reader :file
|
10
8
|
|
11
9
|
# Parser.new accepts a File instance or a String
|
12
10
|
# A InvalidFileFormatError will be raised if file isn't in the Norma43 format
|
13
|
-
def initialize
|
11
|
+
def initialize(file)
|
14
12
|
@file = file
|
15
13
|
validator = validate_file_format
|
16
14
|
@contexts = if validator.has_document?
|
17
|
-
Contexts.new
|
15
|
+
Norma43::Utils::Contexts.new
|
18
16
|
else
|
19
17
|
# in theory Norma43 says that files should start with DocumentStart but
|
20
18
|
# practically doesn't happen, so that we create one artificially
|
21
19
|
# to avoid corner cases in the processors
|
22
|
-
Contexts.new().tap { |ctx| ctx.add Models::Document.new }
|
20
|
+
Norma43::Utils::Contexts.new().tap { |ctx| ctx.add Models::Document.new }
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
@@ -28,52 +26,50 @@ module Norma43
|
|
28
26
|
end
|
29
27
|
|
30
28
|
protected
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
29
|
+
def lines
|
30
|
+
@lines ||= file.each_line
|
31
|
+
end
|
35
32
|
|
36
33
|
private
|
34
|
+
def validate_file_format
|
35
|
+
validator = Norma43::LineParsers::FileFormatValidator.new first_line
|
36
|
+
raise InvalidFileFormatError.new(validator.errors.join(", ")) unless validator.valid?
|
37
|
+
validator
|
38
|
+
end
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
raise InvalidFileFormatError.new(validator.errors.join(", ")) unless validator.valid?
|
41
|
-
validator
|
42
|
-
end
|
43
|
-
|
44
|
-
def parse_lines contexts
|
45
|
-
parse_lines parse_line(self.lines.next, contexts)
|
40
|
+
def parse_lines(contexts)
|
41
|
+
parse_lines parse_line(self.lines.next, contexts)
|
46
42
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
43
|
+
rescue StopIteration # because lines is an enumerator raises StopIteration on end
|
44
|
+
self.lines.rewind # Ensure we do not bomb out when calling result multiple times
|
45
|
+
contexts
|
46
|
+
end
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
48
|
+
# Look up a matching handler for the line and process it
|
49
|
+
# The process method on a handler always returns a Contexts object
|
50
|
+
def parse_line(line, contexts)
|
51
|
+
line = line.encode Encoding::UTF_8 if encode_lines?
|
56
52
|
|
57
|
-
|
53
|
+
handler = handler_for_line line
|
58
54
|
|
59
|
-
|
60
|
-
|
55
|
+
handler.process line, contexts
|
56
|
+
end
|
61
57
|
|
62
|
-
|
63
|
-
|
64
|
-
|
58
|
+
def handler_for_line(line)
|
59
|
+
LineHandlers.mapping.fetch line[0..1]
|
60
|
+
end
|
65
61
|
|
66
|
-
|
67
|
-
|
68
|
-
|
62
|
+
def encode_lines?
|
63
|
+
first_line.encoding != Encoding::UTF_8
|
64
|
+
end
|
69
65
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
def first_line
|
67
|
+
@first_line ||= begin
|
68
|
+
line = self.lines.peek
|
69
|
+
self.lines.rewind # peek seems to move the pointer when file is an actual File object
|
74
70
|
|
75
|
-
|
71
|
+
line
|
72
|
+
end
|
76
73
|
end
|
77
|
-
end
|
78
74
|
end
|
79
75
|
end
|
@@ -1,54 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Norma43
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
module Utils
|
5
|
+
class Contexts
|
6
|
+
def initialize(containers = nil)
|
7
|
+
Array(containers).compact.each do |container|
|
8
|
+
add container
|
9
|
+
end
|
6
10
|
end
|
7
|
-
end
|
8
|
-
|
9
|
-
def result
|
10
|
-
contexts.first
|
11
|
-
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def add container
|
18
|
-
contexts.push container
|
19
|
-
end
|
12
|
+
def result
|
13
|
+
contexts.first
|
14
|
+
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
def current
|
17
|
+
contexts.last
|
18
|
+
end
|
24
19
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end if contexts.any?
|
29
|
-
end
|
20
|
+
def add(container)
|
21
|
+
contexts.push container
|
22
|
+
end
|
30
23
|
|
31
|
-
|
32
|
-
|
24
|
+
def move_up
|
25
|
+
contexts.pop
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
28
|
+
def move_to(container_class)
|
29
|
+
until current.is_a?(container_class) || current.nil?
|
30
|
+
move_up
|
31
|
+
end if contexts.any?
|
36
32
|
end
|
37
33
|
|
38
|
-
|
34
|
+
def move_to_or_add_to_parent(container_class, parent_container_class)
|
35
|
+
return self if current.is_a?(container_class)
|
39
36
|
|
40
|
-
|
41
|
-
|
37
|
+
until current.kind_of?(parent_container_class)
|
38
|
+
move_up
|
39
|
+
end
|
42
40
|
|
43
|
-
|
41
|
+
entity = container_class.new
|
44
42
|
|
45
|
-
|
46
|
-
|
43
|
+
setter_name = StringHelpers.underscore container_class.name.split("::").last
|
44
|
+
current.public_send "#{setter_name}=", entity
|
47
45
|
|
48
|
-
|
46
|
+
add entity
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
private
|
52
|
+
def contexts
|
53
|
+
@contexts ||= []
|
54
|
+
end
|
52
55
|
end
|
53
56
|
end
|
54
57
|
end
|
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Norma43
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module Utils
|
5
|
+
module StringHelpers
|
6
|
+
def self.underscore(word)
|
7
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
8
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
9
|
+
word.tr!("-", "_")
|
10
|
+
word.downcase
|
11
|
+
end
|
8
12
|
end
|
9
13
|
end
|
10
14
|
end
|