hammerhead 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 14377ccc5ecea39d948921b57b2dbaa0ba3587a3358dd8b157cb1ea62c065903
4
+ data.tar.gz: 0715f4dfd5fcd3c05dca7f465aaa3786c063962678d6908689e333acee53bb5a
5
+ SHA512:
6
+ metadata.gz: 8d10e94d35cb84c1b49a798c53a83f7a18569e23cef1fbfdb459225469594828c851dcaaeab45a8aef959847c6e261397a0591dc858c991883c407fbeed150b0
7
+ data.tar.gz: 7c9f0718653bcf825ac604bb24ac1d3bb827c544a3cd9fc201ab05fa140c1cd78117f1b873f68db9c65d7e80d2b2011086727d519a855973483b2c4e77bba7d4
@@ -0,0 +1,14 @@
1
+ # Hammerhead Changes
2
+
3
+ ## 0.1.0 (Initial Release)
4
+
5
+ ### Released on [Date]
6
+
7
+ * Uses TTY Toolkit
8
+ * Calls Harvest v1 API via 'harvested'
9
+ * Returns list of 'active' clients by default
10
+ * Returns all clients
11
+ * Generates weekly status report for specified client
12
+ * Supports client 'nicknames'
13
+ * Supports client exclusions
14
+ * Supports rudimentary configuration file
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at mel@juicyparts.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Juicy Parts Software, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,207 @@
1
+ # Hammerhead
2
+
3
+ A tool to generate status reports from your Harvest timesheet.
4
+
5
+ With this tool you can produce client-specific status reports from the entries you make in your Harvest timesheet.
6
+
7
+ ## Motivation
8
+
9
+ That's the reason I created this tool: I produce weekly status reports for my clients. This is the first iteration, and because of this, it is tailored to my needs.
10
+
11
+ At the end of this document I'll document the road map I have for this tool. Stay tuned.
12
+
13
+ ## Notable Components
14
+
15
+ ### Harvest
16
+
17
+ You need a [Harvest](https://www.getharvest.com/) account, because this information is used to authenticate this tool against the Harvest V1 API.
18
+
19
+ **WARNING**: According to the [API V1 Documentation](https://help.getharvest.com/api-v1/), this API is deprecated but will still be available for 'legacy' applications. Upgrading to use Harvest V2 API is already in the Road Map. However, until then, I'm making note that I'm using the [harvested](https://rubygems.org/gems/harvested) gem to provide API access to the Harvest information.
20
+
21
+ In a configuration file (`'hammerhead.yml'`) you will specify:
22
+
23
+ ```
24
+ subdomain: - Your Harvest subdomain
25
+ username: - Your Harvest username
26
+ password: - Your Harvest password
27
+ ```
28
+
29
+ **NOTE**: This tool uses the Optional Project Code when displaying status reports.
30
+
31
+ ### TTY Toolkit
32
+
33
+ [TTY Toolkit](https://ttytoolkit.org/) provides the skeleton around which Hammerhead is built. It's a wonderfully easy toolkit to use. I'm barely scratching the surface of its capabilities.
34
+
35
+ **WARNING**: As of version `'0.10.0'` there is a dependency on an old version of Bundler. Bundler version `1.17.3` works fine. I bring this up because you will receive warnings/errors when trying to use newer versions. When `tty` upgrades I will update this tool. Until then run bundler like so:
36
+
37
+ ```sh
38
+ $ bundle _1.17.3_ <command>
39
+ ```
40
+
41
+ Because of this specific dependency on `bundler` through `tty` I have elected to replicate the dependecy requirement in Hammerhead's gemspec.
42
+
43
+ ## Installation
44
+
45
+ Add this line to your application's Gemfile:
46
+
47
+ ```ruby
48
+ gem 'hammerhead'
49
+ ```
50
+
51
+ And then execute:
52
+
53
+ $ bundle install
54
+
55
+ Or install it yourself as:
56
+
57
+ $ gem install hammerhead
58
+
59
+ ## Usage
60
+
61
+ ### Commands
62
+
63
+ #### `clients`
64
+
65
+ Use this command to obtain a list of your clients. The default behavior is to return 'active' clients.
66
+
67
+ ```
68
+ Usage:
69
+ hammerhead clients [OPTIONS]
70
+
71
+ Options:
72
+ -a, [--all], [--no-all] # Return all clients from Harvest
73
+ -h, [--help], [--no-help] # Display usage information
74
+
75
+ Description:
76
+ Fetch list of clients from Harvest.
77
+
78
+ The default behavior is to return 'active' clients, only.
79
+
80
+ Add the --all flag to return entire list of clients.
81
+ ```
82
+
83
+ Displayed in the console will be a table of your clients. The first column will be their numeric id; this is assigned by Harvest. The second column will be the name you assigned to them. The `status` command, in its current form, requires the numeric id to be specified.
84
+
85
+ #### `status CLIENT`
86
+
87
+ Use this command to obtain a status report for the specified client.
88
+
89
+ ```
90
+ Usage:
91
+ hammerhead status [OPTIONS] CLIENT
92
+
93
+ Options:
94
+ -s, [--short-cut], [--no-short-cut] # CLIENT value is a user-defined short-cut
95
+ [--start-date=YYYY-MM-DD] # Start date of timesheet query
96
+ [--end-date=YYYY-MM-DD] # End date of timesheet query
97
+ -h, [--help], [--no-help] # Display usage information
98
+
99
+ Description:
100
+ Generate status report for specified client.
101
+
102
+ The default behavior is to query up to a week's worth of timesheet entries for
103
+ the specififed CLIENT.
104
+
105
+ CLIENT can either be the Id or Name of one of your Harvest Clients. Obtain this
106
+ information with the 'clients' command.
107
+
108
+ It can also be a user-defined shortcut. Please see the README for details on
109
+ creating shortcuts. However when used, you must pass the --short-cut flag.
110
+
111
+ Start and end dates are automatically calculated. They are based on _your_ 'Start
112
+ Week On' Setting.
113
+
114
+ You can alter this behavior by specifying --start-date or --end-date.
115
+
116
+ Constraints when specifying dates:
117
+
118
+ - start-date must occur before end-date
119
+
120
+ - start-date and end-date must be at least 1 day apart
121
+
122
+ - both are optional
123
+
124
+ - specifying start-date causes end-date to equal "tomorrow"
125
+
126
+ - specifying end-date requies the presense of start-date
127
+ ```
128
+
129
+ **WARNING**: The `--start-date` and `--end-date` flags are currently not supported. Supporting them is already on the Road Map.
130
+
131
+ This is the heart of the Hammerhead tool. When passed the numeric id of a client, this command attempts to grab timesheet entries for active projects for the specified client. If there are no timesheet entries, or active projects, this command responds with an appropriate message in the console.
132
+
133
+ If you work on multiple projects for a client, the output will be grouped by project, by timesheet entry.
134
+
135
+ Again, this version is tailored to my needs and, as such, is a tiny bit complicated. My business weeks run from Monday to Sunday, therefore this tool attempts to figure out the start-date and end-date based on 'Today'. If 'Today' is Monday, a report for the previous Monday to Sunday is generated, otherwise a report from Monday to 'Today' is generated.
136
+
137
+ ##### Client Name Shortcuts
138
+
139
+ These nicknames, or client name snippets, can be configured in `'hammerhead.yml'`. In lieu of Client Name support you can use this to setup shortcuts for report generation.
140
+
141
+ ```sh
142
+ $ hammerhead status -s <nickname>
143
+ ```
144
+
145
+ ### Configuration
146
+
147
+ Hammerhead requires the presense of a configuration file named: `'hammerhead.yml'`. The tool will search the directory in which the command is executed and in your home directory. An error will be displayed when the file can not be located.
148
+
149
+ #### Sample Configuration File
150
+
151
+ ```yaml
152
+ # REQUIRED
153
+ harvest:
154
+ subdomain: SET_ME
155
+ username: SET_ME
156
+ password: SET_ME
157
+
158
+ # OPTIONAL
159
+ clients:
160
+ exclude:
161
+ - <client id>
162
+ shortcuts:
163
+ <nickname>:
164
+ id: <client id>
165
+ name: <client name>
166
+ ```
167
+
168
+ Because of the `harvested` gem, and its use of Basic Authentication, you need to specify your credentials in order for the tool to authenticate itself against the Harvest V1 API. **GUARD YOUR CREDENTIALS!**
169
+
170
+ There are some optional configuration items that may prove useful to you. If you have a client, for whatever reason, you wish to exclude from any and all client listings, add their Client Id to the list that is `clients.exclude`. Additionally, if you'd like an easy-to-remember nickname for one, or more, of your clients, you can define them until `clients.shortcuts`. For example:
171
+
172
+ ```yaml
173
+ clients:
174
+ shortcuts:
175
+ acme:
176
+ id: 999999999
177
+ name: ACME Co, Inc
178
+ ```
179
+ ```sh
180
+ $ hammerhead status -s acme
181
+ ```
182
+
183
+ ## Development
184
+
185
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
186
+
187
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
188
+
189
+ ### Road Map
190
+
191
+ * Support date range for `status` command
192
+ * Add template support
193
+ * Email status report
194
+ * Use Harvest V2 API
195
+
196
+ ## Contributing
197
+
198
+ Bug reports and pull requests are welcome on GitHub at https://github.com/juicyparts/hammerhead. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/juicyparts/hammerhead/blob/master/CODE_OF_CONDUCT.md).
199
+
200
+
201
+ ## Code of Conduct
202
+
203
+ Everyone interacting in the Hammerhead project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/juicyparts/hammerhead/blob/master/CODE_OF_CONDUCT.md).
204
+
205
+ ## Copyright
206
+
207
+ Copyright (c) 2020 Juicy Parts Software, LLC. See [MIT License](LICENSE.txt) for further details.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
+ require 'hammerhead/cli'
7
+
8
+ Signal.trap('INT') do
9
+ warn("\n#{caller.join("\n")}: interrupted")
10
+ exit(1)
11
+ end
12
+
13
+ begin
14
+ Hammerhead::CLI.start
15
+ rescue Hammerhead::CLI::Error => err
16
+ puts "ERROR: #{err.message}"
17
+ exit 1
18
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require 'hammerhead'
3
+ require 'hammerhead/configuration'
4
+ require 'hammerhead/utils'
5
+ require 'hammerhead/version'
6
+
7
+ require 'harvest'
8
+
9
+ require 'tty-logger'
10
+
11
+ require 'pry-byebug'
12
+ require 'ap'
13
+
14
+ ##
15
+ # Wraps the functionality of the hammerhead tool. Contains the commands
16
+ # defined, and implemented, by the +tty+ gem.
17
+ #
18
+ module Hammerhead
19
+ ##
20
+ # Commands generated by the +tty+ gem are organized in this module.
21
+ #
22
+ module Commands; end
23
+
24
+ ##
25
+ # General error used when something bad, or unexpected, occurs.
26
+ #
27
+ class Error < StandardError; end
28
+
29
+ class << self
30
+ ##
31
+ # Returns a Hammerhead::Configuration instance
32
+ #
33
+ def configuration
34
+ @configuration ||= Hammerhead::Configuration.new
35
+ end
36
+
37
+ ##
38
+ # Returns a TTY::Logger instance
39
+ #
40
+ def logger
41
+ @logger ||= TTY::Logger.new
42
+ end
43
+
44
+ ##
45
+ # Provides an alternate way to access the +configuration+
46
+ def configure
47
+ yield configuration
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Hammerhead
6
+ # Handle the application command line parsing
7
+ # and the dispatch to various command objects
8
+ #
9
+ # @api public
10
+ class CLI < Thor # :nodoc: all
11
+ # Error raised by this runner
12
+ Error = Class.new(StandardError)
13
+
14
+ desc 'version', 'hammerhead version'
15
+ def version
16
+ require_relative 'version'
17
+ puts "v#{Hammerhead::VERSION} Hammerhead 🦈"
18
+ end
19
+ map %w[--version -v] => :version
20
+
21
+ desc 'status [OPTIONS] CLIENT', 'Generate status report for specified client'
22
+ long_desc <<-DESC
23
+ Generate status report for specified client.
24
+
25
+ The default behavior is to query up to a week's worth of timesheet entries for the specififed CLIENT.
26
+
27
+ CLIENT can either be the Id or Name of one of your Harvest Clients. Obtain this information with the 'clients' command.
28
+
29
+ It can also be a user-defined shortcut. Please see the README for details on creating shortcuts. However when used, you must pass the --short-cut flag.
30
+
31
+ Start and end dates are automatically calculated. They are based on _your_ 'Start Week On' Setting.
32
+
33
+ You can alter this behavior by specifying --start-date or --end-date.
34
+
35
+ Constraints when specifying dates:\n
36
+ - start-date must occur before end-date\n
37
+ - start-date and end-date must be at least 1 day apart\n
38
+ - both are optional\n
39
+ - specifying start-date causes end-date to equal "tomorrow"\n
40
+ - specifying end-date requies the presense of start-date
41
+ DESC
42
+ method_option :short_cut, aliases: '-s', type: :boolean, desc: 'CLIENT value is a user-defined short-cut'
43
+ method_option :start_date, banner: 'YYYY-MM-DD', type: :string, desc: 'Start date of timesheet query'
44
+ method_option :end_date, banner: 'YYYY-MM-DD', type: :string, desc: 'End date of timesheet query'
45
+ method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
46
+ def status client
47
+ if options[:help]
48
+ invoke :help, ['status']
49
+ else
50
+ require_relative 'commands/status'
51
+ Hammerhead::Commands::Status.new(client, options).execute
52
+ end
53
+ end
54
+
55
+ desc 'clients [OPTIONS]', 'Fetch list of clients from Harvest'
56
+ long_desc <<-DESC
57
+ Fetch list of clients from Harvest.
58
+
59
+ The default behavior is to return 'active' clients, only.
60
+
61
+ Add the --all flag to return entire list of clients.
62
+ DESC
63
+ method_option :all, aliases: '-a', type: :boolean, desc: 'Return all clients from Harvest'
64
+ method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
65
+ def clients
66
+ if options[:help]
67
+ invoke :help, ['clients']
68
+ else
69
+ require_relative 'commands/clients'
70
+ Hammerhead::Commands::Clients.new(options).execute
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Hammerhead
6
+ class Command # :nodoc: all
7
+ extend Forwardable
8
+
9
+ def_delegators :command, :run
10
+
11
+ # Execute this command
12
+ #
13
+ # @api public
14
+ def execute
15
+ raise NotImplementedError, "#{self.class}##{__method__} must be implemented"
16
+ end
17
+
18
+ # The external commands runner
19
+ #
20
+ # @see http://www.rubydoc.info/gems/tty-command
21
+ #
22
+ # @api public
23
+ def command **options
24
+ require 'tty-command'
25
+ TTY::Command.new(options)
26
+ end
27
+
28
+ # The cursor movement
29
+ #
30
+ # @see http://www.rubydoc.info/gems/tty-cursor
31
+ #
32
+ # @api public
33
+ def cursor
34
+ require 'tty-cursor'
35
+ TTY::Cursor
36
+ end
37
+
38
+ # Open a file or text in the user's preferred editor
39
+ #
40
+ # @see http://www.rubydoc.info/gems/tty-editor
41
+ #
42
+ # @api public
43
+ def editor
44
+ require 'tty-editor'
45
+ TTY::Editor
46
+ end
47
+
48
+ # File manipulation utility methods
49
+ #
50
+ # @see http://www.rubydoc.info/gems/tty-file
51
+ #
52
+ # @api public
53
+ def generator
54
+ require 'tty-file'
55
+ TTY::File
56
+ end
57
+
58
+ # Terminal output paging
59
+ #
60
+ # @see http://www.rubydoc.info/gems/tty-pager
61
+ #
62
+ # @api public
63
+ def pager **options
64
+ require 'tty-pager'
65
+ TTY::Pager.new(options)
66
+ end
67
+
68
+ # Terminal platform and OS properties
69
+ #
70
+ # @see http://www.rubydoc.info/gems/tty-pager
71
+ #
72
+ # @api public
73
+ def platform
74
+ require 'tty-platform'
75
+ TTY::Platform.new
76
+ end
77
+
78
+ # The interactive prompt
79
+ #
80
+ # @see http://www.rubydoc.info/gems/tty-prompt
81
+ #
82
+ # @api public
83
+ def prompt **options
84
+ require 'tty-prompt'
85
+ TTY::Prompt.new(options)
86
+ end
87
+
88
+ # Get terminal screen properties
89
+ #
90
+ # @see http://www.rubydoc.info/gems/tty-screen
91
+ #
92
+ # @api public
93
+ def screen
94
+ require 'tty-screen'
95
+ TTY::Screen
96
+ end
97
+
98
+ # The unix which utility
99
+ #
100
+ # @see http://www.rubydoc.info/gems/tty-which
101
+ #
102
+ # @api public
103
+ def which *args
104
+ require 'tty-which'
105
+ TTY::Which.which(*args)
106
+ end
107
+
108
+ # Check if executable exists
109
+ #
110
+ # @see http://www.rubydoc.info/gems/tty-which
111
+ #
112
+ # @api public
113
+ def exec_exist? *args
114
+ require 'tty-which'
115
+ TTY::Which.exist?(*args)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-table'
4
+ require 'hammerhead'
5
+ require_relative '../command'
6
+
7
+ module Hammerhead
8
+ module Commands
9
+ ##
10
+ # Implements the +clients+ command.
11
+ #
12
+ # This command, using a Harvest.connection, knows how to obtain a list of clients
13
+ # and display them in a +TTY::Table+. It also outputs the number of clients returned.
14
+ #
15
+ # If any errors are caught, they are sent to the Hammerhead.logger.
16
+ #
17
+ class Clients < Hammerhead::Command
18
+ def initialize options # :nodoc:
19
+ self.options = options
20
+ end
21
+
22
+ ##
23
+ # :stopdoc:
24
+ #
25
+ # TODO: Format Table
26
+ # TODO: Display different table with '--all'
27
+ # TODO: Colorize Output
28
+ def execute input: $stdin, output: $stdout
29
+ clients = Harvest.connection.clients options
30
+
31
+ output.puts "Pass the Id to the 'status' command, or enough of the name to uniquely match."
32
+ output.puts
33
+
34
+ table = TTY::Table.new headers, data(clients)
35
+ output.puts table.render(:unicode)
36
+
37
+ output.puts
38
+ output.puts "CLIENTS FOUND: #{clients.size}"
39
+ rescue StandardError => e
40
+ Hammerhead.logger.error 'CLIENTS COMMAND ERROR:', e
41
+ end
42
+
43
+ private
44
+
45
+ attr_accessor :options
46
+
47
+ def headers
48
+ %w[Id Name]
49
+ end
50
+
51
+ def data clients
52
+ clients.collect do |client|
53
+ [client.id, client.name]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry-byebug'
4
+
5
+ require 'date'
6
+ require 'hammerhead'
7
+ require_relative '../command'
8
+
9
+ module Hammerhead
10
+ module Commands
11
+ ##
12
+ # Implements the +status+ command.
13
+ #
14
+ # Using a Harvest.connection and a client Id, like one obtained via the
15
+ # +clients+ command, this command prepares a 'status report' for display in
16
+ # the console.
17
+ #
18
+ # This command warns about: an inactive client, a client with no active
19
+ # projects, or no timesheet entries for the period specified.
20
+ #
21
+ # The output consists of the client's name, the text: 'Status Report (week
22
+ # ending <date>)', along with the timesheet entries returned. They are
23
+ # listed in project order, in entry order:
24
+ # ----------------------------------------
25
+ #
26
+ # ACME Co, Inc
27
+ #
28
+ # Status Report (week ending 9/19/20)
29
+ #
30
+ # I worked 0 hours.
31
+ # ----------------------------------------
32
+ #
33
+ class Status < Hammerhead::Command
34
+ include Hammerhead::Utils
35
+
36
+ def initialize client, options # :nodoc:
37
+ self.specified_client = client
38
+ self.options = options
39
+ configure_query_dates
40
+ end
41
+
42
+ ##
43
+ # :stopdoc:
44
+ #
45
+ def execute input: $stdin, output: $stdout
46
+ process_short_cut
47
+ connection = Harvest.connection
48
+ output.puts "Fetching details for specified client: #{specified_client}"
49
+ client = connection.client specified_client
50
+ unless client.active?
51
+ output.puts "#{client.name} is not active."
52
+ return
53
+ end
54
+ projects = connection.projects_for_client client
55
+ if projects.empty?
56
+ output.puts "#{client.name} has no active projects."
57
+ return
58
+ end
59
+ entries = connection.my_time_sheet_entries start_date, end_date
60
+ if entries.empty?
61
+ output.puts "There are no timesheet entries for the period specified: #{start_date} -> #{end_date}"
62
+ return
63
+ end
64
+
65
+ status_output = {
66
+ hours: 0.0,
67
+ projects: {},
68
+ entries: {}
69
+ }
70
+
71
+ projects.each do |project|
72
+ status_output[:projects][project.id] = project.code
73
+ end
74
+
75
+ entries.each do |entry|
76
+ next unless status_output[:projects].key? entry.project_id
77
+
78
+ status_output[:hours] += entry.hours
79
+ project_code = status_output[:projects][entry.project_id]
80
+ status_output[:entries][project_code] = [] unless status_output[:entries].key? project_code
81
+ status_output[:entries][project_code] << entry.notes
82
+ end
83
+
84
+ output.puts '----------------------------------------'
85
+ output.puts
86
+ output.puts client.name.to_s
87
+ output.puts
88
+ output.puts "Status Report (week ending #{end_date.strftime('%-m/%-d/%y')})"
89
+ output.puts
90
+
91
+ if status_output[:hours] > 0.0
92
+ output.puts "I worked #{status_output[:hours]} hours on the following:"
93
+ status_output[:entries].each do |code, _entries|
94
+ output.puts "\n[#{code}]"
95
+ _entries.each do |entry|
96
+ output.puts "- #{entry}"
97
+ end
98
+ end
99
+ else
100
+ output.puts 'I worked 0 hours.'
101
+ end
102
+
103
+ output.puts
104
+ output.puts '----------------------------------------'
105
+ rescue StandardError => e
106
+ Hammerhead.logger.error 'STATUS COMMAND ERROR:', e
107
+ end
108
+
109
+ private
110
+
111
+ attr_accessor :end_date, :options, :start_date, :specified_client
112
+
113
+ def configure_query_dates
114
+ today = Date.today
115
+ start_of_week = Date::DAYNAMES.index(Harvest.connection.week_start_day)
116
+ adjustment = today.wday - start_of_week
117
+
118
+ case today.wday
119
+ when 0 # Sunday
120
+ self.start_date = if start_of_week.zero?
121
+ today - 7
122
+ else
123
+ today - 6
124
+ end
125
+ self.end_date = start_date + 6
126
+
127
+ when 1 # Monday
128
+ if start_of_week.zero?
129
+ self.start_date = today - adjustment
130
+ self.end_date = start_date + adjustment
131
+ else
132
+ self.start_date = today - 7
133
+ self.end_date = start_date + 6
134
+ end
135
+ else
136
+ self.start_date = today - adjustment
137
+ self.end_date = start_date + adjustment
138
+ end
139
+ end
140
+
141
+ def process_short_cut
142
+ return unless options['short_cut']
143
+
144
+ configuration = Hammerhead.configuration
145
+
146
+ return if digits? specified_client
147
+
148
+ client = configuration.client_shortcut specified_client
149
+ raise Hammerhead::Error, "Specified shortcut: '#{specified_client}' does not exist" if client.nil?
150
+ self.specified_client = client['id']
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-config'
4
+
5
+ module Hammerhead
6
+ ##
7
+ # :markup: markdown
8
+ # Represents the configuration for the hammerhead tool. It wraps the [tty-config](https://rubygems.org/gems/tty-config)
9
+ # gem.
10
+ #
11
+ class Configuration
12
+ attr_reader :config # :nodoc:
13
+
14
+ ##
15
+ # Creates a new instance of TTY::Config, configured with filename
16
+ # 'hammerhead.yml' and to look for said file in the current working directory
17
+ # or in the user's home directory.
18
+ #
19
+ def initialize
20
+ self.config = TTY::Config.new
21
+ config.append_path Dir.pwd # look in current working directory
22
+ config.append_path Dir.home # look in user home directory
23
+ config.filename = File.basename configuration_filename, '.*'
24
+ end
25
+
26
+ ##
27
+ # :category: harvest
28
+ # Returns the Harvest +password+ as defined in the configuration file.
29
+ #
30
+ # This configuration items is <b>required</b>.
31
+ #
32
+ def password
33
+ harvest_configuration['password']
34
+ end
35
+
36
+ ##
37
+ # :category: harvest
38
+ # Returns the Harvest +subdomain+ as defined in the configuration file.
39
+ #
40
+ # This configuration items is <b>required</b>.
41
+ #
42
+ def subdomain
43
+ harvest_configuration['subdomain']
44
+ end
45
+
46
+ ##
47
+ # :category: harvest
48
+ # Returns the Harvest +username+ as defined in the configuration file.
49
+ #
50
+ # This configuration items is <b>required</b>.
51
+ #
52
+ def username
53
+ harvest_configuration['username']
54
+ end
55
+
56
+ ##
57
+ # :category: clients
58
+ # Convenience method to obtain a configured +shortcut+. Returns a Hash or nil
59
+ #
60
+ # client_shortcut 'acme' => { id: 999999999, name: 'ACME Co, Inc' }
61
+ #
62
+ def client_shortcut shortcut
63
+ client_shortcuts[shortcut]
64
+ end
65
+
66
+ ##
67
+ # :category: clients
68
+ # Returns an Array of entries defined under +clients.shortcuts+
69
+ #
70
+ def client_shortcuts
71
+ config.fetch('clients.shortcuts', default: [])
72
+ end
73
+
74
+ ##
75
+ # :category: clients
76
+ # Returns an Array of client ids to be excluded from all operations.
77
+ #
78
+ def clients_to_exclude
79
+ config.fetch('clients.exclude', default: [])
80
+ end
81
+
82
+ ##
83
+ # :category: validation
84
+ # Performs 2 important checks: 1) if the file exists and 2) if it contains
85
+ # the required harvest information. If either of those fail this method
86
+ # raises an Hammerhead::Error.
87
+ #
88
+ def validate!
89
+ load_configuration!
90
+ validate_harvest_information
91
+ end
92
+
93
+ private
94
+
95
+ attr_writer :config # :nodoc:
96
+
97
+ def configuration_filename
98
+ 'hammerhead.yml'
99
+ end
100
+
101
+ def harvest_configuration
102
+ config.fetch :harvest
103
+ end
104
+
105
+ def load_configuration!
106
+ unless config.exist?
107
+ raise Hammerhead::Error, "HarvestClient configuration file, '#{configuration_filename}' does not exist."
108
+ end
109
+
110
+ config.read
111
+ end
112
+
113
+ def validate_harvest_information
114
+ configuration_missing_messages = []
115
+ if harvest_configuration.nil?
116
+ configuration_missing_messages << "'#{configuration_filename}' does not define harvest configuration."
117
+ else
118
+ if subdomain.nil?
119
+ configuration_missing_messages << "'#{configuration_filename}' does not define harvest subdomain."
120
+ end
121
+ if username.nil?
122
+ configuration_missing_messages << "'#{configuration_filename}' does not define harvest username."
123
+ end
124
+ if password.nil?
125
+ configuration_missing_messages << "'#{configuration_filename}' does not define harvest password."
126
+ end
127
+ end
128
+ raise Hammerhead::Error, configuration_missing_messages.join("\n") unless configuration_missing_messages.empty?
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Hammerhead
3
+ ##
4
+ # Contains utility-type methods available for use throughout the tool.
5
+ #
6
+ module Utils
7
+
8
+ ##
9
+ # Returns true if the specified +input+ consists of digits
10
+ #
11
+ # digits? '1234' => true
12
+ # digits? 'abcd' => false
13
+ #
14
+ # Uses a regular expression to determine if all the specified characters
15
+ # are numeric.
16
+ #
17
+ def digits? input
18
+ input.match?(/\A\d+\Z/)
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Hammerhead # :nodoc:
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'harvest/connection'
4
+
5
+ ##
6
+ # Exposes the Harvest API to Hammerhead.
7
+ #
8
+ module Harvest
9
+ class << self
10
+ ##
11
+ # Returns the sole Harvest::Connection instance.
12
+ #
13
+ def connection
14
+ Harvest::Connection.instance
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'harvested' # NOTE: support for Harvest API V1
5
+
6
+ module Harvest
7
+ ##
8
+ # :markup: markdown
9
+ # Represents the link between Harvest and Hammerhead and the Harvest V1 API.
10
+ #
11
+ # Backed by ['harvested'](https://rubygems.org/gems/harvested) gem.
12
+ #
13
+ class Connection
14
+ include Singleton
15
+ include Hammerhead::Utils
16
+
17
+ ##
18
+ # Use .instance to grab an initialied harvest connection:
19
+ # connection = Harvest::Connection.instance
20
+ #
21
+ # Because this attempts to establish a connection to the API, ensure a
22
+ # correctly defined 'hammerhead.yml' file exists.
23
+ #
24
+ # Returns Harvest.hardy_client
25
+ #
26
+ def initialize
27
+ new_connection!
28
+ end
29
+
30
+ ##
31
+ # This is the hammerhead user, as defined in by the harvest credentials. It
32
+ # is for this user the client list will be provided, and the status report
33
+ # will be generated.
34
+ #
35
+ def authenticated_user
36
+ harvest.account.who_am_i
37
+ end
38
+
39
+ ##
40
+ # For the specified +client_id+ return its Harvest definition.
41
+ #
42
+ # If +client_id+ contains alpha-characters NotImplementedError is raised.
43
+ #
44
+ def client client_id
45
+ raise NotImplementedError, 'Client by name is not implemented yet.' unless digits?(client_id)
46
+ harvest.clients.find client_id
47
+ end
48
+
49
+ ##
50
+ # Return a list of clients. Currently only +options[:all]+ is supported.
51
+ # The default behavior is to only return 'active' clients.
52
+ #
53
+ # If the configuration file defines a list of ids to exclude, they're removed
54
+ # before 'active' or all clients are returned.
55
+ #
56
+ def clients options = {}
57
+ clients = harvest.clients.all.reject { |client| clients_to_exclude.include? client.id }
58
+ clients = clients.select { |client| client.active == true } unless options['all']
59
+ clients
60
+ end
61
+
62
+ ##
63
+ # Return a list of timesheet entries between +start_date+ and +end_date+, inclusive.
64
+ #
65
+ # This is for the +authenticated_user+.
66
+ #
67
+ def my_time_sheet_entries start_date, end_date
68
+ harvest.reports.time_by_user authenticated_user, start_date, end_date
69
+ end
70
+
71
+ ##
72
+ # Returns 'active' projects for the specified +client+.
73
+ #
74
+ # If the +client+ is not active, an empty list is returned.
75
+ #
76
+ def projects_for_client client
77
+ return [] unless client.active?
78
+
79
+ harvest.reports.projects_by_client(client).select(&:active?)
80
+ end
81
+
82
+ ##
83
+ # Return begining of work week as defined in Harvest
84
+ #
85
+ def week_start_day
86
+ authenticated_user.company.week_start_day
87
+ end
88
+
89
+ private
90
+
91
+ attr_accessor :harvest
92
+
93
+ def new_connection!
94
+ Hammerhead.configuration.validate!
95
+ self.harvest = Harvest.hardy_client subdomain: subdomain, username: username, password: password if harvest.nil?
96
+ harvest
97
+ rescue StandardError => e
98
+ Hammerhead.logger.fatal 'Fatal:', e
99
+ end
100
+
101
+ def subdomain
102
+ Hammerhead.configuration.subdomain
103
+ end
104
+
105
+ def username
106
+ Hammerhead.configuration.username
107
+ end
108
+
109
+ def password
110
+ Hammerhead.configuration.password
111
+ end
112
+
113
+ def clients_to_exclude
114
+ Hammerhead.configuration.clients_to_exclude
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # Define Rake Tasks 'internal' to Hammerhead development
3
+
4
+ def configuration_filename
5
+ 'hammerhead.yml'
6
+ end
7
+
8
+ def example_configuration_filename
9
+ "#{configuration_filename}.example"
10
+ end
11
+
12
+ namespace :hh do
13
+ desc %(Flip Config File
14
+
15
+ Simple Rake Task to move the configuration file from one state to another.
16
+
17
+ )
18
+ task :flip_config_file do
19
+ if File.exist? configuration_filename
20
+ FileUtils.mv configuration_filename, example_configuration_filename, verbose: true
21
+ elsif File.exist? example_configuration_filename
22
+ FileUtils.mv example_configuration_filename, configuration_filename, verbose: true
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hammerhead
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mel Riffe
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-09-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.16'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: harvested
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ description: Generate status reports from Harvest timesheets. TBD...
62
+ email:
63
+ - mel@juicyparts.com
64
+ executables:
65
+ - hammerhead
66
+ extensions: []
67
+ extra_rdoc_files:
68
+ - README.md
69
+ - CHANGELOG.md
70
+ - CODE_OF_CONDUCT.md
71
+ - LICENSE.txt
72
+ files:
73
+ - CHANGELOG.md
74
+ - CODE_OF_CONDUCT.md
75
+ - LICENSE.txt
76
+ - README.md
77
+ - exe/hammerhead
78
+ - lib/hammerhead.rb
79
+ - lib/hammerhead/cli.rb
80
+ - lib/hammerhead/command.rb
81
+ - lib/hammerhead/commands/clients.rb
82
+ - lib/hammerhead/commands/status.rb
83
+ - lib/hammerhead/configuration.rb
84
+ - lib/hammerhead/utils.rb
85
+ - lib/hammerhead/version.rb
86
+ - lib/harvest.rb
87
+ - lib/harvest/connection.rb
88
+ - lib/tasks/hammerhead.rake
89
+ homepage: http://juicyparts.com/hammerhead
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ allowed_push_host: https://rubygems.org
94
+ homepage_uri: http://juicyparts.com/hammerhead
95
+ source_code_uri: https://github.com/juicyparts/hammerhead
96
+ changelog_uri: https://github.com/juicyparts/hammerhead/blob/master/CHANGELOG.md
97
+ bug_tracker_uri: https://github.com/juicyparts/hammerhead/issues
98
+ documentation_uri: https://www.rubydoc.info/gems/hammerhead
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.7.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.1.2
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Generate status reports from Harvest timesheets.
118
+ test_files: []