trak 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ # Change log
2
+
3
+ ## v0.0.0 (2012-04-25)
4
+
5
+ Tracker is now known as trak, and it's a RubyGem!
6
+
7
+ Changed:
8
+
9
+ * Trak has now been ported from Perl to Ruby. The main reason for this
10
+ was for the ability to support the awesome gem Chronic for natural
11
+ language date parsing in an upcoming release. But beyond that,
12
+ RubyGems is fantastic for packaging and distribution and the plan is
13
+ to make this code seriously more modular over the next little while
14
+ (the code is a joke at the moment :O).
15
+
16
+ ## v0.4 (2011-10-17)
17
+
18
+ Added:
19
+
20
+ * If the EDITOR environment variable is set, this is now used by default
21
+ to edit a log file with `track -e`.
22
+
23
+ * The current time is displayed at the end of the report, like so:
24
+
25
+ # Today's logged work
26
+ ...
27
+ Hours logged until 9:30 AM (since 9:15 AM). Currently 10:13 AM.
28
+
29
+ ## v0.3
30
+
31
+ Added:
32
+
33
+ * Report now calculates the total time spent on each task throughout the
34
+ day
35
+ * Time can now be logged as either minutes or hours. The time argument
36
+ can take the format `##<denom>`, where `##` is the amount of time
37
+ spent, and `<denom>` signifies hours (`h/hr/hour/hours`) or minutes
38
+ (`m/min/minute/minutes`).
39
+
40
+ Changed:
41
+
42
+ * Report formatting improvements
43
+
44
+ Fixed:
45
+
46
+ * Time wrap-around bug where if time logged passed midnight AM/PM would
47
+ display incorrectly.
48
+
49
+ ## v0.2.1
50
+
51
+ Changed:
52
+
53
+ * Change log is now found in CHANGELOG.md rather than README.md.
54
+
55
+ Fixed:
56
+
57
+ * Fixed a bug where invoking with no arguments would create an entry in
58
+ the log with no time or message. No arguments is a synonym for `-l` or
59
+ `-r`.
60
+
61
+ ## v0.2
62
+
63
+ Added:
64
+
65
+ * Tracker now automatically calculates the time you started your day
66
+ based on your first log for that day. This is stored in the time log
67
+ after the date, and used to calculate how far into the day you have
68
+ tracked time.
69
+
70
+ Changed:
71
+
72
+ * Everything after the first argument (the amount of time) is now
73
+ considered to be the task name (i.e., the task is no longer truncated
74
+ to the first word).
75
+ * Formatting improvements
76
+
77
+ ## v0.1
78
+
79
+ Added:
80
+
81
+ * Tracker.pl -l will now tell you what time you've logged time until.
82
+ For example, if your start time is 8:00 AM and you've logged 3 hours,
83
+ it will output the following:
84
+
85
+ Hours logged until 11:00 AM (since 8:00 AM).
86
+
87
+ Changed:
88
+
89
+ * `-l` and `-r` switches are now synonyms
90
+ * Formatting improvements
@@ -0,0 +1,107 @@
1
+ # Trak: track chunks of time from the command line
2
+
3
+ Trak, v0.0.0 (Apr 25, 2012)
4
+ Written by Adam Sharp
5
+
6
+ ## Notice
7
+
8
+ Trak was recently (i.e., last week) a Perl script. It has been ported to
9
+ Ruby, but the code really looks like it's taken a beating and is
10
+ definitely NOT what I want it to ultimately look like. Much more
11
+ ruby-fying to happen yet, as well as support for the excellent
12
+ [Chronic](https://github.com/mojombo/chronic) gem for natural language
13
+ date parsing in the pipeline.
14
+
15
+ It's now structured as a RubyGem and should hopefully be available on
16
+ RubyGems soon.
17
+
18
+ Stay tuned.
19
+
20
+ ## Description
21
+
22
+ Trak is a utility for Mac OS X that allows you to quickly make a record
23
+ of how much time you've spent on various tasks throughout the day.
24
+
25
+ Work logs are stored in `/Users/yourusername/Documents/Tracker/` with
26
+ the format `YEAR-MONTH-DAY-time-log.txt`.
27
+
28
+ An example work log that trak will create:
29
+
30
+ 2011-09-01 9:00
31
+ 30: nap
32
+ 45: procrastinate
33
+ 30: uni
34
+ 120: trak
35
+
36
+ ## Installation
37
+
38
+ Check out this repository and then create a symlink to bin/trak
39
+ somewhere in your path:
40
+
41
+ $ ln -s <repo>/bin/trak /usr/bin/trak
42
+
43
+ ## Usage
44
+
45
+ trak [-d|--date DATE] ##<denom> <description> # => data entry
46
+ trak [-d|--date DATE] [-r|-l] # => reporting
47
+ trak [-d|--date DATE] -e # => manually edit time log
48
+
49
+ Where:
50
+
51
+ * `##` is a decimal signifying how much time has been spent.
52
+ * `<denom>` is either hours (`h/hr/hour/hours`) or minutes
53
+ (`m/min/minute/minutes`). `<denom>` is optional and if ommitted,
54
+ Tracker will interpret the time entered as minutes.
55
+ * `<description>` is a string containing a brief description of the
56
+ activity.
57
+ * `DATE` is a string of the format `YYYY-MM-DD` which represents any
58
+ date. This effects any of Tracker's modes, i.e., insertion, editing or
59
+ reporting.
60
+
61
+ ### Descriptions
62
+
63
+ You can use either
64
+
65
+ $ trak 30 "Foo bar"
66
+
67
+ or
68
+
69
+ $ trak 30 Foo bar
70
+
71
+ as everything after the first argument is considered the name of the
72
+ task.
73
+
74
+ ### Entering time
75
+
76
+ These are all valid commands:
77
+
78
+ $ trak 1h Write trak documentation # => 1 hour
79
+ $ trak 30min Rewrite trak documentation # => 30 minutes
80
+ $ trak 4hours Refactor trak # => 4 hours
81
+ $ trak 15 Lunch # => 15 minutes
82
+
83
+ ## To do
84
+
85
+ * Proper handling of incorrectly formatted dates
86
+ * Write a report that gives a weekly breakdown given a date in that week
87
+ * Add a `-h` usage/help switch
88
+ * Make the personal time search more configurable by putting keywords to
89
+ search in an array
90
+ * Have the different types of reports, and keywords for those reports,
91
+ completely stored in a configuration file. The last category in the
92
+ file is the default report. Because keywords for custom reports would
93
+ work on a whitelist system, everything that doesn't match goes into
94
+ the default. For example:
95
+
96
+ # Personal
97
+ lunch
98
+ uni
99
+ news
100
+ # Default
101
+ Work
102
+
103
+ * Have the keywords in the configuration file actually be regexes. When
104
+ reading the config file, any empty lines or whitespace are ignored.
105
+ * Give an estimate of completion time, with a configurable default for
106
+ the length of the work day. Also take into account the default length
107
+ of lunch break (configurable) if lunch hasn't yet been logged.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'trak'
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # command:
4
+ # $ Tracker.pl 30 "I did stuff"
5
+ # $ Tracker.pl 1h "I did twice as much stuff"
6
+ #
7
+ # format:
8
+ # - First arg is how much time spent
9
+ # - Second arg is a description
10
+
11
+ require 'trollop'
12
+ require 'debugger'
13
+
14
+ require "trak/tracker_util"
15
+
16
+ # place where data is stored
17
+ datadir = "#{ENV['HOME']}/Documents/Tracker/"
18
+ %x[mkdir -p #{datadir}]
19
+
20
+ # define command line options
21
+ opts = Trollop::options do
22
+ opt :report, "Reporting mode", :short => "-l"
23
+ opt :edit, "Edit mode"
24
+ opt :date, "The date", :type => String, :short => "-d"
25
+ opt :debug, "Debugging mode", :short => "-i"
26
+ end
27
+
28
+ $g_opts = opts
29
+ def debug(steps = 1)
30
+ debugger if $g_opts[:debug]
31
+ end
32
+
33
+ # all valid options have been processed, so figure out which mode
34
+ # we're in...
35
+ #
36
+ # if we found a -r or -l option, ignore everything else
37
+ if opts[:report]
38
+ MODE = 'report'
39
+ # now check if the user wants edit mode
40
+ elsif opts[:edit]
41
+ MODE = 'edit'
42
+ # if there are still unprocessed args (that didn't look like switches),
43
+ # we're in insert mode
44
+ elsif ARGV.length > 0
45
+ MODE = 'insert'
46
+ # if all else fails, there were probably no args to begin with, so we're
47
+ # in report mode
48
+ else
49
+ MODE = 'report'
50
+ end
51
+
52
+ today = Time.now.strftime '%F'
53
+
54
+ # did the user supply a date argument that isn't today?
55
+ if opts[:date] && opts[:date] != today
56
+ fdate = opts[:date]
57
+ # otherwise use today's date, formatted, and set date_arg to be false
58
+ else
59
+ fdate = today
60
+ opts[:date] = nil
61
+ end
62
+
63
+ # set the output file name
64
+ filename = "#{datadir}#{fdate}-time-log.txt"
65
+
66
+ if MODE == 'report'
67
+ if File.exist? filename
68
+ # open the file and get it as an array
69
+ begin
70
+ file = File.open(filename).readlines.map &:chomp
71
+ rescue
72
+ STDERR.puts "#{__FILE__}: #{$!}"
73
+ exit 1
74
+ end
75
+
76
+ # The keys for each hash are the titles of the various tasks logged.
77
+ # The values are the total time spent on the task.
78
+ work = {}
79
+ personal = {}
80
+
81
+ # find the start time for the day we're reporting on
82
+ startTime = file.first.split[1]
83
+
84
+ # process each line of the file
85
+ file[1..file.size].each do |line|
86
+ minutes, text = line.split(': ')
87
+ unless text =~ /personal|uni|lunch|home/
88
+ work[text] = 0 unless work.include? text
89
+ work[text] += minutes.to_i
90
+ else
91
+ personal[text] = 0 unless personal.include? text
92
+ personal[text] += minutes.to_i
93
+ end
94
+ end
95
+
96
+ # print the report
97
+ if opts[:date]
98
+ puts "# Logged work for #{fdate}"
99
+ else
100
+ puts "# Today's logged work"
101
+ end
102
+
103
+ workTotal = TrackerUtil::printSubReport(work, "Work")
104
+ personalTotal = TrackerUtil::printSubReport(personal, "Personal")
105
+
106
+ newTimeString = TrackerUtil::to12HourTime(TrackerUtil::newTimeWithMinutes(startTime, workTotal + personalTotal))
107
+ puts "Hours logged until #{newTimeString} (since #{TrackerUtil::to12HourTime(startTime)}). "
108
+
109
+ # if we're reporting for today, print the current time
110
+ puts "Currently #{TrackerUtil::to12HourTime(TrackerUtil::currentTimeFormatted())}." unless opts[:date]
111
+ else
112
+ if opts[:date]
113
+ STDERR.puts "No time log for #{fdate}. Track some time first."
114
+ else
115
+ STDERR.puts "No time log for today. Track some time first.\n"
116
+ end
117
+ end
118
+
119
+ elsif MODE == 'edit'
120
+ if File.exist? filename
121
+ if ENV['EDITOR']
122
+ exec "#{ENV['EDITOR']} #{filename}"
123
+ else
124
+ exec "open", filename
125
+ end
126
+ exit
127
+ else
128
+ STDERR.puts "#{__FILE__}: #{filename} does not exist or unable to open."
129
+ exit 1
130
+ end
131
+
132
+ elsif MODE == 'insert'
133
+ if opts[:date]
134
+ puts "WARNING: Adding time to a day other than today is not recommended."
135
+ print "Continue? (y/n) "
136
+ input = STDIN.readline.chomp
137
+ unless input =~ /^y(es)?/i
138
+ STDERR.puts "Timelog update cancelled."
139
+ exit 1
140
+ end
141
+ end
142
+
143
+ # process arguments
144
+ debug
145
+ minutes = TrackerUtil::processTimeArgument ARGV.shift
146
+ message = ARGV.join(" ")
147
+
148
+ # open the output file
149
+ first_time = !File.exist?(filename)
150
+ # debug
151
+ begin
152
+ File.open filename, 'a', :autoclose => true do |file|
153
+ if first_time
154
+ currentTimeInMinutes = TrackerUtil::timeToMinutes(TrackerUtil::currentTimeFormatted)
155
+ startTime = TrackerUtil::minutesToTime((currentTimeInMinutes - minutes).round_to_nearest 15)
156
+ file.puts "#{fdate} #{startTime}"
157
+ end
158
+ file.puts "#{minutes}: #{message}"
159
+ end
160
+ rescue
161
+ STDERR.puts "Couldn't open #{filename}: #{$!}"
162
+ exit 1
163
+ end
164
+
165
+ else
166
+ STDERR.puts "Couldn't determine the correct mode (I was given '#{MODE}'): #{$!}"
167
+ exit 1
168
+ end
@@ -0,0 +1,16 @@
1
+ # https://github.com/rails/rails/blob/2a371368c91789a4d689d6a84eb20b238c37678a/activesupport/lib/active_support/core_ext/object/blank.rb#L91
2
+ class String
3
+ # 0x3000: fullwidth whitespace
4
+ NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
5
+
6
+ # A string is blank if it's empty or contains whitespaces only:
7
+ #
8
+ # "".blank? # => true
9
+ # " ".blank? # => true
10
+ # " ".blank? # => true
11
+ # " something here ".blank? # => false
12
+ #
13
+ def blank?
14
+ self !~ NON_WHITESPACE_REGEXP
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class Integer
2
+ def round_to_nearest(integer = 1)
3
+ if integer == 1
4
+ self.round
5
+ else
6
+ ((self.to_f / integer).round * integer).to_i
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,103 @@
1
+ require "trak/blank"
2
+ require "trak/round_to_nearest"
3
+
4
+ module TrackerUtil
5
+ TIME_FORMAT_12HOUR = "%l:%M %p"
6
+ TIME_FORMAT_24HOUR = "%k:%M"
7
+
8
+ # expects a hash of tasks mapped to time spent, and a sub-report name
9
+ # (e.g., work, personal)
10
+ # prints a formatted sub-report
11
+ # returns the total hours worked
12
+ def self.printSubReport(report_hash, report_title)
13
+ total = 0
14
+ unless report_hash.empty?
15
+ count = 0
16
+ report_out = ""
17
+ report_hash.each do |title, minutes|
18
+ total += minutes.to_i
19
+ count += 1
20
+ report_out += "=> #{timeString(minutes)}: #{title}"
21
+ report_out += "\n" unless count == report_hash.size
22
+ end
23
+ puts "# #{report_title} time (#{timeString(total)})"
24
+ puts report_out
25
+ end
26
+ total
27
+ end
28
+
29
+ def self.time_with_hours_minutes(*hm)
30
+ dmy = Time.now.to_a[3..5].reverse
31
+ Time.new(*dmy, *hm)
32
+ end
33
+
34
+ # expects a number of minutes
35
+ # if less than 60 returns the number with an "m"
36
+ # otherwise converts to hours and adds an "h"
37
+ def self.timeString(minutes)
38
+ if minutes >= 60
39
+ hours = minutes/60.0
40
+ if hours % 1 == 0
41
+ hours = hours.to_i
42
+ end
43
+ "#{hours}h"
44
+ else
45
+ "#{minutes}m"
46
+ end
47
+ end
48
+
49
+ def self.newTimeWithMinutes(start_time, minutes)
50
+ hm = start_time.split ':'
51
+ Time.at(time_with_hours_minutes(*hm).to_i + minutes.to_i*60).strftime(TIME_FORMAT_24HOUR).strip
52
+ end
53
+
54
+ def self.to12HourTime(time)
55
+ unless time.blank?
56
+ hm = time.split ':'
57
+ time_with_hours_minutes(*hm).strftime(TIME_FORMAT_12HOUR).strip
58
+ end
59
+ end
60
+
61
+ def self.currentTimeFormatted
62
+ Time.now.strftime(TIME_FORMAT_24HOUR)
63
+ end
64
+
65
+ # expects a single argument - the time argument in the format ##m or ##h
66
+ # if argument has no m/h qualifier, assume m
67
+ # returns a number of minutes
68
+ def self.processTimeArgument(time_string)
69
+ if time_string =~ /^(\d*\.?\d+)((m|min|minute|minutes)|(h|hr|hour|hours))?$/i
70
+ time = $1.to_i
71
+ modifier = $2
72
+ minutes = (modifier =~ /h.*/) ? time * 60 : time
73
+
74
+ # check enough time has been logged
75
+ if minutes < 15
76
+ STDERR.puts "You must log at least 15 minutes."
77
+ exit 1
78
+ end
79
+
80
+ minutes.round_to_nearest 15
81
+ else
82
+ STDERR.puts "Incorrectly formatted argument."
83
+ exit 1
84
+ end
85
+ end
86
+
87
+ # expects a time string formatted HH24:MM
88
+ def self.timeToMinutes(time)
89
+ hours, minutes = time.split(':')
90
+ hours*60 + minutes
91
+ end
92
+
93
+ # expects an integer
94
+ def self.minutesToTime(minutes)
95
+ time_with_hours_minutes(minutes / 60, minutes % 60).sprintf TIME_FORMAT_24HOUR
96
+ end
97
+
98
+ # expects an integer which is the amount of minutes logged
99
+ def self.startTimeInMinutes(minutes)
100
+ currentTimeInMinutes = timeToMinutes(currentTimeFormatted).round_to_nearest 15
101
+ currentTimeInMinutes - minutes
102
+ end
103
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trak
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adam Sharp
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: trollop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.16'
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: '1.16'
30
+ description: ! 'Problem: when tracking time, I don''t want to have to
31
+
32
+ start and stop a timer. Trak is a tool that lets me say "I just spent
33
+
34
+ 15 minutes working on email", instead of "I''m starting to email
35
+
36
+ now...whoops! I forgot to tell the computer I stopped." Then later in
37
+
38
+ the day when you spend some more time emailing, you don''t have to keep
39
+
40
+ the total time you''ve spent for the day in your head. When you tell trak
41
+
42
+ to report on your time spent for the day, it tallies each task and gives
43
+
44
+ you a breakdown.'
45
+ email:
46
+ - adsharp@me.com
47
+ executables:
48
+ - trak
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - lib/trak/blank.rb
53
+ - lib/trak/round_to_nearest.rb
54
+ - lib/trak/tracker_util.rb
55
+ - lib/trak.rb
56
+ - bin/trak
57
+ - CHANGELOG.md
58
+ - README.md
59
+ homepage: http://github.com/sharplet/trak
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.21
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: A command line tool for tracking chunks of time
83
+ test_files: []