harvest-worklog 0.5.0
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 +7 -0
- data/README.md +89 -0
- data/bin/harvest-worklog +6 -0
- data/harvest-worklog.rb +7 -0
- data/lib/harvest_worklog/version.rb +5 -0
- data/lib/harvest_worklog/work_entry_cli.rb +87 -0
- data/lib/harvest_worklog.rb +167 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c1397fdec13d0a89c0023e926fa42043f701c351e0fbca487e40331c61bbfbcf
|
|
4
|
+
data.tar.gz: '09b9a725ea95df79e94cf204a1f12735c6db8e4d55d4a38f8442fc3f947f981f'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 33f8bbfa7e4d6f4b98bcc014d6ba3cdb51193a2582a3fd3bed37e3d4051fb606440065f60076d9afd1efc9ab64aa4d6f14a4d6fdb4dd0ff92775419a34012d74
|
|
7
|
+
data.tar.gz: 32d59d73fc353d159e7e7a6c93373147f0a7756b4f3b0ba56b4bb877b2cd177000ee8898739eeb1813c3830640cb8a6ce57d1272ab9aa1a5f1bdec1146f3adf9
|
data/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Harvest Worklog
|
|
2
|
+
|
|
3
|
+
Harvest Worklog is a Ruby CLI and OMP plugin for personal Harvest work logs. It creates holiday-aware time-off entries, writes reviewed ordinary-work entries, and imports reviewed OMP Project Time sessions.
|
|
4
|
+
|
|
5
|
+
## Credentials
|
|
6
|
+
|
|
7
|
+
1. Open [Harvest ID Developers](https://id.getharvest.com/developers) and create a Personal Access Token for this local tool.
|
|
8
|
+
2. Harvest displays the token and the available Harvest account IDs once. Copy the account ID for the account to track time in.
|
|
9
|
+
3. Fill the ignored `.envrc` in this repository:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
export HARVEST_ACCESS_TOKEN="your-personal-access-token"
|
|
13
|
+
export HARVEST_ACCOUNT_ID="your-harvest-account-id"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
4. Run `direnv allow` if you use direnv; otherwise run `source .envrc` in the shell that starts OMP or the CLI.
|
|
17
|
+
|
|
18
|
+
Personal Access Tokens are the intended Harvest authentication method for personal scripts. Revoke and replace a token from Harvest ID if it is exposed. [Harvest authentication documentation](https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/).
|
|
19
|
+
|
|
20
|
+
## CLI
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
harvest-worklog time-off FROM TO --project NAME --task NAME [options]
|
|
24
|
+
harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Time off
|
|
28
|
+
|
|
29
|
+
The `time-off` command creates one 7-hour entry for each Yukon business day in an inclusive range. It skips weekends and observed Yukon holidays by default.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
harvest-worklog time-off 2026-08-17 2026-08-28 \
|
|
33
|
+
--project 'Time Off - Marlen' \
|
|
34
|
+
--task 'Vacation / PTO' \
|
|
35
|
+
--notes 'regular time off' \
|
|
36
|
+
--dry-run
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The default holiday region is `ca_yt`. Add `--holiday-region REGION` or set `HARVEST_HOLIDAY_REGIONS` to include other [Holidays](https://github.com/holidays/holidays) regions. `business_time` calculates business days and `holidays` supplies observed statutory holidays.
|
|
40
|
+
|
|
41
|
+
A time-off block never creates weekend entries. When names are supplied, it resolves them from your active personal Harvest assignments, so a Project Manager role is not required.
|
|
42
|
+
|
|
43
|
+
### Reviewed ordinary work
|
|
44
|
+
|
|
45
|
+
The `work-entry` command checks for existing or locked entries for its project, task, and date before creating a new duration entry. Use `--dry-run` to complete that preflight without a write. OMP Project Time uses the same path.
|
|
46
|
+
|
|
47
|
+
## OMP settings
|
|
48
|
+
|
|
49
|
+
- `defaultHours`: hours per business day when a time-off tool call omits `hours`; defaults to `7`.
|
|
50
|
+
- `holidayRegions`: comma-separated Holidays regions; defaults to `ca_yt`.
|
|
51
|
+
- `command`: direct path to the `harvest-worklog` executable.
|
|
52
|
+
- `projectTimeMappings`: JSON mapping from OMP Project Time project names to Harvest project/task names.
|
|
53
|
+
- `projectTimeLogPath`: optional override for `~/.omp/project-time/time-log.json`.
|
|
54
|
+
|
|
55
|
+
## OMP Project Time integration
|
|
56
|
+
|
|
57
|
+
Configure `projectTimeMappings` with the recorded OMP Project Time project name as the key:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"Harvest API": { "project": "Internal", "task": "Development" }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`harvest_preview_project_time_entries` reads the configured time log, splits sessions across local dates, generates descriptions from the project and repository, and checks Harvest for existing or locked entries without writing. `harvest_record_project_time_entries` performs the same preflight then creates only new entries; OMP treats it as a write requiring approval. Unmapped sessions are reported and never written.
|
|
66
|
+
|
|
67
|
+
## Migration from harvest-time-off
|
|
68
|
+
|
|
69
|
+
Version `0.5.0` is a clean identity cutover. Uninstall the prior `harvest-time-off` gem and OMP plugin, then install `harvest-worklog`; replace former top-level CLI commands with the `harvest-worklog time-off` and `harvest-worklog work-entry` subcommands. The old package, executables, and `workEntryCommand` setting are not retained.
|
|
70
|
+
|
|
71
|
+
## Release
|
|
72
|
+
|
|
73
|
+
Before merging a release PR, self-review its complete diff, address every actionable review comment, and rerun focused QA after fixups. Record the review and QA evidence on the PR. Then merge to `main`, build and publish the Ruby gem, install the released OMP plugin from GitHub, and verify it:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
ruby test_harvest_worklog.rb
|
|
77
|
+
npm run test:omp
|
|
78
|
+
harvest-worklog time-off 2026-08-17 2026-08-28 \
|
|
79
|
+
--project 'Time Off - Marlen' \
|
|
80
|
+
--task 'Vacation / PTO' \
|
|
81
|
+
--holiday-region ca_yt \
|
|
82
|
+
--dry-run
|
|
83
|
+
gem build harvest-worklog.gemspec
|
|
84
|
+
gem push harvest-worklog-<version>.gem
|
|
85
|
+
omp plugin uninstall harvest-time-off
|
|
86
|
+
omp plugin install --force github:klondikemarlen/harvest-worklog
|
|
87
|
+
omp plugin list
|
|
88
|
+
harvest-worklog --help
|
|
89
|
+
```
|
data/bin/harvest-worklog
ADDED
data/harvest-worklog.rb
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarvestWorklog
|
|
4
|
+
class WorkEntryCLI
|
|
5
|
+
EXISTING_ENTRY = 2
|
|
6
|
+
LOCKED_ENTRY = 3
|
|
7
|
+
|
|
8
|
+
def self.run(arguments, output: $stdout, error: $stderr, client: nil)
|
|
9
|
+
options = { dry_run: false }
|
|
10
|
+
parser = option_parser(options)
|
|
11
|
+
dates = parser.parse(arguments)
|
|
12
|
+
validate!(dates, options)
|
|
13
|
+
|
|
14
|
+
spent_date = Date.iso8601(dates.first)
|
|
15
|
+
client ||= Marlens::HarvestApiV2::Client.from_environment
|
|
16
|
+
project_id, task_id = if options[:project_id]
|
|
17
|
+
[options[:project_id], options[:task_id]]
|
|
18
|
+
else
|
|
19
|
+
TimeOffCLI.resolve_assignment(client, options[:project], options[:task])
|
|
20
|
+
end
|
|
21
|
+
existing_entries = client.request(
|
|
22
|
+
:get,
|
|
23
|
+
"/v2/time_entries",
|
|
24
|
+
params: { project_id:, task_id:, from: spent_date.iso8601, to: spent_date.iso8601 }
|
|
25
|
+
).fetch("time_entries")
|
|
26
|
+
|
|
27
|
+
if existing_entries.any? { |entry| entry["is_locked"] }
|
|
28
|
+
output.puts "Locked existing Harvest entry on #{spent_date.iso8601}; skipped"
|
|
29
|
+
return LOCKED_ENTRY
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if existing_entries.any?
|
|
33
|
+
output.puts "Existing Harvest entry on #{spent_date.iso8601}; skipped"
|
|
34
|
+
return EXISTING_ENTRY
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if options[:dry_run]
|
|
38
|
+
output.puts "Would create #{spent_date.iso8601}: #{HarvestWorklog.display_hours(options[:hours])}h on #{options[:project]} / #{options[:task]}; #{options[:notes]}"
|
|
39
|
+
return 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
entry = client.create_time_entry(
|
|
43
|
+
project_id:,
|
|
44
|
+
task_id:,
|
|
45
|
+
spent_date:,
|
|
46
|
+
hours: options[:hours],
|
|
47
|
+
notes: options[:notes]
|
|
48
|
+
)
|
|
49
|
+
output.puts "Created #{spent_date.iso8601}: #{HarvestWorklog.display_hours(options[:hours])}h (entry ##{entry.fetch("id")})"
|
|
50
|
+
0
|
|
51
|
+
rescue Error, Marlens::HarvestApiV2::Error, OptionParser::ParseError, Date::Error => e
|
|
52
|
+
error.puts "Error: #{e.message}"
|
|
53
|
+
error.puts parser if parser
|
|
54
|
+
1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.option_parser(options)
|
|
58
|
+
OptionParser.new do |opts|
|
|
59
|
+
opts.banner = "Usage: harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [--dry-run]"
|
|
60
|
+
opts.on("--project NAME", "Harvest project name") { |value| options[:project] = value }
|
|
61
|
+
opts.on("--task NAME", "Harvest task name") { |value| options[:task] = value }
|
|
62
|
+
opts.on("--project-id ID", Integer, "Harvest project ID") { |value| options[:project_id] = value }
|
|
63
|
+
opts.on("--task-id ID", Integer, "Harvest task ID") { |value| options[:task_id] = value }
|
|
64
|
+
opts.on("--hours HOURS", Float, "Hours for this entry") { |value| options[:hours] = value }
|
|
65
|
+
opts.on("--notes NOTES", "Entry description") { |value| options[:notes] = value }
|
|
66
|
+
opts.on("--dry-run", "Check for existing entries without writing") { options[:dry_run] = true }
|
|
67
|
+
opts.on("-h", "--help", "Show this help") do
|
|
68
|
+
puts opts
|
|
69
|
+
exit 0
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.validate!(dates, options)
|
|
75
|
+
raise Error, "DATE is required" unless dates.length == 1
|
|
76
|
+
raise Error, "--hours must be a positive finite number" unless options[:hours]&.positive? && options[:hours].finite?
|
|
77
|
+
raise Error, "--notes is required" unless options[:notes] && !options[:notes].empty?
|
|
78
|
+
|
|
79
|
+
names_provided = options[:project] || options[:task]
|
|
80
|
+
ids_provided = options[:project_id] || options[:task_id]
|
|
81
|
+
valid_names = options[:project] && options[:task]
|
|
82
|
+
valid_ids = options[:project_id] && options[:task_id]
|
|
83
|
+
raise Error, "supply --project and --task, or --project-id and --task-id" unless valid_names || valid_ids
|
|
84
|
+
raise Error, "do not combine project/task names with IDs" if names_provided && ids_provided
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "harvest_worklog/version"
|
|
4
|
+
require "business_time"
|
|
5
|
+
require "date"
|
|
6
|
+
require "holidays"
|
|
7
|
+
require "optparse"
|
|
8
|
+
require "marlens/harvest_api_v2"
|
|
9
|
+
|
|
10
|
+
module HarvestWorklog
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def dates_between(from, to, holiday_regions:)
|
|
14
|
+
raise Error, "end date must not be before start date" if to < from
|
|
15
|
+
raise Error, "at least one holiday region is required" if holiday_regions.empty?
|
|
16
|
+
|
|
17
|
+
holidays = Holidays.between(from, to, *(holiday_regions.map { |region| region.downcase.to_sym } + [:observed])).map { |holiday| holiday[:date] }
|
|
18
|
+
(from..to).select { |date| date.workday?(holidays:) }
|
|
19
|
+
rescue Holidays::InvalidRegion
|
|
20
|
+
raise Error, "invalid holiday region: #{holiday_regions.join(", ")}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def display_hours(hours)
|
|
24
|
+
format("%g", hours)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Error < StandardError; end
|
|
28
|
+
|
|
29
|
+
class CLI
|
|
30
|
+
def self.run(arguments, output: $stdout, error: $stderr, client: nil)
|
|
31
|
+
command, *command_arguments = arguments
|
|
32
|
+
case command
|
|
33
|
+
when "time-off"
|
|
34
|
+
TimeOffCLI.run(command_arguments, output:, error:, client:)
|
|
35
|
+
when "work-entry"
|
|
36
|
+
WorkEntryCLI.run(command_arguments, output:, error:, client:)
|
|
37
|
+
when "-h", "--help"
|
|
38
|
+
output.puts usage
|
|
39
|
+
0
|
|
40
|
+
else
|
|
41
|
+
error.puts "Error: choose time-off or work-entry"
|
|
42
|
+
error.puts usage
|
|
43
|
+
1
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.usage
|
|
48
|
+
<<~USAGE
|
|
49
|
+
Usage:
|
|
50
|
+
harvest-worklog time-off FROM TO --project NAME --task NAME [options]
|
|
51
|
+
harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
|
|
52
|
+
|
|
53
|
+
Commands:
|
|
54
|
+
time-off Create one entry per local business day in a date range.
|
|
55
|
+
work-entry Create one reviewed ordinary-work entry.
|
|
56
|
+
USAGE
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class TimeOffCLI
|
|
61
|
+
def self.run(arguments, output: $stdout, error: $stderr, client: nil)
|
|
62
|
+
options = { hours: 7.0, dry_run: false, holiday_regions: holiday_regions_from_environment }
|
|
63
|
+
parser = option_parser(options)
|
|
64
|
+
dates = parser.parse(arguments)
|
|
65
|
+
validate!(dates, options)
|
|
66
|
+
|
|
67
|
+
from = Date.iso8601(dates[0])
|
|
68
|
+
to = Date.iso8601(dates[1])
|
|
69
|
+
workdays = HarvestWorklog.dates_between(from, to, holiday_regions: options[:holiday_regions])
|
|
70
|
+
raise Error, "date range contains no days to enter" if workdays.empty?
|
|
71
|
+
|
|
72
|
+
if options[:dry_run]
|
|
73
|
+
print_dry_run(output, workdays, options)
|
|
74
|
+
return 0
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
client ||= Marlens::HarvestApiV2::Client.from_environment
|
|
78
|
+
project_id, task_id = if options[:project_id]
|
|
79
|
+
[options[:project_id], options[:task_id]]
|
|
80
|
+
else
|
|
81
|
+
resolve_assignment(client, options[:project], options[:task])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
workdays.each do |date|
|
|
85
|
+
entry = client.create_time_entry(
|
|
86
|
+
project_id:,
|
|
87
|
+
task_id:,
|
|
88
|
+
spent_date: date,
|
|
89
|
+
hours: options[:hours],
|
|
90
|
+
notes: options[:notes]
|
|
91
|
+
)
|
|
92
|
+
output.puts "Created #{date.iso8601}: #{HarvestWorklog.display_hours(options[:hours])}h (entry ##{entry.fetch("id")})"
|
|
93
|
+
end
|
|
94
|
+
0
|
|
95
|
+
rescue Error, Marlens::HarvestApiV2::Error, OptionParser::ParseError, Date::Error => e
|
|
96
|
+
error.puts "Error: #{e.message}"
|
|
97
|
+
error.puts parser if parser
|
|
98
|
+
1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.option_parser(options)
|
|
102
|
+
OptionParser.new do |opts|
|
|
103
|
+
opts.banner = <<~USAGE
|
|
104
|
+
Usage: harvest-worklog time-off FROM TO --project NAME --task NAME [options]
|
|
105
|
+
harvest-worklog time-off FROM TO --project-id ID --task-id ID [options]
|
|
106
|
+
|
|
107
|
+
Creates one Harvest duration entry for each local business day from FROM through TO.
|
|
108
|
+
USAGE
|
|
109
|
+
opts.on("--project NAME", "Harvest project name") { |value| options[:project] = value }
|
|
110
|
+
opts.on("--task NAME", "Harvest task name") { |value| options[:task] = value }
|
|
111
|
+
opts.on("--project-id ID", Integer, "Harvest project ID") { |value| options[:project_id] = value }
|
|
112
|
+
opts.on("--task-id ID", Integer, "Harvest task ID") { |value| options[:task_id] = value }
|
|
113
|
+
opts.on("--hours HOURS", Float, "Hours per day (default: 7)") { |value| options[:hours] = value }
|
|
114
|
+
opts.on("--notes NOTES", "Optional note on every entry") { |value| options[:notes] = value }
|
|
115
|
+
opts.on("--holiday-region REGION", "Holidays region; repeat for each locality") { |value| options[:holiday_regions] << value.strip.downcase }
|
|
116
|
+
opts.on("--dry-run", "Print entries without calling Harvest") { options[:dry_run] = true }
|
|
117
|
+
opts.on("-h", "--help", "Show this help") do
|
|
118
|
+
puts opts
|
|
119
|
+
exit 0
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.validate!(dates, options)
|
|
125
|
+
raise Error, "FROM and TO are required" unless dates.length == 2
|
|
126
|
+
raise Error, "--hours must be a positive finite number" unless options[:hours].positive? && options[:hours].finite?
|
|
127
|
+
raise Error, "--holiday-region or HARVEST_HOLIDAY_REGIONS is required" if options[:holiday_regions].empty?
|
|
128
|
+
|
|
129
|
+
names_provided = options[:project] || options[:task]
|
|
130
|
+
ids_provided = options[:project_id] || options[:task_id]
|
|
131
|
+
valid_names = options[:project] && options[:task]
|
|
132
|
+
valid_ids = options[:project_id] && options[:task_id]
|
|
133
|
+
raise Error, "supply --project and --task, or --project-id and --task-id" unless valid_names || valid_ids
|
|
134
|
+
raise Error, "do not combine project/task names with IDs" if names_provided && ids_provided
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.holiday_regions_from_environment
|
|
138
|
+
ENV.fetch("HARVEST_HOLIDAY_REGIONS", "ca_yt").split(",").map { |region| region.strip.downcase }.reject(&:empty?)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.resolve_assignment(client, project_name, task_name)
|
|
142
|
+
# ponytail: one 2,000-item page covers personal assignments; follow cursor pagination if that ceiling is exceeded.
|
|
143
|
+
matches = client.active_personal_task_assignments.select do |assignment|
|
|
144
|
+
assignment.dig("project", "name")&.casecmp?(project_name) &&
|
|
145
|
+
assignment.dig("task", "name")&.casecmp?(task_name)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
return [matches.first.dig("project", "id"), matches.first.dig("task", "id")] if matches.one?
|
|
149
|
+
|
|
150
|
+
qualifier = "project #{project_name.inspect} and task #{task_name.inspect}"
|
|
151
|
+
raise Error, "No active task assignment matches #{qualifier}. Pass --project-id and --task-id instead." if matches.empty?
|
|
152
|
+
|
|
153
|
+
raise Error, "Multiple active task assignments match #{qualifier}. Pass --project-id and --task-id instead."
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.print_dry_run(output, dates, options)
|
|
157
|
+
project = options[:project] || "project ##{options[:project_id]}"
|
|
158
|
+
task = options[:task] || "task ##{options[:task_id]}"
|
|
159
|
+
notes = options[:notes] ? "; #{options[:notes]}" : ""
|
|
160
|
+
dates.each do |date|
|
|
161
|
+
output.puts "Would create #{date.iso8601}: #{HarvestWorklog.display_hours(options[:hours])}h on #{project} / #{task}#{notes}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
require "harvest_worklog/work_entry_cli"
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: harvest-worklog
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Marlen Brunner
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: marlens-harvest-api-v2
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: business_time
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.13'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.13'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: holidays
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '9.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '9.2'
|
|
54
|
+
email:
|
|
55
|
+
- klondikemarlen@gmail.com
|
|
56
|
+
executables:
|
|
57
|
+
- harvest-worklog
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- README.md
|
|
62
|
+
- bin/harvest-worklog
|
|
63
|
+
- harvest-worklog.rb
|
|
64
|
+
- lib/harvest_worklog.rb
|
|
65
|
+
- lib/harvest_worklog/version.rb
|
|
66
|
+
- lib/harvest_worklog/work_entry_cli.rb
|
|
67
|
+
homepage: https://github.com/klondikemarlen/harvest-worklog
|
|
68
|
+
licenses:
|
|
69
|
+
- MIT
|
|
70
|
+
metadata:
|
|
71
|
+
source_code_uri: https://github.com/klondikemarlen/harvest-worklog
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '3.2'
|
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubygems_version: 4.0.3
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Harvest work-log CLI and OMP integration.
|
|
89
|
+
test_files: []
|