ofx_kit 1.0.2 → 1.1.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -54
  3. data/lib/generators/ofx_kit/install_generator.rb +23 -0
  4. data/lib/generators/ofx_kit/templates/ofx_kit.rb +40 -0
  5. data/lib/ofx_kit/bank_statement.rb +8 -0
  6. data/lib/ofx_kit/configuration/core.rb +19 -80
  7. data/lib/ofx_kit/configuration/mapping_applicator.rb +1 -1
  8. data/lib/ofx_kit/configuration/section_proxy.rb +8 -2
  9. data/lib/ofx_kit/credit_card_account.rb +8 -0
  10. data/lib/ofx_kit/credit_card_statement.rb +8 -0
  11. data/lib/ofx_kit/errors/error.rb +2 -7
  12. data/lib/ofx_kit/errors/invalid_body.rb +8 -0
  13. data/lib/ofx_kit/errors/invalid_configuration.rb +8 -0
  14. data/lib/ofx_kit/errors/invalid_header.rb +8 -0
  15. data/lib/ofx_kit/errors/multiple_statements.rb +9 -0
  16. data/lib/ofx_kit/errors/parse.rb +8 -0
  17. data/lib/ofx_kit/errors/unsupported_encoding.rb +8 -0
  18. data/lib/ofx_kit/errors/unsupported_version.rb +15 -0
  19. data/lib/ofx_kit/errors.rb +10 -0
  20. data/lib/ofx_kit/parser.rb +4 -4
  21. data/lib/ofx_kit/tokenizer/ofx1.rb +2 -2
  22. data/lib/ofx_kit/tokenizer/ofx2.rb +1 -1
  23. data/lib/ofx_kit/version.rb +1 -1
  24. data/lib/ofx_kit.rb +4 -11
  25. metadata +12 -24
  26. data/lib/generators/ofx_kit/eject_generator.rb +0 -27
  27. data/lib/generators/ofx_kit/templates/ofx_mappings.yml +0 -33
  28. data/lib/ofx_kit/errors/configuration_error.rb +0 -8
  29. data/lib/ofx_kit/errors/encoding_error.rb +0 -8
  30. data/lib/ofx_kit/errors/invalid_body_error.rb +0 -8
  31. data/lib/ofx_kit/errors/invalid_header_error.rb +0 -8
  32. data/lib/ofx_kit/errors/multiple_statements_error.rb +0 -8
  33. data/lib/ofx_kit/errors/parse_error.rb +0 -8
  34. data/lib/ofx_kit/errors/unsupported_version_error.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b023f7e1d7c5381113d3743b0aba99ab79a0a297081df3d5943f02d55a9390b
4
- data.tar.gz: '0898db82c3556619deab40d7caad84b72e518db65e2e1933422387f313fe4ae3'
3
+ metadata.gz: 131031f6d262d168634bd74678d9c7c8aa2820a1ef87c6cd1077b6d06106404d
4
+ data.tar.gz: b5018a6676ec15d0f28f5640d1fdc5048efacc92f350be689ee53264e230027a
5
5
  SHA512:
6
- metadata.gz: ff60df963fdd5084ddfc5a2ee7a66633f2f6d9f55fa7d50416aeac92393c14617ed6978c87e4bbe9c3efb1a071abf487a3790dbdc71e29bcf460fb2b425e0efd
7
- data.tar.gz: 34995498f62d7cdbedb8e1d395d81568af47876edfe4c51890f862a05b898226ee9d27cd967003dae7fb67d060cf99d5d56958ee12c948e188afde57198592cf
6
+ metadata.gz: 3e548553b82237b1c56e193dc85f3bb5463abdf2d0082074b1731cb73dfdf435c1677e410aa9ad1a1e8a1bc37d68fe0d128c633948733e6d7fd72e3400edb97d
7
+ data.tar.gz: c37a45193aee74dedb292442965de84e79223eb672496b29cd079f09bfbdaadafefadd71d9ffbc116b089cf28c1b2706b6183b29fef746f3ca862cc1f322a255
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # OFX Kit
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/ofx_kit.svg)](https://badge.fury.io/rb/ofx_kit) [![Downloads](https://img.shields.io/gem/dt/ofx_kit?label=Downloads)](https://rubygems.org/gems/ofx_kit)
3
+ [![GitHub Repo](https://img.shields.io/badge/OFX%20Kit-blue?label=github&logo=github)](https://github.com/lucasgeron/ofx_kit)
4
+ [![Gem Version](https://img.shields.io/gem/v/ofx_kit?logo=rubygems&logoColor=%23e9573f&label=rubygems)](https://rubygems.org/gems/ofx_kit)
5
+ [![Gem Total Downloads](https://img.shields.io/gem/dt/ofx_kit?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMiAzdjEwbDQtNGgtM1YzaC0ydjZIOGw0IDRWM3pNNSAyMGgxNHYtMkg1djJ6IiBmaWxsPSIjZmZmIi8%2BPC9zdmc%2B&logoColor=white)](https://rubygems.org/gems/ofx_kit)
4
6
 
5
7
 
6
8
  A Ruby gem for parsing OFX (Open Financial Exchange) files. Supports OFX 1.x (SGML) and OFX 2.x (XML), bank statements and credit card statements, with a fluent API and configurable field mappings.
@@ -10,7 +12,7 @@ A Ruby gem for parsing OFX (Open Financial Exchange) files. Supports OFX 1.x (SG
10
12
  Add to your Gemfile:
11
13
 
12
14
  ```ruby
13
- gem 'ofx_kit', '~> 1.0'
15
+ gem 'ofx_kit', '~> 1.0', '>= 1.0.2'
14
16
  ```
15
17
 
16
18
  ## Usage
@@ -126,18 +128,28 @@ ofx.balance # => OFX::MultipleStatementsError (use `balances`)
126
128
 
127
129
  ## Configuration
128
130
 
131
+ ### Rails setup
132
+
133
+ Generate a pre-filled initializer with all options commented out:
134
+
135
+ ```bash
136
+ rails generate ofx_kit:install
137
+ ```
138
+
139
+ This creates `config/initializers/ofx_kit.rb`. Uncomment and adjust whatever you need — everything else stays at its default.
140
+
129
141
  ### Field mappings
130
142
 
131
- Use `map` to add new attributes or rename built-in ones:
143
+ Use `map` inside an `OFX.configure` block to add custom attributes or rename built-in ones:
132
144
 
133
145
  ```ruby
134
146
  OFX.configure do |config|
135
- # New field: your bank emits a tag the gem doesn't know about by default
147
+ # Add a bank-specific tag the gem doesn't know about by default
136
148
  config.bank_account.map "AGENCIA", to: "branch_code"
137
149
  config.transaction.map "HISPAYEEMEMO", to: "extended_memo"
138
150
 
139
- # Rename a built-in field to a name that fits your domain
140
- config.transaction.map "FITID", to: "uid" # default is fit_id
151
+ # Rename a built-in field to match your domain
152
+ config.transaction.map "FITID", to: "uid" # default is fit_id
141
153
  config.transaction.map "NAME", to: "payee_name"
142
154
  end
143
155
 
@@ -149,64 +161,21 @@ ofx.transactions.first.uid # => "20240115001"
149
161
  ```
150
162
 
151
163
  > **Protected core fields** — `CURDEF`, `TRNAMT`, `DTPOSTED`, `DTUSER`, `BALAMT`, `DTASOF`
152
- > are used internally to build Money objects and parse dates. They cannot be renamed;
164
+ > are used internally to build Money objects and parse dates. They cannot be remapped;
153
165
  > attempting to do so raises `OFX::ConfigurationError`.
154
166
 
155
- ### Loading mappings from a YAML file
156
-
157
- For larger configurations or Rails apps, a YAML file is cleaner than inline `map` calls.
158
-
159
- **Rails** — eject the template with:
160
-
161
- ```bash
162
- rails generate ofx:eject
163
- ```
164
-
165
- This creates `config/initializers/ofx_mappings.yml`, which the gem detects and loads
166
- automatically on boot — no `OFX.configure` call needed.
167
-
168
- **Standalone** — point `load_mappings` at any YAML file:
169
-
170
- ```ruby
171
- OFX.configure do |config|
172
- config.load_mappings("config/ofx_mappings.yml")
173
- end
174
- ```
175
-
176
- The file must have a `FIELDS:` top-level key:
177
-
178
- ```yaml
179
- FIELDS:
180
- STMTTRN:
181
- HISPAYEEMEMO: extended_memo # → transaction.extended_memo (new field)
182
- FITID: uid # → transaction.uid (default was fit_id)
183
- BANKACCTFROM:
184
- AGENCIA: branch_code # → account.branch_code (new field)
185
- ```
186
-
187
167
  ### Silencing warnings
188
168
 
189
- `transactions` and `balances` aggregate across all statements in a multi-statement file and emit a warning. To silence them:
169
+ `transactions` and `balances` emit a warning when aggregating across multiple statements in the same file. To silence them:
190
170
 
191
171
  ```ruby
192
- OFX.config.multi_statement_warnings = false
193
- ```
194
-
195
- ### Currency
196
-
197
- The OFX specification requires `CURDEF` in every statement (`STMTRS` / `CCSTMTRS`). If the tag is absent, the gem raises `OFX::Errors::InvalidBodyError` rather than silently assuming a currency.
198
-
199
- ### Rails
200
-
201
- **Behavioral options** — create a standard initializer:
202
-
203
- ```ruby
204
- # config/initializers/ofx.rb
205
172
  OFX.configure do |config|
206
- config.multi_statement_warnings = false # silence aggregation warnings
173
+ config.multi_statement_warnings = false
207
174
  end
208
175
  ```
209
176
 
177
+ This option is already included (commented out) in the initializer generated by `ofx_kit:install`.
178
+
210
179
  ## Contributing
211
180
 
212
181
  1. Fork the repository and create a feature branch.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module OfxKit # :nodoc:
6
+ module Generators # :nodoc:
7
+ # Generates an OFX Kit initializer with all configuration options
8
+ # pre-written and commented, ready to uncomment and customize.
9
+ #
10
+ # === Example
11
+ #
12
+ # rails generate ofx_kit:install
13
+ class InstallGenerator < Rails::Generators::Base
14
+ source_root File.expand_path('templates', __dir__)
15
+
16
+ desc 'Creates an OFX Kit initializer at config/initializers/ofx_kit.rb'
17
+
18
+ def copy_initializer
19
+ copy_file 'ofx_kit.rb', 'config/initializers/ofx_kit.rb'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # config/initializers/ofx_kit.rb
2
+
3
+ OFX.configure do |config|
4
+ # ---------------------------------------------------------------------------
5
+ # Field Mappings
6
+ # ---------------------------------------------------------------------------
7
+ # Map OFX XML tags to Ruby attribute names on domain objects.
8
+ # Uncomment any line to override its default, or add new lines for custom tags.
9
+ #
10
+ # Protected core fields cannot be remapped (raises OFX::Error::InvalidConfiguration):
11
+ # CURDEF, TRNAMT, DTPOSTED, DTUSER, BALAMT, DTASOF
12
+ #
13
+ # Default transaction fields (STMTTRN):
14
+ # config.transaction.map "FITID", to: "fit_id"
15
+ # config.transaction.map "TRNTYPE", to: "type"
16
+ # config.transaction.map "NAME", to: "name"
17
+ # config.transaction.map "MEMO", to: "memo"
18
+ # config.transaction.map "PAYEE", to: "payee"
19
+ # config.transaction.map "CHECKNUM", to: "check_number"
20
+ # config.transaction.map "REFNUM", to: "ref_number"
21
+ # config.transaction.map "SIC", to: "sic"
22
+ # config.transaction.map "HISPAYEEMEMO", to: "extended_memo" # custom field example
23
+ #
24
+ # Default bank account fields (BANKACCTFROM):
25
+ # config.bank_account.map "BANKID", to: "bank_id"
26
+ # config.bank_account.map "ACCTID", to: "account_id"
27
+ # config.bank_account.map "ACCTTYPE", to: "account_type"
28
+ # config.bank_account.map "BRANCHID", to: "branch_id"
29
+ # config.bank_account.map "AGENCIA", to: "branch_code" # custom field example
30
+ #
31
+ # Default credit card account fields (CCACCTFROM):
32
+ # config.credit_card_account.map "ACCTID", to: "account_id"
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Behavioral Options
36
+ # ---------------------------------------------------------------------------
37
+ # `transactions` and `balances` emit a warning when aggregating across
38
+ # multiple statements in the same file. Silence them:
39
+ # config.multi_statement_warnings = false
40
+ end
@@ -3,6 +3,14 @@
3
3
  module OFX
4
4
  # Represents a complete bank statement parsed from an OFX file,
5
5
  # aggregating the account, its transactions, and the closing balance.
6
+ #
7
+ # === Example
8
+ #
9
+ # stmt = OFX.new("bank.ofx").statements.first
10
+ # stmt.account #=> #<OFX::BankAccount ...>
11
+ # stmt.balance #=> #<OFX::Balance ...>
12
+ # stmt.transactions #=> #<OFX::TransactionCollection ...>
13
+ # stmt.bank_statement? #=> true
6
14
  class BankStatement < Base::Statement
7
15
  # Always +true+.
8
16
  def bank_statement? = true
@@ -6,23 +6,20 @@ module OFX
6
6
  ##
7
7
  # Manages XML-to-Ruby field mappings used during OFX document parsing.
8
8
  #
9
- # Mappings are split into two layers:
10
- # - *Core* (+core_mappings.yml+): OFX-standard fields whose Ruby attribute names are
11
- # referenced by name inside Base::Builder. These cannot be overridden.
12
- # - *User* (+field_mappings.yml+): convenience mappings that can be added to or replaced
13
- # at runtime via #load_mappings or the OFX.configure block.
9
+ # Mappings are resolved in three layers (last wins):
10
+ # - *Core* (+core_mappings.yml+): OFX-standard fields referenced by name inside
11
+ # Base::Builder. These cannot be overridden.
12
+ # - *Default* (+field_mappings.yml+): built-in convenience mappings (e.g. FITID fit_id).
13
+ # They can be renamed via the OFX.configure block.
14
+ # - *User*: explicit mappings added at runtime via the OFX.configure block.
14
15
  class Configuration
15
16
  ##
16
17
  # Absolute path to the built-in core OFX field mappings (read-only).
17
18
  CORE_MAPPINGS_PATH = File.join(__dir__, '..', 'mappings', 'core_mappings.yml')
18
- ##
19
- # Absolute path to the built-in user-layer field mappings.
20
- MAPPINGS_PATH = File.join(__dir__, '..', 'mappings', 'field_mappings.yml')
21
19
 
22
20
  ##
23
- # Conventional path for user mappings in a Rails application.
24
- # Auto-loaded on boot when present. Ejected via +rails generate ofx_kit:eject+.
25
- RAILS_MAPPINGS_PATH = 'config/initializers/ofx_mappings.yml'
21
+ # Absolute path to the built-in default field mappings.
22
+ MAPPINGS_PATH = File.join(__dir__, '..', 'mappings', 'field_mappings.yml')
26
23
 
27
24
  ##
28
25
  # Controls whether a warning is emitted when OFX::Parser#transactions or
@@ -37,21 +34,19 @@ module OFX
37
34
  end
38
35
 
39
36
  ##
40
- # Creates a new Configuration instance.
41
- # +auto_load_path+ is the path to a YAML mappings file loaded automatically on
42
- # initialization. Defaults to RAILS_MAPPINGS_PATH expanded from the working directory.
43
- def initialize(auto_load_path: File.expand_path(RAILS_MAPPINGS_PATH))
37
+ # Creates a new Configuration instance with the built-in field mappings loaded.
38
+ def initialize
44
39
  @multi_statement_warnings = true
45
40
 
46
41
  core = YAML.safe_load_file(CORE_MAPPINGS_PATH)
47
- @sections = core.fetch('SECTIONS', {})
48
- @core_fields = core.fetch('FIELDS', {})
42
+ @sections = core.fetch('SECTIONS', {})
43
+ @core_fields = core.fetch('FIELDS', {})
49
44
  @section_to_tag = @sections.invert
50
45
 
51
- user = YAML.safe_load_file(MAPPINGS_PATH)
52
- @user_fields = user.fetch('FIELDS', {})
46
+ defaults = YAML.safe_load_file(MAPPINGS_PATH)
47
+ @default_fields = defaults.fetch('FIELDS', {})
53
48
 
54
- load_mappings(auto_load_path) if File.exist?(auto_load_path)
49
+ @user_fields = {}
55
50
  end
56
51
 
57
52
  ##
@@ -88,70 +83,14 @@ module OFX
88
83
  ##
89
84
  # Returns the merged Hash of XML tag to Ruby attribute mappings for the given
90
85
  # +section_name+ (String or Symbol).
91
- # Core mappings take precedence; user mappings extend them.
86
+ # Resolution order: core default → user (user wins).
92
87
  def xml_mappings_for(section_name)
93
88
  tag = xml_tag_for(section_name)
94
89
  return {} unless tag
95
90
 
96
- (@core_fields[tag] || {}).merge(@user_fields[tag] || {})
97
- end
98
-
99
- ##
100
- # Merges additional field mappings from a YAML file at +path+ (String)
101
- # into the user-layer configuration.
102
- # The file must have a top-level +FIELDS+ key. Core OFX fields cannot be overridden.
103
- #
104
- # Raises Errors::ConfigurationError if the file is missing, malformed, references
105
- # unknown sections, or attempts to override a core field mapping.
106
- #
107
- # === Example: Load a custom mappings file
108
- #
109
- # OFX.configure do |config|
110
- # config.load_mappings 'config/my_ofx_mappings.yml'
111
- # end
112
- #
113
- # === Example: Expected YAML format
114
- #
115
- # # config/my_ofx_mappings.yml
116
- # FIELDS:
117
- # STMTTRN:
118
- # MYFIELD: my_attribute
119
- def load_mappings(path)
120
- raise Errors::ConfigurationError, "Mappings file not found: #{path}" unless File.exist?(path)
121
-
122
- raw = YAML.safe_load_file(path)
123
- raise Errors::ConfigurationError, 'Invalid mappings file: expected a Hash' unless raw.is_a?(Hash)
124
-
125
- fields = raw.fetch('FIELDS') do
126
- raise Errors::ConfigurationError, "Invalid mappings file: missing top-level 'FIELDS' key"
127
- end
128
-
129
- fields.each { |tag, mappings| merge_user_section(tag, mappings) }
130
- end
131
-
132
- private
133
-
134
- def merge_user_section(xml_tag, mappings)
135
- unless @sections.key?(xml_tag.to_s)
136
- raise Errors::ConfigurationError, "Unknown section '#{xml_tag}'. Valid sections: #{@sections.keys.join(', ')}"
137
- end
138
-
139
- unless mappings.is_a?(Hash)
140
- raise Errors::ConfigurationError, "Mapping value for '#{xml_tag}' must be a Hash, got #{mappings.class}"
141
- end
142
-
143
- mappings.each_key { |k| assert_not_core!(xml_tag, k) }
144
-
145
- @user_fields[xml_tag.to_s] ||= {}
146
- @user_fields[xml_tag.to_s].merge!(mappings)
147
- end
148
-
149
- def assert_not_core!(xml_tag, xml_key)
150
- core_attr = @core_fields.dig(xml_tag.to_s, xml_key.to_s)
151
- return unless core_attr
152
-
153
- raise Errors::ConfigurationError,
154
- "Cannot override core mapping '#{xml_tag}.#{xml_key}' (reserved as '#{core_attr}')"
91
+ (@core_fields[tag] || {})
92
+ .merge(@default_fields[tag] || {})
93
+ .merge(@user_fields[tag] || {})
155
94
  end
156
95
  end
157
96
  end
@@ -23,7 +23,7 @@ module OFX
23
23
  def currency_for(node, section)
24
24
  xml_tag = OFX.config.xml_mappings_for(section).key('currency')
25
25
  value = xml_tag && text_at(node, xml_tag)
26
- raise Errors::InvalidBodyError, 'Missing required CURDEF tag' if value.nil? || value.empty?
26
+ raise OFX::Error::InvalidBody, 'Missing required CURDEF tag' if value.nil? || value.empty?
27
27
 
28
28
  value
29
29
  end
@@ -16,7 +16,7 @@ module OFX
16
16
  # Maps +xml_key+ (the OFX XML element name, String) to a Ruby attribute name
17
17
  # via the +to+ keyword (String or Symbol) for this section.
18
18
  #
19
- # Raises Errors::ConfigurationError if +xml_key+ is a core-protected field.
19
+ # Raises ConfigurationError if +xml_key+ is a core-protected field or has already been mapped.
20
20
  #
21
21
  # === Example: Map a custom bank-specific field
22
22
  #
@@ -27,11 +27,17 @@ module OFX
27
27
  def map(xml_key, to:)
28
28
  core_attr = @core_fields.dig(@xml_tag.to_s, xml_key.to_s)
29
29
  if core_attr
30
- raise OFX::Errors::ConfigurationError,
30
+ raise OFX::Error::InvalidConfiguration,
31
31
  "Cannot override core mapping '#{@xml_tag}.#{xml_key}' (reserved as '#{core_attr}')"
32
32
  end
33
33
 
34
34
  @user_fields[@xml_tag] ||= {}
35
+
36
+ if @user_fields[@xml_tag].key?(xml_key)
37
+ raise OFX::Error::InvalidConfiguration,
38
+ "Duplicate mapping for '#{@xml_tag}.#{xml_key}' — already mapped to '#{@user_fields[@xml_tag][xml_key]}'"
39
+ end
40
+
35
41
  @user_fields[@xml_tag][xml_key] = to
36
42
  end
37
43
  end
@@ -2,6 +2,14 @@
2
2
 
3
3
  module OFX
4
4
  # Represents a credit card account parsed from an OFX statement.
5
+ #
6
+ # === Example
7
+ #
8
+ # account = OFX.new("credit_card.ofx").account
9
+ # account.account_id #=> "4111111111111111"
10
+ # account.currency #=> "USD"
11
+ # account.balance #=> #<OFX::Balance ...>
12
+ # account.transactions #=> #<OFX::TransactionCollection ...>
5
13
  class CreditCardAccount < Base::Account
6
14
  # Credit card account number (String).
7
15
  attr_accessor :account_id
@@ -3,6 +3,14 @@
3
3
  module OFX
4
4
  # Represents a complete credit card statement parsed from an OFX file,
5
5
  # aggregating the account, its transactions, and the closing balance.
6
+ #
7
+ # === Example
8
+ #
9
+ # stmt = OFX.new("credit_card.ofx").statements.first
10
+ # stmt.account #=> #<OFX::CreditCardAccount ...>
11
+ # stmt.balance #=> #<OFX::Balance ...>
12
+ # stmt.transactions #=> #<OFX::TransactionCollection ...>
13
+ # stmt.credit_card_statement? #=> true
6
14
  class CreditCardStatement < Base::Statement
7
15
  # Always +true+.
8
16
  def credit_card_statement? = true
@@ -1,11 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OFX
4
- # Namespace for all gem-specific exception classes.
5
- # All errors inherit from Errors::Error, so callers can rescue the entire hierarchy
6
- # with <tt>rescue OFX::Errors::Error</tt>.
7
- module Errors
8
- # Base error class for all OFX-related exceptions.
9
- class Error < StandardError; end
10
- end
4
+ # Base class for all OFX Kit exceptions. Rescue from this to catch any gem error.
5
+ class Error < StandardError; end
11
6
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when the file body is malformed or missing required fields (e.g. CURDEF).
6
+ class InvalidBody < Parse; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when +OFX.configure+ receives an invalid argument.
6
+ class InvalidConfiguration < Error; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when the file header is malformed or missing required fields.
6
+ class InvalidHeader < Parse; end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when a singular helper (e.g. +#account+, +#balance+) is called
6
+ # on a file that contains multiple statements. Use +#accounts+ or +#balances+ instead.
7
+ class MultipleStatements < Error; end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when the OFX header or body cannot be parsed.
6
+ class Parse < Error; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when the file encoding cannot be handled.
6
+ class UnsupportedEncoding < Error; end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ class Error
5
+ # Raised when the OFX version declared in the header is not supported.
6
+ class UnsupportedVersion < Error
7
+ attr_reader :version
8
+
9
+ def initialize(version)
10
+ @version = version
11
+ super("Unsupported OFX version: #{version}")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors/error'
4
+ require_relative 'errors/parse'
5
+ require_relative 'errors/invalid_header'
6
+ require_relative 'errors/invalid_body'
7
+ require_relative 'errors/unsupported_version'
8
+ require_relative 'errors/unsupported_encoding'
9
+ require_relative 'errors/invalid_configuration'
10
+ require_relative 'errors/multiple_statements'
@@ -47,7 +47,7 @@ module OFX
47
47
  ##
48
48
  # Returns the account for files containing a single statement
49
49
  # (BankAccount, CreditCardAccount, or +nil+).
50
- # Raises Errors::MultipleStatementsError if the file contains more than one statement.
50
+ # Raises Error::MultipleStatements if the file contains more than one statement.
51
51
  #
52
52
  # === Example
53
53
  #
@@ -57,7 +57,7 @@ module OFX
57
57
  # ofx.account.bank_id #=> "021000021" # BankAccount only
58
58
  def account
59
59
  if statements.length > 1
60
- raise Errors::MultipleStatementsError, 'File contains multiple statements. Use `accounts` to get all accounts.'
60
+ raise Error::MultipleStatements, 'File contains multiple statements. Use `accounts` to get all accounts.'
61
61
  end
62
62
 
63
63
  statements.first&.account
@@ -98,7 +98,7 @@ module OFX
98
98
 
99
99
  ##
100
100
  # Returns the balance for files containing a single statement (Balance or +nil+).
101
- # Raises Errors::MultipleStatementsError if the file contains more than one statement.
101
+ # Raises Error::MultipleStatements if the file contains more than one statement.
102
102
  #
103
103
  # === Example
104
104
  #
@@ -108,7 +108,7 @@ module OFX
108
108
  # ofx.balance.posted_at #=> 2024-01-31 00:00:00 +0000
109
109
  def balance
110
110
  if statements.length > 1
111
- raise Errors::MultipleStatementsError, 'File contains multiple statements. Use `balances` to get all balances.'
111
+ raise Error::MultipleStatements, 'File contains multiple statements. Use `balances` to get all balances.'
112
112
  end
113
113
 
114
114
  statements.first&.balance
@@ -14,7 +14,7 @@ module OFX
14
14
  content = convert_to_utf8(@content)
15
15
  parts = content.split(/<OFX>/i, 2)
16
16
 
17
- raise Errors::InvalidHeaderError, 'Missing <OFX> tag in OFX file' if parts.size < 2
17
+ raise OFX::Error::InvalidHeader, 'Missing <OFX> tag in OFX file' if parts.size < 2
18
18
 
19
19
  @headers = parse_headers(parts[0])
20
20
  @body = parse_body("<OFX>#{parts[1]}")
@@ -35,7 +35,7 @@ module OFX
35
35
  def parse_body(raw)
36
36
  normalized = normalize_sgml(raw)
37
37
  doc = Nokogiri::XML(normalized, &:nonet)
38
- raise Errors::InvalidBodyError, "OFX body could not be parsed: #{doc.errors.first}" if doc.errors.any?
38
+ raise OFX::Error::InvalidBody, "OFX body could not be parsed: #{doc.errors.first}" if doc.errors.any?
39
39
 
40
40
  doc
41
41
  end
@@ -11,7 +11,7 @@ module OFX
11
11
  content = convert_to_utf8(@content)
12
12
  doc = Nokogiri::XML(content, &:nonet)
13
13
 
14
- raise Errors::InvalidBodyError, "OFX2 body could not be parsed: #{doc.errors.first}" if doc.errors.any?
14
+ raise OFX::Error::InvalidBody, "OFX2 body could not be parsed: #{doc.errors.first}" if doc.errors.any?
15
15
 
16
16
  @headers = parse_headers(doc)
17
17
  @body = doc
@@ -2,5 +2,5 @@
2
2
 
3
3
  module OFX
4
4
  # Current gem version (String).
5
- VERSION = '1.0.2'
5
+ VERSION = '1.1.0'
6
6
  end
data/lib/ofx_kit.rb CHANGED
@@ -8,14 +8,7 @@ require 'stringio'
8
8
  require 'time'
9
9
 
10
10
  require_relative 'ofx_kit/version'
11
- require_relative 'ofx_kit/errors/error'
12
- require_relative 'ofx_kit/errors/parse_error'
13
- require_relative 'ofx_kit/errors/invalid_header_error'
14
- require_relative 'ofx_kit/errors/invalid_body_error'
15
- require_relative 'ofx_kit/errors/unsupported_version_error'
16
- require_relative 'ofx_kit/errors/encoding_error'
17
- require_relative 'ofx_kit/errors/configuration_error'
18
- require_relative 'ofx_kit/errors/multiple_statements_error'
11
+ require_relative 'ofx_kit/errors'
19
12
  require_relative 'ofx_kit/configuration/core'
20
13
  require_relative 'ofx_kit/configuration/section_proxy'
21
14
  require_relative 'ofx_kit/configuration/date_parser'
@@ -78,13 +71,13 @@ module OFX
78
71
  #
79
72
  # :yields: config
80
73
  #
81
- # Raises Errors::ConfigurationError if the block raises any error.
74
+ # Raises OFX::Error::InvalidConfiguration if the block raises any error.
82
75
  def configure
83
76
  yield config
84
- rescue Errors::ConfigurationError
77
+ rescue Error::InvalidConfiguration
85
78
  raise
86
79
  rescue StandardError => e
87
- raise Errors::ConfigurationError, e.message
80
+ raise Error::InvalidConfiguration, e.message
88
81
  end
89
82
 
90
83
  ##
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ofx_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Geron
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '13.0'
68
- - !ruby/object:Gem::Dependency
69
- name: rdoc
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: 7.2.0
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: 7.2.0
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: rspec
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -116,8 +102,8 @@ extra_rdoc_files: []
116
102
  files:
117
103
  - LICENSE
118
104
  - README.md
119
- - lib/generators/ofx_kit/eject_generator.rb
120
- - lib/generators/ofx_kit/templates/ofx_mappings.yml
105
+ - lib/generators/ofx_kit/install_generator.rb
106
+ - lib/generators/ofx_kit/templates/ofx_kit.rb
121
107
  - lib/ofx_kit.rb
122
108
  - lib/ofx_kit/balance.rb
123
109
  - lib/ofx_kit/bank_account.rb
@@ -133,14 +119,15 @@ files:
133
119
  - lib/ofx_kit/configuration/section_proxy.rb
134
120
  - lib/ofx_kit/credit_card_account.rb
135
121
  - lib/ofx_kit/credit_card_statement.rb
136
- - lib/ofx_kit/errors/configuration_error.rb
137
- - lib/ofx_kit/errors/encoding_error.rb
122
+ - lib/ofx_kit/errors.rb
138
123
  - lib/ofx_kit/errors/error.rb
139
- - lib/ofx_kit/errors/invalid_body_error.rb
140
- - lib/ofx_kit/errors/invalid_header_error.rb
141
- - lib/ofx_kit/errors/multiple_statements_error.rb
142
- - lib/ofx_kit/errors/parse_error.rb
143
- - lib/ofx_kit/errors/unsupported_version_error.rb
124
+ - lib/ofx_kit/errors/invalid_body.rb
125
+ - lib/ofx_kit/errors/invalid_configuration.rb
126
+ - lib/ofx_kit/errors/invalid_header.rb
127
+ - lib/ofx_kit/errors/multiple_statements.rb
128
+ - lib/ofx_kit/errors/parse.rb
129
+ - lib/ofx_kit/errors/unsupported_encoding.rb
130
+ - lib/ofx_kit/errors/unsupported_version.rb
144
131
  - lib/ofx_kit/mappings/core_mappings.yml
145
132
  - lib/ofx_kit/mappings/field_mappings.yml
146
133
  - lib/ofx_kit/parser.rb
@@ -150,6 +137,7 @@ files:
150
137
  - lib/ofx_kit/transaction.rb
151
138
  - lib/ofx_kit/transaction_collection.rb
152
139
  - lib/ofx_kit/version.rb
140
+ homepage: https://github.com/lucasgeron/ofx_kit
153
141
  licenses:
154
142
  - MIT
155
143
  metadata:
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails/generators'
4
-
5
- module OFX
6
- # Namespace for Rails generator classes provided by the ofx_kit gem.
7
- module Generators
8
- # Ejects OFX field mappings into the Rails application so they can be customized.
9
- #
10
- # Creates +config/initializers/ofx_mappings.yml+ with the gem's default field
11
- # mappings. The file is auto-detected and loaded by the OFX gem on boot —
12
- # no initializer or +OFX.configure+ call is needed.
13
- #
14
- # === Example
15
- #
16
- # rails generate ofx_kit:eject
17
- class EjectGenerator < Rails::Generators::Base
18
- source_root File.expand_path('templates', __dir__)
19
-
20
- desc 'Ejects OFX field mappings into config/initializers/ofx_mappings.yml'
21
-
22
- def eject_mappings
23
- copy_file 'ofx_mappings.yml', 'config/initializers/ofx_mappings.yml'
24
- end
25
- end
26
- end
27
- end
@@ -1,33 +0,0 @@
1
- # config/initializers/ofx_mappings.yml
2
- #
3
- # Each value is the Ruby attribute name exposed on the domain object.
4
- # Example: changing FITID from "fit_id" to "uid" means
5
- # transaction.fit_id → transaction.uid
6
- #
7
- # Bank-specific XML tags not listed below can also be captured.
8
- # Simply add them to the relevant section:
9
- #
10
- # STMTTRN:
11
- # HISPAYEEMEMO: "extended_memo" # → transaction.extended_memo
12
- # BANKACCTFROM:
13
- # AGENCIA: "branch_code" # → account.branch_code
14
- #
15
- FIELDS:
16
- STMTTRN:
17
- FITID: "fit_id"
18
- TRNTYPE: "type"
19
- NAME: "name"
20
- MEMO: "memo"
21
- PAYEE: "payee"
22
- CHECKNUM: "check_number"
23
- REFNUM: "ref_number"
24
- SIC: "sic"
25
-
26
- BANKACCTFROM:
27
- BANKID: "bank_id"
28
- ACCTID: "account_id"
29
- ACCTTYPE: "account_type"
30
- BRANCHID: "branch_id"
31
-
32
- CCACCTFROM:
33
- ACCTID: "account_id"
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- # Raised when the library configuration is invalid or inconsistent.
6
- class ConfigurationError < Error; end
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- # Raised when a character encoding error occurs while reading the OFX file.
6
- class EncodingError < Error; end
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- # Raised when the OFX file body cannot be parsed into a valid document.
6
- class InvalidBodyError < ParseError; end
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- # Raised when the OFX file header is malformed or missing.
6
- class InvalidHeaderError < ParseError; end
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- # Raised when an operation requires a single statement but multiple were found.
6
- class MultipleStatementsError < Error; end
7
- end
8
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- # Raised when the OFX file cannot be parsed.
6
- class ParseError < Error; end
7
- end
8
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OFX
4
- module Errors
5
- ##
6
- # Raised when the OFX version declared in the file is not supported.
7
- class UnsupportedVersionError < Error
8
- ##
9
- # The unsupported version string found in the file (String).
10
- attr_reader :version
11
-
12
- ##
13
- # Creates a new error for the given unsupported +version+ string.
14
- def initialize(version)
15
- @version = version
16
- super("Unsupported OFX version: #{version}")
17
- end
18
- end
19
- end
20
- end