ofx_kit 0.1.0 → 1.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -81
  3. data/lib/generators/{ofx → ofx_kit}/eject_generator.rb +5 -3
  4. data/lib/ofx_kit/balance.rb +25 -0
  5. data/lib/ofx_kit/bank_account.rb +25 -0
  6. data/lib/ofx_kit/{domain/bank_statement.rb → bank_statement.rb} +1 -0
  7. data/lib/ofx_kit/base/account.rb +3 -0
  8. data/lib/ofx_kit/base/builder.rb +8 -4
  9. data/lib/ofx_kit/base/document.rb +16 -4
  10. data/lib/ofx_kit/base/entity.rb +15 -5
  11. data/lib/ofx_kit/base/statement.rb +19 -1
  12. data/lib/ofx_kit/configuration/core.rb +75 -28
  13. data/lib/ofx_kit/configuration/date_parser.rb +1 -1
  14. data/lib/ofx_kit/configuration/mapping_applicator.rb +5 -3
  15. data/lib/ofx_kit/configuration/section_proxy.rb +14 -5
  16. data/lib/ofx_kit/{domain/credit_card_account.rb → credit_card_account.rb} +1 -0
  17. data/lib/ofx_kit/{domain/credit_card_statement.rb → credit_card_statement.rb} +1 -0
  18. data/lib/ofx_kit/errors/configuration_error.rb +8 -0
  19. data/lib/ofx_kit/errors/encoding_error.rb +8 -0
  20. data/lib/ofx_kit/errors/error.rb +11 -0
  21. data/lib/ofx_kit/errors/invalid_body_error.rb +8 -0
  22. data/lib/ofx_kit/errors/invalid_header_error.rb +8 -0
  23. data/lib/ofx_kit/errors/multiple_statements_error.rb +8 -0
  24. data/lib/ofx_kit/errors/parse_error.rb +8 -0
  25. data/lib/ofx_kit/errors/unsupported_version_error.rb +20 -0
  26. data/lib/ofx_kit/parser.rb +81 -29
  27. data/lib/ofx_kit/tokenizer/base.rb +11 -6
  28. data/lib/ofx_kit/tokenizer/ofx1.rb +2 -2
  29. data/lib/ofx_kit/tokenizer/ofx2.rb +1 -1
  30. data/lib/ofx_kit/transaction.rb +45 -0
  31. data/lib/ofx_kit/transaction_collection.rb +126 -0
  32. data/lib/ofx_kit/version.rb +2 -1
  33. data/lib/ofx_kit.rb +43 -24
  34. metadata +35 -14
  35. data/lib/ofx_kit/configuration.rb +0 -6
  36. data/lib/ofx_kit/domain/balance.rb +0 -10
  37. data/lib/ofx_kit/domain/bank_account.rb +0 -8
  38. data/lib/ofx_kit/domain/transaction.rb +0 -13
  39. data/lib/ofx_kit/domain/transaction_collection.rb +0 -94
  40. data/lib/ofx_kit/errors.rb +0 -36
  41. /data/lib/generators/{ofx → ofx_kit}/templates/ofx_mappings.yml +0 -0
@@ -3,6 +3,7 @@
3
3
  module OFX
4
4
  # Represents a credit card account parsed from an OFX statement.
5
5
  class CreditCardAccount < Base::Account
6
+ # Credit card account number (String).
6
7
  attr_accessor :account_id
7
8
  end
8
9
  end
@@ -4,6 +4,7 @@ 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
6
  class CreditCardStatement < Base::Statement
7
+ # Always +true+.
7
8
  def credit_card_statement? = true
8
9
  end
9
10
  end
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
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
11
+ end
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,20 @@
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
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OFX
4
- # Implementation class for OFX parsing.
5
- # Prefer the top-level {OFX.new} entry point over instantiating this class directly.
6
- #
7
- # @api private
4
+ ##
5
+ # The object returned by OFX.new. Provides access to the parsed statements,
6
+ # accounts, transactions, and balances from an OFX file or IO object.
7
+ # Prefer the top-level OFX.new entry point over instantiating this class directly.
8
8
  class Parser
9
- # @param resource [String, IO] file path or IO object containing OFX data
10
- # @param block [Proc] optional block; receives the parser instance (arity 1)
11
- # or is evaluated in the parser's context (arity != 1)
9
+ ##
10
+ # Parses the given OFX +resource+ and builds the statement graph.
11
+ # +resource+ is a file path (String) or IO object containing OFX data.
12
+ # If a +block+ is given with arity 1, it receives the parser instance;
13
+ # otherwise it is evaluated in the parser's context.
12
14
  def initialize(resource, &block)
13
- @filename = extract_filename(resource)
14
- content = read_resource(resource)
15
- tokenizer = build_tokenizer(content).new(content)
16
- @document = Base::Document.new(headers: tokenizer.headers, body: tokenizer.body)
15
+ @filename = extract_filename(resource)
16
+ content = read_resource(resource)
17
+ tokenizer = build_tokenizer(content).new(content)
18
+ @document = Base::Document.new(headers: tokenizer.headers, body: tokenizer.body)
17
19
  @statements = Base::Builder.new(@document).statements
18
20
 
19
21
  return unless block_given?
@@ -21,34 +23,69 @@ module OFX
21
23
  block.arity == 1 ? block.call(self) : instance_eval(&block)
22
24
  end
23
25
 
24
- # @return [Array<BankStatement, CreditCardStatement>] all statements in the file
25
- # @return [String, nil] original file path, if a path string was provided
26
- attr_reader :statements, :filename
27
-
28
- # @return [Hash] parsed OFX header fields
26
+ ##
27
+ # All statements in the file (Array of BankStatement or CreditCardStatement).
28
+ #
29
+ # === Example: Iterate statements in a multi-account file
30
+ #
31
+ # ofx = OFX.new("multi.ofx")
32
+ # ofx.statements.each do |stmt|
33
+ # puts "#{stmt.account.account_id}: #{stmt.transactions.length} transactions"
34
+ # end
35
+ attr_reader :statements
36
+
37
+ ##
38
+ # Original file path, if a path string was provided (String or +nil+).
39
+ attr_reader :filename
40
+
41
+ ##
42
+ # Returns the parsed OFX header fields (Hash).
29
43
  def headers
30
44
  @document.headers
31
45
  end
32
46
 
33
- # Returns the account for files containing a single statement.
34
- # @return [Account, nil]
35
- # @raise [MultipleStatementsError] if the file contains more than one statement
47
+ ##
48
+ # Returns the account for files containing a single statement
49
+ # (BankAccount, CreditCardAccount, or +nil+).
50
+ # Raises Errors::MultipleStatementsError if the file contains more than one statement.
51
+ #
52
+ # === Example
53
+ #
54
+ # ofx = OFX.new("statement.ofx")
55
+ # ofx.account.account_id #=> "123456789"
56
+ # ofx.account.currency #=> "USD"
57
+ # ofx.account.bank_id #=> "021000021" # BankAccount only
36
58
  def account
37
59
  if statements.length > 1
38
- raise MultipleStatementsError, 'File contains multiple statements. Use `accounts` to get all accounts.'
60
+ raise Errors::MultipleStatementsError, 'File contains multiple statements. Use `accounts` to get all accounts.'
39
61
  end
40
62
 
41
63
  statements.first&.account
42
64
  end
43
65
 
44
- # @return [Array<Account>] all accounts across all statements
66
+ ##
67
+ # Returns all accounts across all statements
68
+ # (Array of BankAccount or CreditCardAccount).
69
+ #
70
+ # === Example: Multi-statement file
71
+ #
72
+ # ofx = OFX.new("multi.ofx")
73
+ # ofx.accounts.map(&:account_id) #=> ["123456", "789012"]
45
74
  def accounts
46
75
  statements.map(&:account)
47
76
  end
48
77
 
49
- # Returns all transactions aggregated across all statements.
78
+ ##
79
+ # Returns all transactions aggregated across all statements as a TransactionCollection.
50
80
  # Emits a warning when the file contains multiple statements.
51
- # @return [TransactionCollection]
81
+ #
82
+ # === Example
83
+ #
84
+ # ofx = OFX.new("statement.ofx")
85
+ # ofx.transactions.length #=> 42
86
+ # ofx.transactions.credits.length #=> 10
87
+ # ofx.transactions.total_debits.format #=> "-$1,234.56"
88
+ # ofx.transactions.net.format #=> "$500.00"
52
89
  def transactions
53
90
  if statements.length > 1 && OFX.config.multi_statement_warnings?
54
91
  warn "[OFX] `transactions` is aggregating across #{statements.length} statements. " \
@@ -59,18 +96,26 @@ module OFX
59
96
  TransactionCollection.new(statements.flat_map { |s| s.transactions.to_a })
60
97
  end
61
98
 
62
- # Returns the balance for files containing a single statement.
63
- # @return [Balance, nil]
64
- # @raise [MultipleStatementsError] if the file contains more than one statement
99
+ ##
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.
102
+ #
103
+ # === Example
104
+ #
105
+ # ofx = OFX.new("statement.ofx")
106
+ # ofx.balance.amount.format #=> "$2,500.00"
107
+ # ofx.balance.amount_cents #=> 250000
108
+ # ofx.balance.posted_at #=> 2024-01-31 00:00:00 +0000
65
109
  def balance
66
110
  if statements.length > 1
67
- raise MultipleStatementsError, 'File contains multiple statements. Use `balances` to get all balances.'
111
+ raise Errors::MultipleStatementsError, 'File contains multiple statements. Use `balances` to get all balances.'
68
112
  end
69
113
 
70
114
  statements.first&.balance
71
115
  end
72
116
 
73
- # @return [Array<Balance, nil>] balances for each statement
117
+ ##
118
+ # Returns balances for each statement (Array of Balance or +nil+).
74
119
  def balances
75
120
  if statements.length > 1 && OFX.config.multi_statement_warnings?
76
121
  warn "[OFX] `balances` is aggregating across #{statements.length} statements. " \
@@ -81,9 +126,16 @@ module OFX
81
126
  statements.map(&:balance)
82
127
  end
83
128
 
129
+ ##
84
130
  # Returns a structured summary of all statements, including transaction counts,
85
131
  # credit/debit totals (in cents), and closing balance.
86
- # @return [Hash]
132
+ #
133
+ # Returns a Hash with keys:
134
+ # - +:headers+ — compact OFX header fields
135
+ # - +:statements+ — hash keyed by account_id, each with:
136
+ # +:currency+, +:transactions+ ({count:, net_cents:}),
137
+ # +:credits+ ({count:, total_cents:}), +:debits+ ({count:, total_cents:}),
138
+ # +:balance_cents+
87
139
  def summary
88
140
  {
89
141
  headers: headers.compact,
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OFX
4
- module Tokenizer
4
+ module Tokenizer # :nodoc:
5
+ ##
5
6
  # Abstract base class for OFX tokenizers.
6
- # Subclasses must implement {#parse!} to populate +@headers+ and +@body+
7
+ # Subclasses must implement #parse! to populate +@headers+ and +@body+
7
8
  # from the raw file content.
8
9
  class Base
9
- # @return [Hash] parsed header key/value pairs
10
- # @return [Nokogiri::XML::Document] parsed XML body
11
- attr_reader :headers, :body
10
+ ##
11
+ # Parsed header key/value pairs (Hash).
12
+ attr_reader :headers
13
+ ##
14
+ # Parsed XML body (Nokogiri::XML::Document).
15
+ attr_reader :body
12
16
 
13
- # @param content [String] raw OFX file content
17
+ ##
18
+ # Creates a new tokenizer and immediately parses +content+ (raw OFX String).
14
19
  def initialize(content)
15
20
  @content = content.dup.force_encoding('UTF-8')
16
21
  parse!
@@ -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 InvalidHeaderError, 'Missing <OFX> tag in OFX file' if parts.size < 2
17
+ raise Errors::InvalidHeaderError, '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 InvalidBodyError, "OFX body could not be parsed: #{doc.errors.first}" if doc.errors.any?
38
+ raise Errors::InvalidBodyError, "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 InvalidBodyError, "OFX2 body could not be parsed: #{doc.errors.first}" if doc.errors.any?
14
+ raise Errors::InvalidBodyError, "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
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ # Represents a single financial transaction parsed from an OFX statement.
5
+ #
6
+ # === Example
7
+ #
8
+ # txn = OFX.new("statement.ofx").transactions.first
9
+ # txn.type #=> "DEBIT"
10
+ # txn.name #=> "AMAZON.COM"
11
+ # txn.amount #=> #<Money fractional:-5099 currency:USD>
12
+ # txn.posted_at #=> 2024-01-15 00:00:00 +0000
13
+ # txn.account #=> #<OFX::BankAccount ...>
14
+ # txn.statement #=> #<OFX::BankStatement ...>
15
+ class Transaction < Base::Entity
16
+ # Unique transaction identifier / FITID (String).
17
+ attr_accessor :fit_id
18
+ # Transaction type, e.g. "DEBIT", "CREDIT", "CHECK" (String).
19
+ attr_accessor :type
20
+ # Date the transaction was posted (Time or +nil+).
21
+ attr_accessor :posted_at
22
+ # Date the transaction actually occurred (Time or +nil+).
23
+ attr_accessor :occurred_at
24
+ # Transaction amount as a Money object (or +nil+).
25
+ attr_accessor :amount
26
+ # Transaction amount in the smallest currency unit, e.g. cents (Integer or +nil+).
27
+ attr_accessor :amount_cents
28
+ # Payee or description name (String or +nil+).
29
+ attr_accessor :name
30
+ # Memo or additional description (String or +nil+).
31
+ attr_accessor :memo
32
+ # Payee name from the PAYEE field, when present (String or +nil+).
33
+ attr_accessor :payee
34
+ # Check number, if applicable (String or +nil+).
35
+ attr_accessor :check_number
36
+ # Reference number (String or +nil+).
37
+ attr_accessor :ref_number
38
+ # Standard industry code / SIC (String or +nil+).
39
+ attr_accessor :sic
40
+
41
+ # The statement (BankStatement or CreditCardStatement) and account
42
+ # (BankAccount or CreditCardAccount) this transaction belongs to.
43
+ wired_by_builder :statement, :account
44
+ end
45
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OFX
4
+ ##
5
+ # An +Enumerable+ collection of Transaction objects parsed from an OFX statement.
6
+ # Provides convenience filters for credits and debits.
7
+ class TransactionCollection
8
+ include Enumerable
9
+
10
+ ##
11
+ # The statement (BankStatement, CreditCardStatement, or +nil+) this collection belongs to.
12
+ # Overridden per-instance by Base::Builder at build time.
13
+ def statement = nil
14
+
15
+ ##
16
+ # Creates a new collection from +transactions+ (Array of Transaction).
17
+ def initialize(transactions)
18
+ @transactions = transactions
19
+ end
20
+
21
+ ##
22
+ # Iterates over each transaction.
23
+ #
24
+ # :yields: transaction
25
+ def each(&)
26
+ @transactions.each(&)
27
+ end
28
+
29
+ ##
30
+ # Returns the number of transactions in the collection (Integer).
31
+ def length
32
+ @transactions.length
33
+ end
34
+
35
+ ##
36
+ # Returns a new TransactionCollection containing only positive-amount transactions.
37
+ #
38
+ # === Example
39
+ #
40
+ # ofx.transactions.credits.length #=> 5
41
+ # ofx.transactions.credits.first.amount #=> #<Money fractional:10000 currency:USD>
42
+ def credits
43
+ sub = self.class.new(select { |t| t.amount.positive? })
44
+ # Propagate statement wiring so inferred_currency resolves to the correct
45
+ # account currency even when this sub-collection has no transactions.
46
+ if (stmt = statement)
47
+ sub.define_singleton_method(:statement) { stmt }
48
+ end
49
+ sub
50
+ end
51
+
52
+ ##
53
+ # Returns a new TransactionCollection containing only negative-amount transactions.
54
+ #
55
+ # === Example
56
+ #
57
+ # ofx.transactions.debits.length #=> 37
58
+ # ofx.transactions.debits.first.name #=> "AMAZON.COM"
59
+ # ofx.transactions.debits.first.amount_cents #=> -5099
60
+ def debits
61
+ sub = self.class.new(select { |t| t.amount.negative? })
62
+ # Same as credits — statement is the authoritative source of currency.
63
+ if (stmt = statement)
64
+ sub.define_singleton_method(:statement) { stmt }
65
+ end
66
+ sub
67
+ end
68
+
69
+ ##
70
+ # Returns the sum of all positive transaction amounts as a Money object.
71
+ #
72
+ # === Example
73
+ #
74
+ # ofx.transactions.total_credits.format #=> "$350.00"
75
+ def total_credits
76
+ sum_amounts(credits)
77
+ end
78
+
79
+ ##
80
+ # Returns the sum of all negative transaction amounts as a Money object.
81
+ #
82
+ # === Example
83
+ #
84
+ # ofx.transactions.total_debits.format #=> "-$1,234.56"
85
+ def total_debits
86
+ sum_amounts(debits)
87
+ end
88
+
89
+ ##
90
+ # Returns the net amount (credits + debits) as a Money object.
91
+ # This is NOT the account balance — it reflects only the transactions present in
92
+ # the OFX file and ignores any accumulated prior balance (e.g. opening cash balance
93
+ # or unpaid invoice from a previous period). Use Balance for the actual account balance.
94
+ #
95
+ # === Example
96
+ #
97
+ # ofx.transactions.net.format #=> "$500.00"
98
+ def net
99
+ total_credits + total_debits
100
+ end
101
+
102
+ ##
103
+ # Returns a duplicate of the internal transactions array.
104
+ def to_a
105
+ @transactions.dup
106
+ end
107
+
108
+ ##
109
+ # Returns +true+ if both collections contain the same transactions as +other+.
110
+ def ==(other)
111
+ @transactions == other.to_a
112
+ end
113
+
114
+ private
115
+
116
+ def currency
117
+ statement&.account&.currency
118
+ end
119
+
120
+ def sum_amounts(collection)
121
+ return Money.new(0, currency) if collection.none?
122
+
123
+ collection.map(&:amount).inject(:+)
124
+ end
125
+ end
126
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OFX
4
- VERSION = '0.1.0'
4
+ # Current gem version (String).
5
+ VERSION = '1.0.0'
5
6
  end
data/lib/ofx_kit.rb CHANGED
@@ -8,50 +8,64 @@ require 'stringio'
8
8
  require 'time'
9
9
 
10
10
  require_relative 'ofx_kit/version'
11
- require_relative 'ofx_kit/errors'
12
- require_relative 'ofx_kit/configuration'
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'
19
+ require_relative 'ofx_kit/configuration/core'
20
+ require_relative 'ofx_kit/configuration/section_proxy'
21
+ require_relative 'ofx_kit/configuration/date_parser'
22
+ require_relative 'ofx_kit/configuration/mapping_applicator'
13
23
  require_relative 'ofx_kit/base/entity'
14
24
  require_relative 'ofx_kit/base/account'
15
25
  require_relative 'ofx_kit/base/statement'
16
26
  require_relative 'ofx_kit/base/document'
17
- require_relative 'ofx_kit/domain/bank_account'
18
- require_relative 'ofx_kit/domain/credit_card_account'
19
- require_relative 'ofx_kit/domain/transaction'
20
- require_relative 'ofx_kit/domain/transaction_collection'
21
- require_relative 'ofx_kit/domain/balance'
22
- require_relative 'ofx_kit/domain/bank_statement'
23
- require_relative 'ofx_kit/domain/credit_card_statement'
27
+ require_relative 'ofx_kit/bank_account'
28
+ require_relative 'ofx_kit/credit_card_account'
29
+ require_relative 'ofx_kit/transaction'
30
+ require_relative 'ofx_kit/transaction_collection'
31
+ require_relative 'ofx_kit/balance'
32
+ require_relative 'ofx_kit/bank_statement'
33
+ require_relative 'ofx_kit/credit_card_statement'
24
34
  require_relative 'ofx_kit/base/builder'
25
35
  require_relative 'ofx_kit/tokenizer/base'
26
36
  require_relative 'ofx_kit/tokenizer/ofx1'
27
37
  require_relative 'ofx_kit/tokenizer/ofx2'
28
38
  require_relative 'ofx_kit/parser'
29
39
 
40
+ ##
30
41
  # Top-level namespace for the ofx_kit gem.
31
- # Provides module-level access to the shared {Configuration} instance and
32
- # a {.configure} block for customizing field mappings and XML tags.
42
+ # Provides module-level access to the shared Configuration instance and
43
+ # a configure block for customizing field mappings and XML tags.
44
+ #
45
+ # === Example: Configure custom field mappings
33
46
  #
34
- # @example Configure custom field mappings
35
47
  # OFX.configure do |config|
36
48
  # config.transaction.map 'MYFIELD', to: :my_attribute
37
49
  # end
38
50
  module OFX
39
51
  class << self
40
- # Parses an OFX file or IO object and returns a {Parser} instance.
52
+ ##
53
+ # Parses an OFX file or IO object and returns a Parser instance.
41
54
  # This is the primary entry point for the gem.
55
+ # +resource+ is a file path (String) or IO object containing OFX data.
42
56
  #
43
- # @param resource [String, IO] file path or IO object containing OFX data
44
- # @return [Parser]
57
+ # === Example: Parse a file path
45
58
  #
46
- # @example Parse a file path
47
59
  # ofx = OFX.new("statement.ofx")
48
60
  # ofx.account #=> OFX::BankAccount
49
61
  # ofx.transactions #=> OFX::TransactionCollection
50
62
  #
51
- # @example Parse an IO object
63
+ # === Example: Parse an IO object
64
+ #
52
65
  # ofx = OFX.new(File.open("statement.ofx"))
53
66
  #
54
- # @example Block form
67
+ # === Example: Block form
68
+ #
55
69
  # OFX.new("statement.ofx") do |ofx|
56
70
  # puts ofx.balance
57
71
  # end
@@ -59,22 +73,27 @@ module OFX
59
73
  Parser.new(resource, &)
60
74
  end
61
75
 
62
- # Yields the current {Configuration} instance for customization.
63
- # @yieldparam config [Configuration]
64
- # @raise [ConfigurationError] if the block raises any error
76
+ ##
77
+ # Yields the current Configuration instance for customization.
78
+ #
79
+ # :yields: config
80
+ #
81
+ # Raises Errors::ConfigurationError if the block raises any error.
65
82
  def configure
66
83
  yield config
67
- rescue ConfigurationError
84
+ rescue Errors::ConfigurationError
68
85
  raise
69
86
  rescue StandardError => e
70
- raise ConfigurationError, e.message
87
+ raise Errors::ConfigurationError, e.message
71
88
  end
72
89
 
73
- # @return [Configuration] the shared configuration instance (lazy-initialized)
90
+ ##
91
+ # Returns the shared configuration instance (lazy-initialized).
74
92
  def config
75
93
  @config ||= Configuration.new
76
94
  end
77
95
 
96
+ ##
78
97
  # Resets the configuration to its default state.
79
98
  # Useful in tests to restore default field mappings between examples.
80
99
  def reset_config!