hcl 0.4.6 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: