libofx-ffi 0.1.3

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.
data/lib/ofx.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'ffi'
2
+
3
+ require 'ofx/ffi'
4
+
5
+ require 'ofx/statement'
6
+ require 'ofx/account'
7
+ require 'ofx/transaction'
8
+
9
+ module OFX
10
+ AccountCallback = Proc.new do |acct_data, junk|
11
+ account = Account.new(acct_data)
12
+ @statement[account.number] = account
13
+ 0
14
+ end
15
+
16
+ TransactionCallback = Proc.new do |t, junk|
17
+ acct_data = FFI::AccountData.new t[:account_ptr]
18
+ acct_num = acct_data.account_number
19
+ @statement[acct_num].transactions << Transaction.new(t)
20
+ 0
21
+ end
22
+
23
+ def self.parse(file_name)
24
+ raise Errno::ENOENT unless File.exist? file_name
25
+
26
+ @statement = OFX::Statement.new
27
+
28
+ context = FFI.libofx_get_new_context
29
+ FFI.ofx_set_account_cb context, AccountCallback
30
+ FFI.ofx_set_transaction_cb context, TransactionCallback
31
+ FFI.ofx_proc_file context, file_name, 0
32
+ FFI.libofx_free_context context
33
+
34
+ @statement
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module OFX
2
+ class Account
3
+ attr_reader :number, :type
4
+ attr_accessor :transactions
5
+
6
+ def initialize(ffi_account)
7
+ @number = ffi_account.account_number.to_s
8
+ @type = ffi_account.account_type
9
+
10
+ @transactions = []
11
+ end
12
+ end
13
+ end
data/lib/ofx/ffi.rb ADDED
@@ -0,0 +1,100 @@
1
+ require 'ofx/ffi/valid_access'
2
+
3
+ module OFX
4
+ module FFI
5
+ extend ::FFI::Library
6
+ ffi_lib 'ofx'
7
+
8
+ # LibofxContextPtr is void*
9
+ attach_function :libofx_get_new_context, [], :pointer
10
+ attach_function :libofx_free_context, [:pointer], :int
11
+
12
+ attach_function :ofx_proc_file, [:pointer, :pointer, :int], :int
13
+
14
+ AccountType = enum :checking, :savings, :money_market, :credit_line,
15
+ :cma, :credit_card, :investment
16
+ class AccountData < ::FFI::Struct
17
+ include ValidAccess
18
+
19
+ layout :account_id, [:char, 57],
20
+ :account_name, [:char, 255],
21
+ :account_id_valid, :int,
22
+ :account_type, AccountType,
23
+ :account_type_valid, :int,
24
+ :currency, [:char, 4],
25
+ :currency_valid, :int,
26
+ :account_number, [:char, 23],
27
+ :account_number_valid, :int,
28
+ :bank_id, [:char, 10],
29
+ :bank_id_valid, :int,
30
+ :broker_id, [:char, 23],
31
+ :broker_id_valid, :int,
32
+ :branch_id, [:char, 23],
33
+ :branch_id_valid, :int
34
+ end
35
+ callback :account_callback, [AccountData.by_value, :pointer], :int
36
+ attach_function :ofx_set_account_cb, [:pointer, :account_callback], :void
37
+
38
+
39
+ TransactionType = enum :credit, :debit, :interest, :dividend, :fee,
40
+ :service_change, :deposit, :atm, :point_of_sale,
41
+ :transfer, :check, :payment, :cash_withdrawal,
42
+ :direct_deposit, :direct_debit, :repeat_payment,
43
+ :other
44
+ class TransactionData < ::FFI::Struct
45
+ include ValidAccess
46
+
47
+ layout :account_id, [:char, 57],
48
+ :account_ptr, :pointer,
49
+ :account_id_valid, :int,
50
+ :transaction_type, TransactionType,
51
+ :transaction_type_valid, :int,
52
+ :investment_transaction_type, :int, # this is really an enum but i don't care about it right now
53
+ :investment_transaction_type_valid, :int,
54
+ :units, :double,
55
+ :units_valid, :int,
56
+ :unit_price, :double,
57
+ :units_price_valid, :int,
58
+ :amount, :double,
59
+ :amount_valid, :int,
60
+ :fi_id, [:char, 256],
61
+ :fi_id_valid, :int,
62
+ :unique_id, [:char, 33],
63
+ :unique_id_valid, :int,
64
+ :unique_id_type, [:char, 11],
65
+ :unique_id_type_valid, :int,
66
+ :security_data, :pointer,
67
+ :security_data_valid, :int,
68
+ :date_posted, :int, # really a time_t
69
+ :date_posted_valid, :int,
70
+ :date_initiated, :int, #time_t
71
+ :date_initiated_valid, :int,
72
+ :date_funds_available, :int, # time_t
73
+ :date_funds_available_valid, :int,
74
+ :fi_id_corrected, [:char, 256],
75
+ :fi_id_corrected_valid, :int,
76
+ :fi_id_corrected_action, :int, #really an enum
77
+ :fi_id_corrected_action_valid, :int,
78
+ :server_transaction_id, [:char, 37],
79
+ :server_transaction_id_valid, :int,
80
+ :check_number, [:char, 13],
81
+ :check_number_valid, :int,
82
+ :reference_number, [:char, 33],
83
+ :reference_number_valid, :int,
84
+ :standard_industrial_code, :long,
85
+ :standard_industrial_code_valid, :int,
86
+ :payee_id, [:char, 37],
87
+ :payee_id_valid, :int,
88
+ :name, [:char, 33],
89
+ :name_valid, :int,
90
+ :memo, [:char, 391],
91
+ :memo_valid, :int,
92
+ :commission, :double,
93
+ :commission_valid, :int,
94
+ :fees, :double,
95
+ :fees_valid, :int
96
+ end
97
+ callback :transaction_callback, [TransactionData.by_value, :pointer], :int
98
+ attach_function :ofx_set_transaction_cb, [:pointer, :transaction_callback], :void
99
+ end
100
+ end
@@ -0,0 +1,13 @@
1
+ module OFX
2
+ module FFI
3
+ module ValidAccess
4
+ def self.included(c)
5
+ class_eval do
6
+ def method_missing(attribute, *args)
7
+ self[:"#{attribute}_valid"] == 1 ? self[attribute] : nil
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module OFX
2
+ class Statement
3
+ def initialize
4
+ @accounts = {}
5
+ end
6
+
7
+ def [](key)
8
+ # have to call to_s because the original key from ffi is a CharArray
9
+ # (equality doesn't do what i want)
10
+ @accounts[key.to_s]
11
+ end
12
+
13
+ def []=(key, val)
14
+ @accounts[key.to_s] = val
15
+ end
16
+
17
+ def each
18
+ @accounts.each do |number, account|
19
+ yield account
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'time'
2
+
3
+ module OFX
4
+ class Transaction
5
+ attr_reader :number, :date, :type, :name, :memo, :amount
6
+
7
+ def initialize(ffi_transaction)
8
+ @number = ffi_transaction.fi_id.to_s
9
+ @date = Time.at ffi_transaction.date_posted
10
+ @memo = ffi_transaction.memo.to_s
11
+ @name = ffi_transaction.name.to_s
12
+ @type = ffi_transaction.transaction_type
13
+ @amount = ffi_transaction.amount
14
+ end
15
+ end
16
+ end
data/test/ofx_test.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'ofx'
2
+ require 'test/unit'
3
+
4
+ class OfxTest < Test::Unit::TestCase
5
+ def setup
6
+ @statement = OFX.parse 'test/test.ofx'
7
+ end
8
+
9
+ def test_ofx_file_must_exist
10
+ assert_raise Errno::ENOENT do
11
+ OFX.parse 'test/blah.ofx'
12
+ end
13
+ end
14
+
15
+ def test_account_must_exist
16
+ refute_nil @statement["987654321"]
17
+ end
18
+
19
+ def test_transactions_are_correct
20
+ transactions = @statement["987654321"].transactions
21
+
22
+ expected = [
23
+ { :type => :debit, :amount => -100, :number => "0000009",
24
+ :name => /AUTOMATIC WITHDRAWAL/, :memo => /AUTOMATIC WITHDRAWAL/,
25
+ :date => Time.new(2011, 1, 3, 11, 0, 0, "-07:00") },
26
+ { :type => :check, :amount => -146.64, :number => "0000008",
27
+ :name => "CHECK # 515",
28
+ :date => Time.new(2011, 1, 10, 11, 0, 0, "-07:00") },
29
+ { :type => :credit, :amount => 1600.01, :number => "0000007",
30
+ :name => /AUTOMATIC DEPOSIT/, :memo => /AUTOMATIC DEPOSIT/,
31
+ :date => Time.new(2011, 1, 13, 11, 0, 0, "-07:00") }
32
+ ]
33
+
34
+ transactions.each do |transaction|
35
+ expected.shift.each do |attribute, expected_value|
36
+ got = transaction.send(attribute)
37
+ assert expected_value === got, "expected #{expected_value}, but got #{got}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ require 'ofx/statement'
2
+ require 'test/unit'
3
+
4
+ class StatementTest < Test::Unit::TestCase
5
+ def setup
6
+ @s = OFX::Statement.new
7
+ @s[:foo] = 1
8
+ @s[:bar] = 2
9
+ end
10
+
11
+ def test_account_setting_and_getting
12
+ assert_equal 1, @s[:foo]
13
+ assert_equal 2, @s[:bar]
14
+ assert_nil @s[:baz]
15
+ end
16
+
17
+ def test_each
18
+ expected = ["foo", 1, "bar", 2]
19
+
20
+ @s.each do |k,v|
21
+ assert_equal expected.shift, k
22
+ assert_equal expected.shift, v
23
+ end
24
+
25
+ assert_equal [], expected
26
+ end
27
+ end
data/test/test.ofx ADDED
@@ -0,0 +1,81 @@
1
+ OFXHEADER:100
2
+ DATA:OFXSGML
3
+ VERSION:102
4
+ SECURITY:NONE
5
+ ENCODING:USASCII
6
+ CHARSET:1252
7
+ COMPRESSION:NONE
8
+ OLDFILEUID:NONE
9
+ NEWFILEUID:NONE
10
+
11
+ <OFX>
12
+ <SIGNONMSGSRSV1>
13
+ <SONRS>
14
+ <STATUS>
15
+ <CODE>0
16
+ <SEVERITY>INFO
17
+ </STATUS>
18
+ <DTSERVER>20110301225905.944
19
+ <LANGUAGE>ENG
20
+ <DTPROFUP>20050531060000.000
21
+ <FI>
22
+ <ORG>BLAH
23
+ <FID>1000
24
+ </FI>
25
+ <INTU.BID>99999
26
+ <INTU.USERID>12345678
27
+ </SONRS>
28
+ </SIGNONMSGSRSV1>
29
+ <BANKMSGSRSV1>
30
+ <STMTTRNRS>
31
+ <TRNUID>0
32
+ <STATUS>
33
+ <CODE>0
34
+ <SEVERITY>INFO
35
+ </STATUS>
36
+ <STMTRS>
37
+ <CURDEF>USD
38
+ <BANKACCTFROM>
39
+ <BANKID>345678910
40
+ <ACCTID>987654321
41
+ <ACCTTYPE>CHECKING
42
+ </BANKACCTFROM>
43
+ <BANKTRANLIST>
44
+ <DTSTART>20110101070000.000
45
+ <DTEND>20110301070000.000
46
+ <STMTTRN>
47
+ <TRNTYPE>DEBIT
48
+ <DTPOSTED>20110103120000.000
49
+ <TRNAMT>-100
50
+ <FITID>0000009
51
+ <NAME>AUTOMATIC WITHDRAWAL, BLAH BLAH
52
+ <MEMO>AUTOMATIC WITHDRAWAL, BLAH BLAH BLAH BLAH BLAH BLAH B
53
+ </STMTTRN>
54
+ <STMTTRN>
55
+ <TRNTYPE>CHECK
56
+ <DTPOSTED>20110110120000.000
57
+ <TRNAMT>-146.64
58
+ <FITID>0000008
59
+ <CHECKNUM>515
60
+ <NAME>CHECK # 515
61
+ </STMTTRN>
62
+ <STMTTRN>
63
+ <TRNTYPE>CREDIT
64
+ <DTPOSTED>20110113120000.000
65
+ <TRNAMT>1600.01
66
+ <FITID>0000007
67
+ <NAME>AUTOMATIC DEPOSIT, SOME EMPLOYER
68
+ <MEMO>AUTOMATIC DEPOSIT, SOME EMPLOYER
69
+ </STMTTRN>
70
+ </BANKTRANLIST>
71
+ <LEDGERBAL>
72
+ <BALAMT>9999.99
73
+ <DTASOF>20110301225905.944
74
+ </LEDGERBAL>
75
+ <AVAILBAL> <BALAMT>9999.99
76
+ <DTASOF>20110301225905.944
77
+ </AVAILBAL>
78
+ </STMTRS>
79
+ </STMTTRNRS>
80
+ </BANKMSGSRSV1>
81
+ </OFX>
@@ -0,0 +1,21 @@
1
+ require 'ofx/ffi/valid_access'
2
+ require 'test/unit'
3
+
4
+ class ValidAccessTest < Test::Unit::TestCase
5
+ def test_valid_access
6
+ h = {
7
+ :foo => 'blah',
8
+ :foo_valid => 1,
9
+ :bar => 'glah',
10
+ :bar_valid => 0,
11
+ :baz => 'zoing'
12
+ }
13
+ class << h
14
+ include OFX::FFI::ValidAccess
15
+ end
16
+
17
+ assert_equal 'blah', h.foo
18
+ assert_nil h.bar
19
+ assert_nil h.baz
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libofx-ffi
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 3
9
+ version: 0.1.3
10
+ platform: ruby
11
+ authors:
12
+ - Cameron Matheson
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-26 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ffi
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 6
32
+ version: 1.0.6
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Ruby/FFI wrapper for libofx
36
+ email: cameron.matheson@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/ofx/ffi/valid_access.rb
45
+ - lib/ofx/statement.rb
46
+ - lib/ofx/transaction.rb
47
+ - lib/ofx/ffi.rb
48
+ - lib/ofx/account.rb
49
+ - lib/ofx.rb
50
+ - test/valid_access_test.rb
51
+ - test/statement_test.rb
52
+ - test/test.ofx
53
+ - test/ofx_test.rb
54
+ has_rdoc: true
55
+ homepage: http://github.com/cmatheson/libofx-ffi
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.7
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: libofx-ffi parses financial documents supported by libofx
86
+ test_files: []
87
+