norma43_parser 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +13 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  4. data/.rubocop.yml +234 -0
  5. data/Gemfile +7 -1
  6. data/Gemfile.lock +73 -0
  7. data/README.md +1 -1
  8. data/Rakefile +2 -0
  9. data/lib/norma43.rb +9 -3
  10. data/lib/norma43/line_handlers.rb +2 -3
  11. data/lib/norma43/line_parsers/account_end.rb +18 -0
  12. data/lib/norma43/line_parsers/account_start.rb +18 -0
  13. data/lib/norma43/line_parsers/additional_currency.rb +12 -0
  14. data/lib/norma43/line_parsers/additional_item.rb +11 -0
  15. data/lib/norma43/line_parsers/document_end.rb +9 -0
  16. data/lib/norma43/line_parsers/document_start.rb +13 -0
  17. data/lib/norma43/line_parsers/file_format_validator.rb +15 -12
  18. data/lib/norma43/line_parsers/line_parser.rb +29 -28
  19. data/lib/norma43/line_parsers/transaction.rb +18 -0
  20. data/lib/norma43/line_processors.rb +2 -2
  21. data/lib/norma43/models.rb +5 -4
  22. data/lib/norma43/parser.rb +38 -42
  23. data/lib/norma43/utils/contexts.rb +40 -37
  24. data/lib/norma43/utils/string_helpers.rb +10 -6
  25. data/lib/norma43/utils/typecaster.rb +16 -12
  26. data/lib/norma43/version.rb +3 -1
  27. data/norma43_parser.gemspec +11 -9
  28. data/spec/example1_parse_spec.rb +12 -11
  29. data/spec/norma43/line_parsers/account_end_spec.rb +1 -1
  30. data/spec/norma43/line_parsers/account_start_spec.rb +1 -2
  31. data/spec/norma43/line_parsers/additional_currency_spec.rb +1 -1
  32. data/spec/norma43/line_parsers/additional_items_spec.rb +1 -1
  33. data/spec/norma43/line_parsers/document_end_spec.rb +1 -2
  34. data/spec/norma43/line_parsers/document_start_spec.rb +1 -1
  35. data/spec/norma43/line_parsers/transaction_spec.rb +1 -3
  36. data/spec/norma43/line_processors/account_end_spec.rb +6 -8
  37. data/spec/norma43/line_processors/account_start_spec.rb +8 -9
  38. data/spec/norma43/line_processors/additional_currency_spec.rb +6 -7
  39. data/spec/norma43/line_processors/additional_items_spec.rb +6 -7
  40. data/spec/norma43/line_processors/document_end_spec.rb +5 -6
  41. data/spec/norma43/line_processors/document_start_spec.rb +3 -4
  42. data/spec/norma43/line_processors/transaction_spec.rb +6 -7
  43. data/spec/norma43/parser_spec.rb +5 -4
  44. data/spec/norma43_spec.rb +2 -0
  45. data/spec/spec_helper.rb +1 -4
  46. data/spec/support/shared_examples_for_values_line_parsers.rb +2 -0
  47. metadata +41 -32
  48. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Norma43
4
+ module LineParsers
5
+ class AdditionalItem < LineParser
6
+ field :data_code, 2..3, :integer
7
+ field :item_1, 4..41
8
+ field :item_2, 42..79
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Norma43
4
+ module LineParsers
5
+ class DocumentEnd < LineParser
6
+ field :record_number, 20..25, :integer
7
+ end
8
+ end
9
+ 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
- require "norma43/line_parsers/line_parser"
1
+ # frozen_string_literal: true
2
+
2
3
  module Norma43
3
- class FileFormatValidator < LineParser
4
- field :record_type, 0..1, :raw
5
- field :file_type, 36..38
4
+ module LineParsers
5
+ class FileFormatValidator < LineParser
6
+ field :record_type, 0..1, :raw
7
+ field :file_type, 36..38
6
8
 
7
- def has_document?; file_type=="00" end
9
+ def has_document?; file_type == "00" end
8
10
 
9
- def valid?
10
- errors.empty?
11
- end
11
+ def valid?
12
+ errors.empty?
13
+ end
12
14
 
13
- def errors
14
- errors = []
15
- %w(11 00).include? record_type or errors << "Must start with 00 (was ”#{record_type}”)"
16
- errors
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
- require "norma43/utils/typecaster"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Norma43
4
- class LineParser
5
- attr_reader :line
6
- def initialize line
7
- @line = line
8
- end
4
+ module LineParsers
5
+ class LineParser
6
+ attr_reader :line
7
+ def initialize(line)
8
+ @line = line
9
+ end
9
10
 
10
- def attributes
11
- self.class.field_names.each_with_object({}) do |field, attrs|
12
- attrs[field] = self.public_send(field)
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
- def self.field name, range, type = :string
17
- self.field_names.push name
17
+ def self.field(name, range, type = :string)
18
+ self.field_names.push name
18
19
 
19
- define_method name do
20
- if range.is_a?(Array) # let multivalued attribute
21
- range.map { |r| value_at_position(r, type) }.compact
22
- else
23
- value_at_position range, type
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
- def self.field_names
31
- @field_names ||= []
32
- end
29
+ private
30
+ def self.field_names
31
+ @field_names ||= []
32
+ end
33
33
 
34
- def value_at_position range, type
35
- typecast line[range].to_s.strip, type
36
- end
34
+ def value_at_position(range, type)
35
+ typecast line[range].to_s.strip, type
36
+ end
37
37
 
38
- def typecast value, type
39
- Typecaster.cast value, type
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
- require "norma43/models"
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
@@ -1,9 +1,10 @@
1
- require "norma43/utils/string_helpers"
2
- require 'virtus'
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
@@ -1,25 +1,23 @@
1
- require "norma43/line_parsers/file_format_validator"
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 file
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
- def lines
33
- @lines ||= file.each_line
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
- def validate_file_format
39
- validator = FileFormatValidator.new first_line
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
- rescue StopIteration# because lines is an enumerator raises StopIteration on end
48
- self.lines.rewind # Ensure we do not bomb out when calling result multiple times
49
- contexts
50
- end
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
- # Look up a matching handler for the line and process it
53
- # The process method on a handler always returns a Contexts object
54
- def parse_line line, contexts
55
- line = line.encode Encoding::UTF_8 if encode_lines?
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
- handler = handler_for_line line
53
+ handler = handler_for_line line
58
54
 
59
- handler.process line, contexts
60
- end
55
+ handler.process line, contexts
56
+ end
61
57
 
62
- def handler_for_line line
63
- LineHandlers.mapping.fetch line[0..1]
64
- end
58
+ def handler_for_line(line)
59
+ LineHandlers.mapping.fetch line[0..1]
60
+ end
65
61
 
66
- def encode_lines?
67
- first_line.encoding != Encoding::UTF_8
68
- end
62
+ def encode_lines?
63
+ first_line.encoding != Encoding::UTF_8
64
+ end
69
65
 
70
- def first_line
71
- @first_line ||= begin
72
- line = self.lines.peek
73
- self.lines.rewind # peek seems to move the pointer when file is an actual File object
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
- line
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
- class Contexts
3
- def initialize containers = nil
4
- Array(containers).compact.each do |container|
5
- add container
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
- def current
14
- contexts.last
15
- end
16
-
17
- def add container
18
- contexts.push container
19
- end
12
+ def result
13
+ contexts.first
14
+ end
20
15
 
21
- def move_up
22
- contexts.pop
23
- end
16
+ def current
17
+ contexts.last
18
+ end
24
19
 
25
- def move_to container_class
26
- until current.is_a?(container_class) or current.nil?
27
- move_up
28
- end if contexts.any?
29
- end
20
+ def add(container)
21
+ contexts.push container
22
+ end
30
23
 
31
- def move_to_or_add_to_parent container_class, parent_container_class
32
- return self if current.is_a?(container_class)
24
+ def move_up
25
+ contexts.pop
26
+ end
33
27
 
34
- until current.kind_of?(parent_container_class)
35
- move_up
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
- entity = container_class.new
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
- setter_name = StringHelpers.underscore container_class.name.split("::").last
41
- current.public_send "#{setter_name}=", entity
37
+ until current.kind_of?(parent_container_class)
38
+ move_up
39
+ end
42
40
 
43
- add entity
41
+ entity = container_class.new
44
42
 
45
- self
46
- end
43
+ setter_name = StringHelpers.underscore container_class.name.split("::").last
44
+ current.public_send "#{setter_name}=", entity
47
45
 
48
- private
46
+ add entity
47
+
48
+ self
49
+ end
49
50
 
50
- def contexts
51
- @contexts ||= []
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 StringHelpers
3
- def self.underscore word
4
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
5
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
6
- word.tr!("-", "_")
7
- word.downcase
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