rledger 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7147808ab081244140f4404b9c6a289a9595737a
4
+ data.tar.gz: f7f0128c167f34e2f93450877a5cbd9668ee020c
5
+ SHA512:
6
+ metadata.gz: bec37a601ef9b3d8a722165d7823632887e9dc6ab74416cddc10bd4beffffd415af00375ed552bf1ec7a485f024daebce076ed7d1679de81fb94c5c6e61877f7
7
+ data.tar.gz: 9b055a9d990765af6b66fdbfc01775bcfd6fa751c70683b229df0115b4cc665d21e4444bd599bb0c5240404adc9e8b115c150bb508445d723ed99ed7c189ca15
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *~
19
+ .DS_Store
20
+ #.*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rledger.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Adolfo Villafiorita
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Rledger
2
+
3
+ Rledger reads ledgers in the [Ledger CLI](http://www.ledger-cli.org)
4
+ format and performs simple operations on them.
5
+
6
+ Only the basic format is recognized: **do not expect this gem to
7
+ perform well with complex ledger files.** The goal, in fact, is not
8
+ that of building a clone of [ledger](http://www.ledger-cli.org), but
9
+ being able to read simple ledger files in ruby.
10
+
11
+ Please signal any bug or issues you might find through the [Github
12
+ Repository](http://github.io/avillafiorita/rledger)
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'rledger'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install rledger
27
+
28
+ ## Usage
29
+
30
+ Try the following commands on a ledger file:
31
+
32
+ rledger --command statement ledger.txt
33
+ rledger --command balance ledger.txt
34
+
35
+ The mileage of the gem will vary, according to the complexity of the
36
+ format.
37
+
38
+ Please signal any bug report through Github.
39
+
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/rledger ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/rledger'
4
+
5
+ Rledger::Runner.run(ARGV)
6
+
@@ -0,0 +1,71 @@
1
+ require 'bigdecimal'
2
+
3
+ module Rledger
4
+ # Amount stores an amount, as an Hash currency => value.
5
+ #
6
+ # The peculiar storage simplifies operations on multiple currencies
7
+ # (adding different currencies is equivalent to merging hashes)
8
+ #
9
+ class Amount
10
+ def initialize
11
+ @amount = Hash.new
12
+ end
13
+
14
+ def parse(s)
15
+ currency = "([a-zA-Z$]+|)"
16
+ amount = "(-?[0-9]+\\.?[0-9]+|)"
17
+ ob = "[\t ]*"
18
+
19
+ match = Regexp.new(currency + ob + amount).match(s)
20
+
21
+ if match
22
+ currency = match[1]
23
+ amount = match[2] == "" ? BigDecimal.new('0.00') : BigDecimal.new(match[2])
24
+ @amount[currency] = amount
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ @amount.keys.collect { |key| key + " " + "%.2f" % @amount[key] }.join(", ")
33
+ end
34
+
35
+ def add!(other_amount)
36
+ @amount.merge!(other_amount.hash) { |key, oldval, newval| @amount[key] = oldval + newval }
37
+ self
38
+ end
39
+
40
+ def multiply!(factor)
41
+ @amount.keys.map { |x| @amount[x] = factor * @amount[x] }
42
+ self
43
+ end
44
+
45
+ def single_currency?
46
+ @amount.keys.size == 1
47
+ end
48
+
49
+ def currencies
50
+ @amount.keys
51
+ end
52
+
53
+ def amount_of currency
54
+ @amount[currency]
55
+ end
56
+
57
+ # good only if single amount
58
+ def amount
59
+ @amount[@amount.keys[0]]
60
+ end
61
+
62
+ # good only if single currency
63
+ def currency
64
+ @amount.keys[0]
65
+ end
66
+
67
+ def hash
68
+ @amount
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'amount'
2
+
3
+ module Rledger
4
+ # Entry contains the specification of an operation on an account
5
+ #
6
+ # Entries are the basis for Transactions.
7
+ #
8
+ # We allow for operations with no amount, in which case the amount
9
+ # is computed from remaining entries in the Transaction (see the
10
+ # class Transaction)
11
+ #
12
+ class Post
13
+ attr_accessor :voice, :amount, :comment
14
+
15
+ def initialize
16
+ @voice = ""
17
+ @amount = Amount.new
18
+ @comment = ""
19
+ end
20
+
21
+ def parse(s)
22
+ voice = "([a-zA-Z:-_!@']+)"
23
+ amount = "([^;]*|)" # a regexp which allows to move to the next token
24
+ comment = ";? ?(.*|)"
25
+ b = "[\t ]+"
26
+ ob = "[\t ]*"
27
+
28
+ match = Regexp.new(b + voice + ob + amount + ob + comment).match(s)
29
+
30
+ if match
31
+ @voice = match[1]
32
+ @amount.parse(match[2])
33
+ @derived = match[2] == ""
34
+ @comment = match[3]
35
+ true
36
+ else
37
+ false
38
+ end
39
+ end
40
+
41
+ def derived?
42
+ @derived
43
+ end
44
+
45
+ def to_s
46
+ "\t#{@voice}#{amount_to_s}\t#{comment_to_s}\n"
47
+ end
48
+
49
+ private
50
+
51
+ def amount_to_s
52
+ derived? ? "" : "\t#{@amount.to_s}"
53
+ end
54
+
55
+ def comment_to_s
56
+ @comment == "" ? "" : "; #{@comment}"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ module Rledger
2
+ # Reconciliation maintains the reconciliation status of a Transaction
3
+ class Reconciliation
4
+ attr_reader :status
5
+
6
+ def pending!
7
+ @status = '!'
8
+ end
9
+
10
+ def pending?
11
+ @status == '!'
12
+ end
13
+
14
+ def discarded!
15
+ @status = '?'
16
+ end
17
+
18
+ def discarded?
19
+ @status == '?'
20
+ end
21
+
22
+ def reconciled!
23
+ @status = '*'
24
+ end
25
+
26
+ def reconciled?
27
+ @status == '*'
28
+ end
29
+
30
+ def unknown!
31
+ @status = ''
32
+ end
33
+
34
+ def unknown?
35
+ @status == ''
36
+ end
37
+
38
+ def to_s
39
+ @status
40
+ end
41
+
42
+ def parse(s)
43
+ case s
44
+ when "*" then reconciled!
45
+ when "!" then pending!
46
+ when "?" then discarded!
47
+ when "" then unknown!
48
+ else unknown!
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,152 @@
1
+ require 'Date'
2
+ require 'bigdecimal'
3
+
4
+ require_relative 'reconciliation'
5
+ require_relative 'post'
6
+
7
+ module Rledger
8
+ # Transaction base contains the basic elements of any transaction
9
+ class TransactionBase
10
+ attr_accessor :date, :id, :reconciliation, :payee
11
+
12
+ # initialize variables with the correct types
13
+ def initialize
14
+ @date = Date.new
15
+ @id = ""
16
+ @reconciliation = Reconciliation.new
17
+ @payee = ""
18
+ end
19
+
20
+ def to_s
21
+ "#{@date.strftime("%d/%m/%Y")}#{rec_to_s}#{id_to_s}#{payee_to_s}\n"
22
+ end
23
+
24
+ def to_qif(account)
25
+ "D#{@date}\n" +
26
+ (@payee != "" ? "P#{@payee}\n" : "") +
27
+ (@id != "" ? "N#{@id}\n" : "")
28
+ end
29
+
30
+ def parse(s)
31
+ # regular expressions of various components of a transaction base
32
+ date = "([0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9])"
33
+ ob = "[\t ]*"
34
+ rec = "([!?*]|)"
35
+ id = "(\\([^)]+\\)|)"
36
+ payee = "(.*)"
37
+
38
+ match = Regexp.new(date + ob + rec + ob + id + ob + payee).match(s)
39
+ if match
40
+ @date = s_to_date(match[1]) # TODO: replace with Chronic o Date.parse
41
+ @reconciliation.parse(match[2])
42
+ @id = match[3] == "" ? "" : match[3][1..-2] # so that id is always a string
43
+ @payee = match[4]
44
+ true
45
+ else
46
+ false
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def id_to_s
53
+ @id == "" ? "" : "\t(#{@id})"
54
+ end
55
+
56
+ def rec_to_s
57
+ @reconciliation.unknown? ? "" : "\t#{@reconciliation.to_s}"
58
+ end
59
+
60
+ def payee_to_s
61
+ @payee == "" ? "" : "\t#{@payee}"
62
+ end
63
+
64
+ def s_to_date(s)
65
+ day, month, year = s.split("/");
66
+ Date.civil(year.to_i, month.to_i, day.to_i)
67
+ end
68
+
69
+ end
70
+
71
+ # A transaction models a multiple entry transactions
72
+ # It should contain a minimum of two entries
73
+ class Transaction < TransactionBase
74
+ attr_accessor :posts
75
+
76
+ def initialize
77
+ super
78
+ @posts = []
79
+ end
80
+
81
+ def self.read(filename)
82
+ transactions = []
83
+ file_string = IO.read(filename)
84
+ array = file_string.split(/\n([\t ]*\n)+/) # ignore empty lines (possibly with blanks)
85
+ array.each do |record|
86
+ if record =~ /^[0-9]/
87
+ t = Transaction.new
88
+ t.parse(record)
89
+ transactions << t
90
+ end
91
+ end
92
+ transactions
93
+ end
94
+
95
+ def parse(s)
96
+ lines = s.split("\n")
97
+ super(lines[0]) # call super.parse!
98
+ lines[1..-1].each do |line|
99
+ p = Post.new
100
+ p.parse(line)
101
+ @posts << p
102
+ end
103
+
104
+ # fix the derived amount in the derived entry (if there is one)
105
+ # TODO Raise a lot of errors:
106
+ # - the other entries are not in a single currency
107
+ # - there is more than a derived entry
108
+ p = @posts.select { |x| x.derived? }
109
+ if p[0] != nil then
110
+ p[0].amount = total_no_derived.multiply!(BigDecimal.new(-1))
111
+ end
112
+ end
113
+
114
+ def total_no_derived
115
+ a = Amount.new
116
+ @posts.each do |e|
117
+ a.add!(e.amount) if not e.derived?
118
+ end
119
+ a
120
+ end
121
+
122
+ def posts_with voice
123
+ @posts.select { |e| e.voice == voice }
124
+ end
125
+
126
+ def posts_without voice
127
+ @posts.select { |e| e.voice != voice }
128
+ end
129
+
130
+ def contains? voice
131
+ posts_with(voice).size > 0
132
+ end
133
+
134
+ def to_s
135
+ accumulator = ""
136
+ @posts.each { |post| accumulator << post.to_s }
137
+ super.to_s + accumulator + "\n"
138
+ end
139
+
140
+ def to_qif(account)
141
+ accumulator = super.to_s
142
+ @posts.each { |post|
143
+ accumulator <<
144
+ "L[#{post.item}]\n" +
145
+ "$#{post.amount}\n" +
146
+ "M#{post.comment}\n"
147
+ }
148
+ accumulator + "^"
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,42 @@
1
+ module Rledger
2
+ # Balance computes the balance of leaves (at the moment)
3
+ class Balance
4
+ def initialize(transactions)
5
+ @transactions = transactions
6
+ end
7
+
8
+ def compute
9
+ @balance = Hash.new
10
+
11
+ @transactions.each do |t|
12
+ t.posts.each do |p|
13
+ elements = disaggregate p.voice
14
+
15
+ elements.each do |element|
16
+ @balance[element] ? @balance[element].add!(p.amount) : @balance[element] = p.amount
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ @balance.keys.sort.map { |k| puts "#{k} #{@balance[k]}" }
24
+ ""
25
+ end
26
+
27
+ private
28
+
29
+ # return an array of all the components of a voice
30
+ # Expenses:Dining:Fish -> [Expenses, Expenses:Dining, Expenses:Dining:Fish ]
31
+ def disaggregate voice
32
+ output = []
33
+ aggregator = ""
34
+ voice.split(":").each do |element|
35
+ aggregator = aggregator == "" ? element : aggregator + ":" + element
36
+ output << aggregator
37
+ end
38
+ output
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,74 @@
1
+ module Rledger
2
+ class Statement
3
+ def initialize(transactions)
4
+ @transactions = transactions
5
+ end
6
+
7
+ def compute voice
8
+ @statement = []
9
+ cumulative = Amount.new
10
+
11
+ @transactions.each do |transaction|
12
+ if transaction.contains? voice
13
+
14
+ # the cumulative is computed by adding all posts with voice
15
+ #
16
+ # this is to ensure cumulative is in the same currency of
17
+ # voice (if we used the currency in the posts not containing
18
+ # voice to compute the cumulative, we would risk using
19
+ # different currencies, if the voice appears in multi
20
+ # currency transactions
21
+ transaction.posts_with(voice).each do |post|
22
+ cumulative.add!(post.amount)
23
+ end
24
+
25
+ # This is to generate a new amount per line
26
+ # (if we used cumulative instead, the array will
27
+ # insert a reference to the object and since add! has a side effect,
28
+ # we would build N-references to the same object, which has the last
29
+ # value assigned
30
+ line_total = Amount.new
31
+ line_total.add!(cumulative)
32
+
33
+ complement = transaction.posts_without(voice)
34
+ statement_line = { :date => transaction.date,
35
+ :id => transaction.id,
36
+ :payee => transaction.payee,
37
+ :voice => complement.size > 1 ? "-- split --" : complement[0].voice,
38
+ :amount => complement[0].amount,
39
+ :cumulative => line_total }
40
+ @statement << statement_line
41
+
42
+ # transaction.posts_without(voice).each do |post|
43
+ # # This is to generate a new amount per line
44
+ # # (if we used cumulative instead, the array will
45
+ # # insert a reference to the object and since add! has a side effect,
46
+ # # we would build N-references to the same object, which has the last
47
+ # # value assigned
48
+ # line_total = Amount.new
49
+ # line_total.add!(cumulative)
50
+ #
51
+ # statement_line = { :date => transaction.date,
52
+ # :id => transaction.id,
53
+ # :payee => transaction.payee,
54
+ # :voice => post.voice,
55
+ # :amount => post.amount,
56
+ # :cumulative => line_total }
57
+ # @statement << statement_line
58
+ # end
59
+
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ def to_s
66
+ @statement.each do |line|
67
+ printf "%10s %5.5s %-30.30s %-30.30s %10s %10s\n",
68
+ line[:date], line[:id], line[:payee],
69
+ line[:voice], line[:amount], line[:cumulative]
70
+ end
71
+ ""
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module Rledger
2
+ VERSION = "0.0.1"
3
+ end
data/lib/rledger.rb ADDED
@@ -0,0 +1,96 @@
1
+ require_relative "rledger/version"
2
+ require_relative "rledger/ledger/transaction"
3
+ require_relative "rledger/report/balance"
4
+ require_relative "rledger/report/statement"
5
+
6
+ require 'optparse'
7
+ require 'optparse/date'
8
+
9
+ module Rledger
10
+ class Runner
11
+ # parse command line
12
+ def self.parse(args)
13
+ options = Hash.new
14
+
15
+ opt_parser = OptionParser.new do |opts|
16
+ opts.banner = "Usage: rledger [options]"
17
+
18
+ opts.separator ""
19
+ opts.separator "Specific options:"
20
+
21
+ opts.on("-c", "--command STRING", String, "Command (one of check, statement, balance)") do |command|
22
+ options[:command] = command
23
+ end
24
+
25
+ opts.on("-v", "--voice VOICE", String, "Voice") do |voice|
26
+ options[:voice] = voice
27
+ end
28
+
29
+ #opts.on("-f", "--from_date DATE", Date, "From date") do |from_date|
30
+ # options[:from_date] = from_date
31
+ #end
32
+
33
+ #opts.on("-to", "--to_date DATE", Date, "To date") do |to_date|
34
+ # options[:to_date] = to_date
35
+ #end
36
+
37
+ opts.on( '-h', '--help', 'Display this screen' ) do
38
+ puts opts
39
+ puts <<EOF
40
+ Reads a ledger (http://www.ledger-cli.org) and performs simple operations.
41
+
42
+ Only the basic format is recognized: do not expect this gem to perform well
43
+ with virtual transactions. Different date formats and different currencies
44
+ should, however, be supported.
45
+
46
+ Example usages
47
+
48
+ rledger --command statement ledger.txt
49
+ rledger --command balance ledger.txt
50
+
51
+ EOF
52
+ exit
53
+ end
54
+ end
55
+
56
+ opt_parser.parse!(args)
57
+ options
58
+ end
59
+
60
+ # run: interpret command line and execute
61
+ def self.run(args)
62
+ # read options
63
+ options = parse(args)
64
+
65
+ # read data
66
+ data = []
67
+ ARGV.each do |argv|
68
+ data += Transaction.read(argv)
69
+ end
70
+
71
+ # now do
72
+ case options[:command]
73
+ when "check"
74
+ puts "This is what I understand from the input files:\n"
75
+ data.each do |transaction|
76
+ printf "%s\n", transaction.to_s
77
+ end
78
+
79
+ when "statement"
80
+ report = Statement.new(data)
81
+ report.compute(options[:voice])
82
+ puts "Statement of #{options[:voice]}"
83
+ puts report
84
+
85
+ when "balance"
86
+ report = Balance.new(data)
87
+ report.compute
88
+ puts report
89
+
90
+ else
91
+ puts "Oh, I wish I could understand what you mean."
92
+ puts "Try with --help."
93
+ end
94
+ end
95
+ end
96
+ end
data/rledger.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rledger/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rledger"
8
+ spec.version = Rledger::VERSION
9
+ spec.authors = ["Adolfo Villafiorita"]
10
+ spec.email = ["adolfo.villafiorita@me.com"]
11
+ spec.description = %q{CLI Ledger subset of http://www.ledger-cli.org}
12
+ spec.summary = %q{
13
+ Rledger reads ledgers in the [Ledger CLI](http://www.ledger-cli.org)
14
+ format and performs simple operations on them.
15
+
16
+ Only the basic format is recognized: **do not expect this gem to
17
+ perform well with complex ledger files.** The goal, in fact, is not
18
+ that of building a clone of [ledger](http://www.ledger-cli.org), but
19
+ being able to read simple ledger files in ruby.}
20
+ spec.homepage = "http://www.github.com/avillafiorita/rledger"
21
+ spec.license = "MIT"
22
+
23
+ spec.files = `git ls-files`.split($/)
24
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
25
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.3"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,60 @@
1
+ require 'test/unit'
2
+
3
+ require 'rledger/ledger/amount.rb'
4
+
5
+ class AmountTest < Test::Unit::TestCase
6
+ # def setup
7
+ # end
8
+
9
+ # def teardown
10
+ # end
11
+
12
+ def test_parse
13
+ a1 = Amount.new
14
+ a1.parse("")
15
+ result = (a1.amount == 0.00 and a1.currency == "")
16
+ assert result, "Derived entry not parsed"
17
+
18
+ a2 = Amount.new
19
+ a2.parse("100.00")
20
+ result = (a2.amount = 100.00 and a2.currency = "")
21
+ assert result, "Simple amount not parsed"
22
+
23
+ a3 = Amount.new
24
+ a3.parse("EUR 200.00")
25
+ result = (a3.amount = 200.00 and a1.currency = "EUR")
26
+ assert result, "Amount with currency not parsed."
27
+ end
28
+
29
+ def test_derived
30
+ assert Amount.derived?(""), "Derived does not work."
31
+ assert not Amount.derived?("100.00"), "Derived does not work"
32
+ end
33
+
34
+ def test_add!
35
+ a1 = Amount.new
36
+ a1.parse("EUR 100.00")
37
+
38
+ a2 = Amount.new
39
+ a2.parse("MTN 200.00")
40
+
41
+ a1.add!(a2)
42
+ assert a1.to_s == "EUR 100.00, MTN 200.00", "Adding different currencies does not work"
43
+
44
+ a3 = Amount.new
45
+ a3.parse("EUR 200.00")
46
+
47
+ a1.add!(a3)
48
+ assert a1.to_s == "EUR 300.00, MTN 200.00", "Adding different currencies does not work"
49
+ end
50
+
51
+ def test_multiply!
52
+ a1 = Amount.new
53
+ a1.parse("EUR 100.00")
54
+ a1.multiply(-2)
55
+
56
+ assert (a1.amount == -200.00 and a1.currency == "EUR"), "Multiply single amount does not work"
57
+ end
58
+
59
+ end
60
+
@@ -0,0 +1,52 @@
1
+ require 'test/unit'
2
+
3
+ require 'rledger/ledger/post.rb'
4
+
5
+ class PostTest < Test::Unit::TestCase
6
+ # def setup
7
+ # end
8
+
9
+ # def teardown
10
+ # end
11
+
12
+ def test_parse
13
+ e1 = Post.new
14
+ e1.parse(" voice:voice 100.00 ; comment")
15
+ result =
16
+ e1.item == "voice:voice" and
17
+ e1.amount == "100.00" and
18
+ e1.derived == false and
19
+ e1.comment == "comment"
20
+ assert result, "Parsing e1 failed"
21
+
22
+ e2 = Post.new
23
+ e2.parse(" voice ; comment")
24
+ result =
25
+ e2.item == "voice" and
26
+ e2.comment == "comment" and
27
+ e2.derived == true
28
+ assert result, "Parsing e2 failed"
29
+
30
+ e3 = Post.new
31
+ e3.parse(" voice 100.00")
32
+ result =
33
+ e3.item == "voice" and
34
+ e3.amount = "100.00" and
35
+ e3.derived == false
36
+ assert result, "Parsing e3 failed"
37
+
38
+ e4 = Post.new
39
+ e4.parse(" voice 200.00 ; longer comment")
40
+ result =
41
+ e4.comment == "longer comment"
42
+ assert result, "Parsing e4 failed"
43
+
44
+ # e1.parse(" new_voice ; new comment")
45
+ # result =
46
+ # e1.item == "new_voice" and
47
+ # e1.amount == "" and
48
+ # e1.derived == true and
49
+ # e1.comment == "new comment"
50
+ # assert result, "Setting e1 with parsing failed"
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ require 'test/unit'
2
+
3
+ require 'transactions/reconciliation.rb'
4
+
5
+ class ReconciliationTest < Test::Unit::TestCase
6
+ # def setup
7
+ # end
8
+
9
+ # def teardown
10
+ # end
11
+
12
+ def test_parse_and_set
13
+ r1 = Reconciliation.new
14
+ r1.parse("!")
15
+ assert r1.pending?
16
+
17
+ r2 = Reconciliation.new
18
+ r2.parse("?")
19
+ assert r2.discarded?
20
+
21
+ r3 = Reconciliation.new
22
+ r3.parse("*")
23
+ assert r3.reconciled!
24
+
25
+ r4 = Reconciliation.new
26
+ r4.parse("")
27
+ assert r4.unknown?
28
+
29
+ r2.pending!
30
+ assert r2.pending?
31
+
32
+ r2.reconciled!
33
+ assert r2.reconciled?
34
+
35
+ r2.discarded!
36
+ assert r2.discarded!
37
+
38
+ r2.unknown!
39
+ assert r2.unknown?
40
+
41
+ error = Reconciliation.new
42
+ error.parse("a")
43
+ assert error.unknown!
44
+ end
45
+ end
@@ -0,0 +1,68 @@
1
+ require 'test/unit'
2
+
3
+ require 'Date'
4
+ require 'rledger/ledger/transaction'
5
+
6
+ class TransactionTest < Test::Unit::TestCase
7
+ def test_tbase_parse
8
+ tb = TransactionBase.new
9
+ tb.parse "15/02/2010 ! (ID) Payee long"
10
+ result = tb.date == Date.civil(2010, 02, 15) and
11
+ tb.id == "ID" and
12
+ tb.reconciliation.pending! and
13
+ tb.payee = "Payee long"
14
+ assert result
15
+
16
+ tb = TransactionBase.new
17
+ tb.parse "15/02/2008 (ID) Payee long"
18
+ result = tb.date == Date.civil(2008, 02, 15) and
19
+ tb.id == "ID" and
20
+ tb.reconciliation.unknown? and
21
+ tb.payee = "Payee long"
22
+ assert result
23
+
24
+ tb = TransactionBase.new
25
+ tb.parse "15/06/2010 * Payee very long"
26
+ result = tb.date == Date.civil(2010, 06, 15) and
27
+ tb.id == "" and
28
+ tb.reconciliation.reconciled? and
29
+ tb.payee = "Payee very long"
30
+ assert result
31
+
32
+ tb = TransactionBase.new
33
+ tb.parse "15/02/2010 Payee very long"
34
+ result = tb.date == Date.civil(2010, 02, 15) and
35
+ tb.id == "" and
36
+ tb.reconciliation.unknown? and
37
+ tb.payee = "Payee very long"
38
+ assert result
39
+
40
+ tb = TransactionBase.new
41
+ tb.parse "15/02/2010"
42
+ result = tb.date == Date.civil(2010, 02, 15) and
43
+ tb.id == "" and
44
+ tb.reconciliation.unknown? and
45
+ tb.payee = ""
46
+ assert result
47
+ end
48
+
49
+ def test_t_parse
50
+ t = Transaction.new
51
+ t.parse("15/02/2010 ! (ID) Payee long\n voice 10.00 ; comment\n other_voice 20.00 ; other comment")
52
+ result = t.date == Date.civil(2010, 02, 15) and
53
+ t.id == "ID" and
54
+ t.reconciliation.pending! and
55
+ t.payee = "Payee long" and
56
+ t.entries.size == 2
57
+ assert result
58
+
59
+ post = t.posts[0]
60
+ result = post.item == "voice" and post.amount == "10.00" and post.comment == "comment"
61
+ assert result
62
+
63
+ post = t.posts[1]
64
+ result = post.item == "other_voice" and post.amount == "20.00" and post.comment == "other comment"
65
+ assert result
66
+ end
67
+
68
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rledger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adolfo Villafiorita
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: CLI Ledger subset of http://www.ledger-cli.org
42
+ email:
43
+ - adolfo.villafiorita@me.com
44
+ executables:
45
+ - rledger
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - bin/rledger
55
+ - lib/rledger.rb
56
+ - lib/rledger/ledger/amount.rb
57
+ - lib/rledger/ledger/post.rb
58
+ - lib/rledger/ledger/reconciliation.rb
59
+ - lib/rledger/ledger/transaction.rb
60
+ - lib/rledger/report/balance.rb
61
+ - lib/rledger/report/statement.rb
62
+ - lib/rledger/version.rb
63
+ - rledger.gemspec
64
+ - test/rledger/amount_test.rb
65
+ - test/rledger/post_test.rb
66
+ - test/rledger/reconciliation_test.rb
67
+ - test/rledger/transaction_test.rb
68
+ homepage: http://www.github.com/avillafiorita/rledger
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.0.3
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: 'Rledger reads ledgers in the [Ledger CLI](http://www.ledger-cli.org) format
92
+ and performs simple operations on them. Only the basic format is recognized: **do
93
+ not expect this gem to perform well with complex ledger files.** The goal, in fact,
94
+ is not that of building a clone of [ledger](http://www.ledger-cli.org), but being
95
+ able to read simple ledger files in ruby.'
96
+ test_files:
97
+ - test/rledger/amount_test.rb
98
+ - test/rledger/post_test.rb
99
+ - test/rledger/reconciliation_test.rb
100
+ - test/rledger/transaction_test.rb