tracking 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
- - jruby-19mode #JRuby in 1.9 mode
5
- - rbx-19mode #Rubinius in 1.9 mode
4
+ - jruby-19mode # JRuby in 1.9 mode
5
+ - rbx-19mode # Rubinius in 1.9 mode
6
+ branches:
7
+ only:
8
+ - master
@@ -0,0 +1,9 @@
1
+ 1.1
2
+ - Color coding for the current (last) task (can be disabled)
3
+ - Rename command, lets you rename the last task (tracking -r)
4
+ - Fully documented source code
5
+ - Test improvements
6
+ - More source code improvements
7
+
8
+ 1.0
9
+ - Initial release
data/Gemfile CHANGED
@@ -1,14 +1,21 @@
1
- source "http://rubygems.org"
1
+ source :rubygems
2
2
 
3
3
  # Add dependencies required to use your gem here.
4
+ gem 'colorize', '~> 0.5'
4
5
 
5
6
  # Add dependencies to develop your gem here.
6
7
  # Include everything needed to run rake, tests, features, etc.
7
8
  group :development do
8
- gem "shoulda", ">= 0"
9
- gem "rdoc", "~> 3.12"
10
- gem "bundler", ">= 1.0.0"
11
- gem "jeweler", "~> 1.8.4"
12
- gem "simplecov", ">= 0"
13
- gem "yard", ">= 0"
9
+ gem 'rdoc', '~> 3.12'
10
+ gem 'bundler', '>= 1.0.0'
11
+ gem 'jeweler', '~> 1.8.4'
12
+ gem 'simplecov'
13
+ gem 'yard'
14
+ gem 'rspec'
15
+ platforms :ruby do
16
+ gem 'redcarpet'
17
+ end
18
+ platforms :jruby do
19
+ gem 'kramdown'
20
+ end
14
21
  end
data/README.md CHANGED
@@ -1,11 +1,13 @@
1
- #tracking [![Build Status](https://secure.travis-ci.org/thenickperson/tracking.png?branch=master)](http://travis-ci.org/thenickperson/tracking)
1
+ #tracking [![Build Status](https://secure.travis-ci.org/thenickperson/tracking.png?branch=master)](http://travis-ci.org/thenickperson/tracking) [![Dependency Status](https://gemnasium.com/thenickperson/tracking.png)](https://gemnasium.com/thenickperson/tracking) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thenickperson/tracking)
2
2
  A simple and configurable command line time tracker.
3
3
 
4
4
  ##Installation
5
5
 
6
6
  `gem install tracking`
7
7
 
8
- If you're on Windows, you should set up [Ruby Installer](http://rubyinstaller.org/downloads/) and [DevKit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) first.
8
+ If you're on Windows, you should set up
9
+ [Ruby Installer](http://rubyinstaller.org/downloads/) and
10
+ [DevKit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) first.
9
11
 
10
12
  Also, tracking does not work on Ruby 1.8 (yet). Please upgrade to Ruby 1.9.3.
11
13
 
@@ -73,6 +75,8 @@ The default settings are listed below, along with a description of each setting.
73
75
  :task_width: 40
74
76
  # format to use for elapsed time display (:colons or :letters)
75
77
  :elapsed_format: :colons
78
+ # toggle colored display of the current (last) task
79
+ :color_current_task: true
76
80
  # toggle header describing tracking's display columns (true or false)
77
81
  :show_header: true
78
82
  # toggle display of seconds in elapsed time (true of false)
@@ -80,7 +84,8 @@ The default settings are listed below, along with a description of each setting.
80
84
  ```
81
85
 
82
86
  ##Elapsed Time Formats
83
- Elapsed times are displayed in this order: days, hours, minutes, seconds (if enabled)
87
+ Elapsed times are displayed in this order: days, hours, minutes, seconds (if
88
+ enabled)
84
89
  - hide elapsed seconds
85
90
  - colons: `01:02:03` (default)
86
91
  - letters: `01d 02h 03m`
@@ -89,18 +94,28 @@ Elapsed times are displayed in this order: days, hours, minutes, seconds (if ena
89
94
  - letters: `01d 02h 03m 04s`
90
95
 
91
96
  ##Contributing to tracking
92
- - Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
93
- - Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
97
+ - Check out the latest master to make sure the feature hasn't been implemented
98
+ or the bug hasn't been fixed yet.
99
+ - Check out the issue tracker to make sure someone already hasn't requested it
100
+ and/or contributed it.
94
101
  - Fork the project.
95
102
  - Start a feature/bugfix branch.
96
103
  - Commit and push until you are happy with your contribution.
97
- - Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
98
- - Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
104
+ - Make sure to add tests for it. This is important so I don't break it in a
105
+ future version unintentionally.
106
+ - Please try not to mess with the Rakefile, version, or history. If you want to
107
+ have your own version, or is otherwise necessary, that is fine, but please
108
+ isolate to its own commit so I can cherry-pick around it.
99
109
 
100
110
  ##Similar Projects
101
111
  - [timetrap](https://github.com/samg/timetrap)
102
112
  - [d-time-tracker](https://github.com/DanielVF/d-time-tracker)
113
+ - [to-do](http://github.com/kristenmills/to-do) if you want a good command line
114
+ todo manager to complement tracking
115
+
116
+ ##Special Thanks
117
+ - [to-do](http://github.com/kristenmills/to-do) and
118
+ [timetrap](https://github.com/samg/timetrap) for letting me borrow some code
103
119
 
104
120
  ##Copyright
105
- Copyright (c) 2012 Nicolas McCurdy. See LICENSE.txt for
106
- further details.
121
+ Copyright (c) 2012 Nicolas McCurdy. See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ begin
6
6
  Bundler.setup(:default, :development)
7
7
  rescue Bundler::BundlerError => e
8
8
  $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
9
+ $stderr.puts 'Run `bundle install` to install missing gems'
10
10
  exit e.status_code
11
11
  end
12
12
  require 'rake'
@@ -14,13 +14,13 @@ require 'rake'
14
14
  require 'jeweler'
15
15
  Jeweler::Tasks.new do |gem|
16
16
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
- gem.name = "tracking"
18
- gem.homepage = "http://github.com/thenickperson/tracking"
19
- gem.license = "MIT"
20
- gem.summary = "A simple and configurable command line time tracker."
21
- gem.description = "See README for more information."
22
- gem.email = "thenickperson@gmail.com"
23
- gem.authors = ["Nicolas McCurdy"]
17
+ gem.name = 'tracking'
18
+ gem.homepage = 'http://github.com/thenickperson/tracking'
19
+ gem.license = 'MIT'
20
+ gem.summary = 'A simple and configurable command line time tracker.'
21
+ gem.description = 'See README for more information.'
22
+ gem.email = 'thenickperson@gmail.com'
23
+ gem.authors = ['Nicolas McCurdy']
24
24
  # dependencies defined in Gemfile
25
25
  end
26
26
  Jeweler::RubygemsDotOrgTasks.new
@@ -32,9 +32,9 @@ Rake::TestTask.new(:test) do |test|
32
32
  test.verbose = true
33
33
  end
34
34
 
35
- desc "Code coverage detail"
35
+ desc 'Code coverage detail'
36
36
  task :simplecov do
37
- ENV['COVERAGE'] = "true"
37
+ ENV['COVERAGE'] = 'true'
38
38
  Rake::Task['spec'].execute
39
39
  end
40
40
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -6,6 +6,6 @@ else
6
6
  File.dirname __FILE__
7
7
  end
8
8
 
9
- require File.join(File.expand_path("..", current_directory), "lib", "tracking")
9
+ require File.join(File.expand_path('..', current_directory), 'lib', 'tracking')
10
10
 
11
11
  Tracking::CLI.parse
@@ -1,21 +1,10 @@
1
- require "fileutils"
2
-
3
- require File.join(File.dirname(__FILE__), "tracking", "config")
4
-
5
- #create ~/.tracking
6
- if not File.exist? File.join(ENV["HOME"], ".tracking")
7
- Dir.mkdir File.join(ENV["HOME"], ".tracking")
8
- end
9
-
10
- #create config file
11
- if not File.exist? File.join(ENV["HOME"], ".tracking", "config.yml")
12
- Tracking::Config.write
1
+ # Require all of tracking's stuff
2
+ require File.join(File.dirname(__FILE__), 'tracking', 'config')
3
+ require File.join(File.dirname(__FILE__), 'tracking', 'list')
4
+ require File.join(File.dirname(__FILE__), 'tracking', 'task')
5
+ require File.join(File.dirname(__FILE__), 'tracking', 'cli')
6
+
7
+ # Tracking is the main namespace that all of the other modules and classes are a
8
+ # part of
9
+ module Tracking
13
10
  end
14
-
15
- #create data file
16
- if not File.exist? File.expand_path Tracking::Config[:data_file]
17
- FileUtils.touch File.expand_path Tracking::Config[:data_file]
18
- end
19
-
20
- require File.join(File.dirname(__FILE__), "tracking", "list")
21
- require File.join(File.dirname(__FILE__), "tracking", "cli")
@@ -1,152 +1,153 @@
1
- #tracking's command line interface (view)
1
+ require 'optparse'
2
+ require 'colorize'
2
3
 
3
- #imports
4
- require "optparse"
5
-
6
- #view module methods
7
4
  module Tracking
5
+ # Contains methods for displaying the list in a command line and parsing
6
+ # command line arguments.
8
7
  module CLI
9
-
10
8
  extend self
11
9
 
12
- #displays the entire list
13
- def display
14
- #length of strings produced by the current elapsed time format
15
- elapsed_time_length = List.get_elapsed_time(Time.now, Time.now).length
16
- #horizontal border for the top or bottom of tracking's display
17
- horizontal_border = "+-------+-#{"-"*Config[:task_width]}-+-#{"-"*elapsed_time_length}-+"
18
- #header row describing tracking's display columns
19
- header = "| start | #{pad("task", Config[:task_width], :center)} | #{pad("elapsed", elapsed_time_length, :center)} |"
20
- #intro message, displayed when no valid tasks are found
21
- introduction = <<EOF
22
- +---------------------------------------+
23
- | You haven't started any tasks yet! :( |
24
- | |
25
- | Run this to begin your first task: |
26
- | tracking starting some work |
27
- +---------------------------------------+
28
- EOF
29
- #read data file
30
- data = []
31
- valid_lines = 0
32
- invalid_lines = 0
33
- data_file = CSV.open($data_file, "r", $csv_options)
34
- file_length = data_file.readlines.size
35
- data_file.seek(0)
36
- data_file.each_with_index do |line, index|
37
- if index+1 > file_length - Config[:lines]
38
- data << line
39
- end
40
- end
41
- #display data
42
- for i in 0..data.length-1
43
- if data[i].length == 2
44
- begin
45
- #grab and reformat data
46
- time_string = Time.parse(data[i][0]).strftime("%H:%M")
47
- task_string = data[i][1].chomp
48
- start_time = Time.parse(data[i][0])
49
- end_time = i<data.length-1 ? Time.parse(data[i+1][0]) : Time.now
50
- elapsed_string = List.get_elapsed_time(start_time,end_time)
51
- #format data into lines
52
- lines = []
53
- split_task(task_string).each_with_index do |task_line, i|
54
- col_1 = pad(i==0 ? time_string : nil, 5)
55
- col_2 = pad(task_line, Config[:task_width])
56
- col_3 = pad(i==0 ? elapsed_string : nil, elapsed_time_length)
57
- lines << "| #{col_1} | #{col_2} | #{col_3} |"
58
- end
59
- #display lines
60
- if valid_lines == 0
61
- puts horizontal_border
62
- if Config[:show_header]
63
- puts header
64
- puts horizontal_border
65
- end
10
+ # Width of the first column (start time)
11
+ @start_time_width = 5
12
+
13
+ # Width of the second column (name)
14
+ @name_width = Config[:task_width]
15
+
16
+ # Width of the third column (elapsed time)
17
+ @elapsed_time_width = Task.elapsed_time_length
18
+
19
+ # Displays part of the list in the command line
20
+ #
21
+ # @param [Integer] max the maximum number of items to display
22
+ def display max=Config[:lines]
23
+ display_object :top
24
+ tasks = List.get max
25
+ if tasks.length > 0
26
+ tasks.each_with_index do |task, task_index|
27
+ current_task = (task_index + 1 == tasks.length)
28
+ split_task(task.name).each_with_index do |name_line, line_index|
29
+ col_1 = pad(line_index==0 ? task.start_time : nil, 5)
30
+ col_2 = pad(name_line, @name_width)
31
+ col_3 = pad(line_index==0 ? task.elapsed_time : nil, @elapsed_time_width)
32
+ if current_task and Config[:color_current_task]
33
+ col_1,col_2,col_3 = col_1.yellow,col_2.yellow,col_3.yellow
66
34
  end
67
- lines.each { |line| puts line }
68
- valid_lines += 1
69
- rescue
70
- invalid_lines += 1
35
+ puts "| #{col_1} | #{col_2} | #{col_3} |"
71
36
  end
72
- else
73
- invalid_lines += 1
74
37
  end
75
- end
76
- #display intro, if needed
77
- if valid_lines > 0
78
- puts horizontal_border
79
38
  else
80
- puts introduction
39
+ display_object :intro
81
40
  end
82
- #display a warning, if needed
41
+ display_object :bottom
42
+ # Display a warning, if needed
43
+ =begin
83
44
  if invalid_lines > 0
84
- warn "Error: #{invalid_lines} invalid line#{"s" if invalid_lines > 1} found in data file."
45
+ warn "Error: #{invalid_lines} invalid line#{'s' if invalid_lines > 1} found in data file."
85
46
  end
47
+ =end
86
48
  end
87
49
 
88
- #pads tasks with whitespace to align them for display
50
+ # Displays commonly used text objects in the command line
51
+ #
52
+ # @param type the type of text object to display (:top/:bottom/:intro)
53
+ def display_object type
54
+ horizontal_border = "+-------+-#{'-'*@name_width}-+-#{'-'*@elapsed_time_width}-+"
55
+ case type
56
+ when :top
57
+ puts horizontal_border
58
+ if Config[:show_header]
59
+ puts "| start | #{pad('task', @name_width, :center)} | #{pad('elapsed', @elapsed_time_width, :center)} |"
60
+ puts horizontal_border
61
+ end
62
+ when :bottom
63
+ puts horizontal_border
64
+ when :intro
65
+ intro_text = <<-EOF
66
+ You haven't started any tasks yet! :(
67
+
68
+ Run this to begin your first task:
69
+ tracking starting some work
70
+ EOF
71
+ intro_text.each_line do |line|
72
+ puts "| | #{pad(line.chomp, @name_width)} | #{pad(nil, @elapsed_time_width)} |"
73
+ end
74
+ end
75
+ end
76
+
77
+ # Pads tasks with whitespace to align them for display
78
+ #
79
+ # @param [String] string the string to pad
80
+ # @param [Integer] length the length of the resultant string
81
+ # @param [Symbol] align the alignment of the start string within the end
82
+ # string (:left/:right/:center)
83
+ # @return [String] the padded string
89
84
  def pad(string, length, align=:left)
90
85
  if string == nil
91
- return " " * length
86
+ return ' ' * length
92
87
  elsif string.length >= length
93
88
  return string
94
89
  else
95
90
  difference = (length - string.length).to_f
96
91
  case align
97
92
  when :left
98
- return string + " " * difference
93
+ return string + ' ' * difference
99
94
  when :right
100
- return " " * difference + string
95
+ return ' ' * difference + string
101
96
  when :center
102
- return " "*(difference/2).floor + string + " "*(difference/2).ceil
97
+ return ' '*(difference/2).floor + string + ' '*(difference/2).ceil
103
98
  else
104
99
  return string
105
100
  end
106
101
  end
107
102
  end
108
103
 
109
- #word wraps tasks for display
104
+ # Word wraps tasks into multiple lines for display (based on the user's task
105
+ # width setting)
106
+ #
107
+ # @param [String] task the task string to split up
108
+ # @return [Array] an array of strings, each containing an individual line of
109
+ # wrapped text
110
110
  def split_task task
111
111
 
112
- #if the task fits
112
+ # If the task fits
113
113
  if task.length <= Config[:task_width]
114
114
  return [task]
115
115
 
116
- #if the task needs to be split
116
+ # If the task needs to be split
117
117
  else
118
- words = task.split(" ")
118
+ words = task.split(' ')
119
119
  split = []
120
- line = ""
120
+ line = ''
121
121
  words.each do |word|
122
122
 
123
- #if the word needs to be split
123
+ # If the word needs to be split
124
124
  if word.length > Config[:task_width]
125
- #add the start of the word onto the first line (even if it has already started)
125
+ # Add the start of the word onto the first line (even if it has
126
+ # already started)
126
127
  while line.length < Config[:task_width]
127
128
  line += word[0]
128
129
  word = word[1..-1]
129
130
  end
130
131
  split << line
131
- #split the rest of the word up onto new lines
132
+ # Split the rest of the word up onto new lines
132
133
  split_word = word.scan(%r[.{1,#{Config[:task_width]}}])
133
134
  split_word[0..-2].each do |word_section|
134
135
  split << word_section
135
136
  end
136
137
  line = split_word.last
137
138
 
138
- #if the word would fit on a new line
139
+ # If the word would fit on a new line
139
140
  elsif (line + word).length > Config[:task_width]
140
141
  split << line.chomp
141
142
  line = word
142
143
 
143
- #if the word can be added to this line
144
+ # If the word can be added to this line
144
145
  else
145
146
  line += word
146
147
  end
147
148
 
148
- #add a space to the end of the last word, if it would fit
149
- line += " " if line.length != Config[:task_width]
149
+ # Add a space to the end of the last word, if it would fit
150
+ line += ' ' if line.length != Config[:task_width]
150
151
 
151
152
  end
152
153
  split << line
@@ -154,47 +155,54 @@ EOF
154
155
  end
155
156
  end
156
157
 
157
- #use option parser to parse command line arguments
158
+ # Use option parser to parse command line arguments and run the selected
159
+ # command with its selected options
158
160
  def parse
159
161
  #options = {}
160
162
  done = false
161
163
 
162
164
  OptionParser.new do |opts|
163
- #setup
164
- version_path = File.expand_path("../../VERSION", File.dirname(__FILE__))
165
- opts.version = File.exist?(version_path) ? File.read(version_path) : ""
166
- #start of help text
167
- opts.banner = "Usage: tracking [mode]"
168
- opts.separator " display tasks"
169
- opts.separator " <task description> start a new task with the given text (spaces allowed)"
170
- #modes
171
- opts.on("-c", "--clear", "delete all tasks") do
172
- List.clear
173
- puts "List cleared."
165
+ # Setup
166
+ version_path = File.expand_path('../../VERSION', File.dirname(__FILE__))
167
+ opts.version = File.exist?(version_path) ? File.read(version_path) : ''
168
+ # Start of help text
169
+ opts.banner = 'Usage: tracking [mode]'
170
+ opts.separator ' display tasks'
171
+ opts.separator ' <task description> start a new task with the given text (spaces allowed)'
172
+ # Modes
173
+ opts.on('-r', '--rename', 'rename the last task') do
174
+ List.rename ARGV.join(' ').gsub("\t",'')
175
+ display
174
176
  done = true
175
177
  return
176
178
  end
177
- opts.on("-d", "--delete", "delete the last task") do
179
+ opts.on('-d', '--delete', 'delete the last task') do
178
180
  List.delete
179
181
  display
180
182
  done = true
181
183
  return
182
184
  end
183
- opts.on("-h", "--help", "display this help information") do
185
+ opts.on('-c', '--clear', 'delete all tasks') do
186
+ List.clear
187
+ puts 'List cleared.'
188
+ done = true
189
+ return
190
+ end
191
+ opts.on('-h', '--help', 'display this help information') do
184
192
  puts opts
185
193
  done = true
186
194
  return
187
195
  end
188
196
  end.parse!
189
197
 
190
- #basic modes (display and add)
191
- if not done
198
+ # Basic modes (display and add)
199
+ unless done
192
200
  if ARGV.count == 0
193
- #display all tasks
201
+ # Display all tasks
194
202
  display
195
203
  else
196
- #start a new task
197
- List.add ARGV.join(" ").gsub("\t","")
204
+ # Start a new task
205
+ List.add ARGV.join(' ').gsub("\t",'')
198
206
  display
199
207
  end
200
208
  end