rs_quicken_parser 0.1.5

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,55 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require "rake/testtask"
6
+
7
+ GEM = "quicken_parser"
8
+ GEM_VERSION = "0.1.5"
9
+ AUTHOR = "François Beausoleil"
10
+ EMAIL = "francois@teksol.info"
11
+ HOMEPAGE = "http://github.com/francois/quicken_parser"
12
+ SUMMARY = "This is a quick'n'dirty gem to parse Quicken QFX format."
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = GEM
16
+ s.version = GEM_VERSION
17
+ s.platform = Gem::Platform::RUBY
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ["README", "LICENSE", "TODO"]
20
+ s.summary = SUMMARY
21
+ s.description = s.summary
22
+ s.author = AUTHOR
23
+ s.email = EMAIL
24
+ s.homepage = HOMEPAGE
25
+
26
+ s.add_dependency "money"
27
+ s.add_development_dependency "shoulda"
28
+
29
+ s.require_path = "lib"
30
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,test}/**/*")
31
+ end
32
+
33
+ Rake::GemPackageTask.new(spec) do |pkg|
34
+ pkg.gem_spec = spec
35
+ end
36
+
37
+ desc "install the gem locally"
38
+ task :install => [:package] do
39
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
40
+ end
41
+
42
+ desc "create a gemspec file"
43
+ task :make_spec do
44
+ File.open("#{GEM}.gemspec", "w") do |file|
45
+ file.puts spec.to_ruby
46
+ end
47
+ end
48
+
49
+ Rake::TestTask.new do |t|
50
+ t.libs << "test"
51
+ t.test_files = FileList['test/**/*_test.rb']
52
+ t.verbose = true
53
+ end
54
+
55
+ 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,132 @@
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
+ add_new_lines!
14
+ close_sgml_decl!
15
+ remove_sgml_options!
16
+
17
+ @doc = REXML::Document.new(@input)
18
+ self
19
+ end
20
+
21
+ def accounts
22
+ return @account if @account
23
+ @accounts = Array.new
24
+ REXML::XPath.each(@doc.root, "//STMTRS") do |xml|
25
+ @accounts << account_from_xml(xml)
26
+ end
27
+
28
+ REXML::XPath.each(@doc.root, "//CCSTMTRS") do |xml|
29
+ @accounts << cc_account_from_xml(xml)
30
+ end
31
+
32
+ @accounts
33
+ end
34
+
35
+ def cc_account_from_xml(xml)
36
+ currency = REXML::XPath.first(xml, ".//CURDEF").text
37
+ bank_id = nil
38
+ account_id = REXML::XPath.first(xml, ".//ACCTID").text
39
+ account_type = "CREDITCARD"
40
+
41
+ build_account_details(xml, :currency => currency, :bank_id => bank_id, :number => account_id, :type => account_type)
42
+ end
43
+
44
+ def account_from_xml(xml)
45
+ currency = REXML::XPath.first(xml, ".//CURDEF").text
46
+ bank_id = REXML::XPath.first(xml, ".//BANKID").text
47
+ account_id = REXML::XPath.first(xml, ".//ACCTID").text
48
+ account_type = REXML::XPath.first(xml, ".//ACCTTYPE").text
49
+
50
+ build_account_details(xml, :currency => currency, :bank_id => bank_id, :number => account_id, :type => account_type)
51
+ end
52
+
53
+ def build_account_details(xml, params={})
54
+ account = Account.new(params)
55
+
56
+ xmldatefrom = REXML::XPath.first(xml, ".//DTSTART").text
57
+ xmldateto = REXML::XPath.first(xml, ".//DTEND").text
58
+ account.transactions.timespan = parse_date(xmldatefrom)..parse_date(xmldateto)
59
+
60
+ REXML::XPath.each(xml, ".//STMTTRN") do |xmltxn|
61
+ type = text_or_nil(xmltxn, ".//TRNTYPE")
62
+ date_posted = text_or_nil(xmltxn, ".//DTPOSTED")
63
+ amount = text_or_nil(xmltxn, ".//TRNAMT")
64
+ txnid = text_or_nil(xmltxn, ".//FITID")
65
+ name = text_or_nil(xmltxn, ".//NAME")
66
+ memo = text_or_nil(xmltxn, ".//MEMO")
67
+
68
+ amount_and_currency = "#{amount} #{account.currency}"
69
+ account.transactions << Transaction.new(:type => type, :timestamp => parse_date(date_posted), :amount => amount_and_currency.to_money, :number => txnid, :name => name, :memo => memo)
70
+ end
71
+
72
+ account
73
+ end
74
+
75
+ def parse_date(xmldate)
76
+ if timestamp = Time.parse(xmldate) then
77
+ timestamp
78
+ else
79
+ raise DateParsingError, "Could not parse XML formatted date #{xmldate.inspect}"
80
+ end
81
+ end
82
+
83
+ def normalize_line_endings!
84
+ @input.gsub!("\r\n", "\n")
85
+ @input.gsub!("\r", "\n")
86
+ end
87
+
88
+ ASCII = (32..127).to_a + [10, 13, 9]
89
+
90
+ # Transform anything to ASCII/UTF-8. This is bad, and loses valuable information,
91
+ # but hey, I want something that works NOW, rather than something that might
92
+ # work in 20 years...
93
+ def add_xml_decl! #:nodoc:
94
+ converted = @input.unpack("C*").map do |c|
95
+ if ASCII.include?(c) then
96
+ c
97
+ else
98
+ case c
99
+ when 168, 170, 233; ?e
100
+ when 195; nil
101
+ when 244; ?o
102
+ else; ?_
103
+ end
104
+ end
105
+ end
106
+
107
+ @input = converted.pack("C*")
108
+ @input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{@input}"
109
+ end
110
+
111
+ def add_new_lines!
112
+ @input.gsub!(/<(\/{0,1}[A-Z.]+[0-9]*)>/, "\n<\\1>")
113
+ end
114
+
115
+ def close_sgml_decl!
116
+ @input.gsub!(/<([A-Z.]+)>(.+)$/, "<\\1>\\2</\\1>")
117
+ end
118
+
119
+ def remove_sgml_options!
120
+ @input.gsub!(/^[A-Z]+:[-0-9A-Z]+$/, "")
121
+ end
122
+
123
+ protected
124
+ def text_or_nil(root, xpath)
125
+ if node = REXML::XPath.first(root, xpath) then
126
+ node.text.chomp.strip
127
+ end
128
+ end
129
+
130
+ class UnsupportedEncodingException < RuntimeError; end
131
+ end
132
+ 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,54 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+
4
+ gem "thoughtbot-shoulda", "~> 2.0.5"
5
+ require "shoulda"
6
+
7
+ $:.unshift File.dirname(__FILE__) + "/../lib"
8
+ require "quicken_parser"
9
+
10
+ module Assertions
11
+ def assert_include(object, collection, message=nil)
12
+ message = build_message(message, "Expected <?> to include <?>", collection, object)
13
+ assert_block do
14
+ collection.include?(object)
15
+ end
16
+ end
17
+ end
18
+
19
+ module ShouldaMacros
20
+ def should_be(klass)
21
+ klass = self.klass
22
+ should "have #{klass.name} as an ancestor" do
23
+ assert_include klass, klass.ancestors
24
+ end
25
+ end
26
+
27
+ def should_respond_to(method)
28
+ model_name = self.model_name
29
+ should "respond_to #{method}" do
30
+ assert_respond_to instance_variable_get("@#{model_name}"), method
31
+ end
32
+ end
33
+
34
+ def class_name
35
+ self.name.sub("Test", "")
36
+ end
37
+
38
+ def klass
39
+ begin
40
+ Object.const_get(class_name)
41
+ rescue NameError
42
+ QuickenParser.const_get(class_name)
43
+ end
44
+ end
45
+
46
+ def model_name
47
+ model_name = class_name.downcase
48
+ end
49
+ end
50
+
51
+ class Test::Unit::TestCase
52
+ include Assertions
53
+ extend ShouldaMacros
54
+ 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,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rs_quicken_parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - François Beausoleil
9
+ - Chris Hart
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-04-09 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: money
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: shoulda
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: This is a quick'n'dirty gem to parse Quicken QFX format.
48
+ email: hartct@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files:
52
+ - README
53
+ - LICENSE
54
+ - TODO
55
+ files:
56
+ - LICENSE
57
+ - README
58
+ - Rakefile
59
+ - TODO
60
+ - lib/quicken_parser/account.rb
61
+ - lib/quicken_parser/parser.rb
62
+ - lib/quicken_parser/transaction.rb
63
+ - lib/quicken_parser/transactions.rb
64
+ - lib/quicken_parser.rb
65
+ - test/account_test.rb
66
+ - test/fixtures/no_memo.txt
67
+ - test/fixtures/one_account.txt
68
+ - test/fixtures/one_cc.txt
69
+ - test/fixtures/two_accounts.txt
70
+ - test/money_test.rb
71
+ - test/parser_test.rb
72
+ - test/test_helper.rb
73
+ - test/transaction_test.rb
74
+ - test/transactions_test.rb
75
+ homepage: http://github.com/francois/quicken_parser
76
+ licenses: []
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.25
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: This is a quick'n'dirty gem to parse Quicken QFX format.
99
+ test_files: []