hcl 0.4.6 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e1be76a382c48d42a98e4bad37a360bab90b728
4
- data.tar.gz: 73afc8798c5cf88030394d3429db7c50e37c9ffa
3
+ metadata.gz: 37356337b2821522c84bd05569cbc5e6b00e8316
4
+ data.tar.gz: 506250a1481bce9b61c60c85170d838fc35afcd2
5
5
  SHA512:
6
- metadata.gz: 10ecb6e3ac58681da880d0dffffff802efe12073f885bd6f66cdb3e42a4be44c32f51d15490db6b52ef708ce26686c0c196e15896e96721bb6985795b2710503
7
- data.tar.gz: 831bcaac4c81408f66545380b3de4e76387f30a43592b0ab1210f0f14a1a758bd815ad6a4675b9a770698d9043e67ef56cdff232bc47211355cbc87f9cb87e46
6
+ metadata.gz: 088e3d191f620b7a5443fa3c6cfe74c18148b83fa592811a16b9e1019df5ea48879e03425638c43c7f2720c7168d2e2463dcb72734c49c98eae1f66f75414104
7
+ data.tar.gz: eac562669a54212360f07c93f3c31e8a84d8a1c64ddecd401186879a740c0c73e368c1ca0e36501ad2d51c27a444c287a699fe786cedaf27b099dbf4e45fe3e5
data/CHANGELOG CHANGED
@@ -1,47 +1,54 @@
1
1
  = Recent Changes in HCl
2
2
 
3
- == v0.4.6
3
+ == v0.4.7 2013-11-30
4
+
5
+ * added --reauth option to refresh credentials
6
+ * added support for retrying on API throttle
7
+ * note command without args now displays all notes for a running timer
8
+ * fixed a crash on ruby 1.9.3
9
+
10
+ == v0.4.6 2013-11-21
4
11
 
5
12
  * automatically request credentials on auth-failure
6
13
  * fix user-entered credentials
7
14
 
8
- == v0.4.5
15
+ == v0.4.5 2013-11-21
9
16
 
10
17
  * allow filtering of tasks by project code
11
18
  * eliminate shoulda from development dependencies
12
19
 
13
- == v0.4.4
20
+ == v0.4.4 2013-11-20
14
21
 
15
22
  * added completion command to output a Bash auto-complete script, closes #34
16
23
  * removed jeweler dependency
17
24
 
18
- == v0.4.3
25
+ == v0.4.3 2013-11-19
19
26
 
20
27
  * added cancel command to delete the last running timer, closes #13
21
28
  * properly unescape string from Harvest API, closes #24
22
29
  * stop command now checks for running timers from yesterday, closes #35
23
30
  * added log command to log time/notes without leaving a timer running, closes #30
24
31
 
25
- == v0.4.2
32
+ == v0.4.2 2013-11-19
26
33
 
27
34
  * resume command now accepts an optional task
28
35
 
29
- == v0.4.1
36
+ == v0.4.1 2013-11-18
30
37
 
31
38
  * update dependencies
32
39
 
33
- == v0.4.0
40
+ == v0.4.0 2013-11-18
34
41
 
35
42
  * start a timer or add a note without having to specify the sub-command
36
43
  * aliases can be specified with "@" anywhere on the command line
37
44
  * added alias and unalias to simplify setting task aliases
38
45
 
39
- == v0.3.2
46
+ == v0.3.2 2011-12-30
40
47
 
41
48
  * fixed support for modern Rubies
42
49
  * it's now possible to provide a message with the stop command
43
50
 
44
- == v0.3.1
51
+ == v0.3.1 2011-07-13
45
52
 
46
53
  * use STDERR instead of STDOUT for error reporting
47
54
  * sort tasks before viewing tasks (brian@madebyrocket.com)
@@ -49,46 +56,46 @@
49
56
  * show current time when on 'start', 'stop', and 'show' commands (scharfie@gmail.com)
50
57
  * include client name in tasks list (scharfie@gmail.com)
51
58
 
52
- == v0.3.0 Fri Apr 2 10:42:20 2010 -0700
59
+ == v0.3.0 2010-04-02
53
60
 
54
61
  * added support for free accounts
55
62
 
56
- == v0.2.3 Sun Aug 23 21:39:34 2009 -0700
63
+ == v0.2.3 2009-08-23
57
64
 
58
65
  * Allow decimal time offset without a dot, closes #29.
59
66
  * Reverted and re-fixed: Adding note fails when task is started without notes, #26.
60
67
  * Reinstate the --version option
61
68
 
62
- == v0.2.2 Sun Aug 9 11:16:34 2009 -0700
69
+ == v0.2.2 2009-08-09
63
70
 
64
71
  * Support installation via rip, closes #27.
65
72
  * Fixed: Adding note fails when task is started without notes, closes #26.
66
73
  * Avoid stack trace on missing XML root node, closes #25.
67
74
 
68
- == v0.2.1 Thu Jul 30 14:02:23 2009 -0700
75
+ == v0.2.1 2009-07-30
69
76
 
70
77
  * Fixed: Creating timers without starting them.
71
78
 
72
- == v0.2.0 Thu Jul 30 11:40:33 2009 -0700
79
+ == v0.2.0 2009-07-30
73
80
 
74
81
  * Allow an initial time to be specified when starting a timer, closes #9.
75
82
  * Always display hours as HH:MM, closes #22.
76
83
  * Do not write empty task cache, closes #23.
77
84
 
78
- == v0.1.3 Tue Jul 28 09:19:53 2009 -0700
85
+ == v0.1.3 2009-07-28
79
86
 
80
87
  * Add a note about ruby-dev for debian/ubuntu users, closes #20.
81
88
  * Friendlier error message on unrecognized task, closes #18, #21.
82
89
 
83
- == v0.1.2 Mon Jul 27 11:46:50 2009 -0700
90
+ == v0.1.2 2009-07-27
84
91
 
85
92
  * Automatically include rubygems in bin/hcl.
86
93
 
87
- == v0.1.1 Fri Jul 24 19:32:32 2009 -0700
94
+ == v0.1.1 2009-07-24
88
95
 
89
96
  * Mention gem in README, read version from file.
90
97
 
91
- == v0.1.0 Fri Jul 24 19:09:23 2009 -0700
98
+ == v0.1.0 2009-07-24
92
99
 
93
100
  * Initial public release
94
101
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Zack Hobson <zack@opensourcery.com>, OpenSourcery LLC
1
+ Copyright (c) 2009 Zack Hobson <zack@zackhobson.com>, OpenSourcery LLC
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
@@ -17,16 +17,16 @@ or you can install from source:
17
17
 
18
18
  ## Usage
19
19
 
20
- hcl [start] @<task_alias> [+<time>] [<message>]
21
- hcl note <message>
22
- hcl stop [<message>]
23
- hcl resume [@<task_alias>]
24
- hcl log @<task_alias> [+<time>] [<message>]
25
- hcl show [<date>]
26
- hcl tasks [<project_code>]
27
- hcl alias <task_alias> <project_id> <task_id>
28
- hcl aliases
29
- hcl (cancel | nvm | oops)
20
+ $ hcl [start] @<task_alias> [+<time>] [<message>]
21
+ $ hcl note <message>
22
+ $ hcl stop [<message>]
23
+ $ hcl resume [@<task_alias>]
24
+ $ hcl log @<task_alias> [+<time>] [<message>]
25
+ $ hcl show [<date>]
26
+ $ hcl tasks [<project_code>]
27
+ $ hcl alias <task_alias> <project_id> <task_id>
28
+ $ hcl aliases
29
+ $ hcl (cancel | nvm | oops)
30
30
 
31
31
  ### Available Projects and Tasks
32
32
 
@@ -62,6 +62,12 @@ While a task is running you can append lines to the task notes:
62
62
 
63
63
  $ hcl note Then I did something else
64
64
 
65
+ **Note** that `show` only displays the last line of the timer notes.
66
+ You can list all the notes for a running timer by issuing the note
67
+ command without any arguments:
68
+
69
+ $ hcl note
70
+
65
71
  ### Stopping a Timer
66
72
 
67
73
  The following command will stop a running timer (currently only one timer at
data/Rakefile CHANGED
@@ -1,6 +1,11 @@
1
1
  require 'rubygems/tasks'
2
2
  Gem::Tasks.new
3
3
 
4
+ require 'fileutils'
5
+ task :clean do
6
+ FileUtils.rm_rf %w[ pkg coverage doc ]
7
+ end
8
+
4
9
  require 'rake/testtask'
5
10
  Rake::TestTask.new do |t|
6
11
  t.libs << 'test'
@@ -15,8 +15,8 @@ module HCl
15
15
  OLD_SETTINGS_FILE = "#{ENV['HOME']}/.hcl_settings"
16
16
  OLD_CONFIG_FILE = "#{ENV['HOME']}/.hcl_config"
17
17
 
18
- def configure
19
- FileUtils.mkdir_p(File.join(ENV['HOME'], ".hcl"))
18
+ def initialize
19
+ FileUtils.mkdir_p(HCL_DIR)
20
20
  read_config
21
21
  read_settings
22
22
  self
@@ -24,7 +24,7 @@ module HCl
24
24
 
25
25
  # Run the given command and arguments.
26
26
  def self.command *args
27
- new.configure.process_args(*args).run
27
+ new.process_args(*args).run
28
28
  end
29
29
 
30
30
  # Return true if the string is a known command, false otherwise.
@@ -37,6 +37,7 @@ module HCl
37
37
 
38
38
  # Start the application.
39
39
  def run
40
+ request_config if @options[:reauth]
40
41
  begin
41
42
  if @command
42
43
  if command? @command
@@ -60,6 +61,10 @@ module HCl
60
61
  rescue SocketError => e
61
62
  STDERR.puts "Connection failed. (#{e.message})"
62
63
  exit 1
64
+ rescue TimesheetResource::ThrottleFailure => e
65
+ STDERR.puts "Too many requests, retrying in #{e.retry_after+5} seconds..."
66
+ sleep e.retry_after+5
67
+ run
63
68
  rescue TimesheetResource::AuthFailure => e
64
69
  STDERR.puts "Unable to authenticate: #{e}"
65
70
  request_config
@@ -71,7 +76,7 @@ module HCl
71
76
  end
72
77
 
73
78
  def process_args *args
74
- Trollop::options(args) do
79
+ @options = Trollop::options(args) do
75
80
  stop_on Commands.instance_methods
76
81
  version "HCl version #{VERSION}"
77
82
  banner <<-EOM
@@ -119,6 +124,7 @@ Examples:
119
124
 
120
125
  Options:
121
126
  EOM
127
+ opt :reauth, "Force refresh of auth details"
122
128
  end
123
129
  @command = args.shift
124
130
  @args = args
@@ -108,11 +108,14 @@ module HCl
108
108
  end
109
109
 
110
110
  def note *args
111
- message = args.join ' '
112
111
  entry = DayEntry.with_timer
113
112
  if entry
114
- entry.append_note message
115
- "Added note to #{entry}."
113
+ if args.empty?
114
+ return entry.notes
115
+ else
116
+ entry.append_note args.join(' ')
117
+ "Added note to #{entry}."
118
+ end
116
119
  else
117
120
  puts "No running timers found."
118
121
  exit 1
@@ -24,7 +24,7 @@ module HCl
24
24
  end
25
25
 
26
26
  def self.cache_dir
27
- File.join(ENV['HOME'],'.hcl/cache')
27
+ File.join(HCl::App::HCL_DIR, 'cache')
28
28
  end
29
29
 
30
30
  def self.all
@@ -1,22 +1,18 @@
1
1
  require 'net/http'
2
2
  require 'net/https'
3
-
4
- # Workaround for annoying SSL warning:
5
- # >> warning: peer certificate won't be verified in this SSL session
6
- # http://www.5dollarwhitebox.org/drupal/node/64
7
- class Net::HTTP
8
- alias_method :old_initialize, :initialize
9
- def initialize(*args)
10
- old_initialize(*args)
11
- @ssl_context = OpenSSL::SSL::SSLContext.new
12
- @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
13
- end
14
- end
3
+ require 'cgi'
15
4
 
16
5
  module HCl
17
6
  class TimesheetResource
18
7
  class Failure < StandardError; end
19
8
  class AuthFailure < StandardError; end
9
+ class ThrottleFailure < StandardError
10
+ attr_reader :retry_after
11
+ def initialize response
12
+ @retry_after = response.headers['Retry-After'].to_i
13
+ super "Too many requests! Try again in #{@retry_after} seconds."
14
+ end
15
+ end
20
16
 
21
17
  def self.configure opts = nil
22
18
  if opts
@@ -24,8 +20,6 @@ module HCl
24
20
  self.password = opts['password']
25
21
  self.subdomain = opts['subdomain']
26
22
  self.ssl = opts['ssl']
27
- else
28
- yield self
29
23
  end
30
24
  end
31
25
 
@@ -57,10 +51,16 @@ module HCl
57
51
  http_do Net::HTTP::Delete, action
58
52
  end
59
53
 
54
+ def self.connect
55
+ Net::HTTP.new("#{subdomain}.harvestapp.com", (ssl ? 443 : 80)).tap do |https|
56
+ https.use_ssl = ssl
57
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl
58
+ end
59
+ end
60
+
60
61
  def self.http_do method_class, action, data = nil
61
- https = Net::HTTP.new "#{subdomain}.harvestapp.com", (ssl ? 443 : 80)
62
+ https = connect
62
63
  request = method_class.new "/#{action}"
63
- https.use_ssl = ssl
64
64
  request.basic_auth login, password
65
65
  request.content_type = 'application/xml'
66
66
  request['Accept'] = 'application/xml'
@@ -70,6 +70,8 @@ module HCl
70
70
  response.body
71
71
  when Net::HTTPFound
72
72
  raise Failure, "Redirected! Perhaps your ssl configuration variable is set incorrectly?"
73
+ when Net::HTTPServiceUnavailable
74
+ raise ThrottleFailure, response
73
75
  when Net::HTTPUnauthorized
74
76
  raise AuthFailure, "Login failed."
75
77
  else
@@ -82,11 +84,11 @@ module HCl
82
84
  end
83
85
 
84
86
  def method_missing method, *args
85
- if @data.key? method.to_sym
86
- @data[method]
87
- else
88
- super
89
- end
87
+ @data.key?(method.to_sym) ? @data[method] : super
88
+ end
89
+
90
+ def respond_to? method
91
+ (@data && @data.key?(method.to_sym)) || super
90
92
  end
91
93
 
92
94
  def self.xml_to_hash elem
@@ -1,3 +1,3 @@
1
1
  module HCl
2
- VERSION = '0.4.6'
2
+ VERSION = '0.4.7'
3
3
  end
@@ -1,17 +1,47 @@
1
1
  require 'test_helper'
2
2
  class AppTest < Test::Unit::TestCase
3
3
 
4
+ def setup
5
+ # touch config to avoid triggering manual config
6
+ FileUtils.mkdir_p HCl::App::HCL_DIR
7
+ FileUtils.touch File.join(HCl::App::HCL_DIR, "config.yml")
8
+ end
9
+
4
10
  def test_commands
5
11
  app = HCl::App.new
6
12
  assert HCl::Commands.instance_methods.all? { |c| app.command? c }, 'all methods are commands'
7
13
  end
8
14
 
9
15
  def test_command_show
10
- HCl::DayEntry.expects(:all).returns([HCl::DayEntry.new({
11
- hours:'2.06',
12
- notes: 'hi world',
13
- project: 'App'
14
- })])
16
+ HCl::DayEntry.expects(:all).returns [HCl::DayEntry.new(
17
+ hours:'2.06', notes:'hi world', project:'App'
18
+ )]
15
19
  HCl::App.command 'show'
16
20
  end
21
+
22
+ def test_command_retry_on_throttle
23
+ app = HCl::App.new
24
+ throttled = states('throttled').starts_as(false)
25
+ app.expects(:show).
26
+ raises(HCl::TimesheetResource::ThrottleFailure, stub(headers:{'Retry-After' => 42})).
27
+ then(throttled.is(true))
28
+ app.expects(:sleep).with(47).when(throttled.is(true))
29
+ app.expects(:show).when(throttled.is(true))
30
+ app.process_args('show').run
31
+ end
32
+
33
+ def test_report_generic_failure
34
+ app = HCl::App.new
35
+ app.expects(:show).raises(RuntimeError)
36
+ app.expects(:exit).with(1)
37
+ app.process_args('show').run
38
+ end
39
+
40
+ def test_report_socket_error
41
+ app = HCl::App.new
42
+ app.expects(:show).raises(SocketError)
43
+ app.expects(:exit).with(1)
44
+ app.process_args('show').run
45
+ end
46
+
17
47
  end
@@ -82,6 +82,14 @@ class CommandTest < Test::Unit::TestCase
82
82
  resume
83
83
  end
84
84
 
85
+ def test_resume_with_task_alias
86
+ entry = stub
87
+ expects(:get_task_ids).with('mytask',[]).returns(%w[ 456 789 ])
88
+ HCl::DayEntry.expects(:last_by_task).with('456', '789').returns(entry)
89
+ entry.expects(:toggle)
90
+ resume 'mytask'
91
+ end
92
+
85
93
  def test_cancel
86
94
  entry = stub
87
95
  HCl::DayEntry.expects(:with_timer).returns(entry)
@@ -96,4 +104,10 @@ class CommandTest < Test::Unit::TestCase
96
104
  note 'hi world'
97
105
  end
98
106
 
107
+ def test_note_display
108
+ entry = stub(notes:"your face")
109
+ HCl::DayEntry.expects(:with_timer).returns(entry)
110
+ assert_equal "your face", note
111
+ end
112
+
99
113
  end
@@ -1,5 +1,9 @@
1
1
 
2
2
  class Task < Test::Unit::TestCase
3
+ def test_cache_file
4
+ assert_equal "#{HCl::App::HCL_DIR}/cache/tasks.yml", HCl::Task.cache_file
5
+ end
6
+
3
7
  def test_cache_tasks
4
8
  HCl::Task.cache_tasks(REXML::Document.new(<<-EOD))
5
9
  <daily>
@@ -10,9 +10,9 @@ end
10
10
  require 'test/unit'
11
11
  require 'mocha/setup'
12
12
  require 'fileutils'
13
+ require 'fakeweb'
13
14
 
14
15
  # override the default hcl dir
15
- FileUtils.mkdir_p __dir__+"/dot_hcl"
16
16
  ENV['HCL_DIR'] = __dir__+"/dot_hcl"
17
17
 
18
18
  require 'hcl'
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+
3
+ class TimesheetResourceTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ FakeWeb.allow_net_connect = false
7
+ HCl::TimesheetResource.configure \
8
+ 'login' => 'bob',
9
+ 'password' => 'secret',
10
+ 'subdomain' => 'bobclock',
11
+ 'ssl' => true
12
+ end
13
+
14
+ def test_configure
15
+ assert_equal 'bob', HCl::TimesheetResource.login
16
+ assert_equal 'secret', HCl::TimesheetResource.password
17
+ assert_equal 'bobclock', HCl::TimesheetResource.subdomain
18
+ assert_equal true, HCl::TimesheetResource.ssl
19
+ end
20
+
21
+ def test_http_get
22
+ FakeWeb.register_uri(:get, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'gotten!')
23
+ body = HCl::TimesheetResource.get 'foo'
24
+ assert_equal 'gotten!', body
25
+ end
26
+
27
+ def test_http_post
28
+ FakeWeb.register_uri(:post, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'posted!')
29
+ body = HCl::TimesheetResource.post 'foo', {pizza:'taco'}
30
+ assert_equal 'posted!', body
31
+ end
32
+
33
+ def test_http_delete
34
+ FakeWeb.register_uri(:delete, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'wiped!')
35
+ body = HCl::TimesheetResource.delete 'foo'
36
+ assert_equal 'wiped!', body
37
+ end
38
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hcl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zack Hobson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-21 00:00:00.000000000 Z
11
+ date: 2013-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trollop
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fakeweb
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  description: HCl is a command-line client for manipulating Harvest time sheets.
112
126
  email: zack@zackhobson.com
113
127
  executables:
@@ -135,6 +149,7 @@ files:
135
149
  - test/day_entry_test.rb
136
150
  - test/task_test.rb
137
151
  - test/test_helper.rb
152
+ - test/timesheet_resource_test.rb
138
153
  - test/utility_test.rb
139
154
  homepage: http://zackhobson.com/hcl/
140
155
  licenses: