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 +4 -4
- data/CHANGELOG +25 -18
- data/LICENSE +1 -1
- data/README.markdown +16 -10
- data/Rakefile +5 -0
- data/lib/hcl/app.rb +10 -4
- data/lib/hcl/commands.rb +6 -3
- data/lib/hcl/task.rb +1 -1
- data/lib/hcl/timesheet_resource.rb +23 -21
- data/lib/hcl/version.rb +1 -1
- data/test/app_test.rb +35 -5
- data/test/command_test.rb +14 -0
- data/test/task_test.rb +4 -0
- data/test/test_helper.rb +1 -1
- data/test/timesheet_resource_test.rb +38 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37356337b2821522c84bd05569cbc5e6b00e8316
|
4
|
+
data.tar.gz: 506250a1481bce9b61c60c85170d838fc35afcd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
59
|
+
== v0.3.0 2010-04-02
|
53
60
|
|
54
61
|
* added support for free accounts
|
55
62
|
|
56
|
-
== v0.2.3
|
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
|
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
|
75
|
+
== v0.2.1 2009-07-30
|
69
76
|
|
70
77
|
* Fixed: Creating timers without starting them.
|
71
78
|
|
72
|
-
== v0.2.0
|
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
|
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
|
90
|
+
== v0.1.2 2009-07-27
|
84
91
|
|
85
92
|
* Automatically include rubygems in bin/hcl.
|
86
93
|
|
87
|
-
== v0.1.1
|
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
|
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@
|
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
|
data/README.markdown
CHANGED
@@ -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
data/lib/hcl/app.rb
CHANGED
@@ -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
|
19
|
-
FileUtils.mkdir_p(
|
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.
|
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
|
data/lib/hcl/commands.rb
CHANGED
@@ -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
|
-
|
115
|
-
|
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
|
data/lib/hcl/task.rb
CHANGED
@@ -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
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
data/lib/hcl/version.rb
CHANGED
data/test/app_test.rb
CHANGED
@@ -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
|
11
|
-
hours:'2.06',
|
12
|
-
|
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
|
data/test/command_test.rb
CHANGED
@@ -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
|
data/test/task_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -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.
|
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-
|
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:
|