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 +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
|
+
|