summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG6
-rw-r--r--README.markdown44
-rw-r--r--hcl.gemspec6
-rw-r--r--lib/hcl/app.rb66
-rw-r--r--lib/hcl/commands.rb56
-rw-r--r--lib/hcl/day_entry.rb9
-rw-r--r--lib/hcl/task.rb9
-rw-r--r--test/day_entry_test.rb4
-rw-r--r--test/test_helper.rb2
9 files changed, 137 insertions, 65 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 02a9d1f..d7bd10a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,11 @@
= Recent Changes in HCl
+== v0.4.0
+
+* start a timer or add a note without having to specify the sub-command
+* aliases can be specified with "@" anywhere on the command line
+* added alias and unalias to simplify setting task aliases
+
== v0.3.2
* fixed support for modern Rubies
diff --git a/README.markdown b/README.markdown
index b444237..d3e5da6 100644
--- a/README.markdown
+++ b/README.markdown
@@ -40,51 +40,53 @@ or you can install from source using jeweler:
hcl stop [msg]
hcl resume
-### Starting a Timer
+### Available Projects and Tasks
To start a new timer you need to identify the project and task. After you've
used the show command you can use the tasks command to view a cached list of
-available tasks. The first two numbers in each row are the project and task
-IDs. You need both values to start a timer:
+available tasks.
- $ hcl show
- -------------
- 0:00 total
$ hcl tasks
- 1234 5678 ClientX Software Development
- 1234 9876 ClientX Admin
- $ hcl start 1234 5678 adding a new feature
-### Task Aliases
+### Starting a Timer
Since it's not practical to enter two long numbers every time you want to
-identify a task, HCl supports task aliases. The task alias must start with 'task.', followed by your alias.
+identify a task, HCl supports task aliases:
- $ hcl set task.xdev 1234 5678
- $ hcl start xdev adding a new feature
+ $ hcl alias xdev 1234 5678
+ $ hcl @xdev Adding a new feature!
### Starting a Timer with Initial Time
You can also provide an initial time when starting a new timer.
This can be expressed in floating-point or HH:MM. The following two
-commands are identical:
+commands are equivalent:
- $ hcl start xdev +0:15 adding a new feature
- $ hcl start +.25 xdev adding a new feature
+ $ hcl @xdev +0:15 Adding a new feature!
+ $ hcl +.25 @xdev Adding a new feature!
### Adding Notes to a Running Task
-While a task is running you can append strings to the note for that task:
+While a task is running you can append lines to the task notes.
+Providing the note command is optional, just the bare message will work.
+These two commands are equivalent:
- $ hcl note Found a good time
- $ hcl note or not, whatever...
+ $ hcl Found a good time!
+ $ hcl note Found a good time!
### Stopping a Timer
The following command will stop a running timer (currently only one timer at
-a time is supported):
+a time is supported). You can provide a message when stopping a timer as
+well:
+
+ $ hcl stop All done!
+
+### Resuming a Timer
+
+You can easily resume the last stopped timer:
- $ hcl stop
+ $ hcl resume
### Date Formats
diff --git a/hcl.gemspec b/hcl.gemspec
index c6221ee..1694376 100644
--- a/hcl.gemspec
+++ b/hcl.gemspec
@@ -5,14 +5,14 @@
Gem::Specification.new do |s|
s.name = %q{hcl}
- s.version = "0.3.1"
+ s.version = "0.4.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Zack Hobson"]
- s.date = %q{2011-07-13}
+ s.date = %q{2013-11-18}
s.default_executable = %q{hcl}
s.description = %q{HCl is a command-line client for manipulating Harvest time sheets.}
- s.email = %q{zack@opensourcery.com}
+ s.email = %q{zack@zackhobson.com}
s.executables = ["hcl"]
s.extra_rdoc_files = [
"LICENSE",
diff --git a/lib/hcl/app.rb b/lib/hcl/app.rb
index 9569dc5..6b3e428 100644
--- a/lib/hcl/app.rb
+++ b/lib/hcl/app.rb
@@ -3,6 +3,7 @@ require 'yaml'
require 'rexml/document'
require 'net/http'
require 'net/https'
+require 'fileutils'
## gem dependencies
require 'chronic'
@@ -36,10 +37,13 @@ module HCl
include HCl::Utility
include HCl::Commands
- SETTINGS_FILE = "#{ENV['HOME']}/.hcl_settings"
- CONFIG_FILE = "#{ENV['HOME']}/.hcl_config"
+ SETTINGS_FILE = "#{ENV['HOME']}/.hcl/settings.yml"
+ CONFIG_FILE = "#{ENV['HOME']}/.hcl/config.yml"
+ OLD_SETTINGS_FILE = "#{ENV['HOME']}/.hcl_settings"
+ OLD_CONFIG_FILE = "#{ENV['HOME']}/.hcl_config"
def initialize
+ FileUtils.mkdir_p(File.join(ENV['HOME'], ".hcl"))
read_config
read_settings
end
@@ -57,6 +61,14 @@ module HCl
Commands.method_defined? command
end
+ def start_or_note *args
+ if DayEntry.with_timer
+ note *args
+ else
+ start *args
+ end
+ end
+
# Start the application.
def run
begin
@@ -71,8 +83,7 @@ module HCl
end
end
else
- STDERR.puts "unrecognized command `#{@command}'"
- exit 1
+ start_or_note @command, *@args
end
else
show
@@ -97,23 +108,38 @@ module HCl
HCl is a command-line client for manipulating Harvest time sheets.
Commands:
- hcl show [date]
+ # show all available tasks
hcl tasks
+
+ # set a task alias
+ hcl alias <task> <project_id> <task_id>
+
+ # list task aliases
hcl aliases
- hcl set <key> <value ...>
- hcl unset <key>
- hcl start <task> [msg]
- hcl stop [msg]
+
+ # start a task using an alias
+ hcl @<task> [+time] [message]
+
+ # display the daily timesheet
+ hcl show [date]
+
+ # stop a running timer
+ hcl stop [message]
+
+ # resume the last stopped timer
hcl resume
- hcl note <msg>
+
+ # add a line to your running timer
+ hcl note <message>
Examples:
- $ hcl tasks
- $ hcl start 1234 4567 this is my log message
- $ hcl set task.mytask 1234 4567
- $ hcl start mytask this is my next log message
- $ hcl show yesterday
- $ hcl show last tuesday
+ hcl alias mytask 1234 4567
+ hcl @mytask +:15 Doing a thing that I started 15 minutes ago.
+ hcl Adding a note to my running task.
+ hcl stop That's enough for now.
+ hcl resume
+ hcl show yesterday
+ hcl show last tuesday
Options:
EOM
@@ -129,8 +155,8 @@ EOM
if File.exists? CONFIG_FILE
config = YAML::load File.read(CONFIG_FILE)
TimesheetResource.configure config
- elsif File.exists? old_conf = File.dirname(__FILE__) + "/../hcl_conf.yml"
- config = YAML::load File.read(old_conf)
+ elsif File.exists? OLD_CONFIG_FILE
+ config = YAML::load File.read(OLD_CONFIG_FILE)
TimesheetResource.configure config
write_config config
else
@@ -150,11 +176,15 @@ EOM
File.open(CONFIG_FILE, 'w') do |f|
f.write config.to_yaml
end
+ FileUtils.chmod 0400, CONFIG_FILE
end
def read_settings
if File.exists? SETTINGS_FILE
@settings = YAML.load(File.read(SETTINGS_FILE))
+ elsif File.exists? OLD_SETTINGS_FILE
+ @settings = YAML.load(File.read(OLD_SETTINGS_FILE))
+ write_settings
else
@settings = {}
end
diff --git a/lib/hcl/commands.rb b/lib/hcl/commands.rb
index fdc50a3..2ca3742 100644
--- a/lib/hcl/commands.rb
+++ b/lib/hcl/commands.rb
@@ -29,8 +29,23 @@ module HCl
write_settings
end
+ def unalias task
+ unset "task.#{task}"
+ puts "Removed task alias @#{task}."
+ end
+
+ def alias task_name, *value
+ task = Task.find *value
+ if task
+ set "task.#{task_name}", *value
+ puts "Added alias @#{task_name} for #{task}."
+ else
+ puts "Unrecognized project and task ID: #{value.inspect}"
+ end
+ end
+
def aliases
- @settings.keys.select { |s| s =~ /^task\./ }.map { |s| s.slice(5..-1) }
+ @settings.keys.select { |s| s =~ /^task\./ }.map { |s| "@"+s.slice(5..-1) }
end
def start *args
@@ -39,19 +54,33 @@ module HCl
args.delete(starting_time)
starting_time = time2float starting_time
end
- ident = args.shift
- task_ids = if @settings.key? "task.#{ident}"
- @settings["task.#{ident}"].split(/\s+/)
- else
- [ident, args.shift]
+ ident = args.detect {|a| a[0] == '@' }
+ if ident
+ args.delete(ident)
+ ident = ident.slice(1..-1)
+ else
+ ident = args.shift
+ end
+ if ident
+ task_ids = if @settings.key? "task.#{ident}"
+ @settings["task.#{ident}"].split(/\s+/)
+ else
+ [ident, args.shift]
+ end
+ task = Task.find *task_ids
+ if task.nil?
+ puts "Unknown task alias, try one of the following: ", aliases.join(', ')
+ exit 1
end
- task = Task.find *task_ids
- if task.nil?
- puts "Unknown project/task alias, try one of the following: #{aliases.join(', ')}."
+ timer = task.start(
+ :starting_time => starting_time,
+ :note => args.join(' ')
+ )
+ puts "Started timer for #{timer} (at #{current_time})"
+ else
+ puts "You must provide a task alias to start a timer:", aliases.join(', ')
exit 1
end
- timer = task.start(:starting_time => starting_time, :note => args.join(' '))
- puts "Started timer for #{timer} (at #{current_time})"
end
def stop *args
@@ -70,7 +99,7 @@ module HCl
entry = DayEntry.with_timer
if entry
entry.append_note message
- puts "Added note '#{message}' to #{entry}."
+ puts "Added note to #{entry}."
else
puts "No running timers found."
end
@@ -81,7 +110,8 @@ module HCl
total_hours = 0.0
DayEntry.all(date).each do |day|
running = day.running? ? '(running) ' : ''
- puts "\t#{day.formatted_hours}\t#{running}#{day.project} #{day.notes}"[0..78]
+ columns = HighLine::SystemExtensions.terminal_size[0]
+ puts "\t#{day.formatted_hours}\t#{running}#{day.project}: #{day.notes.lines.last}"[0..columns-1]
total_hours = total_hours + day.hours.to_f
end
puts "\t" + '-' * 13
diff --git a/lib/hcl/day_entry.rb b/lib/hcl/day_entry.rb
index c274126..afcdf08 100644
--- a/lib/hcl/day_entry.rb
+++ b/lib/hcl/day_entry.rb
@@ -31,12 +31,9 @@ module HCl
def append_note new_notes
# If I don't include hours it gets reset.
# This doens't appear to be the case for task and project.
- DayEntry.post("daily/update/#{id}", <<-EOD)
- <request>
- <notes>#{notes << " #{new_notes}"}</notes>
- <hours>#{hours}</hours>
- </request>
- EOD
+ (self.notes << "\n#{new_notes}").lstrip!
+ DayEntry.post "daily/update/#{id}",
+ %{<request><notes>#{notes}</notes><hours>#{hours}</hours></request>}
end
def self.with_timer
diff --git a/lib/hcl/task.rb b/lib/hcl/task.rb
index 3922600..8a5a833 100644
--- a/lib/hcl/task.rb
+++ b/lib/hcl/task.rb
@@ -1,3 +1,5 @@
+require 'fileutils'
+
module HCl
class Task < TimesheetResource
def self.cache_tasks doc
@@ -9,6 +11,7 @@ module HCl
end)
end
unless tasks.empty?
+ FileUtils.mkdir_p(cache_dir)
File.open(cache_file, 'w') do |f|
f.write tasks.uniq.to_yaml
end
@@ -16,7 +19,11 @@ module HCl
end
def self.cache_file
- File.join(ENV['HOME'],'.hcl_tasks')
+ File.join(cache_dir, 'tasks.yml')
+ end
+
+ def self.cache_dir
+ File.join(ENV['HOME'],'.hcl/cache')
end
def self.all
diff --git a/test/day_entry_test.rb b/test/day_entry_test.rb
index c27ffda..a0b49a4 100644
--- a/test/day_entry_test.rb
+++ b/test/day_entry_test.rb
@@ -37,13 +37,13 @@ class DayEntryTest < Test::Unit::TestCase
entry = HCl::DayEntry.new(:id => '1', :notes => 'yourmom.', :hours => '1.0')
HCl::DayEntry.stubs(:post)
entry.append_note('hi world')
- assert_equal 'yourmom. hi world', entry.notes
+ assert_equal "yourmom.\nhi world", entry.notes
end
should "append to an undefined note" do
entry = HCl::DayEntry.new(:id => '1', :notes => nil, :hours => '1.0')
HCl::DayEntry.stubs(:post)
entry.append_note('hi world')
- assert_equal ' hi world', entry.notes
+ assert_equal 'hi world', entry.notes
end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index fa69a86..9e12bac 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -4,4 +4,4 @@ require 'rubygems'
require 'test/unit'
require 'hcl/app'
require 'shoulda'
-require 'mocha'
+require 'mocha/setup'