kale 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.
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: []