qif 1.1.2 → 1.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.
- checksums.yaml +4 -4
- data/README.rdoc +8 -8
- data/Rakefile +6 -0
- data/lib/qif.rb +7 -1
- data/lib/qif/amount_parser.rb +6 -0
- data/lib/qif/reader.rb +23 -13
- data/lib/qif/transaction.rb +9 -10
- data/lib/qif/transaction/builder.rb +61 -0
- data/lib/qif/transaction/builderable.rb +39 -0
- data/lib/qif/transaction/split.rb +26 -0
- data/lib/qif/transaction/split/builder.rb +32 -0
- data/spec/fixtures/splits.qif +20 -0
- data/spec/lib/reader_spec.rb +38 -0
- data/spec/lib/transaction_builder_spec.rb +122 -0
- data/spec/lib/transaction_spec.rb +38 -55
- data/spec/lib/transaction_split_builder_spec.rb +47 -0
- data/spec/lib/writer_spec.rb +32 -36
- data/spec/spec_helper.rb +3 -1
- data/spec/support/shared/builder_method.rb +14 -0
- metadata +25 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 08266f25e1ffcdf187a4ebb8b1962c966174cccf
|
4
|
+
data.tar.gz: f6e324f5d4ad6dd5779998a106999d7a622c124f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ee86e8d5aa5128e25fe50d65ded8ea804290d8b40f7013a4d83825acbf9c4ab3ace060dc2dd4c251b0033e0cecb160bca7cf32ed1bd6ce81c2e04663663592b
|
7
|
+
data.tar.gz: 856b067eaa0618b390c903cb236eeff28790afe131abc5bf570643ef86ddda93f494e09250046cd9a55529fffcff8e25518ebd12a5ea62de8525ffc6b480f8b7
|
data/README.rdoc
CHANGED
@@ -6,23 +6,23 @@
|
|
6
6
|
|
7
7
|
=== INSTALL:
|
8
8
|
|
9
|
-
*
|
9
|
+
* gem install qif
|
10
10
|
|
11
11
|
=== DOCUMENTATION:
|
12
12
|
|
13
|
-
* http://
|
13
|
+
* http://www.rubydoc.info/github/jemmyw/Qif
|
14
14
|
|
15
15
|
=== EXAMPLE:
|
16
16
|
|
17
17
|
require 'qif'
|
18
18
|
qif = Qif::Reader.new(open('/path/to/file.qif'))
|
19
19
|
qif.each do |transaction|
|
20
|
-
puts [transaction.
|
20
|
+
puts [transaction.payee, transaction.amount].join(", ")
|
21
21
|
end
|
22
22
|
|
23
23
|
=== CSV conversion example
|
24
24
|
|
25
|
-
# Ruby 1.
|
25
|
+
# Ruby 2.1.0
|
26
26
|
require 'csv'
|
27
27
|
require 'qif'
|
28
28
|
|
@@ -33,10 +33,10 @@
|
|
33
33
|
# [3] debit amount,
|
34
34
|
# [4] credit amount
|
35
35
|
# Ignore other fields
|
36
|
-
basefile = '
|
37
|
-
bank_input = CSV.read("#{
|
36
|
+
basefile = 'filename'
|
37
|
+
bank_input = CSV.read("#{basefile}.csv")
|
38
38
|
|
39
|
-
Qif::Writer.open("#{
|
39
|
+
Qif::Writer.open("#{basefile}.qif", type = 'Bank', format = 'dd/mm/yyyy') do |qif|
|
40
40
|
bank_input.each do |row|
|
41
41
|
# Fix the values depending on what state your CSV data is in
|
42
42
|
row.each { |value| value.to_s.gsub!(/^\s+|\s+$/,'') }
|
@@ -51,7 +51,7 @@
|
|
51
51
|
|
52
52
|
=== LICENSE:
|
53
53
|
|
54
|
-
Copyright (c)
|
54
|
+
Copyright (c) 2014 Jeremy Wells
|
55
55
|
|
56
56
|
Permission is hereby granted, free of charge, to any person
|
57
57
|
obtaining a copy of this software and associated documentation
|
data/Rakefile
CHANGED
data/lib/qif.rb
CHANGED
data/lib/qif/reader.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'qif/date_format'
|
3
3
|
require 'qif/transaction'
|
4
|
+
require 'qif/transaction/builder'
|
4
5
|
|
5
6
|
module Qif
|
6
7
|
# The Qif::Reader class reads a qif file and provides access to
|
@@ -152,9 +153,8 @@ module Qif
|
|
152
153
|
end
|
153
154
|
|
154
155
|
def read_transaction
|
155
|
-
if
|
156
|
-
transaction
|
157
|
-
cache_transaction(transaction) if transaction
|
156
|
+
if transaction = read_record
|
157
|
+
cache_transaction(transaction)
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
@@ -163,17 +163,27 @@ module Qif
|
|
163
163
|
end
|
164
164
|
|
165
165
|
def read_record
|
166
|
-
|
166
|
+
builder = Qif::Transaction::Builder.new(->(dt){@format.parse(dt)})
|
167
167
|
begin
|
168
|
-
line = @data.readline
|
169
|
-
key = line
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
168
|
+
line = @data.readline.strip
|
169
|
+
key = line.slice!(0, 1)
|
170
|
+
builder =
|
171
|
+
case key
|
172
|
+
when 'D' then builder.set_date(line)
|
173
|
+
when 'T' then builder.set_amount(line)
|
174
|
+
when 'A' then builder.set_address(line)
|
175
|
+
when 'C' then builder.set_status(line)
|
176
|
+
when 'N' then builder.set_number(line)
|
177
|
+
when 'P' then builder.set_payee(line)
|
178
|
+
when 'M' then builder.set_memo(line)
|
179
|
+
when 'L' then builder.set_category(line)
|
180
|
+
when 'S' then builder.add_split(line)
|
181
|
+
when 'E' then builder.set_split_memo(line)
|
182
|
+
when '$' then builder.set_split_amount(line)
|
183
|
+
else builder
|
184
|
+
end
|
185
|
+
end until key == "^"
|
186
|
+
builder.build
|
177
187
|
rescue EOFError => e
|
178
188
|
@data.close
|
179
189
|
nil
|
data/lib/qif/transaction.rb
CHANGED
@@ -12,9 +12,6 @@ module Qif
|
|
12
12
|
:memo => {"M" => "Memo" },
|
13
13
|
:adress => {"A" => "Address (up to five lines; the sixth line is an optional message)" },
|
14
14
|
:category => {"L" => "Category (Category/Subcategory/Transfer/Class)" },
|
15
|
-
:split_category => {"S" => "Category in split (Category/Transfer/Class)" },
|
16
|
-
:split_memo => {"E" => "Memo in split" },
|
17
|
-
:split_amount => {"$" => "Dollar amount of split" },
|
18
15
|
:end => {"^" => "End of entry" }
|
19
16
|
}
|
20
17
|
DEPRECATION_FIELDS = {
|
@@ -24,18 +21,20 @@ module Qif
|
|
24
21
|
}
|
25
22
|
SUPPORTED_FIELDS.keys.each{|s| attr_accessor s}
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
record.reject{|k,v| v.nil?}.each{|k,v| record[k] = record[k].to_f if k.to_s.include? "amount"}
|
31
|
-
Transaction.new record
|
32
|
-
end
|
24
|
+
attr_reader :splits
|
25
|
+
alias :address :adress
|
26
|
+
alias :address= :adress=
|
33
27
|
|
34
28
|
def initialize(attributes = {})
|
29
|
+
@splits = []
|
35
30
|
deprecate_attributes!(attributes)
|
36
31
|
SUPPORTED_FIELDS.keys.each{|s| instance_variable_set("@#{s.to_s}", attributes[s])}
|
37
32
|
end
|
38
33
|
|
34
|
+
def add_split(split)
|
35
|
+
@splits << split
|
36
|
+
end
|
37
|
+
|
39
38
|
# Returns a representation of the transaction as it
|
40
39
|
# would appear in a qif file.
|
41
40
|
def to_s(format = 'dd/mm/yyyy')
|
@@ -52,7 +51,7 @@ module Qif
|
|
52
51
|
else
|
53
52
|
"#{field}#{current}"
|
54
53
|
end
|
55
|
-
end.flatten.compact.join("\n")
|
54
|
+
end.concat(@splits.collect{|s| s.to_s}).flatten.compact.join("\n")
|
56
55
|
end
|
57
56
|
|
58
57
|
private
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative "split/builder"
|
2
|
+
require_relative "builderable"
|
3
|
+
require_relative "../amount_parser"
|
4
|
+
|
5
|
+
#
|
6
|
+
# Factory class for building transactions.
|
7
|
+
#
|
8
|
+
# @usage
|
9
|
+
# txn = Qif::Transaction::Builder.new
|
10
|
+
# .set_date('10/06/1983')
|
11
|
+
# .set_amount('-10.0')
|
12
|
+
# .set_memo('debit $10')
|
13
|
+
# .add_split('jules')
|
14
|
+
# .set_split_memo('half to jules')
|
15
|
+
# .add_split_amount('-5.0')
|
16
|
+
# .build
|
17
|
+
#
|
18
|
+
class Qif::Transaction::Builder
|
19
|
+
include Builderable
|
20
|
+
|
21
|
+
def initialize(date_parser = ->(date) { Time.parse(date) })
|
22
|
+
@date_parser = date_parser
|
23
|
+
@splits = []
|
24
|
+
end
|
25
|
+
|
26
|
+
set_builder_method :date, :parse_date
|
27
|
+
set_builder_method :amount, ->(amt) { AmountParser.parse(amt) }
|
28
|
+
set_builder_method :status
|
29
|
+
set_builder_method :number
|
30
|
+
set_builder_method :payee
|
31
|
+
set_builder_method :memo
|
32
|
+
set_builder_method :category
|
33
|
+
|
34
|
+
def set_address(address)
|
35
|
+
@address = [@address, address].compact.join("\n")
|
36
|
+
self
|
37
|
+
end
|
38
|
+
alias :set_adress :set_address
|
39
|
+
|
40
|
+
def add_split(split)
|
41
|
+
Qif::Transaction::Split::Builder.new(self).set_split_category(split).tap do |builder|
|
42
|
+
@splits << builder
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def build
|
47
|
+
_build(Qif::Transaction.new).tap do |txn|
|
48
|
+
txn.address = @address
|
49
|
+
|
50
|
+
@splits.each do |split_builder|
|
51
|
+
txn.add_split(split_builder.build_split)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse_date(date)
|
59
|
+
@date_parser.call(date)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Builderable
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def builder_options(options)
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_builder_method(attribute, massager = nil)
|
12
|
+
options = @options || {}
|
13
|
+
method_name = ["set", options[:prefix], attribute].compact.join("_")
|
14
|
+
|
15
|
+
define_method(method_name) do |new_value|
|
16
|
+
@_builder_values ||= {}
|
17
|
+
|
18
|
+
unless massager.nil?
|
19
|
+
if massager.kind_of?(Symbol)
|
20
|
+
new_value = self.send(massager, new_value)
|
21
|
+
else
|
22
|
+
new_value = massager.call(new_value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@_builder_values[attribute] = new_value
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def _build(object)
|
33
|
+
(@_builder_values||{}).each do |attribute, value|
|
34
|
+
object.public_send("#{attribute}=", value)
|
35
|
+
end
|
36
|
+
|
37
|
+
object
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Qif::Transaction::Split
|
2
|
+
SUPPORTED_FIELDS = {
|
3
|
+
:category => {"S" => "Category in split (Category/Transfer/Class)" },
|
4
|
+
:memo => {"E" => "Memo in split" },
|
5
|
+
:amount => {"$" => "Dollar amount of split" },
|
6
|
+
}
|
7
|
+
|
8
|
+
attr_accessor :memo, :amount, :category
|
9
|
+
|
10
|
+
# Returns a representation of the split as it
|
11
|
+
# would appear in a qif file.
|
12
|
+
def to_s()
|
13
|
+
SUPPORTED_FIELDS.collect do |k,v|
|
14
|
+
next unless current = instance_variable_get("@#{k}")
|
15
|
+
field = v.keys.first
|
16
|
+
case current.class.to_s
|
17
|
+
when "Float"
|
18
|
+
"#{field}#{'%.2f'%current}"
|
19
|
+
when "String"
|
20
|
+
current.split("\n").collect {|x| "#{field}#{x}" }
|
21
|
+
else
|
22
|
+
"#{field}#{current}"
|
23
|
+
end
|
24
|
+
end.flatten.compact.join("\n")
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../builderable'
|
2
|
+
require_relative '../split'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Factory class for buliding splits. Do not construct this directly, rather use
|
6
|
+
# Qif::Transaction::Builder#add_split
|
7
|
+
#
|
8
|
+
class Qif::Transaction::Split::Builder
|
9
|
+
include Builderable
|
10
|
+
|
11
|
+
def initialize(transaction_builder)
|
12
|
+
@transaction_builder = transaction_builder
|
13
|
+
yield self if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_split(split_memo)
|
17
|
+
@transaction_builder.add_split(split_memo)
|
18
|
+
end
|
19
|
+
|
20
|
+
builder_options prefix: 'split'
|
21
|
+
set_builder_method :memo
|
22
|
+
set_builder_method :amount, ->(amt) { AmountParser.parse(amt) }
|
23
|
+
set_builder_method :category
|
24
|
+
|
25
|
+
def build_split
|
26
|
+
_build(Qif::Transaction::Split.new)
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(name, *args, &block)
|
30
|
+
@transaction_builder.send(name, *args, &block)
|
31
|
+
end
|
32
|
+
end
|
data/spec/lib/reader_spec.rb
CHANGED
@@ -107,4 +107,42 @@ describe Qif::Reader do
|
|
107
107
|
@instance = Qif::Reader.new(open('spec/fixtures/3_records_ddmmyyyy.qif'), 'mm/dd/yyyy')
|
108
108
|
@instance.size.should == 2
|
109
109
|
end
|
110
|
+
|
111
|
+
context 'when reading splits' do
|
112
|
+
let(:reader) { Qif::Reader.new(open('spec/fixtures/splits.qif'), 'd/m/yyyy') }
|
113
|
+
|
114
|
+
context 'the first transaction' do
|
115
|
+
let (:transaction) { reader.transactions[0] }
|
116
|
+
|
117
|
+
it 'should have the correct number of splits' do
|
118
|
+
expect(transaction.splits.size).to eq(2)
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'the first split' do
|
122
|
+
let (:split) { transaction.splits.first }
|
123
|
+
|
124
|
+
it { expect(split.category).to eq("[steve]") }
|
125
|
+
it { expect(split.memo).to eq("Cash") }
|
126
|
+
it { expect(split.amount).to eq(-253.64)}
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'the second split' do
|
130
|
+
let (:split) { transaction.splits[1] }
|
131
|
+
|
132
|
+
it { expect(split.category).to eq("Mort Int") }
|
133
|
+
it { expect(split.amount).to eq(746.36) }
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
context "when the transaction has splits first" do
|
139
|
+
let(:transaction) { reader.transactions[1] }
|
140
|
+
|
141
|
+
it 'should correctly add the amount to the transaction' do
|
142
|
+
expect(transaction.amount).to eq(75)
|
143
|
+
end
|
144
|
+
|
145
|
+
it { expect(transaction.splits.first.amount).to eq(23) }
|
146
|
+
end
|
147
|
+
end
|
110
148
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qif/transaction/builder'
|
3
|
+
require 'qif/transaction/split/builder'
|
4
|
+
|
5
|
+
describe Qif::Transaction::Builder do
|
6
|
+
let(:builder) { Qif::Transaction::Builder.new }
|
7
|
+
def split_builder
|
8
|
+
double(Qif::Transaction::Split::Builder).tap do |b|
|
9
|
+
allow(b).to receive(:set_split_category).and_return(b)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
def do_build
|
13
|
+
builder.build
|
14
|
+
end
|
15
|
+
|
16
|
+
context '#build' do
|
17
|
+
it 'should return a transaction object' do
|
18
|
+
expect(builder.build).to be_kind_of(Qif::Transaction)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with splits' do
|
22
|
+
it 'should call build_split on each of the splits' do
|
23
|
+
sb1 = split_builder
|
24
|
+
sb2 = split_builder
|
25
|
+
allow(Qif::Transaction::Split::Builder).to receive(:new).and_return(sb1, sb2)
|
26
|
+
expect(sb1).to receive(:build_split).ordered
|
27
|
+
expect(sb2).to receive(:build_split).ordered
|
28
|
+
builder.add_split("a")
|
29
|
+
builder.add_split("b")
|
30
|
+
builder.build
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should add the splits to the transaction' do
|
34
|
+
builder.add_split("a")
|
35
|
+
builder.add_split("b")
|
36
|
+
expect(builder.build.splits.count).to eq(2)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context '#set_date' do
|
42
|
+
it_should_behave_like 'builder method', :date, '1994-06-01', :set_date, Time.mktime(1994, 6, 1)
|
43
|
+
it 'should use a given date parser' do
|
44
|
+
b = Qif::Transaction::Builder.new(->(date) { date })
|
45
|
+
b.set_date('1994')
|
46
|
+
expect(b.build.date).to eq('1994')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context '#set_amount' do
|
51
|
+
it_should_behave_like 'builder method', :amount, '-1000.00', :set_amount, -1000.0
|
52
|
+
it 'should parse commas out of the amount' do
|
53
|
+
builder.set_amount('1,000,000')
|
54
|
+
expect(builder.build.amount).to eq(1_000_000)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context '#set_status' do
|
59
|
+
it_should_behave_like 'builder method', :status, 'X', :set_status, 'X'
|
60
|
+
end
|
61
|
+
|
62
|
+
context '#set_number' do
|
63
|
+
it_should_behave_like 'builder method', :number, '1005', :set_number, '1005'
|
64
|
+
end
|
65
|
+
|
66
|
+
context '#set_payee' do
|
67
|
+
it_should_behave_like 'builder method', :payee, 'Bank of Mortgage', :set_payee, 'Bank of Mortgage'
|
68
|
+
end
|
69
|
+
|
70
|
+
context '#set_memo' do
|
71
|
+
it_should_behave_like 'builder method', :memo, 'Some stuff that happened', :set_memo, 'Some stuff that happened'
|
72
|
+
end
|
73
|
+
|
74
|
+
context '#set_category' do
|
75
|
+
it_should_behave_like 'builder method', :category, 'Fishing', :set_category, 'Fishing'
|
76
|
+
end
|
77
|
+
|
78
|
+
context '#set_address' do
|
79
|
+
address = <<-EOA
|
80
|
+
P.O. Box 1234
|
81
|
+
Somewhereton
|
82
|
+
12345
|
83
|
+
EOA
|
84
|
+
|
85
|
+
it_should_behave_like 'builder method', :adress, address, :set_adress, address
|
86
|
+
|
87
|
+
context 'when called consecutively' do
|
88
|
+
it "should append to the address" do
|
89
|
+
builder
|
90
|
+
.set_address("Line 1")
|
91
|
+
.set_address("Line 2")
|
92
|
+
expect(do_build.address).to eq("Line 1\nLine 2")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "#add_split" do
|
98
|
+
it 'should create a new split builder' do
|
99
|
+
expect(Qif::Transaction::Split::Builder).to receive(:new).with(builder).and_return(split_builder)
|
100
|
+
builder.add_split("aaa")
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should set the category on the split builder' do
|
104
|
+
sb = split_builder
|
105
|
+
allow(Qif::Transaction::Split::Builder).to receive(:new).and_return(sb)
|
106
|
+
expect(sb).to receive(:set_split_category).with('aaaa')
|
107
|
+
builder.add_split('aaaa')
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should return a split builder" do
|
111
|
+
expect(builder.add_split('aaaa')).to be_kind_of(Qif::Transaction::Split::Builder)
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when called multiple times" do
|
115
|
+
it "should return multiple different builders" do
|
116
|
+
sb1 = builder.add_split("aaaa")
|
117
|
+
expect(builder.add_split("bbbb")).to_not eq(sb1)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -1,76 +1,59 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Qif::Transaction do
|
4
|
-
describe '::read' do
|
5
|
-
it 'should return a new transaction with all attributes set' do
|
6
|
-
t = Qif::Transaction.read(
|
7
|
-
'D' => Time.parse('1994-06-01'),
|
8
|
-
'T' => '-1000.00'.to_f,
|
9
|
-
'C' => 'X',
|
10
|
-
'N' => '1005',
|
11
|
-
'P' => 'Bank Of Mortgage',
|
12
|
-
'M' => 'aMemo',
|
13
|
-
'L' => 'aCategory',
|
14
|
-
# TODO Support correctly splits with an array of hash
|
15
|
-
# 'S' => '[linda]
|
16
|
-
#Mort Int',
|
17
|
-
# 'E' => 'Cash',
|
18
|
-
# '$' => '-253.64
|
19
|
-
#=746.36',
|
20
|
-
'A' => 'P.O. Box 27027
|
21
|
-
Tucson, AZ
|
22
|
-
85726',
|
23
|
-
'^' => nil
|
24
|
-
)
|
25
|
-
|
26
|
-
t.should be_a(Qif::Transaction)
|
27
|
-
t.date.should == Time.mktime(1994,6,1)
|
28
|
-
t.amount.should == -1000.00
|
29
|
-
t.status.should == 'X'
|
30
|
-
t.number.should == '1005'
|
31
|
-
t.payee.should == 'Bank Of Mortgage'
|
32
|
-
t.memo.should == 'aMemo'
|
33
|
-
t.category.should == 'aCategory'
|
34
|
-
t.adress.should == 'P.O. Box 27027
|
35
|
-
Tucson, AZ
|
36
|
-
85726'
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'should return nil if the date does not respond to strftime' do
|
40
|
-
Qif::Transaction.read('D' => 'hello').should be_nil
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
4
|
describe '#to_s' do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
)
|
5
|
+
let(:builder) do
|
6
|
+
Qif::Transaction::Builder.new
|
7
|
+
.set_date(Time.mktime(2010, 1, 2).to_s)
|
8
|
+
.set_amount('-10.0')
|
9
|
+
.set_category('Debit')
|
10
|
+
.set_memo('Supermarket')
|
11
|
+
.set_payee('abcde')
|
53
12
|
end
|
54
|
-
|
13
|
+
|
14
|
+
let(:txn) do
|
15
|
+
builder.build
|
16
|
+
end
|
17
|
+
|
18
|
+
subject { txn.to_s }
|
19
|
+
|
55
20
|
it 'should format the date in the format specified as D' do
|
56
|
-
|
57
|
-
|
21
|
+
expect(txn.to_s('dd/mm/yyyy')).to include('D02/01/2010')
|
22
|
+
expect(txn.to_s('mm/dd/yyyy')).to include('D01/02/2010')
|
58
23
|
end
|
59
24
|
|
60
25
|
it 'should put the amount in T' do
|
61
|
-
|
26
|
+
expect(subject).to include('T-10.00')
|
62
27
|
end
|
63
28
|
|
64
29
|
it 'should put the category in L' do
|
65
|
-
|
30
|
+
expect(subject).to include('LDebit')
|
66
31
|
end
|
67
32
|
|
68
33
|
it 'should put the description in M' do
|
69
|
-
|
34
|
+
expect(subject).to include('MSupermarket')
|
70
35
|
end
|
71
36
|
|
72
37
|
it 'should put the reference in P' do
|
73
|
-
|
38
|
+
expect(subject).to include('Pabcde')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'adds splits to the output' do
|
42
|
+
builder.add_split('split_1')
|
43
|
+
.set_split_memo('test split 1')
|
44
|
+
.set_split_amount('-5.0')
|
45
|
+
.add_split('split_2')
|
46
|
+
.set_split_memo('test split 2')
|
47
|
+
.set_split_amount('-5.0')
|
48
|
+
|
49
|
+
expect(subject).to include(<<SPLIT.strip)
|
50
|
+
Ssplit_1
|
51
|
+
Etest split 1
|
52
|
+
$-5.00
|
53
|
+
Ssplit_2
|
54
|
+
Etest split 2
|
55
|
+
$-5.0
|
56
|
+
SPLIT
|
74
57
|
end
|
75
58
|
end
|
76
59
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qif/transaction/split/builder'
|
3
|
+
|
4
|
+
describe Qif::Transaction::Split::Builder do
|
5
|
+
let(:parent) { double(Qif::Transaction::Builder) }
|
6
|
+
let(:builder) { Qif::Transaction::Split::Builder.new(parent) }
|
7
|
+
def do_build
|
8
|
+
builder.build_split
|
9
|
+
end
|
10
|
+
|
11
|
+
context '#add_split' do
|
12
|
+
it 'should call add_split on the parent' do
|
13
|
+
expect(parent).to receive(:add_split).with("12345")
|
14
|
+
described_class.new(parent).add_split("12345")
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return the new split builder' do
|
18
|
+
new_builder = double("split-builder")
|
19
|
+
allow(parent).to receive(:add_split).and_return(new_builder)
|
20
|
+
expect(described_class.new(parent).add_split(anything)).to eq(new_builder)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context '#set_memo' do
|
25
|
+
it_should_behave_like "builder method", :memo, 'Split', :set_split_memo
|
26
|
+
end
|
27
|
+
|
28
|
+
context '#set_amount' do
|
29
|
+
it_should_behave_like "builder method", :amount, '-10,000', :set_split_amount, -10000
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#set_category' do
|
33
|
+
it_should_behave_like 'builder method', :category, '[Cash]', :set_split_category
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when receiving an unknown message" do
|
37
|
+
it "should pass the message to the parent" do
|
38
|
+
expect(parent).to receive(:set_date).with('2012-12-31')
|
39
|
+
builder.set_date('2012-12-31')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should return the result of the message' do
|
43
|
+
allow(parent).to receive(:set_date).and_return(parent)
|
44
|
+
expect(builder.set_date(anything)).to eq(parent)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/lib/writer_spec.rb
CHANGED
@@ -2,98 +2,94 @@ require 'spec_helper'
|
|
2
2
|
require 'stringio'
|
3
3
|
|
4
4
|
describe Qif::Writer do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
5
|
+
let(:buffer) { "" }
|
6
|
+
let(:io) { StringIO.new(buffer) }
|
7
|
+
let(:instance) { Qif::Writer.new(io) }
|
8
|
+
let(:path) { "/tmp/test" }
|
10
9
|
|
11
10
|
describe '::open' do
|
12
11
|
before do
|
13
|
-
|
14
|
-
File.stub(:open).and_yield @io
|
12
|
+
File.stub(:open).and_yield io
|
15
13
|
end
|
16
14
|
|
17
15
|
it 'should yield a Qif::Writer' do
|
18
16
|
ran = false
|
19
|
-
Qif::Writer.open(
|
17
|
+
Qif::Writer.open(path) do |writer|
|
20
18
|
ran = true
|
21
19
|
writer.should be_a(Qif::Writer)
|
22
20
|
end
|
23
|
-
ran.should
|
21
|
+
ran.should eq true
|
24
22
|
end
|
25
23
|
|
26
24
|
it 'should write the transactions' do
|
27
25
|
date = Time.now
|
28
26
|
|
29
|
-
Qif::Writer.open(
|
27
|
+
Qif::Writer.open(path) do |writer|
|
30
28
|
writer << Qif::Transaction.new(:date => date, :amount => 10.0, :category => 'Credit')
|
31
29
|
end
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
buffer.should include('D%s' % date.strftime('%d/%m/%Y'))
|
32
|
+
buffer.should include('T10.0')
|
33
|
+
buffer.should include('LCredit')
|
36
34
|
end
|
37
35
|
|
38
36
|
it 'should perform a File.open on the given path' do
|
39
|
-
File.should_receive(:open).with(
|
40
|
-
Qif::Writer.open(
|
37
|
+
File.should_receive(:open).with(path, 'w')
|
38
|
+
Qif::Writer.open(path) do |writer|
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
42
|
+
|
44
43
|
describe '::new' do
|
45
44
|
it 'should yield a Qif::Writer' do
|
46
45
|
ran = false
|
47
|
-
Qif::Writer.new(
|
46
|
+
Qif::Writer.new(io) do |writer|
|
48
47
|
ran = true
|
49
48
|
writer.should be_a(Qif::Writer)
|
50
49
|
end
|
51
|
-
ran.
|
50
|
+
expect(ran).to eq true
|
52
51
|
end
|
53
52
|
|
54
53
|
it 'should write the transactions' do
|
55
54
|
date = Time.now
|
56
55
|
|
57
|
-
Qif::Writer.new(
|
56
|
+
Qif::Writer.new(io) do |writer|
|
58
57
|
writer << Qif::Transaction.new(:date => date, :amount => 10.0, :category => 'Credit')
|
59
58
|
end
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
buffer.should include('D%s' % date.strftime('%d/%m/%Y'))
|
61
|
+
buffer.should include('T10.0')
|
62
|
+
buffer.should include('LCredit')
|
64
63
|
end
|
65
64
|
|
66
65
|
it 'should perform a File.open on the given path' do
|
67
|
-
File.should_receive(:open).with(
|
68
|
-
Qif::Writer.open(
|
66
|
+
File.should_receive(:open).with(path, 'w')
|
67
|
+
Qif::Writer.open(path) do |writer|
|
69
68
|
end
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
72
|
describe '#write' do
|
73
|
+
let(:date) { Time.now }
|
74
|
+
|
78
75
|
it 'should write the header' do
|
79
|
-
|
80
|
-
|
76
|
+
instance.write
|
77
|
+
buffer.should include("!Type:Bank\n")
|
81
78
|
end
|
82
79
|
|
83
80
|
it 'should write any pending transactions' do
|
84
|
-
date
|
85
|
-
@instance << Qif::Transaction.new(:date => date, :amount => 10.0, :category => 'Credit')
|
81
|
+
instance << Qif::Transaction.new(:date => date, :amount => 10.0, :category => 'Credit')
|
86
82
|
|
87
|
-
|
88
|
-
|
89
|
-
|
83
|
+
buffer.should_not include('D%s' % date.strftime('%d/%m/%Y'))
|
84
|
+
instance.write
|
85
|
+
buffer.should include('D%s' % date.strftime('%d/%m/%Y'))
|
90
86
|
end
|
91
87
|
end
|
92
88
|
|
93
89
|
describe '#close' do
|
94
90
|
it 'should close the io stream' do
|
95
|
-
|
96
|
-
|
91
|
+
io.should_receive(:close)
|
92
|
+
instance.close
|
97
93
|
end
|
98
94
|
end
|
99
95
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
shared_context 'builder method' do |attribute, input, method = nil, expected = nil|
|
2
|
+
expected = input if expected.nil?
|
3
|
+
method = "set_#{attribute}" if method.nil?
|
4
|
+
|
5
|
+
it 'and set the #{attribute} on the transaction' do
|
6
|
+
builder.send(method, input)
|
7
|
+
expect(do_build.send(attribute)).to eq(expected)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'and return the builder' do
|
11
|
+
expect(builder.send(method, input)).to be_kind_of described_class
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qif
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Wells
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 3.0.0
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 3.0.0
|
27
27
|
description: A library for reading and writing quicken QIF files.
|
28
28
|
email: jemmyw@gmail.com
|
29
29
|
executables: []
|
@@ -38,15 +38,20 @@ extra_rdoc_files:
|
|
38
38
|
- lib/qif/transaction.rb
|
39
39
|
- lib/qif/writer.rb
|
40
40
|
files:
|
41
|
-
- README.rdoc
|
42
|
-
- LICENSE
|
43
41
|
- CHANGELOG
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
44
|
- Rakefile
|
45
|
+
- lib/qif.rb
|
46
|
+
- lib/qif/amount_parser.rb
|
45
47
|
- lib/qif/date_format.rb
|
46
48
|
- lib/qif/reader.rb
|
47
49
|
- lib/qif/transaction.rb
|
50
|
+
- lib/qif/transaction/builder.rb
|
51
|
+
- lib/qif/transaction/builderable.rb
|
52
|
+
- lib/qif/transaction/split.rb
|
53
|
+
- lib/qif/transaction/split/builder.rb
|
48
54
|
- lib/qif/writer.rb
|
49
|
-
- lib/qif.rb
|
50
55
|
- spec/fixtures/3_records_ddmmyy.qif
|
51
56
|
- spec/fixtures/3_records_ddmmyyyy.qif
|
52
57
|
- spec/fixtures/3_records_dmyy.qif
|
@@ -58,38 +63,42 @@ files:
|
|
58
63
|
- spec/fixtures/not_a_QIF_file.txt
|
59
64
|
- spec/fixtures/quicken_investment_account.qif
|
60
65
|
- spec/fixtures/quicken_non_investement_account.qif
|
66
|
+
- spec/fixtures/splits.qif
|
61
67
|
- spec/lib/date_format_spec.rb
|
62
68
|
- spec/lib/reader_spec.rb
|
69
|
+
- spec/lib/transaction_builder_spec.rb
|
63
70
|
- spec/lib/transaction_spec.rb
|
71
|
+
- spec/lib/transaction_split_builder_spec.rb
|
64
72
|
- spec/lib/writer_spec.rb
|
65
73
|
- spec/spec.opts
|
66
74
|
- spec/spec_helper.rb
|
75
|
+
- spec/support/shared/builder_method.rb
|
67
76
|
homepage: http://qif.github.com/qif
|
68
77
|
licenses: []
|
69
78
|
metadata: {}
|
70
79
|
post_install_message:
|
71
80
|
rdoc_options:
|
72
|
-
- --line-numbers
|
73
|
-
- --inline-source
|
74
|
-
- --title
|
81
|
+
- "--line-numbers"
|
82
|
+
- "--inline-source"
|
83
|
+
- "--title"
|
75
84
|
- Qif
|
76
|
-
- --main
|
85
|
+
- "--main"
|
77
86
|
- README.rdoc
|
78
87
|
require_paths:
|
79
88
|
- lib
|
80
89
|
required_ruby_version: !ruby/object:Gem::Requirement
|
81
90
|
requirements:
|
82
|
-
- -
|
91
|
+
- - ">="
|
83
92
|
- !ruby/object:Gem::Version
|
84
93
|
version: '0'
|
85
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
95
|
requirements:
|
87
|
-
- -
|
96
|
+
- - ">="
|
88
97
|
- !ruby/object:Gem::Version
|
89
98
|
version: '1.2'
|
90
99
|
requirements: []
|
91
100
|
rubyforge_project: qif
|
92
|
-
rubygems_version: 2.1
|
101
|
+
rubygems_version: 2.4.5.1
|
93
102
|
signing_key:
|
94
103
|
specification_version: 4
|
95
104
|
summary: A library for reading and writing quicken QIF files.
|