qif 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ v0.2 Added documentation
2
+
3
+ v0.1 First version
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Jeremy Wells
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ Rakefile
5
+ lib/qif.rb
6
+ lib/qif/date_format.rb
7
+ lib/qif/reader.rb
8
+ lib/qif/transaction.rb
9
+ lib/qif/writer.rb
10
+ spec/fixtures/3_records_ddmmyyyy.qif
11
+ spec/fixtures/3_records_mmddyyyy.qif
12
+ spec/lib/date_format_spec.rb
13
+ spec/lib/reader_spec.rb
14
+ spec/lib/transaction_spec.rb
15
+ spec/lib/writer_spec.rb
16
+ spec/spec.opts
17
+ spec/spec_helper.rb
@@ -0,0 +1,6 @@
1
+ require 'echoe'
2
+ Echoe.new('qif') do |gem|
3
+ gem.author = "Jeremy Wells"
4
+ gem.summary = "A library for reading and writing quicken QIF files."
5
+ gem.email = "jeremy@boost.co.nz"
6
+ end
@@ -0,0 +1,5 @@
1
+ $: << File.dirname(__FILE__)
2
+
3
+ require 'qif/reader'
4
+ require 'qif/writer'
5
+ require 'qif/transaction'
@@ -0,0 +1,47 @@
1
+ require 'time'
2
+
3
+ module Qif
4
+ class DateFormat
5
+ attr_reader :format
6
+
7
+ def initialize(format = 'dd/mm/yyyy')
8
+ @format = format
9
+ end
10
+
11
+ def parse(date)
12
+ regex = convert_format_to_regex
13
+ order = date_order
14
+
15
+ if match = regex.match(date)
16
+ Time.mktime(*%w(y m d).map{|t| match[order.index(t) + 1].to_i })
17
+ end
18
+ end
19
+
20
+ def format(date)
21
+ date.strftime(convert_format_to_strftime)
22
+ end
23
+
24
+ private
25
+
26
+ def date_order
27
+ %w(d m y).sort{|a,b| @format.index(a) <=> @format.index(b) }
28
+ end
29
+
30
+ def convert_format_to_strftime
31
+ format = @format.dup
32
+ format.gsub!('dd', '%d')
33
+ format.gsub!('mm', '%m')
34
+ format.gsub!('yyyy', '%Y')
35
+ format
36
+ end
37
+
38
+ def convert_format_to_regex
39
+ format = @format.dup
40
+ format.gsub!('dd', '([0-3][0-9])')
41
+ format.gsub!('mm', '([0-1][0-9])')
42
+ format.gsub!('yyyy', '([1-2][0-9]{3})')
43
+
44
+ /#{format}/mi
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,122 @@
1
+ require 'stringio'
2
+ require 'qif/date_format'
3
+ require 'qif/transaction'
4
+
5
+ module Qif
6
+ # The Qif::Reader class reads a qif file and provides access to
7
+ # the transactions as Qif::Transaction objects.
8
+ #
9
+ # Usage:
10
+ #
11
+ # reader = Qif::Reader.new(open('/path/to/qif'), 'dd/mm/yyyy')
12
+ # reader.each do |transaction|
13
+ # puts transaction.date.strftime('%d/%m/%Y')
14
+ # puts transaction.amount.to_s
15
+ # end
16
+ class Reader
17
+ include Enumerable
18
+
19
+ # Create a new Qif::Reader object. The data argument must be
20
+ # either an IO object or a String containing the Qif file data.
21
+ #
22
+ # The format argument specifies the date format in the file. This
23
+ # defaults to 'dd/mm/yyyy', but also accepts 'mm/dd/yyyy'.
24
+ def initialize(data, format = 'dd/mm/yyyy')
25
+ @format = DateFormat.new(format)
26
+ @data = data.respond_to?(:read) ? data : StringIO.new(data.to_s)
27
+ read_header
28
+ reset
29
+ end
30
+
31
+ # Return an array of Qif::Transaction objects from the Qif file. This
32
+ # method reads the whole file before returning, so it may not be suitable
33
+ # for very large qif files.
34
+ def transactions
35
+ read_all_transactions
36
+ transaction_cache
37
+ end
38
+
39
+ # Call a block with each Qif::Transaction from the Qif file. This
40
+ # method yields each transaction as it reads the file so it is better
41
+ # to use this than #transactions for large qif files.
42
+ #
43
+ # reader.each do |transaction|
44
+ # puts transaction.amount
45
+ # end
46
+ def each(&block)
47
+ reset
48
+
49
+ while transaction = next_transaction
50
+ yield transaction
51
+ end
52
+ end
53
+
54
+ # Return the number of transactions in the qif file.
55
+ def size
56
+ read_all_transactions
57
+ transaction_cache.size
58
+ end
59
+ alias length size
60
+
61
+ private
62
+
63
+ def read_all_transactions
64
+ while next_transaction; end
65
+ end
66
+
67
+ def transaction_cache
68
+ @transaction_cache ||= []
69
+ end
70
+
71
+ def reset
72
+ @index = -1
73
+ end
74
+
75
+ def next_transaction
76
+ @index += 1
77
+
78
+ if transaction = transaction_cache[@index]
79
+ transaction
80
+ else
81
+ read_transaction
82
+ end
83
+ end
84
+
85
+ def read_header
86
+ @header = read_record
87
+ end
88
+
89
+ def read_transaction
90
+ if record = read_record
91
+ transaction = Transaction.read(record)
92
+ cache_transaction(transaction) if transaction
93
+ end
94
+ end
95
+
96
+ def cache_transaction(transaction)
97
+ transaction_cache[@index] = transaction
98
+ end
99
+
100
+ def read_record
101
+ record = {}
102
+
103
+ begin
104
+ line = @data.readline
105
+ key = line[0,1]
106
+
107
+ record[key] = line[1..-1].strip
108
+
109
+ if date = @format.parse(record[key])
110
+ record[key] = date
111
+ end
112
+ end until line =~ /^\^/
113
+
114
+ record
115
+ rescue EOFError => e
116
+ @data.close
117
+ nil
118
+ rescue Exception => e
119
+ nil
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,40 @@
1
+ require 'qif/date_format'
2
+
3
+ module Qif
4
+ # The Qif::Transaction class represents transactions in a qif file.
5
+ class Transaction
6
+ attr_accessor :date, :amount, :name, :description, :reference
7
+
8
+ def self.read(record) #::nodoc
9
+ return nil unless record['D'].respond_to?(:strftime)
10
+
11
+ Transaction.new(
12
+ :date => record['D'],
13
+ :amount => record['T'].to_f,
14
+ :name => record['L'],
15
+ :description => record['M'],
16
+ :reference => record['P']
17
+ )
18
+ end
19
+
20
+ def initialize(attributes = {})
21
+ @date = attributes[:date]
22
+ @amount = attributes[:amount]
23
+ @name = attributes[:name]
24
+ @description = attributes[:description]
25
+ @reference = attributes[:reference]
26
+ end
27
+
28
+ # Returns a representation of the transaction as it
29
+ # would appear in a qif file.
30
+ def to_s(format = 'dd/mm/yyyy')
31
+ {
32
+ 'D' => DateFormat.new(format).format(date),
33
+ 'T' => '%.2f' % amount,
34
+ 'L' => name,
35
+ 'M' => description,
36
+ 'P' => reference
37
+ }.map{|k,v| "#{k}#{v}" }.join("\n")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,80 @@
1
+ require 'qif/date_format'
2
+ require 'qif/transaction'
3
+
4
+ module Qif
5
+ # The Qif::Writer class takes Qif::Transaction objects and outputs
6
+ # a Qif file.
7
+ #
8
+ # Usage:
9
+ # Qif::Writer.open('/path/to/new/qif') do |writer|
10
+ # writer << Qif::Transaction.new(
11
+ # :date => Time.now,
12
+ # :amount => 10.0,
13
+ # :name => 'Credit'
14
+ # )
15
+ # end
16
+ class Writer
17
+ attr_accessor :type, :format
18
+
19
+ # Open a qif file for writing and yield a Qif::Writer instance.
20
+ # For parameters see #new.
21
+ def self.open(path, type = 'Bank', format = 'dd/mm/yyyy')
22
+ File.open(path, 'w') do |file|
23
+ writer = self.new(file, type, format)
24
+ yield writer
25
+ writer.write
26
+ end
27
+ end
28
+
29
+ # Create a new Qif::Writer. Expects an IO object or a filepath.
30
+ #
31
+ # Parameters:
32
+ #
33
+ # * <tt>io</tt> - An IO object or filepath
34
+ # * <tt>type</tt> - Used to write the header, defaults to 'Bank'
35
+ # * <tt>format</tt> - The format of dates in the qif file, defaults to 'dd/mm/yyyy'. Also accepts 'mm/dd/yyyy'
36
+ def initialize(io, type = 'Bank', format = 'dd/mm/yyyy')
37
+ @io = io.respond_to?(:write) ? io : File.open(io, 'w')
38
+ @type = type
39
+ @format = format
40
+ @transactions = []
41
+ end
42
+
43
+ # Add a transaction for writing
44
+ def <<(transaction)
45
+ @transactions << transaction
46
+ end
47
+
48
+ # Write the qif file
49
+ def write
50
+ write_header
51
+ write_transactions
52
+ end
53
+
54
+ # Close the qif file
55
+ def close
56
+ @io.close
57
+ end
58
+
59
+ private
60
+
61
+ def write_header
62
+ write_record('!Type:%s' % @type)
63
+ end
64
+
65
+ def write_transactions
66
+ @transactions.each do |t|
67
+ write_transaction(t)
68
+ end
69
+ end
70
+
71
+ def write_transaction(transaction)
72
+ write_record(transaction.to_s(@format))
73
+ end
74
+
75
+ def write_record(data)
76
+ @io.write(data)
77
+ @io.write("\n^\n")
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{qif}
5
+ s.version = "0.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jeremy Wells"]
9
+ s.date = %q{2010-01-22}
10
+ s.description = %q{A library for reading and writing quicken QIF files.}
11
+ s.email = %q{jeremy@boost.co.nz}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "lib/qif.rb", "lib/qif/date_format.rb", "lib/qif/reader.rb", "lib/qif/transaction.rb", "lib/qif/writer.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "lib/qif.rb", "lib/qif/date_format.rb", "lib/qif/reader.rb", "lib/qif/transaction.rb", "lib/qif/writer.rb", "spec/fixtures/3_records_ddmmyyyy.qif", "spec/fixtures/3_records_mmddyyyy.qif", "spec/lib/date_format_spec.rb", "spec/lib/reader_spec.rb", "spec/lib/transaction_spec.rb", "spec/lib/writer_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "qif.gemspec"]
14
+ s.homepage = %q{}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Qif"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{qif}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{A library for reading and writing quicken QIF files.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ !Type:Bank
2
+ ^
3
+ D01/01/2010
4
+ T-10.00
5
+ LDebit
6
+ PDescription
7
+ MReference
8
+ ^
9
+ D01/06/2010
10
+ T-20.00
11
+ LDebit
12
+ PDescription
13
+ MReference
14
+ ^
15
+ D29/12/2010
16
+ T30.00
17
+ LCredit
18
+ PDescription
19
+ MReference
20
+ ^
@@ -0,0 +1,20 @@
1
+ !Type:Bank
2
+ ^
3
+ D01/01/2010
4
+ T-10.00
5
+ LDebit
6
+ PDescription
7
+ MReference
8
+ ^
9
+ D06/01/2010
10
+ T-20.00
11
+ LDebit
12
+ PDescription
13
+ MReference
14
+ ^
15
+ D12/29/2010
16
+ T30.00
17
+ LCredit
18
+ PDescription
19
+ MReference
20
+ ^
File without changes
@@ -0,0 +1,60 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Qif::Reader do
4
+ %w(dd/mm/yyyy mm/dd/yyyy).each do |format|
5
+ before do
6
+ @file = 'spec/fixtures/3_records_%s.qif' % format.gsub('/', '')
7
+ @instance = Qif::Reader.new(open(@file), format)
8
+ end
9
+
10
+ it 'should have 3 records' do
11
+ @instance.size.should == 3
12
+ end
13
+
14
+ it 'should have a debit of $10 on the 1st of January 2010' do
15
+ transaction = @instance.transactions.detect{|t| t.date == Time.mktime(2010, 1, 1)}
16
+ transaction.should_not be_nil
17
+ transaction.name.should == 'Debit'
18
+ transaction.amount.should == -10.0
19
+ end
20
+
21
+ it 'should have a debit of $20 on the 1st of June 1020' do
22
+ transaction = @instance.transactions.detect{|t| t.date == Time.mktime(2010, 6, 1)}
23
+ transaction.should_not be_nil
24
+ transaction.name.should == 'Debit'
25
+ transaction.amount.should == -20.0
26
+ end
27
+
28
+ it 'should have a credit of $30 on the 29th of December 2010' do
29
+ transaction = @instance.transactions.detect{|t| t.date == Time.mktime(2010, 12, 29)}
30
+ transaction.should_not be_nil
31
+ transaction.name.should == 'Credit'
32
+ transaction.amount.should == 30.0
33
+ end
34
+
35
+ describe '#each' do
36
+ it 'should yield each transaction' do
37
+ transactions = []
38
+ @instance.each do |t|
39
+ transactions << t
40
+ end
41
+ transactions.should == @instance.transactions
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'should initialize with an io object' do
47
+ @instance = Qif::Reader.new(open('spec/fixtures/3_records_ddmmyyyy.qif'))
48
+ @instance.size.should == 3
49
+ end
50
+
51
+ it 'should initialize with data in a string' do
52
+ @instance = Qif::Reader.new(File.read('spec/fixtures/3_records_ddmmyyyy.qif'))
53
+ @instance.size.should == 3
54
+ end
55
+
56
+ it 'should reject transactions whose date does not match the given date format' do
57
+ @instance = Qif::Reader.new(open('spec/fixtures/3_records_ddmmyyyy.qif'), 'mm/dd/yyyy')
58
+ @instance.size.should == 2
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Qif::Transaction do
4
+ describe '::read' do
5
+ it 'should return a new transaction' do
6
+ date = Time.now
7
+ t = Qif::Transaction.read(
8
+ 'D' => date,
9
+ 'T' => '10.0',
10
+ 'L' => 'Credit',
11
+ 'M' => 'Supermarket',
12
+ 'P' => 'abcde'
13
+ )
14
+ t.should be_a(Qif::Transaction)
15
+ t.date.should == date
16
+ t.amount.should == 10.0
17
+ t.name.should == 'Credit'
18
+ t.description.should == 'Supermarket'
19
+ t.reference.should == 'abcde'
20
+ end
21
+
22
+ it 'should return nil if the date does not respond to strftime' do
23
+ Qif::Transaction.read('D' => 'hello').should be_nil
24
+ end
25
+ end
26
+
27
+ describe '#to_s' do
28
+ before do
29
+ @instance = Qif::Transaction.new(
30
+ :date => Time.mktime(2010, 1, 2),
31
+ :amount => -10.0,
32
+ :name => 'Debit',
33
+ :description => 'Supermarket',
34
+ :reference => 'abcde'
35
+ )
36
+ end
37
+
38
+ it 'should format the date in the format specified as D' do
39
+ @instance.to_s('dd/mm/yyyy').should include('D02/01/2010')
40
+ @instance.to_s('mm/dd/yyyy').should include('D01/02/2010')
41
+ end
42
+
43
+ it 'should put the amount in T' do
44
+ @instance.to_s.should include('T-10.00')
45
+ end
46
+
47
+ it 'should put the name in L' do
48
+ @instance.to_s.should include('LDebit')
49
+ end
50
+
51
+ it 'should put the description in M' do
52
+ @instance.to_s.should include('MSupermarket')
53
+ end
54
+
55
+ it 'should put the reference in P' do
56
+ @instance.to_s.should include('Pabcde')
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec/spec_helper'
2
+ require 'stringio'
3
+
4
+ describe Qif::Writer do
5
+ before do
6
+ @buffer = ''
7
+ @io = StringIO.new(@buffer)
8
+ @instance = Qif::Writer.new(@io)
9
+ end
10
+
11
+ describe '::open' do
12
+ before do
13
+ @path = '/tmp/test'
14
+ File.stub!(:open).and_yield @io
15
+ end
16
+
17
+ it 'should yield a Qif::Writer' do
18
+ ran = false
19
+ Qif::Writer.open(@path) do |writer|
20
+ ran = true
21
+ writer.should be_a(Qif::Writer)
22
+ end
23
+ ran.should be_true
24
+ end
25
+
26
+ it 'should write the transactions' do
27
+ date = Time.now
28
+
29
+ Qif::Writer.open(@path) do |writer|
30
+ writer << Qif::Transaction.new(:date => date, :amount => 10.0, :name => 'Credit')
31
+ end
32
+
33
+ @buffer.should include('D%s' % date.strftime('%d/%m/%Y'))
34
+ @buffer.should include('T10.0')
35
+ @buffer.should include('LCredit')
36
+ end
37
+
38
+ it 'should perform a File.open on the given path' do
39
+ File.should_receive(:open).with(@path, 'w')
40
+ Qif::Writer.open(@path) do |writer|
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#write' do
46
+ it 'should write the header' do
47
+ @instance.write
48
+ @buffer.should include("!Type:Bank\n^")
49
+ end
50
+
51
+ it 'should write any pending transactions' do
52
+ date = Time.now
53
+ @instance << Qif::Transaction.new(:date => date, :amount => 10.0, :name => 'Credit')
54
+
55
+ @buffer.should_not include('D%s' % date.strftime('%d/%m/%Y'))
56
+ @instance.write
57
+ @buffer.should include('D%s' % date.strftime('%d/%m/%Y'))
58
+ end
59
+ end
60
+
61
+ describe '#close' do
62
+ it 'should close the io stream' do
63
+ @io.should_receive(:close)
64
+ @instance.close
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ --reverse
2
+ --color
3
+ --debugger
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../lib/qif'
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qif
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Wells
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-22 00:00:00 +13:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A library for reading and writing quicken QIF files.
17
+ email: jeremy@boost.co.nz
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - CHANGELOG
24
+ - LICENSE
25
+ - lib/qif.rb
26
+ - lib/qif/date_format.rb
27
+ - lib/qif/reader.rb
28
+ - lib/qif/transaction.rb
29
+ - lib/qif/writer.rb
30
+ files:
31
+ - CHANGELOG
32
+ - LICENSE
33
+ - Manifest
34
+ - Rakefile
35
+ - lib/qif.rb
36
+ - lib/qif/date_format.rb
37
+ - lib/qif/reader.rb
38
+ - lib/qif/transaction.rb
39
+ - lib/qif/writer.rb
40
+ - spec/fixtures/3_records_ddmmyyyy.qif
41
+ - spec/fixtures/3_records_mmddyyyy.qif
42
+ - spec/lib/date_format_spec.rb
43
+ - spec/lib/reader_spec.rb
44
+ - spec/lib/transaction_spec.rb
45
+ - spec/lib/writer_spec.rb
46
+ - spec/spec.opts
47
+ - spec/spec_helper.rb
48
+ - qif.gemspec
49
+ has_rdoc: true
50
+ homepage: ""
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --line-numbers
56
+ - --inline-source
57
+ - --title
58
+ - Qif
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "1.2"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project: qif
76
+ rubygems_version: 1.3.5
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: A library for reading and writing quicken QIF files.
80
+ test_files: []
81
+