magicsheet 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/magicsheet.rb +184 -0
  2. metadata +74 -0
@@ -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
+