tasklogger 0.0.2

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.
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
+