moneybook 0.1.6 → 0.1.7
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.
- data/VERSION +1 -1
- data/bin/moneybook +177 -134
- metadata +4 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.7
|
data/bin/moneybook
CHANGED
@@ -1,25 +1,48 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'rubygems'
|
3
|
-
|
3
|
+
###
|
4
4
|
require 'terminal-table/import'
|
5
|
-
require 'commander/import'
|
6
|
-
require 'date'
|
7
5
|
require 'andand'
|
6
|
+
require 'highline/import'
|
7
|
+
###
|
8
|
+
require 'iconv'
|
9
|
+
require 'optparse'
|
10
|
+
require 'date'
|
8
11
|
require 'abbrev'
|
9
12
|
require 'pp'
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
class Array
|
15
|
+
def normalize_length
|
16
|
+
max = self.inject{|longest, current| longest.to_s.length > current.to_s.length ? longest : current }.to_s.length
|
17
|
+
self.map{|x| x = "%-#{max}s" % x.to_s}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_command(cmd)
|
22
|
+
args = Array(cmd)
|
23
|
+
command = args.shift
|
24
|
+
case(command)
|
25
|
+
when "new"
|
26
|
+
command_new(args[0])
|
27
|
+
when "parse"
|
28
|
+
options = {}
|
29
|
+
OptionParser.new do |opts|
|
30
|
+
opts.banner = "Usage: moneybook parse [options] FILENAME"
|
31
|
+
opts.on("-i", "--[no-]intermediate", "Show intermediate computations") do |i|
|
32
|
+
options[:intermediate] = i
|
33
|
+
end
|
34
|
+
end.parse!(args)
|
35
|
+
command_parse(options, args.last)
|
36
|
+
else
|
37
|
+
puts "unknown command: #{command}"
|
38
|
+
end
|
39
|
+
end
|
15
40
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
people = ask('People (separated by spaces): ', lambda{|x|x.split(/\s+/)})
|
22
|
-
text = "### #{title} ###
|
41
|
+
def command_new(title)
|
42
|
+
title = ask("Title: "){|q| q.readline = true} unless title.to_s != ''
|
43
|
+
people = ask('People (separated by spaces): ', lambda{|x|x.split(/\s+/)}){|q| q.readline = true}
|
44
|
+
change= ask("Currency change? (leave blank if not needed) ", Float) { |q| q.default = 0; q.readline = true }
|
45
|
+
text = "### #{title} ###
|
23
46
|
# #{Date.today.to_s}
|
24
47
|
#######
|
25
48
|
# - everyone is included:
|
@@ -32,152 +55,172 @@ command :new do |c|
|
|
32
55
|
# dinner (50Fra 16Tom) +5Tom -2Jack -Mary #Tom spends 5 more than the others, Jack 2 less
|
33
56
|
# - synctactic sugar, instead of ...
|
34
57
|
# pay back (20L) T
|
35
|
-
# - ... you can write
|
58
|
+
# - ... you can write ...
|
36
59
|
# 20L -> T
|
60
|
+
# - ... and since it's a payback it won't count on the spent amount
|
37
61
|
#######
|
38
62
|
|
39
63
|
PEOPLE: #{people.join(' ')}"
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
64
|
+
text += "\nCHANGE: #{change}" if change > 0
|
65
|
+
filename = "moneybook_#{title.downcase.gsub(' ','_')}.txt"
|
66
|
+
if File.open(filename,'w'){|f|f.write text} then
|
67
|
+
puts "File #{filename} created!"
|
68
|
+
else
|
69
|
+
puts "Could not create file #{filename}.."
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
50
73
|
def get_final(sq, people)
|
51
74
|
final = {}
|
52
|
-
|
75
|
+
spent = {}
|
76
|
+
given = {}
|
77
|
+
people.each{|person| final[person]=0;spent[person]=0;given[person]=0}
|
53
78
|
sq.each {|c|
|
54
79
|
c[:balance].each_pair{|person, value|
|
55
80
|
final[person] += value
|
56
81
|
}
|
82
|
+
c[:virtual].each_pair{|person, value|
|
83
|
+
spent[person] += value unless c[:name] == 'PAYBACK'
|
84
|
+
}
|
85
|
+
c[:true].each_pair{|person, value|
|
86
|
+
given[person] += value
|
87
|
+
}
|
57
88
|
}
|
58
|
-
final
|
89
|
+
[final, spent, given]
|
59
90
|
end
|
60
91
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
f=
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
c[:true_str].split(/\s+/).each{|y|
|
94
|
-
temp = y.match(/([\d\.]+)([A-Za-z]+)/)
|
95
|
-
if (person=people_abbreviations[temp[2].to_s.downcase]) != nil
|
96
|
-
c[:true][person] += temp[1].to_f
|
97
|
-
c[:true_total] += temp[1].to_f
|
98
|
-
else
|
99
|
-
puts "error: ambiguos or inexistent name: #{temp[2].to_s}"
|
100
|
-
exit
|
101
|
-
end
|
102
|
-
}
|
92
|
+
def command_parse(options, filename)
|
93
|
+
intermediate = options[:intermediate]
|
94
|
+
people = []
|
95
|
+
currency_change = 0
|
96
|
+
people_abbreviations = []
|
97
|
+
sq = [] #big table to make the computations..
|
98
|
+
f=File.open(filename,'r').read
|
99
|
+
if f[0]==255 && f[1]==254 then
|
100
|
+
f=Iconv.iconv('UTF-8', 'UTF-16LE', f)[0][3..-1]
|
101
|
+
puts "converting from UTF-16LE..."
|
102
|
+
end
|
103
|
+
f.each_line{|line|
|
104
|
+
if line =~ /^\s*\#/ || line =~ /\A\s*\Z/
|
105
|
+
# this is interpreted as a comment line
|
106
|
+
puts line if intermediate
|
107
|
+
elsif (m = line.match(/\A\s*PEOPLE\s*\:?\s*((\s+\w+)+)\s*\Z/).andand[1].andand.split(/\s+/).andand.delete_if{|x|x==""}) != nil then
|
108
|
+
m.each{|z|people<<z.to_s.downcase}
|
109
|
+
people_abbreviations = people.abbrev
|
110
|
+
elsif (currency_change == 0) && ((m = line.match(/\A\s*CHANGE\s*\:\s*([\.\d]+)\s*\Z/).andand[1].to_f) != 0) then
|
111
|
+
currency_change = m
|
112
|
+
puts "currency change is #{currency_change}"
|
113
|
+
else
|
114
|
+
original_str = false
|
115
|
+
if temp=line.match(/\A\s*(.+)\s*\-+\>\s*(.+)\Z/) then
|
116
|
+
original_str = line.strip
|
117
|
+
line = "PAYBACK (#{temp[1].to_s}) #{temp[2].to_s}"
|
118
|
+
end
|
119
|
+
x = line.strip.match(/^\s*([\w\ \']+?)\s*\((.+)\)\s*(.+)?/).to_a.map{|z| z.to_s}
|
120
|
+
sq << {:name => x[1].to_s, :original_str => original_str || line.strip, :true_str => x[2].to_s, :virtual_str => x[3].to_s, :true => {}, :virtual => {}, :balance => {},
|
121
|
+
:true_total => 0, :virtual_total => 0}
|
122
|
+
c=sq.last
|
123
|
+
people.each{|person| c[:true][person]=0;c[:virtual][person]=0;c[:balance][person]=0}
|
103
124
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
125
|
+
# parsing of true_str
|
126
|
+
c[:true_str].split(/\s+/).each{|y|
|
127
|
+
temp = y.match(/([\d\.]+)([A-Za-z]+)/)
|
128
|
+
if (person=people_abbreviations[temp[2].to_s.downcase]) != nil
|
129
|
+
c[:true][person] += temp[1].to_f
|
130
|
+
c[:true_total] += temp[1].to_f
|
108
131
|
else
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
132
|
+
puts "error: ambiguos or inexistent name: #{temp[2].to_s}"
|
133
|
+
exit
|
134
|
+
end
|
135
|
+
}
|
136
|
+
|
137
|
+
# parsing of virtual_str
|
138
|
+
if c[:virtual_str] =~ /\A\s*\Z/ then
|
139
|
+
c[:virtual].each_key{|person|c[:virtual][person] = c[:true_total]/people.length}
|
140
|
+
c[:virtual_total] = c[:true_total]
|
141
|
+
else
|
142
|
+
avoid = []
|
143
|
+
people_to_complete = []
|
144
|
+
complete_automatically = true
|
145
|
+
c[:virtual_str].split(/\s+/).each{|y|
|
146
|
+
if (temp = y.match(/([\+\-\d\.]*)([A-Za-z]+)/)) then
|
147
|
+
if (person=people_abbreviations[temp[2].to_s.downcase]) != nil
|
148
|
+
if temp[1].to_s == "" then
|
149
|
+
complete_automatically = false
|
150
|
+
people_to_complete << person
|
151
|
+
avoid << person
|
152
|
+
elsif temp[1].to_s[0,1] == '+'
|
153
|
+
c[:virtual][person] += temp[1].to_s[1..-1].to_f
|
154
|
+
c[:virtual_total] += temp[1].to_f
|
155
|
+
elsif temp[1].to_s[0,1] == '-'
|
156
|
+
if temp[1].to_s == '-' then
|
157
|
+
c[:virtual][person] = 0
|
118
158
|
avoid << person
|
119
|
-
elsif temp[1].to_s[0,1] == '+'
|
120
|
-
c[:virtual][person] += temp[1].to_s[1..-1].to_f
|
121
|
-
c[:virtual_total] -= temp[1].to_f
|
122
|
-
elsif temp[1].to_s[0,1] == '-'
|
123
|
-
if temp[1].to_s == '-' then
|
124
|
-
c[:virtual][person] = 0
|
125
|
-
avoid << person
|
126
|
-
else
|
127
|
-
c[:virtual][person] -= temp[1].to_s[1..-1].to_f
|
128
|
-
c[:virtual_total] += temp[1].to_f
|
129
|
-
end
|
130
159
|
else
|
131
|
-
c[:virtual][person]
|
132
|
-
c[:virtual_total]
|
133
|
-
avoid << person
|
160
|
+
c[:virtual][person] -= temp[1].to_s[1..-1].to_f
|
161
|
+
c[:virtual_total] -= temp[1].to_f
|
134
162
|
end
|
135
163
|
else
|
136
|
-
|
137
|
-
|
164
|
+
c[:virtual][person] += temp[1].to_f
|
165
|
+
c[:virtual_total] += temp[1].to_f
|
166
|
+
avoid << person
|
138
167
|
end
|
168
|
+
else
|
169
|
+
puts "error: ambiguos or inexistent name: #{temp[2].to_s}"
|
170
|
+
exit
|
139
171
|
end
|
140
|
-
}
|
141
|
-
if c[:virtual_total] > c[:true_total]
|
142
|
-
puts "error: Virtual total doesn't match.."
|
143
|
-
exit
|
144
172
|
end
|
145
|
-
if complete_automatically then
|
146
|
-
others = c[:virtual].map{|i,v|i}-avoid
|
147
|
-
others.each { |p|
|
148
|
-
c[:virtual][p] += (c[:true_total] - c[:virtual_total])/others.length
|
149
|
-
}
|
150
|
-
else
|
151
|
-
people_to_complete.each { |p|
|
152
|
-
c[:virtual][p] += (c[:true_total] - c[:virtual_total])/people_to_complete.length
|
153
|
-
}
|
154
|
-
end
|
155
|
-
end
|
156
|
-
c[:balance].each_key{|person|
|
157
|
-
c[:balance][person] = c[:true][person] - c[:virtual][person]
|
158
173
|
}
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
173
|
-
puts mytable
|
174
|
+
if c[:virtual_total] > c[:true_total]
|
175
|
+
puts "error: Virtual total doesn't match.."
|
176
|
+
exit
|
177
|
+
end
|
178
|
+
if complete_automatically then
|
179
|
+
others = c[:virtual].map{|i,v|i}-avoid
|
180
|
+
others.each { |p|
|
181
|
+
c[:virtual][p] += (c[:true_total] - c[:virtual_total])/others.length
|
182
|
+
}
|
183
|
+
else
|
184
|
+
people_to_complete.each { |p|
|
185
|
+
c[:virtual][p] += (c[:true_total] - c[:virtual_total])/people_to_complete.length
|
186
|
+
}
|
174
187
|
end
|
175
|
-
###
|
176
188
|
end
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
189
|
+
c[:balance].each_key{|person|
|
190
|
+
c[:balance][person] = c[:true][person] - c[:virtual][person]
|
191
|
+
}
|
192
|
+
### PRINT INTERMEDIATE RESULTS ###
|
193
|
+
if intermediate then
|
194
|
+
puts "==== #{c[:original_str].to_s} ===="
|
195
|
+
puts "total: #{c[:true_total].to_s}"
|
196
|
+
mytable = table do |t|
|
197
|
+
t.headings = people.sort
|
198
|
+
t << c[:true].sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
|
199
|
+
t << c[:virtual].sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
|
200
|
+
t.add_separator
|
201
|
+
t << c[:balance].sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
|
202
|
+
t.add_separator
|
203
|
+
final = get_final(sq, people)[0]
|
204
|
+
t << final.sort.map{|z|z[1] == 0 ? "" : "%.1f" % z[1]}
|
205
|
+
end
|
206
|
+
puts mytable
|
207
|
+
end
|
208
|
+
###
|
209
|
+
end
|
210
|
+
}
|
211
|
+
### PRINT FINAL RESULTS ###
|
212
|
+
final = get_final(sq, people)
|
213
|
+
puts "in local currency:" if currency_change > 0
|
214
|
+
people_to_print = people.normalize_length
|
215
|
+
people.each_index{|i|
|
216
|
+
person = people[i]
|
217
|
+
puts "#{people_to_print[i]} \t #{final[0][person] > 0 ? 'receives' : 'gives '} #{"% 8.2f" % final[0][person]} spent #{"% 8.2f" % final[1][person]} given #{"% 8.2f" % final[2][person]}"
|
218
|
+
}
|
219
|
+
puts "in converted currency:" if currency_change > 0
|
220
|
+
people.each_index{|i|
|
221
|
+
person = people[i]
|
222
|
+
puts "#{people_to_print[i]} \t #{final[0][person] > 0 ? 'receives' : 'gives '} #{"% 8.2f" % (final[0][person]*currency_change)} spent #{"% 8.2f" % (final[1][person]*currency_change)} given #{"% 8.2f" % (final[2][person]*currency_change)}" if currency_change > 0
|
223
|
+
}
|
183
224
|
end
|
225
|
+
|
226
|
+
process_command(ARGV)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: moneybook
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 7
|
10
|
+
version: 0.1.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- luca cioria
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-01-
|
18
|
+
date: 2011-01-18 00:00:00 +01:00
|
19
19
|
default_executable: moneybook
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|