total_in 0.5.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/README.md +21 -0
  9. data/Rakefile +1 -0
  10. data/lib/total_in/attribute_methods.rb +62 -0
  11. data/lib/total_in/contexts.rb +54 -0
  12. data/lib/total_in/document.rb +114 -0
  13. data/lib/total_in/line_handlers.rb +35 -0
  14. data/lib/total_in/line_parser.rb +42 -0
  15. data/lib/total_in/line_parsers.rb +80 -0
  16. data/lib/total_in/line_processors.rb +138 -0
  17. data/lib/total_in/parser.rb +48 -0
  18. data/lib/total_in/string_helpers.rb +10 -0
  19. data/lib/total_in/typecaster.rb +18 -0
  20. data/lib/total_in/version.rb +3 -0
  21. data/lib/total_in.rb +8 -0
  22. data/spec/fixtures/total_in_full.txt +61 -0
  23. data/spec/full_file_parse_spec.rb +501 -0
  24. data/spec/spec_helper.rb +20 -0
  25. data/spec/support/shared_examples_for_values_line_parsers.rb +15 -0
  26. data/spec/total_in/line_parsers/account_end_spec.rb +23 -0
  27. data/spec/total_in/line_parsers/account_start_spec.rb +24 -0
  28. data/spec/total_in/line_parsers/addresses_spec.rb +10 -0
  29. data/spec/total_in/line_parsers/deduction_start_spec.rb +50 -0
  30. data/spec/total_in/line_parsers/document_end_spec.rb +15 -0
  31. data/spec/total_in/line_parsers/document_start_spec.rb +32 -0
  32. data/spec/total_in/line_parsers/international_spec.rb +32 -0
  33. data/spec/total_in/line_parsers/locality_spec.rb +23 -0
  34. data/spec/total_in/line_parsers/messages_spec.rb +10 -0
  35. data/spec/total_in/line_parsers/names_spec.rb +11 -0
  36. data/spec/total_in/line_parsers/payment_start_spec.rb +45 -0
  37. data/spec/total_in/line_parsers/reference_numbers_spec.rb +11 -0
  38. data/spec/total_in/line_parsers/sender_account_spec.rb +23 -0
  39. data/spec/total_in/line_processors/account_end_spec.rb +35 -0
  40. data/spec/total_in/line_processors/account_start_spec.rb +50 -0
  41. data/spec/total_in/line_processors/addresses_spec.rb +44 -0
  42. data/spec/total_in/line_processors/deduction_start_spec.rb +54 -0
  43. data/spec/total_in/line_processors/document_end_spec.rb +32 -0
  44. data/spec/total_in/line_processors/document_start_spec.rb +29 -0
  45. data/spec/total_in/line_processors/international_spec.rb +46 -0
  46. data/spec/total_in/line_processors/locality_spec.rb +46 -0
  47. data/spec/total_in/line_processors/messages_spec.rb +25 -0
  48. data/spec/total_in/line_processors/names_spec.rb +45 -0
  49. data/spec/total_in/line_processors/payment_start_spec.rb +53 -0
  50. data/spec/total_in/line_processors/reference_numbers_spec.rb +24 -0
  51. data/spec/total_in/line_processors/sender_account_spec.rb +46 -0
  52. data/spec/total_in_spec.rb +13 -0
  53. data/total-in.gemspec +25 -0
  54. metadata +169 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 58ddedbe4d914ccb31dafda36de258488e2ad768
4
+ data.tar.gz: 1d9deb3b756e9c5f974c231d6992688c8b1f321d
5
+ SHA512:
6
+ metadata.gz: a4ed13121596580de19c2dc78abbd7ac60e4543986bb8132fdf0891a30335ed20600a7204b5c2f76dbeae0486a8970aff8c42a6e30853905879bccfadc4ec7b9
7
+ data.tar.gz: 57ac3813d93ac77374a1de9e7b48d2ec4a94b1e98a28cf37b417fcb87e8f98b7e34e84e8b43184e36120a34345a65949477b8cdc2d6ef5d6f0017208a8bb5a48
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ bin/
19
+ tmp
20
+ bin
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.4
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+ - 2.0.0-p598
5
+ script: rspec spec
6
+ addons:
7
+ code_climate:
8
+ repo_token:
9
+ secure: "MVPl+PV2Ewts0sumoKdjJxIX2A1jPJ80wXrFEEbeJMX3f3ST3PQtj2/XTRU4BBq1N2KzJL6ZUffpI9yKqSVsEHPhZan/ngixj5Z4pEEd59dWSJoC7ZxCWAq32qdoq2IAD2fRA7uvs34wo/D0BukTHqAFJXCfbpcoxskH/uzkLP8="
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "codeclimate-test-reporter", group: :test, require: nil
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Joel Junström
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Total-IN file parser
2
+
3
+ [![Code Climate](https://codeclimate.com/github/Oktavilla/total-in-ruby/badges/gpa.svg)](https://codeclimate.com/github/Oktavilla/total-in-ruby)
4
+ [![Test Coverage](https://codeclimate.com/github/Oktavilla/total-in-ruby/badges/coverage.svg)](https://codeclimate.com/github/Oktavilla/total-in-ruby)
5
+
6
+
7
+ ## Usage
8
+
9
+ ```ruby
10
+ require "total_in"
11
+
12
+ result = TotalIn.parse total_in_file_contents
13
+ ```
14
+
15
+ ## Contributing
16
+
17
+ 1. Fork it ( http://github.com/oktavilla/total-in-ruby/fork )
18
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
19
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
20
+ 4. Push to the branch (`git push origin my-new-feature`)
21
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,62 @@
1
+ module AttributeMethods
2
+ def self.included base
3
+ base.send :include, InstanceMethods
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def attribute name
9
+ register_attribute name
10
+ define_attribute_reader name
11
+ define_attribute_writer name
12
+ end
13
+
14
+ def register_attribute name
15
+ self.attribute_names.push name
16
+ end
17
+
18
+ def attribute_names
19
+ @attribute_names ||= []
20
+ end
21
+
22
+ def define_attribute_reader name
23
+ define_method name do
24
+ read_attribute name
25
+ end
26
+ end
27
+
28
+ def define_attribute_writer name
29
+ define_method "#{name}=" do |value|
30
+ write_attribute name, value
31
+ end
32
+ end
33
+ end
34
+
35
+ module InstanceMethods
36
+ def initialize attrs = {}
37
+ self.assign_attributes attrs
38
+ end
39
+
40
+ def assign_attributes attrs
41
+ attrs.each do |name, value|
42
+ write_attribute name, value
43
+ end
44
+ end
45
+
46
+ def attributes
47
+ @attributes ||= {}
48
+ end
49
+
50
+ def read_attribute name
51
+ attributes[name]
52
+ end
53
+
54
+ def write_attribute name, value
55
+ attributes[name] = value if attribute_names.include?(name)
56
+ end
57
+
58
+ def attribute_names
59
+ self.class.attribute_names
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ module TotalIn
2
+ class Contexts
3
+ def initialize containers = nil
4
+ Array(containers).compact.each do |container|
5
+ add container
6
+ end
7
+ end
8
+
9
+ def result
10
+ contexts.first
11
+ end
12
+
13
+ def current
14
+ contexts.last
15
+ end
16
+
17
+ def add container
18
+ contexts.push container
19
+ end
20
+
21
+ def move_up
22
+ contexts.pop
23
+ end
24
+
25
+ def move_to container_class
26
+ until current.is_a?(container_class)
27
+ move_up
28
+ end if contexts.any?
29
+ end
30
+
31
+ def move_to_or_add_to_parent container_class, parent_container_class
32
+ return self if current.is_a?(container_class)
33
+
34
+ until current.kind_of?(parent_container_class)
35
+ move_up
36
+ end
37
+
38
+ entity = container_class.new
39
+
40
+ setter_name = StringHelpers.underscore container_class.name.split("::").last
41
+ current.public_send "#{setter_name}=", entity
42
+
43
+ add entity
44
+
45
+ self
46
+ end
47
+
48
+ private
49
+
50
+ def contexts
51
+ @contexts ||= []
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,114 @@
1
+ require "total_in/string_helpers"
2
+ require "total_in/attribute_methods"
3
+
4
+ module TotalIn
5
+ class Document
6
+ include AttributeMethods
7
+ attribute :id
8
+ attribute :created_at
9
+ attribute :delivery_number
10
+ attribute :file_type
11
+ attribute :name
12
+ attribute :number_of_lines
13
+
14
+ def accounts
15
+ @accounts ||= []
16
+ end
17
+
18
+ class Account
19
+ include AttributeMethods
20
+ attribute :account_number
21
+ attribute :currency
22
+ attribute :date
23
+ attribute :number_of_transactions
24
+ attribute :amount
25
+ attribute :statement_reference
26
+
27
+ def payments
28
+ @payments ||= []
29
+ end
30
+
31
+ def deductions
32
+ @deductions ||= []
33
+ end
34
+ end
35
+
36
+ class Transaction
37
+ include AttributeMethods
38
+ def reference_numbers
39
+ @reference_numbers ||= []
40
+ end
41
+
42
+ attr_accessor :sender
43
+ attr_accessor :sender_account
44
+ attr_accessor :international
45
+
46
+ def message
47
+ messages.join "\n" if messages.any?
48
+ end
49
+
50
+ def messages
51
+ @messages ||= []
52
+ end
53
+
54
+ def add_message message
55
+ messages.push message
56
+ end
57
+
58
+ def international?
59
+ !!self.international
60
+ end
61
+ end
62
+
63
+ class Payment < Transaction
64
+ attribute :amount
65
+ attribute :serial_number
66
+ attribute :receiving_bankgiro_number
67
+ end
68
+
69
+ class Deduction < Transaction
70
+ attribute :amount
71
+ attribute :serial_number
72
+ attribute :receiving_bankgiro_number
73
+ attribute :code
74
+ end
75
+
76
+ class Sender
77
+ include AttributeMethods
78
+ attribute :name
79
+ attribute :address
80
+ attribute :postal_code
81
+ attribute :city
82
+ attribute :country_code
83
+
84
+ def self.add_to_contexts contexts
85
+ contexts.move_to_or_add_to_parent self, Transaction
86
+ end
87
+ end
88
+
89
+ class SenderAccount
90
+ include AttributeMethods
91
+ attribute :name
92
+ attribute :address
93
+ attribute :postal_code
94
+ attribute :city
95
+ attribute :country_code
96
+ attribute :account_number
97
+ attribute :origin_code
98
+ attribute :company_organization_number
99
+
100
+ def self.add_to_contexts contexts
101
+ contexts.move_to_or_add_to_parent self, Transaction
102
+ end
103
+ end
104
+
105
+ class International
106
+ include AttributeMethods
107
+ attribute :cost
108
+ attribute :cost_currency
109
+ attribute :amount
110
+ attribute :amount_currency
111
+ attribute :exchange_rate
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,35 @@
1
+ require "total_in/line_parsers"
2
+ require "total_in/line_processors"
3
+
4
+ module TotalIn
5
+ module LineHandlers
6
+ Handler = Struct.new :parser, :processor do
7
+ def handle line, contexts
8
+ line_parser = self.parser.new(line)
9
+
10
+ processor.call line_parser, contexts
11
+ end
12
+ end
13
+
14
+ def self.all
15
+ {
16
+ "00" => Handler.new(LineParsers::DocumentStart, LineProcessors::DocumentStart),
17
+ "99" => Handler.new(LineParsers::DocumentEnd, LineProcessors::DocumentEnd),
18
+ "10" => Handler.new(LineParsers::AccountStart, LineProcessors::AccountStart),
19
+ "90" => Handler.new(LineParsers::AccountEnd, LineProcessors::AccountEnd),
20
+ "20" => Handler.new(LineParsers::PaymentStart, LineProcessors::PaymentStart),
21
+ "25" => Handler.new(LineParsers::DeductionStart, LineProcessors::DeductionStart),
22
+ "30" => Handler.new(LineParsers::ReferenceNumbers, LineProcessors::ReferenceNumbers),
23
+ "40" => Handler.new(LineParsers::Messages, LineProcessors::Messages),
24
+ "50" => Handler.new(LineParsers::Names, LineProcessors::Names.new(Document::Sender)),
25
+ "51" => Handler.new(LineParsers::Addresses, LineProcessors::Addresses.new(Document::Sender)),
26
+ "52" => Handler.new(LineParsers::Locality, LineProcessors::Locality.new(Document::Sender)),
27
+ "60" => Handler.new(LineParsers::SenderAccount, LineProcessors::SenderAccount),
28
+ "61" => Handler.new(LineParsers::Names, LineProcessors::Names.new(Document::SenderAccount)),
29
+ "62" => Handler.new(LineParsers::Addresses, LineProcessors::Addresses.new(Document::SenderAccount)),
30
+ "63" => Handler.new(LineParsers::Locality, LineProcessors::Locality.new(Document::SenderAccount)),
31
+ "70" => Handler.new(LineParsers::International, LineProcessors::International)
32
+ }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ require "total_in/typecaster"
2
+
3
+ module TotalIn
4
+ class LineParser
5
+ attr_reader :line
6
+ def initialize line
7
+ @line = line
8
+ end
9
+
10
+ def attributes
11
+ self.class.field_names.each_with_object({}) do |field, attrs|
12
+ attrs[field] = self.public_send(field)
13
+ end
14
+ end
15
+
16
+ def self.field name, range, type = :string
17
+ self.field_names.push name
18
+
19
+ define_method name do
20
+ if range.is_a?(Array)
21
+ range.map { |r| value_at_position(r, type) }.compact
22
+ else
23
+ value_at_position range, type
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def self.field_names
31
+ @field_names ||= []
32
+ end
33
+
34
+ def value_at_position range, type
35
+ typecast line[range].strip, type
36
+ end
37
+
38
+ def typecast value, type
39
+ Typecaster.cast value, type
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,80 @@
1
+ require "total_in/line_parser"
2
+
3
+ module TotalIn
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
+
13
+ class DocumentEnd < LineParser
14
+ field :number_of_lines, 2..16, :integer
15
+ end
16
+
17
+ class AccountStart < LineParser
18
+ field :account_number, 2..37
19
+ field :currency, 38..40
20
+ field :date, 41..48, :date
21
+ end
22
+
23
+ class AccountEnd < LineParser
24
+ field :number_of_transactions, 2..9, :integer
25
+ field :amount, 10..26, :integer
26
+ field :statement_reference, 27..37
27
+ end
28
+
29
+ class PaymentStart < LineParser
30
+ field :reference_number, 2..36
31
+ field :amount, 37..51, :integer
32
+ field :serial_number, 52..68, :integer
33
+ field :receiving_bankgiro_number, 69..76
34
+ end
35
+
36
+ class DeductionStart < LineParser
37
+ field :reference_number, 2..36
38
+ field :amount, 37..51, :integer
39
+ field :serial_number, 52..68, :integer
40
+ field :code, 69..69, :integer
41
+ field :receiving_bankgiro_number, 70..77
42
+ end
43
+
44
+ class Values < LineParser
45
+ field :values, [2..36, 37..71]
46
+ end
47
+
48
+ class ReferenceNumbers < Values
49
+ end
50
+
51
+ class Messages < Values
52
+ end
53
+
54
+ class Names < Values
55
+ end
56
+
57
+ class Addresses < Values
58
+ end
59
+
60
+ class Locality < LineParser
61
+ field :postal_code, 2..10
62
+ field :city, 11..45
63
+ field :country_code, 46..47
64
+ end
65
+
66
+ class SenderAccount < LineParser
67
+ field :account_number, 2..37
68
+ field :origin_code, 38..38, :integer
69
+ field :company_organization_number, 39..58
70
+ end
71
+
72
+ class International < LineParser
73
+ field :cost, 2..16, :integer
74
+ field :cost_currency, 17..19
75
+ field :amount, 38..52, :integer
76
+ field :amount_currency, 53..55
77
+ field :exchange_rate, 56..67, :integer
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,138 @@
1
+ require "total_in/document"
2
+
3
+ module TotalIn
4
+ module LineProcessors
5
+ DocumentStart = ->(line, contexts) {
6
+ document = Document.new line.attributes
7
+
8
+ contexts.add document
9
+
10
+ contexts
11
+ }
12
+
13
+ DocumentEnd = ->(line, contexts) {
14
+ contexts.move_to Document
15
+
16
+ contexts.current.number_of_lines = line.number_of_lines
17
+
18
+ contexts
19
+ }
20
+
21
+ AccountStart = ->(line, contexts) {
22
+ contexts.move_to Document
23
+
24
+ account = Document::Account.new line.attributes
25
+
26
+ contexts.current.accounts << account
27
+ contexts.add account
28
+
29
+ contexts
30
+ }
31
+
32
+ AccountEnd = ->(line, contexts) {
33
+ contexts.move_to Document::Account
34
+
35
+ contexts.current.assign_attributes line.attributes
36
+
37
+ contexts
38
+ }
39
+
40
+ PaymentStart = ->(line, contexts) {
41
+ contexts.move_to Document::Account
42
+
43
+ payment = Document::Payment.new line.attributes
44
+ payment.reference_numbers << line.reference_number if line.reference_number
45
+
46
+ contexts.current.payments << payment
47
+
48
+ contexts.add payment
49
+
50
+ contexts
51
+ }
52
+
53
+ DeductionStart = ->(line, contexts) {
54
+ contexts.move_to Document::Account
55
+
56
+ deduction = Document::Deduction.new line.attributes
57
+ deduction.reference_numbers << line.reference_number if line.reference_number
58
+
59
+ contexts.current.deductions << deduction
60
+ contexts.add deduction
61
+
62
+ contexts
63
+ }
64
+
65
+ ReferenceNumbers = ->(line, contexts) {
66
+ contexts.current.reference_numbers.concat line.values
67
+
68
+ contexts
69
+ }
70
+
71
+ Messages = ->(line, contexts) {
72
+ line.values.each do |message|
73
+ contexts.current.add_message message
74
+ end
75
+
76
+ contexts
77
+ }
78
+
79
+ class WithTargetClass
80
+ def initialize target_class
81
+ @target_class = target_class
82
+ end
83
+
84
+ attr_reader :target_class
85
+ end
86
+
87
+ class Names < WithTargetClass
88
+ def call line, contexts
89
+ contexts = target_class.add_to_contexts contexts
90
+
91
+ contexts.current.name = line.values.join " "
92
+
93
+ contexts
94
+ end
95
+ end
96
+
97
+
98
+ class Addresses < WithTargetClass
99
+ def call line, contexts
100
+ contexts = target_class.add_to_contexts contexts
101
+
102
+ contexts.current.address = line.values.join " "
103
+
104
+ contexts
105
+ end
106
+ end
107
+
108
+ class Locality < WithTargetClass
109
+ def call line, contexts
110
+ contexts = target_class.add_to_contexts contexts
111
+
112
+ contexts.current.assign_attributes line.attributes
113
+
114
+ contexts
115
+ end
116
+ end
117
+
118
+ SenderAccount = ->(line, contexts) {
119
+ contexts = Document::SenderAccount.add_to_contexts contexts
120
+
121
+ contexts.current.assign_attributes line.attributes
122
+
123
+ contexts
124
+ }
125
+
126
+ International = ->(line, contexts) {
127
+ until contexts.current.kind_of?(Document::Transaction)
128
+ contexts.move_up
129
+ end
130
+
131
+ international = Document::International.new line.attributes
132
+
133
+ contexts.current.international = international
134
+
135
+ contexts
136
+ }
137
+ end
138
+ end
@@ -0,0 +1,48 @@
1
+ require "total_in/line_handlers"
2
+ require "total_in/contexts"
3
+
4
+ module TotalIn
5
+ class Parser
6
+ attr_reader :text
7
+
8
+ def initialize text
9
+ @text = text
10
+ end
11
+
12
+ def result
13
+ contexts = parse_lines text.each_line.to_a, Contexts.new
14
+
15
+ contexts.result
16
+ end
17
+
18
+ private
19
+
20
+ def parse_lines lines, contexts
21
+ if line = lines.shift
22
+ parse_lines lines, parse_line(line, contexts)
23
+ else
24
+ contexts
25
+ end
26
+ end
27
+
28
+ def parse_line line, contexts
29
+ handler = handler_for_line line
30
+
31
+ handler.handle line, contexts
32
+ end
33
+
34
+ def handler_for_line line
35
+ self.class.handlers.fetch line[0..1]
36
+ end
37
+
38
+ def self.handlers
39
+ @handlers ||= {}
40
+ end
41
+
42
+ def self.register_handlers handlers
43
+ @handlers = handlers
44
+ end
45
+
46
+ register_handlers LineHandlers.all
47
+ end
48
+ end
@@ -0,0 +1,10 @@
1
+ module TotalIn
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
8
+ end
9
+ end
10
+ end