ofx_br 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +13 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +50 -0
  8. data/README.rdoc +51 -0
  9. data/Rakefile +7 -0
  10. data/lib/ofx_br/account.rb +11 -0
  11. data/lib/ofx_br/balance.rb +7 -0
  12. data/lib/ofx_br/errors.rb +3 -0
  13. data/lib/ofx_br/foundation.rb +9 -0
  14. data/lib/ofx_br/parser/ofx102.rb +201 -0
  15. data/lib/ofx_br/parser/ofx103.rb +7 -0
  16. data/lib/ofx_br/parser/ofx211.rb +40 -0
  17. data/lib/ofx_br/parser.rb +72 -0
  18. data/lib/ofx_br/sign_on.rb +8 -0
  19. data/lib/ofx_br/statement.rb +5 -0
  20. data/lib/ofx_br/status.rb +12 -0
  21. data/lib/ofx_br/transaction.rb +16 -0
  22. data/lib/ofx_br/version.rb +8 -0
  23. data/lib/ofx_br.rb +33 -0
  24. data/ofx_br.gemspec +32 -0
  25. data/spec/fixtures/avatar.gif +0 -0
  26. data/spec/fixtures/bb.ofx +700 -0
  27. data/spec/fixtures/bradesco.ofx +96 -0
  28. data/spec/fixtures/creditcard.ofx +79 -0
  29. data/spec/fixtures/dtsof_balance_issue.ofx +54 -0
  30. data/spec/fixtures/error.ofx +24 -0
  31. data/spec/fixtures/invalid_version.ofx +308 -0
  32. data/spec/fixtures/nd-amex-sample.ofx +12 -0
  33. data/spec/fixtures/sample.ofx +315 -0
  34. data/spec/fixtures/santander.ofx +91 -0
  35. data/spec/fixtures/utf8.ofx +308 -0
  36. data/spec/fixtures/v103.ofx +80 -0
  37. data/spec/fixtures/v211.ofx +85 -0
  38. data/spec/ofx_br/account_spec.rb +131 -0
  39. data/spec/ofx_br/ofx102_spec.rb +67 -0
  40. data/spec/ofx_br/ofx103_spec.rb +50 -0
  41. data/spec/ofx_br/ofx211_spec.rb +84 -0
  42. data/spec/ofx_br/ofx_parser_spec.rb +123 -0
  43. data/spec/ofx_br/ofx_spec.rb +21 -0
  44. data/spec/ofx_br/sign_on_spec.rb +27 -0
  45. data/spec/ofx_br/statement_spec.rb +131 -0
  46. data/spec/ofx_br/status_spec.rb +47 -0
  47. data/spec/ofx_br/transaction_spec.rb +201 -0
  48. data/spec/spec_helper.rb +9 -0
  49. metadata +174 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7762b34050e1cb1c3cd7191d88bdbff23902c771a4e76e50ec960ead46464bb7
4
+ data.tar.gz: 4fb5f842f6592c125a07007dc7e33aefb7ec6b72a37c0c9d270f374d3f16e424
5
+ SHA512:
6
+ metadata.gz: ec56cc287b9b1c4accc141a97ffbb7f10c649cb4a29ee4b5234ef77aee52d413ee65177b26d20d33a5c468610bf2a4734aff9cedcaee881efc12bbfb3dab800e
7
+ data.tar.gz: ebc5b4d3243f90de88678b0cd696e47fd9c01e160ad529946936b1276ca55f9d892e60b0b11122ab3a7e972dabe9c8234fba0c0b5641509defcc9aecf7d3ba73
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "08:00"
8
+ open-pull-requests-limit: 10
9
+ ignore:
10
+ - dependency-name: nokogiri
11
+ versions:
12
+ - 1.11.1
13
+ - 1.11.2
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ *.gem
3
+ pkg
4
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.7
4
+ - 2.6
5
+ - 2.5
6
+ - 2.4
7
+ - 2.3
8
+ - 2.2
9
+ - 2.1
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ofx (0.3.2)
5
+ nokogiri
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (11.1.1)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.4.4)
13
+ method_source (1.0.0)
14
+ mini_portile2 (2.5.3)
15
+ nokogiri (1.11.7)
16
+ mini_portile2 (~> 2.5.0)
17
+ racc (~> 1.4)
18
+ pry (0.13.0)
19
+ coderay (~> 1.1)
20
+ method_source (~> 1.0)
21
+ pry-byebug (3.9.0)
22
+ byebug (~> 11.0)
23
+ pry (~> 0.13.0)
24
+ racc (1.5.2)
25
+ rake (13.0.3)
26
+ rspec (3.10.0)
27
+ rspec-core (~> 3.10.0)
28
+ rspec-expectations (~> 3.10.0)
29
+ rspec-mocks (~> 3.10.0)
30
+ rspec-core (3.10.1)
31
+ rspec-support (~> 3.10.0)
32
+ rspec-expectations (3.10.1)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.10.0)
35
+ rspec-mocks (3.10.2)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.10.0)
38
+ rspec-support (3.10.2)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ ofx!
45
+ pry-byebug
46
+ rake
47
+ rspec (~> 3.10)
48
+
49
+ BUNDLED WITH
50
+ 1.16.4
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = OFX
2
+
3
+ {<img src="https://badge.fury.io/rb/ofx.png" alt="Gem Version" />}[http://badge.fury.io/rb/ofx]
4
+ {<img src="https://travis-ci.org/annacruz/ofx.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/annacruz/ofx]
5
+
6
+ A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports both OFX 1.0.2 and 2.1.1.
7
+
8
+ Works on both ruby 1.9 and 2.0.
9
+
10
+ == Usage
11
+
12
+ require "ofx"
13
+
14
+ OFX("file.ofx") do
15
+ p account
16
+ p account.balance
17
+ p account.transactions
18
+ end
19
+
20
+ Invalid files will raise an OFX::UnsupportedFileError.
21
+
22
+ == Creator
23
+
24
+ * Nando Vieira - http://simplesideias.com.br
25
+
26
+ == Maintainer
27
+
28
+ * Anna Cruz - http://anna-cruz.com
29
+
30
+ == License
31
+
32
+ (The MIT License)
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining
35
+ a copy of this software and associated documentation files (the
36
+ 'Software'), to deal in the Software without restriction, including
37
+ without limitation the rights to use, copy, modify, merge, publish,
38
+ distribute, sublicense, and/or sell copies of the Software, and to
39
+ permit persons to whom the Software is furnished to do so, subject to
40
+ the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be
43
+ included in all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
46
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
49
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
50
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
51
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
@@ -0,0 +1,11 @@
1
+ module OFX
2
+ class Account < Foundation
3
+ attr_accessor :balance
4
+ attr_accessor :bank_id
5
+ attr_accessor :currency
6
+ attr_accessor :id
7
+ attr_accessor :transactions
8
+ attr_accessor :type
9
+ attr_accessor :available_balance
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module OFX
2
+ class Balance < Foundation
3
+ attr_accessor :amount
4
+ attr_accessor :amount_in_pennies
5
+ attr_accessor :posted_at
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module OFX
2
+ class UnsupportedFileError < StandardError; end
3
+ end
@@ -0,0 +1,9 @@
1
+ module OFX
2
+ class Foundation
3
+ def initialize(attrs)
4
+ attrs.each do |key, value|
5
+ send("#{key}=", value)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,201 @@
1
+ module OFX
2
+ module Parser
3
+ class OFX102
4
+ VERSION = '1.0.2'
5
+
6
+ ACCOUNT_TYPES = {
7
+ 'CHECKING' => :checking,
8
+ 'SAVINGS' => :savings,
9
+ 'CREDITLINE' => :creditline,
10
+ 'MONEYMRKT' => :moneymrkt
11
+ }
12
+
13
+ TRANSACTION_TYPES = %w[
14
+ ATM CASH CHECK CREDIT DEBIT DEP DIRECTDEBIT DIRECTDEP DIV
15
+ FEE INT OTHER PAYMENT POS REPEATPMT SRVCHG XFER
16
+ ].each_with_object({}) { |tran_type, hash| hash[tran_type] = tran_type.downcase.to_sym; }
17
+
18
+ SEVERITY = {
19
+ 'INFO' => :info,
20
+ 'WARN' => :warn,
21
+ 'ERROR' => :error
22
+ }
23
+
24
+ attr_reader :headers, :body, :html, :date_start, :date_end
25
+
26
+ def initialize(options = {})
27
+ @headers = options[:headers]
28
+ @body = options[:body]
29
+ @html = Nokogiri::HTML.parse(body)
30
+
31
+ @date_start ||= build_date(@html.search('banktranlist > dtstart').inner_text)
32
+ @date_end ||= build_date(@html.search('banktranlist > dtend').inner_text)
33
+ end
34
+
35
+ def statements
36
+ @statements ||= html.search('stmttrnrs, ccstmttrnrs').collect { |node| build_statement(node) }
37
+ end
38
+
39
+ def accounts
40
+ @accounts ||= html.search('stmttrnrs, ccstmttrnrs').collect { |node| build_account(node) }
41
+ end
42
+
43
+ # DEPRECATED: kept for legacy support
44
+ def account
45
+ @account ||= build_account(html.search('stmttrnrs, ccstmttrnrs').first)
46
+ end
47
+
48
+ def sign_on
49
+ @sign_on ||= build_sign_on
50
+ end
51
+
52
+ def self.parse_headers(header_text)
53
+ # Change single CR's to LF's to avoid issues with some banks
54
+ header_text.gsub!(/\r(?!\n)/, "\n")
55
+
56
+ # Parse headers. When value is NONE, convert it to nil.
57
+ headers = header_text.to_enum(:each_line).each_with_object({}) do |line, memo|
58
+ _, key, value = *line.match(/^(.*?):(.*?)\s*(\r?\n)*$/)
59
+
60
+ unless key.nil?
61
+ memo[key] = value == 'NONE' ? nil : value
62
+ end
63
+ end
64
+
65
+ return headers unless headers.empty?
66
+ end
67
+
68
+ private
69
+
70
+ def build_statement(node)
71
+ stmrs_node = node.search('stmtrs, ccstmtrs')
72
+ account = build_account(node)
73
+ OFX::Statement.new(
74
+ currency: stmrs_node.search('curdef').inner_text,
75
+ account: account,
76
+ transactions: account.transactions,
77
+ balance: account.balance,
78
+ available_balance: account.available_balance
79
+ )
80
+ end
81
+
82
+ def build_account(node)
83
+ OFX::Account.new({
84
+ bank_id: node.search('bankacctfrom > bankid').inner_text,
85
+ id: node.search('bankacctfrom > acctid, ccacctfrom > acctid').inner_text,
86
+ type: ACCOUNT_TYPES[node.search('bankacctfrom > accttype').inner_text.to_s.upcase],
87
+ transactions: build_transactions(node),
88
+ balance: build_balance(node),
89
+ available_balance: build_available_balance(node),
90
+ currency: node.search('stmtrs > curdef, ccstmtrs > curdef').inner_text
91
+ })
92
+ end
93
+
94
+ def build_status(node)
95
+ OFX::Status.new({
96
+ code: node.search('code').inner_text.to_i,
97
+ severity: SEVERITY[node.search('severity').inner_text],
98
+ message: node.search('message').inner_text
99
+ })
100
+ end
101
+
102
+ def build_sign_on
103
+ OFX::SignOn.new({
104
+ language: html.search('signonmsgsrsv1 > sonrs > language').inner_text,
105
+ fi_id: html.search('signonmsgsrsv1 > sonrs > fi > fid').inner_text,
106
+ fi_name: html.search('signonmsgsrsv1 > sonrs > fi > org').inner_text,
107
+ status: build_status(html.search('signonmsgsrsv1 > sonrs > status'))
108
+ })
109
+ end
110
+
111
+ def build_transactions(node)
112
+ node.search('banktranlist > stmttrn').collect do |element|
113
+ build_transaction(element)
114
+ end
115
+ end
116
+
117
+ def build_transaction(element)
118
+ occurred_at = begin
119
+ build_date(element.search('dtuser').inner_text)
120
+ rescue StandardError
121
+ nil
122
+ end
123
+
124
+ OFX::Transaction.new({
125
+ amount: build_amount(element),
126
+ amount_in_pennies: (build_amount(element) * 100).to_i,
127
+ fit_id: element.search('fitid').inner_text,
128
+ memo: element.search('memo').inner_text,
129
+ name: element.search('name').inner_text,
130
+ payee: element.search('payee').inner_text,
131
+ check_number: element.search('checknum').inner_text,
132
+ ref_number: element.search('refnum').inner_text,
133
+ posted_at: build_date(element.search('dtposted').inner_text),
134
+ occurred_at: occurred_at,
135
+ type: build_type(element),
136
+ sic: element.search('sic').inner_text
137
+ })
138
+ end
139
+
140
+ def build_type(element)
141
+ TRANSACTION_TYPES[element.search('trntype').inner_text.to_s.upcase]
142
+ end
143
+
144
+ def build_amount(element)
145
+ to_decimal(element.search('trnamt').inner_text)
146
+ end
147
+
148
+ # Input format is `YYYYMMDDHHMMSS.XXX[gmt offset[:tz name]]`
149
+ def build_date(date)
150
+ tz_pattern = /(?:\[([+-]?\d{1,4}):\S{3}\])?\z/
151
+
152
+ # Timezone offset handling
153
+ date.sub!(tz_pattern, '')
154
+ offset = Regexp.last_match(1)
155
+
156
+ if offset
157
+ # Offset padding
158
+ _, hours, mins = *offset.match(/\A([+-]?\d{1,2})(\d{0,2})?\z/)
159
+ offset = format('%+03d%02d', hours.to_i, mins.to_i)
160
+ else
161
+ offset = '+0000'
162
+ end
163
+
164
+ date << " #{offset}"
165
+
166
+ Time.parse(date)
167
+ end
168
+
169
+ def build_balance(node)
170
+ amount = to_decimal(node.search('ledgerbal > balamt').inner_text)
171
+ posted_at = begin
172
+ build_date(node.search('ledgerbal > dtasof').inner_text)
173
+ rescue StandardError
174
+ nil
175
+ end
176
+
177
+ OFX::Balance.new({
178
+ amount: amount,
179
+ amount_in_pennies: (amount * 100).to_i,
180
+ posted_at: posted_at
181
+ })
182
+ end
183
+
184
+ def build_available_balance(node)
185
+ if node.search('availbal').size > 0
186
+ amount = to_decimal(node.search('availbal > balamt').inner_text)
187
+
188
+ OFX::Balance.new({
189
+ amount: amount,
190
+ amount_in_pennies: (amount * 100).to_i,
191
+ posted_at: build_date(node.search('availbal > dtasof').inner_text)
192
+ })
193
+ end
194
+ end
195
+
196
+ def to_decimal(amount)
197
+ BigDecimal(amount.to_s.gsub(',', '.'))
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,7 @@
1
+ module OFX
2
+ module Parser
3
+ class OFX103 < OFX102
4
+ VERSION = "1.0.3"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module OFX
2
+ module Parser
3
+ class OFX211 < OFX102
4
+ VERSION = "2.1.1"
5
+
6
+ def self.parse_headers(header_text)
7
+ doc = Nokogiri::XML(header_text)
8
+
9
+ # Nokogiri can't search for processing instructions, so we
10
+ # need to do this manually.
11
+ doc.children.each do |e|
12
+ if e.type == Nokogiri::XML::Node::PI_NODE && e.name == "OFX"
13
+ # Getting the attributes from the element doesn't seem to
14
+ # work either.
15
+ return extract_headers(e.text)
16
+ end
17
+ end
18
+
19
+ nil
20
+ end
21
+
22
+ private
23
+ def self.extract_headers(text)
24
+ headers = {}
25
+ text.split(/\s+/).each do |attr_text|
26
+ match = /(.+)="(.+)"/.match(attr_text)
27
+ next unless match
28
+ k, v = match[1], match[2]
29
+ headers[k] = v
30
+ end
31
+ headers
32
+ end
33
+
34
+ def self.strip_quotes(s)
35
+ return unless s
36
+ s.sub(/^"(.*)"$/, '\1')
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ module OFX
2
+ module Parser
3
+ class Base
4
+ attr_reader :headers
5
+ attr_reader :body
6
+ attr_reader :content
7
+ attr_reader :parser
8
+
9
+ def initialize(resource)
10
+ resource = open_resource(resource)
11
+ resource.rewind
12
+ begin
13
+ @content = convert_to_utf8(resource.read)
14
+ @headers, @body = prepare(content)
15
+ rescue
16
+ raise OFX::UnsupportedFileError
17
+ end
18
+
19
+ case headers["VERSION"]
20
+ when /102/ then
21
+ @parser = OFX102.new(:headers => headers, :body => body)
22
+ when /103/ then
23
+ @parser = OFX103.new(:headers => headers, :body => body)
24
+ when /200|202|211|220/ then
25
+ @parser = OFX211.new(:headers => headers, :body => body)
26
+ else
27
+ raise OFX::UnsupportedFileError
28
+ end
29
+ end
30
+
31
+ def open_resource(resource)
32
+ if resource.respond_to?(:read)
33
+ resource
34
+ else
35
+ open(resource)
36
+ end
37
+ rescue
38
+ StringIO.new(resource)
39
+ end
40
+
41
+ private
42
+ def prepare(content)
43
+ # split headers & body
44
+ header_text, body = content.dup.split(/<OFX>/, 2)
45
+
46
+ raise OFX::UnsupportedFileError unless body
47
+
48
+ # Header format is different between versions. Give each
49
+ # parser a chance to parse the headers.
50
+ headers = nil
51
+
52
+ OFX::Parser.constants.grep(/OFX/).each do |name|
53
+ headers = OFX::Parser.const_get(name).parse_headers(header_text)
54
+ break if headers
55
+ end
56
+
57
+ # Replace body tags to parse it with Nokogiri
58
+ body.gsub!(/>\s+</m, "><")
59
+ body.gsub!(/\s+</m, "<")
60
+ body.gsub!(/>\s+/m, ">")
61
+ body.gsub!(/<(\w+?)>([^<]+)/m, '<\1>\2</\1>')
62
+
63
+ [headers, body]
64
+ end
65
+
66
+ def convert_to_utf8(string)
67
+ return string if Kconv.isutf8(string)
68
+ string.encode("UTF-8", "ISO-8859-1")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,8 @@
1
+ module OFX
2
+ class SignOn < Foundation
3
+ attr_accessor :language
4
+ attr_accessor :fi_id
5
+ attr_accessor :fi_name
6
+ attr_accessor :status
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module OFX
2
+ class Statement < Foundation
3
+ attr_accessor :account, :available_balance, :balance, :currency, :transactions
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module OFX
2
+ # Error Reporting Aggregate
3
+ class Status < Foundation
4
+ attr_accessor :code # Error code
5
+ attr_accessor :severity # Severity of the error
6
+ attr_accessor :message # Textual explanation
7
+
8
+ def success?
9
+ code == 0
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module OFX
2
+ class Transaction < Foundation
3
+ attr_accessor :amount
4
+ attr_accessor :amount_in_pennies
5
+ attr_accessor :check_number
6
+ attr_accessor :fit_id
7
+ attr_accessor :memo
8
+ attr_accessor :name
9
+ attr_accessor :payee
10
+ attr_accessor :posted_at
11
+ attr_accessor :occurred_at
12
+ attr_accessor :ref_number
13
+ attr_accessor :type
14
+ attr_accessor :sic
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ module OFX
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ PATCH = 3
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ end
data/lib/ofx_br.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'bigdecimal'
4
+
5
+ require 'kconv'
6
+
7
+ require 'ofx_br/errors'
8
+ require 'ofx_br/parser'
9
+ require 'ofx_br/parser/ofx102'
10
+ require 'ofx_br/parser/ofx103'
11
+ require 'ofx_br/parser/ofx211'
12
+ require 'ofx_br/foundation'
13
+ require 'ofx_br/balance'
14
+ require 'ofx_br/account'
15
+ require 'ofx_br/sign_on'
16
+ require 'ofx_br/status'
17
+ require 'ofx_br/statement'
18
+ require 'ofx_br/transaction'
19
+ require 'ofx_br/version'
20
+
21
+ def OFX(resource, &block)
22
+ parser = OFX::Parser::Base.new(resource).parser
23
+
24
+ if block_given?
25
+ if block.arity == 1
26
+ yield parser
27
+ else
28
+ parser.instance_eval(&block)
29
+ end
30
+ end
31
+
32
+ parser
33
+ end
data/ofx_br.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ $:.push File.expand_path('lib', __dir__)
2
+ require 'ofx_br/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'ofx_br'
6
+ s.version = OFX::Version::STRING
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Nando Vieira', 'Anna Cruz']
9
+ s.email = ['fnando.vieira@gmail.com', 'anna.cruz@gmail.com']
10
+ s.homepage = 'http://rubygems.org/gems/ofx'
11
+ s.summary = 'A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports OFX 102, 200 and 211.'
12
+ s.description = <<~TXT
13
+ A simple OFX (Open Financial Exchange) parser built on top of Nokogiri.
14
+ Currently supports OFX 102, 200 and 211.
15
+ #{' '}
16
+ Usage:
17
+ #{' '}
18
+ OFX("sample.ofx") do |ofx|
19
+ p ofx
20
+ end
21
+ TXT
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
26
+ s.require_paths = ['lib']
27
+
28
+ s.add_dependency 'nokogiri'
29
+ s.add_development_dependency 'pry-byebug'
30
+ s.add_development_dependency 'rake'
31
+ s.add_development_dependency 'rspec', '~> 3.10'
32
+ end
Binary file