rtt 0.0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/Manifest +28 -0
- data/README.rdoc +78 -0
- data/Rakefile +16 -0
- data/USAGE.txt +13 -0
- data/bin/rtt +8 -0
- data/db/rtt.sqlite3 +0 -0
- data/db/test.sqlite3 +0 -0
- data/init.sh +5 -0
- data/lib/rtt.rb +137 -0
- data/lib/rtt/client.rb +25 -0
- data/lib/rtt/cmd_line_interpreter.rb +137 -0
- data/lib/rtt/hash_extensions.rb +86 -0
- data/lib/rtt/project.rb +35 -0
- data/lib/rtt/query_builder.rb +22 -0
- data/lib/rtt/report_generator.rb +190 -0
- data/lib/rtt/storage.rb +19 -0
- data/lib/rtt/task.rb +136 -0
- data/lib/rtt/user.rb +54 -0
- data/lib/rtt/user_configurator.rb +24 -0
- data/rtt.gemspec +50 -0
- data/rtt_report +401 -0
- data/spec/datamapper_spec_helper.rb +1 -0
- data/spec/lib/rtt/task_spec.rb +106 -0
- data/spec/lib/rtt_spec.rb +186 -0
- data/tasks/rtt.rake +66 -0
- data/todo.txt +3 -0
- metadata +207 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2007, 2008, 2009 Blake Mizerany
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
LICENSE
|
2
|
+
Manifest
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
USAGE.txt
|
6
|
+
bin/rtt
|
7
|
+
db/rtt.sqlite3
|
8
|
+
db/test.sqlite3
|
9
|
+
db/test.sqlite3-journal
|
10
|
+
init.sh
|
11
|
+
lib/rtt.rb
|
12
|
+
lib/rtt/client.rb
|
13
|
+
lib/rtt/cmd_line_interpreter.rb
|
14
|
+
lib/rtt/hash_extensions.rb
|
15
|
+
lib/rtt/project.rb
|
16
|
+
lib/rtt/query_builder.rb
|
17
|
+
lib/rtt/report_generator.rb
|
18
|
+
lib/rtt/storage.rb
|
19
|
+
lib/rtt/task.rb
|
20
|
+
lib/rtt/user.rb
|
21
|
+
lib/rtt/user_configurator.rb
|
22
|
+
rtt.gemspec
|
23
|
+
rtt_report
|
24
|
+
spec/datamapper_spec_helper.rb
|
25
|
+
spec/lib/rtt/task_spec.rb
|
26
|
+
spec/lib/rtt_spec.rb
|
27
|
+
tasks/rtt.rake
|
28
|
+
todo.txt
|
data/README.rdoc
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
RTT - Ruby Time Tracker
|
2
|
+
-----------------------
|
3
|
+
-----------------------
|
4
|
+
|
5
|
+
RTT is a tool for tracking time. It's primary extend is to be used from command line. It could be used by simple typing:
|
6
|
+
|
7
|
+
$ rtt start <task-name>
|
8
|
+
|
9
|
+
If no <task-name> is specified the last paused one is activated, otherwise a task with 'Default task' name would be created.
|
10
|
+
|
11
|
+
Then to stop the timer, you can do:
|
12
|
+
|
13
|
+
$ rtt stop
|
14
|
+
|
15
|
+
Also, by just typying: 'rtt start', without specifying the task, it would default to the previous task.
|
16
|
+
|
17
|
+
If you start a task with the same name as one already stored for the very same day, then the elapsed time for both activities will be sum into just one task.
|
18
|
+
|
19
|
+
Installation
|
20
|
+
------------
|
21
|
+
|
22
|
+
[sudo] gem install rtt
|
23
|
+
|
24
|
+
After installing the gem you will need to setup some basic information of yours (data to be printed in the reports). With the following command:
|
25
|
+
|
26
|
+
$ rtt user <user-nick-name>
|
27
|
+
|
28
|
+
Then you will be prompt for First name, Last name, country, city, e-mail, site, etc. Information that will be used to fill-in the reports.
|
29
|
+
|
30
|
+
The only required field is the Nickname, which identifies the user.
|
31
|
+
|
32
|
+
How to start a task?
|
33
|
+
--------------------
|
34
|
+
|
35
|
+
$ rtt '<task-name>' ( or the more explicit way: 'rtt start <task-name>')
|
36
|
+
..
|
37
|
+
|
38
|
+
That simple!
|
39
|
+
|
40
|
+
More about the API
|
41
|
+
------------------
|
42
|
+
|
43
|
+
Task can be grouped in Projects. For that you must set the project before starting the timer, in the following way:
|
44
|
+
|
45
|
+
$ rtt project <project-name>
|
46
|
+
..
|
47
|
+
|
48
|
+
Anagolous, you can define a Client for the current project by typing:
|
49
|
+
|
50
|
+
$ rtt client <client-name>
|
51
|
+
|
52
|
+
To list all task you can use
|
53
|
+
|
54
|
+
$ rtt list
|
55
|
+
|
56
|
+
Also, if you have added times for different projects or clients and you may want to filtered this list by one of those dimensions. Then you can do that, by using environment variables like this:
|
57
|
+
|
58
|
+
$ CLIENT=MyClient PROJECT=SomeProject rtt list
|
59
|
+
|
60
|
+
This command will list all tasks that belongs to the client called 'MyClient' and to the project called 'SomeProject'.
|
61
|
+
|
62
|
+
Report
|
63
|
+
-------
|
64
|
+
|
65
|
+
RTT allow you to build a pdf document with all the entries using the following command:
|
66
|
+
|
67
|
+
$ rtt report <output-filename>
|
68
|
+
|
69
|
+
Also, you can filter the entries of this report in a similar manner as you would do for 'list' command. For example:
|
70
|
+
|
71
|
+
$ PROJECT=SomeProject rtt report
|
72
|
+
|
73
|
+
This will generate a report for the project 'SomeProject'.
|
74
|
+
|
75
|
+
Questions/Comments
|
76
|
+
------------------
|
77
|
+
|
78
|
+
Feel free to email {Marcelo Andrés Giorgi Martínez}[mailto:marklazz.uy@gmail.com] for any comment or question.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'echoe'
|
3
|
+
|
4
|
+
# PACKAGING ============================================================
|
5
|
+
|
6
|
+
Echoe.new('rtt', '0.0.0.1') do |p|
|
7
|
+
p.description = 'RTT is a tool for tracking time'
|
8
|
+
p.url = 'http://www.marklazz.com'
|
9
|
+
p.author = 'Marcelo Giorgi'
|
10
|
+
p.email = 'marklazz.uy@gmail.com'
|
11
|
+
p.ignore_pattern = [ 'tmp/*', 'script/*' ]
|
12
|
+
p.runtime_dependencies = [ ['highline', ">= 1.5.2"], ['activesupport', '>= 2.3.0'], ['prawn', '>= 0.8.0'], ['dm-core', '>= 1.0.0'], [ 'dm-migrations', '>= 1.0.0'] ]
|
13
|
+
p.development_dependencies = [ 'spec' ]
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/USAGE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
RTT is a tool for tracking time. It's primary extend is to be used from command line.
|
2
|
+
|
3
|
+
Usage:
|
4
|
+
|
5
|
+
rtt start <task-name>
|
6
|
+
rtt pause
|
7
|
+
rtt stop
|
8
|
+
rtt user <nickname>
|
9
|
+
rtt client <client-name>
|
10
|
+
rtt project <project-name>
|
11
|
+
rtt rename <new-name-current-task>
|
12
|
+
rtt list
|
13
|
+
rtt report <filename>.pdf
|
data/bin/rtt
ADDED
data/db/rtt.sqlite3
ADDED
Binary file
|
data/db/test.sqlite3
ADDED
Binary file
|
data/init.sh
ADDED
data/lib/rtt.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
%w( rubygems spec dm-core dm-validations dm-migrations active_support highline/import).each { |lib| require lib }
|
3
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__), 'rtt', '*'))].each { |lib| require lib; }
|
4
|
+
|
5
|
+
module Rtt
|
6
|
+
|
7
|
+
VERSION = '1.0'
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
include CmdLineInterpreter
|
14
|
+
include QueryBuilder
|
15
|
+
include ReportGenerator
|
16
|
+
include Storage
|
17
|
+
|
18
|
+
def current_user
|
19
|
+
User.first :active => true
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_user(nickname = nil)
|
23
|
+
current_user.deactivate if current_user
|
24
|
+
if nickname && (user = User.first(:nickname => nickname)).present?
|
25
|
+
user.activate
|
26
|
+
else
|
27
|
+
extend(UserConfigurator)
|
28
|
+
configure_user(nickname)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Change the name of the current task.
|
33
|
+
#
|
34
|
+
# Usage
|
35
|
+
#
|
36
|
+
# rename 'new_timer_name' => Doesn't change task#start_at
|
37
|
+
|
38
|
+
def rename task_name
|
39
|
+
task = current_task
|
40
|
+
if task
|
41
|
+
task.name = task_name
|
42
|
+
task.save
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Lists all entries filtered by parameters
|
47
|
+
#
|
48
|
+
# For example:
|
49
|
+
# Rtt.list :from => '2010-5-3', :to => '2010-5-20'
|
50
|
+
#
|
51
|
+
def list options = {}
|
52
|
+
puts 'Task List'
|
53
|
+
puts '========='
|
54
|
+
query(options).each do |task|
|
55
|
+
puts "Name: #{task.name} elapsed time: #{task.duration} #{'[ACTIVE]' if task.active}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Used to set the client at system level.
|
61
|
+
#
|
62
|
+
# Usage
|
63
|
+
#
|
64
|
+
# pause
|
65
|
+
def pause
|
66
|
+
current_task.pause if current_task
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Used to set the client at system level.
|
71
|
+
#
|
72
|
+
# Usage
|
73
|
+
#
|
74
|
+
# set_client name
|
75
|
+
def set_client name
|
76
|
+
deactivate_current_client if current_client
|
77
|
+
client = client(name)
|
78
|
+
client.activate
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_project project_name, client_name = nil
|
82
|
+
deactivate_current_project if current_project
|
83
|
+
client = client(client_name) unless client_name.nil?
|
84
|
+
project = Project.first_or_create :name => project_name, :description => project_name
|
85
|
+
project.activate_with_client(client)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Starts a new timer. It stops the current task if there is any.
|
89
|
+
#
|
90
|
+
# Usage
|
91
|
+
#
|
92
|
+
# start a time new:
|
93
|
+
#
|
94
|
+
# start 'new_task'
|
95
|
+
# TODO: Make it start PAUSED TASKS!
|
96
|
+
def start(task_name = nil)
|
97
|
+
current_task.stop if current_task.present? && task_name.present?
|
98
|
+
Task::task(task_name).start
|
99
|
+
end
|
100
|
+
|
101
|
+
# Stops the current task.
|
102
|
+
#
|
103
|
+
def stop
|
104
|
+
current_task.stop if current_task
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def client(name)
|
110
|
+
Client.first_or_create :name => name, :description => name
|
111
|
+
end
|
112
|
+
|
113
|
+
def deactivate_current_client
|
114
|
+
client = current_client
|
115
|
+
client.active = false
|
116
|
+
client.save
|
117
|
+
end
|
118
|
+
|
119
|
+
def deactivate_current_project
|
120
|
+
project = current_project
|
121
|
+
project.active = false
|
122
|
+
project.save
|
123
|
+
end
|
124
|
+
|
125
|
+
def current_client
|
126
|
+
Client.first :active => true
|
127
|
+
end
|
128
|
+
|
129
|
+
def current_project
|
130
|
+
Project.first :active => true
|
131
|
+
end
|
132
|
+
|
133
|
+
def current_task
|
134
|
+
Task.first :active => true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/rtt/client.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
module Rtt
|
3
|
+
class Client
|
4
|
+
include DataMapper::Resource
|
5
|
+
|
6
|
+
DEFAULT_NAME = 'default'
|
7
|
+
DEFAULT_DESCRIPTION = 'Default Client'
|
8
|
+
|
9
|
+
property :id, Serial
|
10
|
+
property :name, String, :required => true, :unique => true, :default => DEFAULT_NAME
|
11
|
+
property :description, String, :default => DEFAULT_DESCRIPTION
|
12
|
+
property :active, Boolean, :default => false
|
13
|
+
has n, :projects #, :through => Resource
|
14
|
+
|
15
|
+
def self.default
|
16
|
+
first_or_create :active => true
|
17
|
+
end
|
18
|
+
|
19
|
+
def activate
|
20
|
+
self.active = true
|
21
|
+
self.save
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
module Rtt
|
3
|
+
class Command
|
4
|
+
attr_accessor :name, :optional
|
5
|
+
end
|
6
|
+
class PauseCommand < Command
|
7
|
+
NUMBER_OF_PARAM_REQUIRED = 0
|
8
|
+
end
|
9
|
+
class SetProjectCommand < Command
|
10
|
+
NUMBER_OF_PARAM_REQUIRED = 1
|
11
|
+
end
|
12
|
+
class SetUserCommand < Command
|
13
|
+
NUMBER_OF_PARAM_REQUIRED = 0
|
14
|
+
end
|
15
|
+
class SetClientCommand < Command
|
16
|
+
NUMBER_OF_PARAM_REQUIRED = 1
|
17
|
+
end
|
18
|
+
class StartCommand < Command
|
19
|
+
NUMBER_OF_PARAM_REQUIRED = 0
|
20
|
+
end
|
21
|
+
class StopCommand < Command
|
22
|
+
NUMBER_OF_PARAM_REQUIRED = 0
|
23
|
+
end
|
24
|
+
class RenameCommand < Command
|
25
|
+
NUMBER_OF_PARAM_REQUIRED = 1
|
26
|
+
end
|
27
|
+
class ReportCommand < Command
|
28
|
+
NUMBER_OF_PARAM_REQUIRED = 0
|
29
|
+
end
|
30
|
+
class QueryCommand < Command
|
31
|
+
NUMBER_OF_PARAM_REQUIRED = 0
|
32
|
+
end
|
33
|
+
class CommandNotFoundError < StandardError; end
|
34
|
+
class TaskNotStartedError < StandardError; end
|
35
|
+
|
36
|
+
module CmdLineInterpreter
|
37
|
+
|
38
|
+
COMMAND_MAPPING = {
|
39
|
+
:project => SetProjectCommand,
|
40
|
+
:client => SetClientCommand,
|
41
|
+
:report => ReportCommand,
|
42
|
+
:stop => StopCommand,
|
43
|
+
:start => StartCommand,
|
44
|
+
:rename => RenameCommand,
|
45
|
+
:list => QueryCommand,
|
46
|
+
:pause => PauseCommand,
|
47
|
+
:resume => StartCommand,
|
48
|
+
:user => SetUserCommand
|
49
|
+
}
|
50
|
+
|
51
|
+
def capture(arguments)
|
52
|
+
unless arguments.length == 0
|
53
|
+
operation = arguments.shift.to_sym
|
54
|
+
if COMMAND_MAPPING.keys.include?(operation)
|
55
|
+
klazz = COMMAND_MAPPING[operation]
|
56
|
+
if arguments.length >= klazz::NUMBER_OF_PARAM_REQUIRED
|
57
|
+
command = klazz.new
|
58
|
+
command.name = arguments.shift
|
59
|
+
command.optional = arguments if arguments.present?
|
60
|
+
Array(command)
|
61
|
+
end
|
62
|
+
elsif operation.present?
|
63
|
+
command = StartCommand.new
|
64
|
+
command.name = operation
|
65
|
+
command.optional = arguments if arguments.present?
|
66
|
+
Array(command)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def env_filters
|
72
|
+
[ 'client', 'project' ].inject({}) do |filters, key|
|
73
|
+
filters[key.to_sym] = env_variable(key) if env_variable(key).present?
|
74
|
+
filters
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def env_variable(key)
|
79
|
+
ENV[key] || ENV[key.upcase] || ENV[key.downcase]
|
80
|
+
end
|
81
|
+
|
82
|
+
def execute(cmds)
|
83
|
+
cmds.each { |cmd| execute_single(cmd) }
|
84
|
+
puts "Operation(s) succeded."
|
85
|
+
rescue => e
|
86
|
+
handle_error(e)
|
87
|
+
end
|
88
|
+
|
89
|
+
def execute_single(cmd)
|
90
|
+
case cmd
|
91
|
+
when SetProjectCommand
|
92
|
+
client = cmd.optional if cmd.optional.present?
|
93
|
+
set_project(cmd.name, client)
|
94
|
+
when SetClientCommand
|
95
|
+
set_client(cmd.name)
|
96
|
+
when StartCommand
|
97
|
+
start(cmd.name)
|
98
|
+
when PauseCommand
|
99
|
+
if current_task.present?
|
100
|
+
pause
|
101
|
+
else
|
102
|
+
raise TaskNotStartedError
|
103
|
+
end
|
104
|
+
when StopCommand
|
105
|
+
stop
|
106
|
+
when ReportCommand
|
107
|
+
options = env_filters.merge!(:pdf => cmd.name)
|
108
|
+
report(options)
|
109
|
+
when QueryCommand
|
110
|
+
list(env_filters)
|
111
|
+
when SetUserCommand
|
112
|
+
set_user(cmd.name)
|
113
|
+
else
|
114
|
+
raise CommandNotFoundError
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_error(e)
|
119
|
+
case e
|
120
|
+
when CommandNotFoundError
|
121
|
+
return puts_usage
|
122
|
+
when TaskNotStartedError
|
123
|
+
puts "There is no active task. Pause is not valid at this point."
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def puts_usage
|
128
|
+
puts
|
129
|
+
File.open("USAGE.txt") do |file|
|
130
|
+
while content = file.gets
|
131
|
+
puts content
|
132
|
+
end
|
133
|
+
end
|
134
|
+
puts
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|