quicken_parser 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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,11 @@
1
+ require "rexml/document"
2
+ require "rexml/xpath"
3
+ Dir[File.dirname(__FILE__) + "/**/*.rb"].each {|f| require f}
4
+
5
+ require "money"
6
+
7
+ module QuickenParser
8
+ def self.parse(stream_or_string)
9
+ Parser.new(stream_or_string).parse.accounts
10
+ end
11
+ end
@@ -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>
@@ -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
@@ -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
@@ -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
+