libofx-ffi 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ofx.rb +36 -0
- data/lib/ofx/account.rb +13 -0
- data/lib/ofx/ffi.rb +100 -0
- data/lib/ofx/ffi/valid_access.rb +13 -0
- data/lib/ofx/statement.rb +23 -0
- data/lib/ofx/transaction.rb +16 -0
- data/test/ofx_test.rb +41 -0
- data/test/statement_test.rb +27 -0
- data/test/test.ofx +81 -0
- data/test/valid_access_test.rb +21 -0
- metadata +87 -0
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
|
data/lib/ofx/account.rb
ADDED
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,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
|
+
|