tasklogger 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/Rakefile +80 -0
  2. data/bin/tasklogger +161 -0
  3. data/lib/task.rb +53 -0
  4. data/lib/task_logger.rb +68 -0
  5. metadata +76 -0
data/Rakefile ADDED
@@ -0,0 +1,80 @@
1
+ require "rake/testtask"
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.verbose = true
9
+ end
10
+
11
+ require "rubygems"
12
+ require "rake/gempackagetask"
13
+ require "rake/rdoctask"
14
+
15
+ # This builds the actual gem. For details of what all these options
16
+ # mean, and other ones you can add, check the documentation here:
17
+ #
18
+ # http://rubygems.org/read/chapter/20
19
+ #
20
+ spec = Gem::Specification.new do |s|
21
+
22
+ # Change these as appropriate
23
+ s.name = "tasklogger"
24
+ s.version = "0.0.2"
25
+ s.summary = "A simple task/time logging utility"
26
+ s.author = "Chris Roos"
27
+ s.email = "chris@seagul.co.uk"
28
+ s.homepage = "http://chrisroos.co.uk"
29
+
30
+ s.has_rdoc = true
31
+ # You should probably have a README of some kind. Change the filename
32
+ # as appropriate
33
+ # s.extra_rdoc_files = %w(README)
34
+ # s.rdoc_options = %w(--main README)
35
+
36
+ # Add any extra files to include in the gem (like your README)
37
+ s.files = %w(Rakefile) + Dir.glob("{bin,test,lib/**/*}")
38
+ s.executables = FileList["bin/**"].map { |f| File.basename(f) }
39
+ s.require_paths = ["lib"]
40
+
41
+ # If you want to depend on other gems, add them here, along with any
42
+ # relevant versions
43
+ # s.add_dependency("some_other_gem", "~> 0.1.0")
44
+ s.add_dependency 'fastercsv'
45
+
46
+ # If your tests use any gems, include them here
47
+ # s.add_development_dependency("mocha") # for example
48
+ s.add_development_dependency 'mocha'
49
+ end
50
+
51
+ # This task actually builds the gem. We also regenerate a static
52
+ # .gemspec file, which is useful if something (i.e. GitHub) will
53
+ # be automatically building a gem for this project. If you're not
54
+ # using GitHub, edit as appropriate.
55
+ #
56
+ # To publish your gem online, install the 'gemcutter' gem; Read more
57
+ # about that here: http://gemcutter.org/pages/gem_docs
58
+ Rake::GemPackageTask.new(spec) do |pkg|
59
+ pkg.gem_spec = spec
60
+ end
61
+
62
+ desc "Build the gemspec file #{spec.name}.gemspec"
63
+ task :gemspec do
64
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
65
+ File.open(file, "w") {|f| f << spec.to_ruby }
66
+ end
67
+
68
+ task :package => :gemspec
69
+
70
+ # Generate documentation
71
+ Rake::RDocTask.new do |rd|
72
+
73
+ rd.rdoc_files.include("lib/**/*.rb")
74
+ rd.rdoc_dir = "rdoc"
75
+ end
76
+
77
+ desc 'Clear out RDoc and generated packages'
78
+ task :clean => [:clobber_rdoc, :clobber_package] do
79
+ rm "#{spec.name}.gemspec"
80
+ end
data/bin/tasklogger ADDED
@@ -0,0 +1,161 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'rubygems'
5
+ require 'fastercsv'
6
+ require 'task_logger'
7
+
8
+ unless description = ARGV[0]
9
+ puts "Please enter a description for this task."
10
+ exit 1
11
+ end
12
+
13
+ default_timelog = File.join(ENV['HOME'], 'timelog.csv')
14
+ timelog = ENV['TIMELOG_DATA'] ? ENV['TIMELOG_DATA'] : default_timelog
15
+
16
+ # puts "Logging task data to #{timelog}"
17
+
18
+ time_format = "%Y-%m-%d %H:%M"
19
+
20
+ if description == 'open'
21
+ `mate #{timelog}`
22
+ exit
23
+ end
24
+
25
+ if description == 'stop'
26
+ entries = FasterCSV.read(timelog)
27
+ last_entry = entries.last
28
+ if last_entry[1]
29
+ puts "The last entry already has an end time and I'm not going to overwrite it."
30
+ exit 1
31
+ end
32
+ last_entry[1] = Time.now.strftime(time_format)
33
+ File.open(timelog, 'w') do |file|
34
+ entries.each do |entry|
35
+ file.puts entry.to_csv
36
+ end
37
+ end
38
+ `mate #{timelog}`
39
+ exit
40
+ end
41
+
42
+ def hms(duration)
43
+ hours = duration.to_i / 3600
44
+ seconds = duration.to_i % 3600
45
+ minutes = seconds / 60
46
+ seconds = seconds % 60
47
+ [hours, minutes, seconds].join(":")
48
+ end
49
+
50
+ def decimal_hours(seconds)
51
+ minutes = seconds / 60.0
52
+ minutes / 60.0
53
+ end
54
+
55
+ if description == 'report'
56
+ require 'time'
57
+
58
+ if project_filter = ARGV[1]
59
+ csv_data = File.read(timelog)
60
+ total_duration = 0
61
+
62
+ puts ''
63
+ header = 'Start'.ljust(23) + 'End'.ljust(23) + 'Seconds'.ljust(8) + 'H:M:S'.ljust(8) + "Hours".ljust(8) + 'Project'.ljust(12) + 'Description'
64
+ puts header
65
+ puts '=' * header.length
66
+
67
+ previous_day, day_duration = nil, 0
68
+
69
+ rows = FasterCSV.parse(csv_data)
70
+ matching_entries = rows.select do |row|
71
+ start_time, end_time, project_name, description = row
72
+ project_name == project_filter
73
+ end
74
+
75
+ matching_entries.each do |row|
76
+ start_time, end_time, project_name, description = row
77
+
78
+ duration = 0
79
+ if end_time
80
+ start_time = Time.parse(start_time)
81
+ end_time = Time.parse(end_time)
82
+ duration = (end_time - start_time)
83
+ end
84
+
85
+ if (row != matching_entries.first and previous_day != start_time.send(:to_date))
86
+ # We're on a new day
87
+ puts ' ' + '-'*7 + ' ' + '-'*7 + ' ' + '-'*7
88
+ print ' '
89
+ print day_duration.to_s.ljust(8)
90
+ print hms(day_duration).ljust(8)
91
+ print format("%0.2f", decimal_hours(day_duration)).ljust(8)
92
+ puts ''
93
+ puts ''
94
+ day_duration = 0
95
+ end
96
+ previous_day = start_time.send(:to_date)
97
+ day_duration += duration
98
+
99
+ total_duration += duration
100
+ row.insert(2, duration)
101
+ row.insert(3, hms(duration))
102
+ row.insert(4, decimal_hours(duration))
103
+
104
+ print Time.parse(row[0]).strftime("%a %d %b %Y %H:%M").ljust(23)
105
+ print Time.parse(row[1]).strftime("%a %d %b %Y %H:%M").ljust(23)
106
+ print row[2].to_s.ljust(8)
107
+ print row[3].to_s.ljust(8)
108
+ print format("%0.2f", row[4]).ljust(8)
109
+ print row[5].to_s.ljust(12)
110
+ puts row[6]
111
+
112
+ if row == matching_entries.last
113
+ # We're on the last day
114
+ print ' '
115
+ print day_duration.to_s.ljust(8)
116
+ print hms(day_duration).ljust(8)
117
+ print format("%0.2f", decimal_hours(day_duration)).ljust(8)
118
+ puts ''
119
+ puts ''
120
+ day_duration = 0
121
+ end
122
+
123
+ end
124
+ puts ''
125
+ puts 'Total duration (H:M:S): ' + hms(total_duration)
126
+ puts "Total duration (decimal hours): #{decimal_hours(total_duration)}"
127
+ puts ''
128
+
129
+ else
130
+ d = Hash.new(0)
131
+
132
+ csv_data = File.read(timelog)
133
+ FasterCSV.parse(csv_data).each do |row|
134
+ start_time, end_time, project_name, description = row
135
+ if end_time
136
+ start_time = Time.parse(start_time)
137
+ end_time = Time.parse(end_time)
138
+ duration = (end_time - start_time)
139
+ d[project_name] += duration
140
+ end
141
+ end
142
+
143
+ d.each_pair do |project_name, duration|
144
+ p [project_name, hms(duration)]
145
+ end
146
+ end
147
+ exit
148
+ end
149
+
150
+ if description == 'resume'
151
+ TaskLogger.new(timelog).resume
152
+ exit
153
+ end
154
+
155
+ if description == 'list'
156
+ TaskLogger.new(timelog).list
157
+ exit
158
+ end
159
+
160
+ # Otherwise we're starting a new task
161
+ TaskLogger.new(timelog).start description
data/lib/task.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'time'
2
+
3
+ class Task
4
+
5
+ TIME_FORMAT = "%Y-%m-%d %H:%M"
6
+ DEFAULT_PROJECT = '<project-name>'
7
+
8
+ attr_reader :started_at, :finished_at
9
+ attr_accessor :project, :description
10
+
11
+ def self.from_array(task_data)
12
+ description = task_data[3]
13
+ started_at = Time.parse(task_data[0]) if task_data[0]
14
+ finished_at = Time.parse(task_data[1]) if task_data[1]
15
+ project = task_data[2]
16
+ new(description, started_at, finished_at, project)
17
+ end
18
+
19
+ def initialize(description, started_at = nil, finished_at = nil, project = nil)
20
+ @started_at = started_at || Time.now
21
+ @finished_at = finished_at
22
+ @project = project || DEFAULT_PROJECT
23
+ @description = description
24
+ end
25
+
26
+ def restart!
27
+ @started_at = Time.now
28
+ @finished_at = nil
29
+ end
30
+
31
+ def finish!
32
+ @finished_at = Time.now unless finished?
33
+ end
34
+
35
+ def to_a
36
+ [formatted_started_at, formatted_finished_at, @project, @description]
37
+ end
38
+
39
+ private
40
+
41
+ def finished?
42
+ @finished_at
43
+ end
44
+
45
+ def formatted_started_at
46
+ @started_at.strftime(TIME_FORMAT) if @started_at
47
+ end
48
+
49
+ def formatted_finished_at
50
+ @finished_at.strftime(TIME_FORMAT) if @finished_at
51
+ end
52
+
53
+ end
@@ -0,0 +1,68 @@
1
+ require 'task'
2
+
3
+ class TaskLogger
4
+
5
+ TIME_FORMAT = "%Y-%m-%d %H:%M"
6
+
7
+ def initialize(tasks_path)
8
+ @tasks_path = tasks_path
9
+ end
10
+
11
+ def start(task_description)
12
+ task = Task.new(task_description)
13
+ if last_task_data = tasks.pop
14
+ last_task = Task.from_array(last_task_data)
15
+ last_task.finish!
16
+ tasks << last_task.to_a
17
+ end
18
+ tasks << task.to_a
19
+ write_tasks
20
+ end
21
+
22
+ def resume
23
+ exit_if_missing_data
24
+
25
+ last_task_data = tasks.pop
26
+ last_task = Task.from_array(last_task_data)
27
+ last_task.finish!
28
+
29
+ penultimate_task_data = tasks.last
30
+ penultimate_task = Task.from_array(penultimate_task_data)
31
+ penultimate_task.restart!
32
+
33
+ tasks << last_task.to_a
34
+ tasks << penultimate_task.to_a
35
+
36
+ write_tasks
37
+ end
38
+
39
+ def list
40
+ exit_if_missing_data
41
+
42
+ tasks[-5, 5].each do |task|
43
+ puts task.to_csv
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def tasks
50
+ @tasks ||= File.exists?(@tasks_path) ? FasterCSV.read(@tasks_path) : []
51
+ end
52
+
53
+ def exit_if_missing_data
54
+ if tasks.empty?
55
+ puts "Error. The timelog data cannot be found."
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ def write_tasks
61
+ File.open(@tasks_path, 'w') do |file|
62
+ tasks.each do |task|
63
+ file.puts task.to_csv
64
+ end
65
+ end
66
+ end
67
+
68
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tasklogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Chris Roos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-26 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fastercsv
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mocha
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: chris@seagul.co.uk
37
+ executables:
38
+ - tasklogger
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Rakefile
45
+ - lib/task.rb
46
+ - lib/task_logger.rb
47
+ has_rdoc: true
48
+ homepage: http://chrisroos.co.uk
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.5
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: A simple task/time logging utility
75
+ test_files: []
76
+