magicsheet 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/magicsheet.rb +184 -0
- metadata +74 -0
data/lib/magicsheet.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'yahoo_finance'
|
2
|
+
|
3
|
+
module MagicSheet
|
4
|
+
class Currency
|
5
|
+
attr_reader :name
|
6
|
+
alias :to_s :name
|
7
|
+
|
8
|
+
LIST = %w(USD EUR)
|
9
|
+
|
10
|
+
def self.[](str)
|
11
|
+
name = LIST.find do |name|
|
12
|
+
str.upcase.strip.end_with?(name)
|
13
|
+
end
|
14
|
+
return name ? self.new(name) : nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(name)
|
18
|
+
@name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
self.name == other.name
|
23
|
+
end
|
24
|
+
|
25
|
+
def conversion_rate_to(other)
|
26
|
+
symbol = "#{self.name}#{other.name}=X".upcase
|
27
|
+
YahooFinance.quotes(symbol).first.last_trade_price.to_f
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Cost
|
32
|
+
attr_reader :currency, :amount
|
33
|
+
|
34
|
+
def self.[](arg)
|
35
|
+
arg = arg.to_s.strip
|
36
|
+
currency = Currency[arg]
|
37
|
+
if currency
|
38
|
+
amount = (arg[/\d+(\.\d+)?/,0] || 0).to_f
|
39
|
+
return self.new(amount,currency)
|
40
|
+
else
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(amount,currency)
|
46
|
+
@amount = amount
|
47
|
+
@currency = currency
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"#{"%.2f" % @amount} #{@currency}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Sheet
|
56
|
+
DELEGATE = %w(rate fees convert_to chunk width log) # Which methods to make available in the global scope
|
57
|
+
DEFAULT_WIDTH = 20
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@fees = []
|
61
|
+
@chunk = 1
|
62
|
+
@width = DEFAULT_WIDTH
|
63
|
+
end
|
64
|
+
|
65
|
+
def width(w)
|
66
|
+
@width = w
|
67
|
+
end
|
68
|
+
|
69
|
+
def convert_to(currency_name)
|
70
|
+
@convert_to = Currency[currency_name]
|
71
|
+
end
|
72
|
+
|
73
|
+
def rate(rate)
|
74
|
+
@rate = Cost[rate]
|
75
|
+
end
|
76
|
+
|
77
|
+
def fees(*list)
|
78
|
+
@fees = list.map {|f| Cost[f]}.compact
|
79
|
+
end
|
80
|
+
|
81
|
+
def chunk(val)
|
82
|
+
unless val.between?(1,60)
|
83
|
+
raise ArgumentError, "Chunk must be between 1 (minute-wise calculation) and 60 (full-hour calculation)"
|
84
|
+
end
|
85
|
+
@chunk = val
|
86
|
+
end
|
87
|
+
|
88
|
+
def scan
|
89
|
+
STDERR.puts "Scanning sheet..."
|
90
|
+
@hours, @minutes = 0, 0;
|
91
|
+
if defined?(DATA)
|
92
|
+
DATA.read.scan(/(\d+)\:(\d+)/) do |h,m|
|
93
|
+
@hours += h.to_i
|
94
|
+
@minutes += m.to_i
|
95
|
+
end
|
96
|
+
@hours += @minutes / 60
|
97
|
+
@minutes = @minutes % 60
|
98
|
+
end
|
99
|
+
return self
|
100
|
+
end
|
101
|
+
|
102
|
+
def log(str)
|
103
|
+
@log << ((str == '-') ? ''.rjust(@width,'-') : str.to_s.rjust(@width))
|
104
|
+
end
|
105
|
+
|
106
|
+
def process
|
107
|
+
@log = []
|
108
|
+
log "#{"%.2d" % @hours}h:#{"%.2d" % @minutes}m"
|
109
|
+
|
110
|
+
if @chunk > 1 && @minutes > 0
|
111
|
+
@minutes = (@minutes.to_f/@chunk).ceil * @chunk
|
112
|
+
if @minutes == 60
|
113
|
+
@hours += 1
|
114
|
+
@minutes = 0
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
log "= #{"%.2d" % @hours}h:#{"%.2d" % @minutes}m" if @chunk > 1
|
119
|
+
|
120
|
+
# if @minutes == 60
|
121
|
+
# @hours += 1
|
122
|
+
# @minutes = 0
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
|
126
|
+
log "x #{@rate}"
|
127
|
+
total = @hours * @rate.amount + (@minutes * @rate.amount/60.0)
|
128
|
+
log '-'
|
129
|
+
log "= #{Cost.new(total,@rate.currency)}"
|
130
|
+
|
131
|
+
fees = false
|
132
|
+
|
133
|
+
@fees.select {|f| f.currency == @rate.currency}.each do |f|
|
134
|
+
fees = true
|
135
|
+
total -= f.amount
|
136
|
+
log "- #{f}"
|
137
|
+
end
|
138
|
+
|
139
|
+
if fees
|
140
|
+
#log '-'
|
141
|
+
log "= #{Cost.new(total,@rate.currency)}"
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
# Convert to target currency, if any
|
146
|
+
if @convert_to && (@convert_to != @rate.currency)
|
147
|
+
STDERR.puts "Getting conversion rate..."
|
148
|
+
c = @rate.currency.conversion_rate_to(@convert_to)
|
149
|
+
log ''
|
150
|
+
log "x #{c}"
|
151
|
+
log "(1h = #{Cost.new(@rate.amount * c,@convert_to)})"
|
152
|
+
log ''
|
153
|
+
total *= c
|
154
|
+
log "= #{Cost.new(total,@convert_to)}"
|
155
|
+
|
156
|
+
fees = false
|
157
|
+
# Substract fees in target currency
|
158
|
+
@fees.select {|f| f.currency == @convert_to}.each do |f|
|
159
|
+
total -= f.amount
|
160
|
+
log "- #{f}"
|
161
|
+
fees = true
|
162
|
+
end
|
163
|
+
|
164
|
+
if fees
|
165
|
+
log '-'
|
166
|
+
log "= #{Cost.new(total,@convert_to)}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
puts
|
171
|
+
puts @log.join("\n")
|
172
|
+
end # process
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.current
|
176
|
+
@current ||= Sheet.new.scan
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
MagicSheet::Sheet::DELEGATE.each { |name| eval "def #{name}(*args); MagicSheet.current.#{name}(*args); end" }
|
181
|
+
|
182
|
+
at_exit do
|
183
|
+
MagicSheet.current.process
|
184
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: magicsheet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Esad Hajdarevic
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-03-06 00:00:00 +01:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: yahoo-finance
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
segments:
|
27
|
+
- 0
|
28
|
+
- 0
|
29
|
+
- 2
|
30
|
+
version: 0.0.2
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Write your timesheets in ruby after the __END__. Supports currencies and tells you how much you've earned so far to keep you motivated
|
34
|
+
email: esad@eigenbyte.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- lib/magicsheet.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/esad/magicsheet
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.3.6
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Timesheets in ruby
|
73
|
+
test_files: []
|
74
|
+
|