quicken_parser 0.2.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.
- data/LICENSE +20 -0
- data/README +27 -0
- data/Rakefile +50 -0
- data/TODO +0 -0
- data/lib/quicken_parser.rb +11 -0
- data/lib/quicken_parser/account.rb +16 -0
- data/lib/quicken_parser/parser.rb +127 -0
- data/lib/quicken_parser/transaction.rb +17 -0
- data/lib/quicken_parser/transactions.rb +36 -0
- data/test/account_test.rb +14 -0
- data/test/fixtures/no_memo.txt +61 -0
- data/test/fixtures/one_account.txt +62 -0
- data/test/fixtures/one_cc.txt +60 -0
- data/test/fixtures/two_accounts.txt +89 -0
- data/test/money_test.rb +11 -0
- data/test/parser_test.rb +161 -0
- data/test/test_helper.rb +53 -0
- data/test/transaction_test.rb +28 -0
- data/test/transactions_test.rb +30 -0
- metadata +126 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2008 François Beausoleil
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
= QuickenParser
|
|
2
|
+
|
|
3
|
+
This is a quick'n'dirty gem to parse Quicken QFX format.
|
|
4
|
+
|
|
5
|
+
Given an input stream, or a file, this library will return Ruby structures
|
|
6
|
+
representing the transactions, accounts and credit cards contained in the
|
|
7
|
+
file / stream.
|
|
8
|
+
|
|
9
|
+
== Example
|
|
10
|
+
|
|
11
|
+
accounts = QuickenParser.parse(STDIN)
|
|
12
|
+
accounts.length #=> 3
|
|
13
|
+
account = accounts.first
|
|
14
|
+
#=> <QuickenParser::Account ...>
|
|
15
|
+
account.number #=> "123456789012"
|
|
16
|
+
account.currency #=> "CAD"
|
|
17
|
+
account.bank_id #=> "900000100"
|
|
18
|
+
account.transactions.length #=> 97
|
|
19
|
+
account.transactions.timespan #=> Sun Aug 31, 2008..Sat Sep 13
|
|
20
|
+
account.transactions.each do |transaction|
|
|
21
|
+
transaction.type #=> "DEBIT"
|
|
22
|
+
transaction.amount #=> <Money @cents=13209, @currency="CAD">
|
|
23
|
+
transaction.timestamp #=> Sun Aug 31, 2008 08:15 AM
|
|
24
|
+
transaction.name #=> "..."
|
|
25
|
+
transaction.memo #=> "..."
|
|
26
|
+
transaction.id #=> "932374"
|
|
27
|
+
end
|
data/Rakefile
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rubygems/specification'
|
|
3
|
+
require 'date'
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
GEM = "quicken_parser"
|
|
7
|
+
GEM_VERSION = "0.1.5"
|
|
8
|
+
AUTHOR = "François Beausoleil"
|
|
9
|
+
EMAIL = "francois@teksol.info"
|
|
10
|
+
HOMEPAGE = "http://github.com/francois/quicken_parser"
|
|
11
|
+
SUMMARY = "This is a quick'n'dirty gem to parse Quicken QFX format."
|
|
12
|
+
|
|
13
|
+
spec = Gem::Specification.new do |s|
|
|
14
|
+
s.name = GEM
|
|
15
|
+
s.version = GEM_VERSION
|
|
16
|
+
s.platform = Gem::Platform::RUBY
|
|
17
|
+
s.has_rdoc = true
|
|
18
|
+
s.extra_rdoc_files = ["README", "LICENSE", "TODO"]
|
|
19
|
+
s.summary = SUMMARY
|
|
20
|
+
s.description = s.summary
|
|
21
|
+
s.author = AUTHOR
|
|
22
|
+
s.email = EMAIL
|
|
23
|
+
s.homepage = HOMEPAGE
|
|
24
|
+
|
|
25
|
+
s.add_dependency "FooBarWidget-money", "~> 2"
|
|
26
|
+
s.add_development_dependency "francois-shoulda", "~> 2.0"
|
|
27
|
+
|
|
28
|
+
s.require_path = "lib"
|
|
29
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,test}/**/*")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "install the gem locally"
|
|
33
|
+
task :install => [:package] do
|
|
34
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "create a gemspec file"
|
|
38
|
+
task :make_spec do
|
|
39
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
|
40
|
+
file.puts spec.to_ruby
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Rake::TestTask.new do |t|
|
|
45
|
+
t.libs << "test"
|
|
46
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
47
|
+
t.verbose = true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
task :default => :test
|
data/TODO
ADDED
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module QuickenParser
|
|
2
|
+
class Account
|
|
3
|
+
attr_accessor :number, :type, :currency, :bank_id, :transactions
|
|
4
|
+
|
|
5
|
+
def initialize(args={})
|
|
6
|
+
args.each_pair do |key, value|
|
|
7
|
+
send("#{key}=", value)
|
|
8
|
+
end
|
|
9
|
+
@transactions = Transactions.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_s
|
|
13
|
+
"#{bank_id}:#{number}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require "money"
|
|
2
|
+
require "time"
|
|
3
|
+
|
|
4
|
+
module QuickenParser
|
|
5
|
+
class Parser #:nodoc:
|
|
6
|
+
def initialize(source)
|
|
7
|
+
@input = source.respond_to?(:read) ? source.read : source
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def parse
|
|
11
|
+
normalize_line_endings!
|
|
12
|
+
add_xml_decl!
|
|
13
|
+
close_sgml_decl!
|
|
14
|
+
remove_sgml_options!
|
|
15
|
+
|
|
16
|
+
@doc = REXML::Document.new(@input)
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def accounts
|
|
21
|
+
return @account if @account
|
|
22
|
+
@accounts = Array.new
|
|
23
|
+
REXML::XPath.each(@doc.root, "//STMTRS") do |xml|
|
|
24
|
+
@accounts << account_from_xml(xml)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
REXML::XPath.each(@doc.root, "//CCSTMTRS") do |xml|
|
|
28
|
+
@accounts << cc_account_from_xml(xml)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@accounts
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def cc_account_from_xml(xml)
|
|
35
|
+
currency = REXML::XPath.first(xml, ".//CURDEF").text
|
|
36
|
+
bank_id = nil
|
|
37
|
+
account_id = REXML::XPath.first(xml, ".//ACCTID").text
|
|
38
|
+
account_type = "CREDITCARD"
|
|
39
|
+
|
|
40
|
+
build_account_details(xml, :currency => currency, :bank_id => bank_id, :number => account_id, :type => account_type)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def account_from_xml(xml)
|
|
44
|
+
currency = REXML::XPath.first(xml, ".//CURDEF").text
|
|
45
|
+
bank_id = REXML::XPath.first(xml, ".//BANKID").text
|
|
46
|
+
account_id = REXML::XPath.first(xml, ".//ACCTID").text
|
|
47
|
+
account_type = REXML::XPath.first(xml, ".//ACCTTYPE").text
|
|
48
|
+
|
|
49
|
+
build_account_details(xml, :currency => currency, :bank_id => bank_id, :number => account_id, :type => account_type)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_account_details(xml, params={})
|
|
53
|
+
account = Account.new(params)
|
|
54
|
+
|
|
55
|
+
xmldatefrom = REXML::XPath.first(xml, ".//DTSTART").text
|
|
56
|
+
xmldateto = REXML::XPath.first(xml, ".//DTEND").text
|
|
57
|
+
account.transactions.timespan = parse_date(xmldatefrom)..parse_date(xmldateto)
|
|
58
|
+
|
|
59
|
+
REXML::XPath.each(xml, ".//STMTTRN") do |xmltxn|
|
|
60
|
+
type = text_or_nil(xmltxn, ".//TRNTYPE")
|
|
61
|
+
date_posted = text_or_nil(xmltxn, ".//DTPOSTED")
|
|
62
|
+
amount = text_or_nil(xmltxn, ".//TRNAMT")
|
|
63
|
+
txnid = text_or_nil(xmltxn, ".//FITID")
|
|
64
|
+
name = text_or_nil(xmltxn, ".//NAME")
|
|
65
|
+
memo = text_or_nil(xmltxn, ".//MEMO")
|
|
66
|
+
|
|
67
|
+
amount_and_currency = "#{amount} #{account.currency}"
|
|
68
|
+
account.transactions << Transaction.new(:type => type, :timestamp => parse_date(date_posted), :amount => amount_and_currency.to_money, :number => txnid, :name => name, :memo => memo)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
account
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_date(xmldate)
|
|
75
|
+
if timestamp = Time.parse(xmldate) then
|
|
76
|
+
timestamp
|
|
77
|
+
else
|
|
78
|
+
raise DateParsingError, "Could not parse XML formatted date #{xmldate.inspect}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def normalize_line_endings!
|
|
83
|
+
@input.gsub!("\r\n", "\n")
|
|
84
|
+
@input.gsub!("\r", "\n")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
ASCII = (32..127).to_a + [10, 13, 9]
|
|
88
|
+
|
|
89
|
+
# Transform anything to ASCII/UTF-8. This is bad, and loses valuable information,
|
|
90
|
+
# but hey, I want something that works NOW, rather than something that might
|
|
91
|
+
# work in 20 years...
|
|
92
|
+
def add_xml_decl! #:nodoc:
|
|
93
|
+
converted = @input.unpack("C*").map do |c|
|
|
94
|
+
if ASCII.include?(c) then
|
|
95
|
+
c
|
|
96
|
+
else
|
|
97
|
+
case c
|
|
98
|
+
when 168, 170, 233; ?e
|
|
99
|
+
when 195; nil
|
|
100
|
+
when 244; ?o
|
|
101
|
+
else; ?_
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@input = converted.pack("C*")
|
|
107
|
+
@input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{@input}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def close_sgml_decl!
|
|
111
|
+
@input.gsub!(/<([A-Z.]+)>(.+)$/, "<\\1>\\2</\\1>")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def remove_sgml_options!
|
|
115
|
+
@input.gsub!(/^[A-Z]+:[-0-9A-Z]+$/, "")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
protected
|
|
119
|
+
def text_or_nil(root, xpath)
|
|
120
|
+
if node = REXML::XPath.first(root, xpath) then
|
|
121
|
+
node.text.chomp.strip
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class UnsupportedEncodingException < RuntimeError; end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module QuickenParser
|
|
2
|
+
class Transaction
|
|
3
|
+
attr_accessor :type, :timestamp, :amount, :number, :name, :memo
|
|
4
|
+
|
|
5
|
+
def initialize(args={})
|
|
6
|
+
args.each_pair do |key, value|
|
|
7
|
+
send("#{key}=", value)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
@memo = nil if @name.to_s.strip == @memo.to_s.strip
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"%s: %s %s %s" % [timestamp.to_s, type, name, amount.to_s]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module QuickenParser
|
|
2
|
+
class Transactions
|
|
3
|
+
include Enumerable
|
|
4
|
+
attr_accessor :timespan
|
|
5
|
+
|
|
6
|
+
def initialize(args={})
|
|
7
|
+
@txns = Array.new
|
|
8
|
+
args.each_pair do |key, value|
|
|
9
|
+
send("#{key}=", value)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def first
|
|
14
|
+
@txns.first
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def last
|
|
18
|
+
@txns.last
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def length
|
|
22
|
+
@txns.length
|
|
23
|
+
end
|
|
24
|
+
alias_method :size, :length
|
|
25
|
+
|
|
26
|
+
def <<(txn)
|
|
27
|
+
@txns << txn
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def each
|
|
31
|
+
@txns.each do |txn|
|
|
32
|
+
yield txn
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
|
2
|
+
|
|
3
|
+
class AccountTest < Test::Unit::TestCase
|
|
4
|
+
context "An account" do
|
|
5
|
+
setup do
|
|
6
|
+
@account = QuickenParser::Account.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
%w(number bank_id type currency transactions).each do |attr|
|
|
10
|
+
should_respond_to attr
|
|
11
|
+
should_respond_to "#{attr}="
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
OFXHEADER:100
|
|
2
|
+
DATA:OFXSGML
|
|
3
|
+
VERSION:102
|
|
4
|
+
SECURITY:TYPE1
|
|
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
|
+
<MESSAGE>OK
|
|
18
|
+
</STATUS>
|
|
19
|
+
<DTSERVER>20081102135744[-5]
|
|
20
|
+
<LANGUAGE>ENG
|
|
21
|
+
<INTU.BID>00016
|
|
22
|
+
</SONRS>
|
|
23
|
+
</SIGNONMSGSRSV1>
|
|
24
|
+
<BANKMSGSRSV1>
|
|
25
|
+
<STMTTRNRS>
|
|
26
|
+
<TRNUID>C021384014026471
|
|
27
|
+
<STATUS>
|
|
28
|
+
<CODE>0
|
|
29
|
+
<SEVERITY>INFO
|
|
30
|
+
<MESSAGE>OK
|
|
31
|
+
</STATUS>
|
|
32
|
+
<STMTRS>
|
|
33
|
+
<CURDEF>CAD
|
|
34
|
+
<BANKACCTFROM>
|
|
35
|
+
<BANKID>302010140
|
|
36
|
+
<ACCTID>065412036771
|
|
37
|
+
<ACCTTYPE>CHECKING
|
|
38
|
+
</BANKACCTFROM>
|
|
39
|
+
<BANKTRANLIST>
|
|
40
|
+
<DTSTART>20080708120000[-5]
|
|
41
|
+
<DTEND>20081028120000[-5]
|
|
42
|
+
<STMTTRN>
|
|
43
|
+
<TRNTYPE>DEBIT
|
|
44
|
+
<DTPOSTED>20080708120000[-5]
|
|
45
|
+
<TRNAMT>-14.24
|
|
46
|
+
<FITID>90000010020080708C00131479599
|
|
47
|
+
<NAME>PAIEMENT DIVERS
|
|
48
|
+
</STMTTRN>
|
|
49
|
+
</BANKTRANLIST>
|
|
50
|
+
<LEDGERBAL>
|
|
51
|
+
<BALAMT>-10000.00
|
|
52
|
+
<DTASOF>20081101
|
|
53
|
+
</LEDGERBAL>
|
|
54
|
+
<AVAILBAL>
|
|
55
|
+
<BALAMT>-10000.00
|
|
56
|
+
<DTASOF>20081101
|
|
57
|
+
</AVAILBAL>
|
|
58
|
+
</STMTRS>
|
|
59
|
+
</STMTTRNRS>
|
|
60
|
+
</BANKMSGSRSV1>
|
|
61
|
+
</OFX>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
OFXHEADER:100
|
|
2
|
+
DATA:OFXSGML
|
|
3
|
+
VERSION:102
|
|
4
|
+
SECURITY:TYPE1
|
|
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
|
+
<MESSAGE>OK
|
|
18
|
+
</STATUS>
|
|
19
|
+
<DTSERVER>20081102135744[-5]
|
|
20
|
+
<LANGUAGE>ENG
|
|
21
|
+
<INTU.BID>00016
|
|
22
|
+
</SONRS>
|
|
23
|
+
</SIGNONMSGSRSV1>
|
|
24
|
+
<BANKMSGSRSV1>
|
|
25
|
+
<STMTTRNRS>
|
|
26
|
+
<TRNUID>C021384014026471
|
|
27
|
+
<STATUS>
|
|
28
|
+
<CODE>0
|
|
29
|
+
<SEVERITY>INFO
|
|
30
|
+
<MESSAGE>OK
|
|
31
|
+
</STATUS>
|
|
32
|
+
<STMTRS>
|
|
33
|
+
<CURDEF>CAD
|
|
34
|
+
<BANKACCTFROM>
|
|
35
|
+
<BANKID>302010140
|
|
36
|
+
<ACCTID>065412036771
|
|
37
|
+
<ACCTTYPE>CHECKING
|
|
38
|
+
</BANKACCTFROM>
|
|
39
|
+
<BANKTRANLIST>
|
|
40
|
+
<DTSTART>20080708120000[-5]
|
|
41
|
+
<DTEND>20081028120000[-5]
|
|
42
|
+
<STMTTRN>
|
|
43
|
+
<TRNTYPE>DEBIT
|
|
44
|
+
<DTPOSTED>20080708120000[-5]
|
|
45
|
+
<TRNAMT>-14.24
|
|
46
|
+
<FITID>90000010020080708C00131479599
|
|
47
|
+
<NAME>PAIEMENT DIVERS
|
|
48
|
+
<MEMO>PAYPAL PTE LTD
|
|
49
|
+
</STMTTRN>
|
|
50
|
+
</BANKTRANLIST>
|
|
51
|
+
<LEDGERBAL>
|
|
52
|
+
<BALAMT>-10000.00
|
|
53
|
+
<DTASOF>20081101
|
|
54
|
+
</LEDGERBAL>
|
|
55
|
+
<AVAILBAL>
|
|
56
|
+
<BALAMT>-10000.00
|
|
57
|
+
<DTASOF>20081101
|
|
58
|
+
</AVAILBAL>
|
|
59
|
+
</STMTRS>
|
|
60
|
+
</STMTTRNRS>
|
|
61
|
+
</BANKMSGSRSV1>
|
|
62
|
+
</OFX>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
OFXHEADER:100
|
|
2
|
+
DATA:OFXSGML
|
|
3
|
+
VERSION:102
|
|
4
|
+
SECURITY:TYPE1
|
|
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
|
+
<MESSAGE>OK
|
|
18
|
+
</STATUS>
|
|
19
|
+
<DTSERVER>20081102135744[-5]
|
|
20
|
+
<LANGUAGE>ENG
|
|
21
|
+
<INTU.BID>00016
|
|
22
|
+
</SONRS>
|
|
23
|
+
</SIGNONMSGSRSV1>
|
|
24
|
+
<CREDITCARDMSGSRSV1>
|
|
25
|
+
<CCSTMTTRNRS>
|
|
26
|
+
<TRNUID>C021384014026471
|
|
27
|
+
<STATUS>
|
|
28
|
+
<CODE>0
|
|
29
|
+
<SEVERITY>INFO
|
|
30
|
+
<MESSAGE>OK
|
|
31
|
+
</STATUS>
|
|
32
|
+
<CCSTMTRS>
|
|
33
|
+
<CURDEF>CAD
|
|
34
|
+
<CCACCTFROM>
|
|
35
|
+
<ACCTID>4510912839238
|
|
36
|
+
</CCACCTFROM>
|
|
37
|
+
<BANKTRANLIST>
|
|
38
|
+
<DTSTART>20080708120000[-5]
|
|
39
|
+
<DTEND>20081028120000[-5]
|
|
40
|
+
<STMTTRN>
|
|
41
|
+
<TRNTYPE>DEBIT
|
|
42
|
+
<DTPOSTED>20080708120000[-5]
|
|
43
|
+
<TRNAMT>-14.24
|
|
44
|
+
<FITID>90000010020080708C00131479599
|
|
45
|
+
<NAME>PAIEMENT DIVERS
|
|
46
|
+
<MEMO>PAYPAL PTE LTD
|
|
47
|
+
</STMTTRN>
|
|
48
|
+
</BANKTRANLIST>
|
|
49
|
+
<LEDGERBAL>
|
|
50
|
+
<BALAMT>-10000.00
|
|
51
|
+
<DTASOF>20081101
|
|
52
|
+
</LEDGERBAL>
|
|
53
|
+
<AVAILBAL>
|
|
54
|
+
<BALAMT>-10000.00
|
|
55
|
+
<DTASOF>20081101
|
|
56
|
+
</AVAILBAL>
|
|
57
|
+
</CCSTMTRS>
|
|
58
|
+
</CCSTMTTRNRS>
|
|
59
|
+
</CREDITCARDMSGSRSV1>
|
|
60
|
+
</OFX>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
OFXHEADER:100
|
|
2
|
+
DATA:OFXSGML
|
|
3
|
+
VERSION:102
|
|
4
|
+
SECURITY:TYPE1
|
|
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
|
+
<MESSAGE>OK
|
|
18
|
+
</STATUS>
|
|
19
|
+
<DTSERVER>20081102135744[-5]
|
|
20
|
+
<LANGUAGE>ENG
|
|
21
|
+
<INTU.BID>00016
|
|
22
|
+
</SONRS>
|
|
23
|
+
</SIGNONMSGSRSV1>
|
|
24
|
+
<BANKMSGSRSV1>
|
|
25
|
+
<STMTTRNRS>
|
|
26
|
+
<TRNUID>C021384014026471
|
|
27
|
+
<STATUS>
|
|
28
|
+
<CODE>0
|
|
29
|
+
<SEVERITY>INFO
|
|
30
|
+
<MESSAGE>OK
|
|
31
|
+
</STATUS>
|
|
32
|
+
<STMTRS>
|
|
33
|
+
<CURDEF>CAD
|
|
34
|
+
<BANKACCTFROM>
|
|
35
|
+
<BANKID>302010140
|
|
36
|
+
<ACCTID>065412036771
|
|
37
|
+
<ACCTTYPE>CHECKING
|
|
38
|
+
</BANKACCTFROM>
|
|
39
|
+
<BANKTRANLIST>
|
|
40
|
+
<DTSTART>20080708120000[-5]
|
|
41
|
+
<DTEND>20081028120000[-5]
|
|
42
|
+
<STMTTRN>
|
|
43
|
+
<TRNTYPE>DEBIT
|
|
44
|
+
<DTPOSTED>20080708120000[-5]
|
|
45
|
+
<TRNAMT>-14.24
|
|
46
|
+
<FITID>90000010020080708C00131479599
|
|
47
|
+
<NAME>PAIEMENT DIVERS
|
|
48
|
+
<MEMO>PAYPAL PTE LTD
|
|
49
|
+
</STMTTRN>
|
|
50
|
+
</BANKTRANLIST>
|
|
51
|
+
<LEDGERBAL>
|
|
52
|
+
<BALAMT>-10000.00
|
|
53
|
+
<DTASOF>20081101
|
|
54
|
+
</LEDGERBAL>
|
|
55
|
+
<AVAILBAL>
|
|
56
|
+
<BALAMT>-10000.00
|
|
57
|
+
<DTASOF>20081101
|
|
58
|
+
</AVAILBAL>
|
|
59
|
+
</STMTRS>
|
|
60
|
+
<STMTRS>
|
|
61
|
+
<CURDEF>USD
|
|
62
|
+
<BANKACCTFROM>
|
|
63
|
+
<BANKID>302010140
|
|
64
|
+
<ACCTID>062412245671
|
|
65
|
+
<ACCTTYPE>CHECKING
|
|
66
|
+
</BANKACCTFROM>
|
|
67
|
+
<BANKTRANLIST>
|
|
68
|
+
<DTSTART>20080708120000[-5]
|
|
69
|
+
<DTEND>20081028120000[-5]
|
|
70
|
+
<STMTTRN>
|
|
71
|
+
<TRNTYPE>CREDIT
|
|
72
|
+
<DTPOSTED>20080708120000[-5]
|
|
73
|
+
<TRNAMT>129.92
|
|
74
|
+
<FITID>90000010020080708C00131479600
|
|
75
|
+
<NAME>DEPOSIT
|
|
76
|
+
</STMTTRN>
|
|
77
|
+
</BANKTRANLIST>
|
|
78
|
+
<LEDGERBAL>
|
|
79
|
+
<BALAMT>-10000.00
|
|
80
|
+
<DTASOF>20081101
|
|
81
|
+
</LEDGERBAL>
|
|
82
|
+
<AVAILBAL>
|
|
83
|
+
<BALAMT>-10000.00
|
|
84
|
+
<DTASOF>20081101
|
|
85
|
+
</AVAILBAL>
|
|
86
|
+
</STMTRS>
|
|
87
|
+
</STMTTRNRS>
|
|
88
|
+
</BANKMSGSRSV1>
|
|
89
|
+
</OFX>
|
data/test/money_test.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
|
2
|
+
|
|
3
|
+
class MoneyTest < Test::Unit::TestCase
|
|
4
|
+
should "parse '-14.24 EUR' as 14.24 EUR" do
|
|
5
|
+
assert_equal Money.new(-1424, "EUR"), "-14.24 EUR".to_money
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
should "parse '14.24 CAD' as 14.24 CAD" do
|
|
9
|
+
assert_equal Money.new(1424, "CAD"), "14.24 CAD".to_money
|
|
10
|
+
end
|
|
11
|
+
end
|
data/test/parser_test.rb
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
|
2
|
+
|
|
3
|
+
class ParserTest < Test::Unit::TestCase
|
|
4
|
+
context "A Quicken file with 2 accounts" do
|
|
5
|
+
setup do
|
|
6
|
+
@content = fixture(:two_accounts)
|
|
7
|
+
@parser = QuickenParser::Parser.new(@content.dup)
|
|
8
|
+
@parser.parse
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
should "return two accounts" do
|
|
12
|
+
assert_equal 2, @parser.accounts.length
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
should "have one transaction per account" do
|
|
16
|
+
assert @parser.accounts.all? {|account| account.transactions.length == 1}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context "A Quicken file whose transactions don't have memos" do
|
|
21
|
+
setup do
|
|
22
|
+
@content = fixture(:no_memo)
|
|
23
|
+
@parser = QuickenParser::Parser.new(@content.dup)
|
|
24
|
+
@parser.parse
|
|
25
|
+
@account = @parser.accounts.first
|
|
26
|
+
@transaction = @account.transactions.first
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
should "return nil as the memo on the transaction" do
|
|
30
|
+
assert_nil @transaction.memo
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "A Quicken file with one credit card" do
|
|
35
|
+
setup do
|
|
36
|
+
@content = fixture(:one_cc)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "that was parsed" do
|
|
40
|
+
setup do
|
|
41
|
+
@parser = QuickenParser::Parser.new(@content.dup)
|
|
42
|
+
@parser.parse
|
|
43
|
+
@account = @parser.accounts.first
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
should "have a single account" do
|
|
48
|
+
assert_equal 1, @parser.accounts.length
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
should "have no bank ID" do
|
|
52
|
+
assert_nil @account.bank_id
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
should "be from account 4510912839238" do
|
|
56
|
+
assert_equal "4510912839238", @account.number
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
should "be a credit card account" do
|
|
60
|
+
assert_equal "CREDITCARD", @account.type
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
should "return 'CAD' as the currency" do
|
|
64
|
+
assert_equal "CAD", @account.currency
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
should "have a single transaction" do
|
|
68
|
+
assert_equal 1, @account.transactions.length
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
should "have transactions covering 2008-07-08 noon to 2008-10-28 noon" do
|
|
72
|
+
span = Time.local(2008, 7, 8, 12, 0, 0) .. Time.local(2008, 10, 28, 12, 0, 0)
|
|
73
|
+
assert_equal span, @account.transactions.timespan
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "the transaction" do
|
|
77
|
+
setup do
|
|
78
|
+
@transaction = @account.transactions.first
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "A Quicken file with one bank account" do
|
|
85
|
+
setup do
|
|
86
|
+
@content = fixture(:one_account)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context "that was parsed" do
|
|
90
|
+
setup do
|
|
91
|
+
@parser = QuickenParser::Parser.new(@content.dup)
|
|
92
|
+
@parser.parse
|
|
93
|
+
@account = @parser.accounts.first
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
should "have a single account" do
|
|
97
|
+
assert_equal 1, @parser.accounts.length
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
should "be from bank id 302010140" do
|
|
101
|
+
assert_equal "302010140", @account.bank_id
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
should "be from account 065412036771" do
|
|
105
|
+
assert_equal "065412036771", @account.number
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
should "be a checking account" do
|
|
109
|
+
assert_equal "CHECKING", @account.type
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
should "return 'CAD' as the currency" do
|
|
113
|
+
assert_equal "CAD", @account.currency
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
should "have a single transaction" do
|
|
117
|
+
assert_equal 1, @account.transactions.length
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
should "have transactions covering 2008-07-08 noon to 2008-10-28 noon" do
|
|
121
|
+
span = Time.local(2008, 7, 8, 12, 0, 0) .. Time.local(2008, 10, 28, 12, 0, 0)
|
|
122
|
+
assert_equal span, @account.transactions.timespan
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context "the transaction" do
|
|
126
|
+
setup do
|
|
127
|
+
@transaction = @account.transactions.first
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
should "dated 2008-07-08 noon" do
|
|
131
|
+
assert_equal Time.local(2008, 7, 8, 12, 0, 0), @transaction.timestamp
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
should "have an ID of 90000010020080708C00131479599" do
|
|
135
|
+
assert_equal "90000010020080708C00131479599", @transaction.number
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
should "be a DEBIT" do
|
|
139
|
+
assert_equal "DEBIT", @transaction.type
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
should "have a name of 'PAIEMENT DIVERS'" do
|
|
143
|
+
assert_equal "PAIEMENT DIVERS", @transaction.name
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
should "have a memo of 'PAYPAL PTE LTD'" do
|
|
147
|
+
assert_equal "PAYPAL PTE LTD", @transaction.memo
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
should "be of an amount of -14.24" do
|
|
151
|
+
assert_equal Money.new(-1424, "CAD"), @transaction.amount
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
protected
|
|
158
|
+
def fixture(name)
|
|
159
|
+
File.read(File.dirname(__FILE__) + "/fixtures/#{name}.txt")
|
|
160
|
+
end
|
|
161
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require "rubygems"
|
|
2
|
+
require "test/unit"
|
|
3
|
+
|
|
4
|
+
require "shoulda"
|
|
5
|
+
|
|
6
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
7
|
+
require "quicken_parser"
|
|
8
|
+
|
|
9
|
+
module Assertions
|
|
10
|
+
def assert_include(object, collection, message=nil)
|
|
11
|
+
message = build_message(message, "Expected <?> to include <?>", collection, object)
|
|
12
|
+
assert_block do
|
|
13
|
+
collection.include?(object)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ShouldaMacros
|
|
19
|
+
def should_be(klass)
|
|
20
|
+
klass = self.klass
|
|
21
|
+
should "have #{klass.name} as an ancestor" do
|
|
22
|
+
assert_include klass, klass.ancestors
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def should_respond_to(method)
|
|
27
|
+
model_name = self.model_name
|
|
28
|
+
should "respond_to #{method}" do
|
|
29
|
+
assert_respond_to instance_variable_get("@#{model_name}"), method
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def class_name
|
|
34
|
+
self.name.sub("Test", "")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def klass
|
|
38
|
+
begin
|
|
39
|
+
Object.const_get(class_name)
|
|
40
|
+
rescue NameError
|
|
41
|
+
QuickenParser.const_get(class_name)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def model_name
|
|
46
|
+
model_name = class_name.downcase
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Test::Unit::TestCase
|
|
51
|
+
include Assertions
|
|
52
|
+
extend ShouldaMacros
|
|
53
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
|
2
|
+
|
|
3
|
+
class TransactionTest < Test::Unit::TestCase
|
|
4
|
+
context "A transaction" do
|
|
5
|
+
setup do
|
|
6
|
+
@transaction = QuickenParser::Transaction.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
%w(type timestamp amount number name memo).each do |attr|
|
|
10
|
+
should_respond_to attr
|
|
11
|
+
should_respond_to "#{attr}="
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context "A new transation with identical name and memo" do
|
|
16
|
+
setup do
|
|
17
|
+
@transaction = QuickenParser::Transaction.new(:name => "FONDUE FOLIE SHERBROOKE QC", :memo => "FONDUE FOLIE SHERBROOKE QC")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
should "NOT change the name" do
|
|
21
|
+
assert_equal "FONDUE FOLIE SHERBROOKE QC", @transaction.name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
should "set the memo to nil" do
|
|
25
|
+
assert_nil @transaction.memo
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
|
2
|
+
|
|
3
|
+
class TransactionsTest < Test::Unit::TestCase
|
|
4
|
+
context "A set of transations" do
|
|
5
|
+
setup do
|
|
6
|
+
@transactions = QuickenParser::Transactions.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
context "adding an object" do
|
|
10
|
+
setup do
|
|
11
|
+
@object = Object.new
|
|
12
|
+
@transactions << @object
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
should "have that object in it's list" do
|
|
16
|
+
assert @transactions.all? {|o| o == @object}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
should_be Enumerable
|
|
21
|
+
%w(<< each first last length size).each do |method|
|
|
22
|
+
should_respond_to method
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
%w(timespan).each do |attr|
|
|
26
|
+
should_respond_to attr
|
|
27
|
+
should_respond_to "#{attr}="
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: quicken_parser
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 2
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.2.0
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- "Fran\xC3\xA7ois Beausoleil"
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2009-05-02 00:00:00 Z
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: rake
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
none: false
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
hash: 3
|
|
29
|
+
segments:
|
|
30
|
+
- 0
|
|
31
|
+
version: "0"
|
|
32
|
+
type: :development
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: thoughtbot-shoulda
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
none: false
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
hash: 3
|
|
43
|
+
segments:
|
|
44
|
+
- 0
|
|
45
|
+
version: "0"
|
|
46
|
+
type: :development
|
|
47
|
+
version_requirements: *id002
|
|
48
|
+
- !ruby/object:Gem::Dependency
|
|
49
|
+
name: FooBarWidget-money
|
|
50
|
+
prerelease: false
|
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
52
|
+
none: false
|
|
53
|
+
requirements:
|
|
54
|
+
- - ~>
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
hash: 7
|
|
57
|
+
segments:
|
|
58
|
+
- 2
|
|
59
|
+
version: "2"
|
|
60
|
+
type: :runtime
|
|
61
|
+
version_requirements: *id003
|
|
62
|
+
description: This is a quick'n'dirty gem to parse Quicken QFX format.
|
|
63
|
+
email: francois@teksol.info
|
|
64
|
+
executables: []
|
|
65
|
+
|
|
66
|
+
extensions: []
|
|
67
|
+
|
|
68
|
+
extra_rdoc_files:
|
|
69
|
+
- README
|
|
70
|
+
- LICENSE
|
|
71
|
+
- TODO
|
|
72
|
+
files:
|
|
73
|
+
- LICENSE
|
|
74
|
+
- README
|
|
75
|
+
- Rakefile
|
|
76
|
+
- TODO
|
|
77
|
+
- lib/quicken_parser/account.rb
|
|
78
|
+
- lib/quicken_parser/parser.rb
|
|
79
|
+
- lib/quicken_parser/transaction.rb
|
|
80
|
+
- lib/quicken_parser/transactions.rb
|
|
81
|
+
- lib/quicken_parser.rb
|
|
82
|
+
- test/account_test.rb
|
|
83
|
+
- test/fixtures/no_memo.txt
|
|
84
|
+
- test/fixtures/one_account.txt
|
|
85
|
+
- test/fixtures/one_cc.txt
|
|
86
|
+
- test/fixtures/two_accounts.txt
|
|
87
|
+
- test/money_test.rb
|
|
88
|
+
- test/parser_test.rb
|
|
89
|
+
- test/test_helper.rb
|
|
90
|
+
- test/transaction_test.rb
|
|
91
|
+
- test/transactions_test.rb
|
|
92
|
+
homepage: http://github.com/francois/quicken_parser
|
|
93
|
+
licenses: []
|
|
94
|
+
|
|
95
|
+
post_install_message:
|
|
96
|
+
rdoc_options: []
|
|
97
|
+
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
none: false
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
hash: 3
|
|
106
|
+
segments:
|
|
107
|
+
- 0
|
|
108
|
+
version: "0"
|
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
|
+
none: false
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
hash: 3
|
|
115
|
+
segments:
|
|
116
|
+
- 0
|
|
117
|
+
version: "0"
|
|
118
|
+
requirements: []
|
|
119
|
+
|
|
120
|
+
rubyforge_project:
|
|
121
|
+
rubygems_version: 1.8.15
|
|
122
|
+
signing_key:
|
|
123
|
+
specification_version: 3
|
|
124
|
+
summary: This is a quick'n'dirty gem to parse Quicken QFX format.
|
|
125
|
+
test_files: []
|
|
126
|
+
|