kale 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kale.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Charles Stuart
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,26 @@
1
+ # Kale
2
+
3
+ A command line application to to keep track of time spent on projects.
4
+
5
+ Uses [sequel](http://sequel.rubyforge.org/index.html) for sqlite database access.
6
+
7
+ ## Installation
8
+
9
+ $ gem install kale
10
+
11
+ ## Usage
12
+
13
+ $ kale
14
+
15
+ ## TODO
16
+
17
+ - ~~add ability to export an invoice~~
18
+ - ~~add ability to track earnings~~
19
+ - add ability to delete sessions
20
+ - add ability to modify sessions
21
+ - add PDF invoice exporting
22
+ - add ability to mark sessions as "paid"
23
+
24
+ ## Screenshot
25
+
26
+ ![screenshot](http://d.pr/i/Dna5+)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/kale ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'kale'
data/kale.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kale/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "kale"
8
+ gem.version = Kale::VERSION
9
+ gem.authors = ["Charles Stuart"]
10
+ gem.email = ["cs@enure.net"]
11
+ gem.description = %q{Track time spent on projects, your earnings and export invoices.}
12
+ gem.summary = %q{Keep track of time spent on projects.}
13
+ gem.homepage = "https://github.com/enure/kale"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency "sequel", "~> 4.1.1"
21
+
22
+ end
data/lib/kale.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ require "sequel"
3
+
4
+ require "kale/version"
5
+ require "kale/db"
6
+ require "kale/util"
7
+ require "kale/html_invoice_exporter"
8
+ require "kale/stopwatch"
9
+ require "kale/project"
10
+ require "kale/session"
11
+ require "kale/ui"
12
+
13
+ module Kale
14
+
15
+ end
data/lib/kale/db.rb ADDED
@@ -0,0 +1,20 @@
1
+ DB = Sequel.connect("sqlite://#{Dir.home}/Dropbox/Timer/timer.db")
2
+
3
+ unless DB.table_exists? :projects
4
+ DB.create_table :projects do
5
+ primary_key :id
6
+ String :name
7
+ Integer :rate_per_hour
8
+ end
9
+ end
10
+
11
+ unless DB.table_exists? :sessions
12
+ DB.create_table :sessions do
13
+ primary_key :id
14
+ Integer :project_id
15
+ String :description
16
+ DateTime :start_time
17
+ DateTime :end_time
18
+ Integer :elapsed_time
19
+ end
20
+ end
@@ -0,0 +1,82 @@
1
+ class HTMLInvoiceExporter
2
+
3
+ def initialize project
4
+ @project = project
5
+
6
+ self.prefix
7
+ self.body
8
+ self.suffix
9
+
10
+ self.write
11
+ end
12
+
13
+ protected
14
+
15
+ def prefix
16
+ @html = %Q(
17
+ <!doctype html>
18
+ <html>
19
+ <head>
20
+ <title>
21
+ #{@project.name} Invoice
22
+ </title>
23
+ </head>
24
+ <body>
25
+ )
26
+ end
27
+
28
+ def body
29
+ self.body_builder
30
+ end
31
+
32
+ def suffix
33
+ @html += "</body></html>"
34
+ end
35
+
36
+ def write
37
+ filename = "#{@project.name}.html"
38
+ file = File.open filename, 'w+'
39
+ file.write @html
40
+ file.close
41
+ puts "Created an invoice - #{filename}"
42
+ end
43
+
44
+ def body_builder
45
+ @html += %Q(
46
+ <h1>#{@project.name}</h1>
47
+ <table>
48
+ <thead>
49
+ <tr>
50
+ <th>Work</th>
51
+ <th>Time</th>
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ )
56
+
57
+ @project.sessions.each do |session|
58
+ @html += %Q(
59
+ <tr>
60
+ <td>#{session.description}</td>
61
+ <td>#{session.elapsed_time}</td>
62
+ </tr>
63
+ )
64
+ end
65
+
66
+ @html += %Q(
67
+ </tbody>
68
+ <tfoot>
69
+ <tr>
70
+ <td>
71
+ #{seconds_to_hms(@project.total_time)}
72
+ </td>
73
+ <td>
74
+ #{@project.total_earned}
75
+ </td>
76
+ </tr>
77
+ </tfoot>
78
+ </table>
79
+ )
80
+ end
81
+
82
+ end
@@ -0,0 +1,14 @@
1
+ class Project < Sequel::Model
2
+
3
+ one_to_many :sessions
4
+
5
+ def total_time
6
+ times = self.sessions.collect { |s| s[:elapsed_time] }
7
+ times.inject{ |sum,x| sum + x }
8
+ end
9
+
10
+ def total_earned
11
+ "$" + sprintf("%.2f", self.rate_per_hour.to_f * (total_time.to_f / 3600))
12
+ end
13
+
14
+ end
@@ -0,0 +1,27 @@
1
+ class Session < Sequel::Model
2
+
3
+ many_to_one :project
4
+
5
+ def initialize
6
+ @stopwatch = Stopwatch.new
7
+ @stopwatch.start
8
+
9
+ super
10
+ end
11
+
12
+ def stop opts
13
+ @stopwatch.stop
14
+
15
+ self.start_time = @stopwatch.start_time
16
+ self.end_time = @stopwatch.end_time
17
+ self.elapsed_time = @stopwatch.elapsed_time
18
+ self.description = opts[:description]
19
+ self.project = opts[:project]
20
+ self.save
21
+ end
22
+
23
+ def earned
24
+ "$" + sprintf("%.2f", self.project.rate_per_hour.to_f * (self.elapsed_time.to_f / 3600))
25
+ end
26
+
27
+ end
@@ -0,0 +1,17 @@
1
+ class Stopwatch
2
+
3
+ attr_accessor :start_time, :end_time
4
+
5
+ def start
6
+ self.start_time = Time.now
7
+ end
8
+
9
+ def stop
10
+ self.end_time = Time.now
11
+ end
12
+
13
+ def elapsed_time
14
+ self.end_time - self.start_time
15
+ end
16
+
17
+ end
data/lib/kale/ui.rb ADDED
@@ -0,0 +1,277 @@
1
+ class Screen
2
+
3
+ @@project = nil
4
+
5
+ def initialize
6
+ @menu = []
7
+
8
+ system "clear"
9
+ info
10
+ puts
11
+ puts "-------"
12
+ puts
13
+ menu
14
+ handle_menu
15
+ end
16
+
17
+ def menu
18
+ @menu.each_with_index do |item, idx|
19
+ puts "#{idx + 1}) #{item}"
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def show_projects show_id
26
+ if Project.all.length == 0
27
+ puts "No projects yet."
28
+ else
29
+ puts "## Your Projects\n\n"
30
+ Project.all.each do |p|
31
+ if show_id
32
+ puts "- #{p.name} (#{p.id})"
33
+ else
34
+ puts "- #{p.name}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def show_project_heading
41
+ puts "You are working on #{@@project.name}"
42
+ end
43
+
44
+ end
45
+
46
+ class WelcomeScreen < Screen
47
+
48
+ def info
49
+ show_projects false
50
+ end
51
+
52
+ def menu
53
+ if Project.all.length > 0
54
+ @menu.push "New project"
55
+ @menu.push "Choose project"
56
+ else
57
+ @menu.push "New project"
58
+ end
59
+ super
60
+ end
61
+
62
+ def handle_menu
63
+ answer = gets.chomp
64
+
65
+ if answer == "1"
66
+ CreateProjectScreen.new
67
+ elsif answer == "2"
68
+ ChooseProjectScreen.new
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ class ChooseProjectScreen < Screen
75
+
76
+ def info
77
+ show_projects true
78
+ end
79
+
80
+ def menu
81
+ puts "Type the number or name of the project"
82
+ end
83
+
84
+ def handle_menu
85
+ answer = gets.chomp
86
+
87
+ if answer.length == 1
88
+ @@project = Project[:id => answer]
89
+ else
90
+ @@project = Project[:name => answer]
91
+ end
92
+
93
+ if @@project
94
+ ShowProjectScreen.new
95
+ else
96
+ system "clear"
97
+ puts "You are a bad typer, or maybe that project does not exist."
98
+ sleep(2)
99
+ ChooseProjectScreen.new
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ class CreateProjectScreen < Screen
106
+
107
+ def info
108
+ puts ""
109
+ end
110
+
111
+ def menu
112
+ puts "Type the name of your new project"
113
+ end
114
+
115
+ def handle_menu
116
+ name = gets.chomp
117
+
118
+ system "clear"
119
+
120
+ puts "Type the rate per hour for this project (ex: 60)"
121
+ rate = gets.chomp
122
+
123
+ @@project = Project.create(:name => name, :rate_per_hour => rate)
124
+ ShowProjectScreen.new
125
+ end
126
+
127
+ end
128
+
129
+ class ShowProjectScreen < Screen
130
+
131
+ def info
132
+ show_project_heading
133
+ end
134
+
135
+ def menu
136
+ @menu.push "Start a work session"
137
+ @menu.push "Show work completed"
138
+ @menu.push "Create invoice"
139
+ @menu.push "Go to welcome screen"
140
+ @menu.push "Delete this project"
141
+ super
142
+ end
143
+
144
+ def handle_menu
145
+ answer = gets.chomp
146
+
147
+ if answer == "1"
148
+ DoWorkScreen.new
149
+ elsif answer == "2"
150
+ ShowSessionsForProjectScreen.new
151
+ elsif answer == "3"
152
+ CreateInvoiceScreen.new
153
+ elsif answer == "4"
154
+ WelcomeScreen.new
155
+ elsif answer == "5"
156
+ DeleteProjectScreen.new
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ class DoWorkScreen < Screen
163
+
164
+ def initialize
165
+ @session = Session.new
166
+ super
167
+ end
168
+
169
+ def info
170
+ show_project_heading
171
+ end
172
+
173
+ def menu
174
+ @menu.push "Stop Working"
175
+ @menu.push "Do not record this work."
176
+ super
177
+ end
178
+
179
+ def handle_menu
180
+ answer = gets.chomp
181
+
182
+ if answer == "1"
183
+ puts "Enter a description for the work you just completed."
184
+ description = gets.chomp
185
+ @session.stop({ :description => description, :project => @@project })
186
+ system "clear"
187
+ puts "You worked for #{seconds_to_hms(@session.elapsed_time)} on \"#{@session.description}\" and earned #{@session.earned}"
188
+ sleep(4)
189
+
190
+ ShowProjectScreen.new
191
+ elsif answer == "2"
192
+ # TODO put in check to verify user wants to discard work
193
+ ShowProjectScreen.new
194
+ end
195
+ end
196
+
197
+ end
198
+
199
+ class ShowSessionsForProjectScreen < Screen
200
+
201
+ def info
202
+ show_project_heading
203
+
204
+ puts
205
+
206
+ if @@project.sessions.length > 0
207
+ puts "## Work Completed\n"
208
+ @@project.sessions.each do |s|
209
+ puts "#{s.description} \t #{seconds_to_hms(s.elapsed_time)} \t #{s.earned}"
210
+ end
211
+
212
+ puts "\n## Total"
213
+ puts "#{seconds_to_hms(@@project.total_time)} \t #{@@project.total_earned}"
214
+ else
215
+ puts "You haven't worked on this project yet."
216
+ end
217
+ end
218
+
219
+ def menu
220
+ @menu.push "Go back to project"
221
+ super
222
+ end
223
+
224
+ def handle_menu
225
+ answer = gets.chomp
226
+
227
+ if answer == "1"
228
+ ShowProjectScreen.new
229
+ end
230
+ end
231
+
232
+ end
233
+
234
+ class DeleteProjectScreen < Screen
235
+
236
+ def info
237
+ puts "Are you sure you want to delete #{@@project.name}"
238
+ end
239
+
240
+ def menu
241
+ @menu.push "Yes"
242
+ @menu.push "NO"
243
+ super
244
+ end
245
+
246
+ def handle_menu
247
+ answer = gets.chomp
248
+
249
+ if answer == "1"
250
+ @@project.delete
251
+ WelcomeScreen.new
252
+ else
253
+ ShowProjectScreen.new
254
+ end
255
+ end
256
+
257
+ end
258
+
259
+ class CreateInvoiceScreen < Screen
260
+
261
+ def info
262
+ HTMLInvoiceExporter.new @@project
263
+ end
264
+
265
+ def menu
266
+ @menu.push "Back to project"
267
+ super
268
+ end
269
+
270
+ def handle_menu
271
+ answer = gets.chomp
272
+ ShowProjectScreen.new
273
+ end
274
+
275
+ end
276
+
277
+ WelcomeScreen.new
data/lib/kale/util.rb ADDED
@@ -0,0 +1,3 @@
1
+ def seconds_to_hms seconds
2
+ Time.at(seconds).gmtime.strftime('%Hhr %Mm %Ss')
3
+ end
@@ -0,0 +1,3 @@
1
+ module Kale
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kale
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Charles Stuart
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 4.1.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 4.1.1
30
+ description: Track time spent on projects, your earnings and export invoices.
31
+ email:
32
+ - cs@enure.net
33
+ executables:
34
+ - kale
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - bin/kale
44
+ - kale.gemspec
45
+ - lib/kale.rb
46
+ - lib/kale/db.rb
47
+ - lib/kale/html_invoice_exporter.rb
48
+ - lib/kale/project.rb
49
+ - lib/kale/session.rb
50
+ - lib/kale/stopwatch.rb
51
+ - lib/kale/ui.rb
52
+ - lib/kale/util.rb
53
+ - lib/kale/version.rb
54
+ homepage: https://github.com/enure/kale
55
+ licenses: []
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 1.8.23
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Keep track of time spent on projects.
78
+ test_files: []