Skip to content

Commit efa0b60

Browse files
committed
Standardise output for all commands
All commands now output the objects they were operating on, in a user configurable format. So the `fill` command will output the timeslips it created to fill gaps, and the `delete` command will output the timeslips it deleted.
1 parent 3f06421 commit efa0b60

3 files changed

Lines changed: 68 additions & 16 deletions

File tree

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ PATH
44
freeagent-cli (0.2)
55
dotenv (> 2.1.2)
66
oauth2 (~> 2)
7+
snaky_hash (~> 2.0)
78
thor (~> 1.3)
89
webrick (~> 1.9)
910

exe/fa

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require 'dotenv/load'
44
require 'thor'
5+
require 'snaky_hash'
56
require_relative '../lib/freeagent'
67

78
def match type, collection, needle, *values
@@ -34,6 +35,29 @@ module Freeagent
3435
def api
3536
@api ||= API.new
3637
end
38+
39+
def users
40+
@users ||= Hash.new {|cache, url| cache[url] = api.user url.split('/').last }
41+
end
42+
43+
def projects
44+
@projects ||= Hash.new {|cache, url| cache[url] = api.project url.split('/').last }
45+
end
46+
47+
def tasks
48+
@tasks ||= Hash.new {|cache, url| cache[url] = api.task url.split('/').last }
49+
end
50+
51+
def human timeslip
52+
[
53+
Date::parse(timeslip.dated_on).strftime('%a %d %b %Y'),
54+
users[timeslip.user].email,
55+
projects[timeslip.project].name,
56+
tasks[timeslip.task].name,
57+
timeslip.hours,
58+
timeslip.comment
59+
].join("\t")
60+
end
3761
end
3862

3963
desc 'list [-U USER] [-P PROJECT [-T TASK]] [-f FROM] [-t TO]', "List timeslips"
@@ -42,6 +66,7 @@ module Freeagent
4266
method_option :task, aliases: '-T', type: :string, default: nil, desc: 'Name of a task within the project'
4367
method_option :from, aliases: '-f', type: :string, default: nil, desc: 'Date to list timeslips from'
4468
method_option :to, aliases: '-t', type: :string, default: nil, desc: 'Date to list timeslips to'
69+
method_option :format, type: :string, default: 'human', desc: 'Output format, one of human or json'
4570
def list
4671
raise ArgumentError, '--project must be specified if --task if used' if options[:project].nil? && !options[:task].nil?
4772

@@ -51,83 +76,108 @@ module Freeagent
5176
project = options[:project].nil? ? nil : match(:project, api.projects, options[:project], :name)
5277
task = options[:task].nil? ? nil : match(:task, api.tasks(project), options[:task], :name)
5378

54-
users = Hash.new {|cache, url| cache[url] = api.user url.split('/').last }
5579
users[user.url] = user unless user.nil?
56-
projects = Hash.new {|cache, url| cache[url] = api.project url.split('/').last }
5780
projects[project.url] = project unless project.nil?
58-
tasks = Hash.new {|cache, url| cache[url] = api.task url.split('/').last }
5981
tasks[task.url] = task unless task.nil?
6082

6183
api.timeslips(user: user, project: project, task: task, from: from, to: to).each do |timeslip|
62-
puts [
63-
Date::parse(timeslip.dated_on).strftime('%a %d %b %Y'),
64-
users[timeslip.user].email,
65-
projects[timeslip.project].name,
66-
tasks[timeslip.task].name,
67-
timeslip.hours,
68-
timeslip.comment
69-
].join("\t")
84+
puts case options[:format].downcase
85+
when 'human'; human(timeslip)
86+
when 'json'; timeslip.to_json
87+
else raise ArgumentError, "unknown output format #{options[:format]}"
88+
end
7089
end
7190
end
7291

7392
desc 'create USER PROJECT [TASK [FROM [TO]]]', "Create timeslips"
7493
method_option :hours, aliases: '-h', type: :numeric, default: 8, desc: "Number of hours per day"
7594
method_option :weekends, aliases: '-w', type: :boolean, default: false, desc: "Create timeslips on Saturdays and Sundays"
7695
method_option :comment, aliases: '-c', type: :string, default: nil, desc: "Comment to add to any created timeslips"
96+
method_option :format, type: :string, default: 'human', desc: 'Output format, one of human or json'
7797
def create user, project, task = nil, from = nil, to = nil
7898
from = from.nil? ? Date::today : Date::parse(from)
7999
to = to.nil? ? from : Date::parse(to)
80100
user = match :user, api.users, user, :first_name, :last_name, :email
81101
project = match :project, api.projects, project, :name
82102
task = select_task project, task
83103

84-
puts "Creating timeslips for #{user.first_name} #{user.last_name} for task '#{task.name}' in project '#{project.name}' between #{from} and #{to} inclusive"
104+
users[user.url] = user unless user.nil?
105+
projects[project.url] = project unless project.nil?
106+
tasks[task.url] = task unless task.nil?
107+
85108
timeslips = from.step(to).map do |date|
86109
next if (date.saturday? || date.sunday?) && !options[:weekends]
87-
{'task' => task.url, 'project' => project.url, 'user' => user.url, 'dated_on' => date.to_s, 'hours' => options[:hours], 'comment' => options[:comment]}
110+
SnakyHash::StringKeyed.new(task: task.url, project: project.url, user: user.url, dated_on: date.to_s, hours: options[:hours], comment: options[:comment])
88111
end.reject(&:nil?)
89112
api.batch_create_timeslips timeslips
113+
timeslips.each do |timeslip|
114+
puts case options[:format].downcase
115+
when 'human'; human(timeslip)
116+
when 'json'; timeslip.to_json
117+
else raise ArgumentError, "unknown output format #{options[:format]}"
118+
end
119+
end
90120
end
91121

92122
desc 'fill USER PROJECT [TASK [FROM [TO]]]', 'Fill remaining hours with new timeslips'
93123
method_option :hours, aliases: '-h', type: :numeric, default: 8, desc: "Number of hours per day"
94124
method_option :weekends, aliases: '-w', type: :boolean, default: false, desc: "Create timeslips on Saturdays and Sundays"
95125
method_option :comment, aliases: '-c', type: :string, default: nil, desc: "Comment to add to any created timeslips"
126+
method_option :format, type: :string, default: 'human', desc: 'Output format, one of human or json'
96127
def fill user, project, task = nil, from = nil, to = nil
97128
from = from.nil? ? Date::today : Date::parse(from)
98129
to = to.nil? ? from : Date::parse(to)
99130
user = match :user, api.users, user, :first_name, :last_name, :email
100131
project = match :project, api.projects, project, :name
101132
task = select_task project, task
102133

134+
users[user.url] = user unless user.nil?
135+
projects[project.url] = project unless project.nil?
136+
tasks[task.url] = task unless task.nil?
137+
103138
existing = api.timeslips(user: user, from: from, to: to).reduce(Hash.new) do |hash, timeslip|
104139
date = Date::parse timeslip.dated_on
105140
hash.update({date => (hash[date] || 0) + timeslip.hours.to_f})
106141
end
107142

108-
puts "Filling timeslips for #{user.first_name} #{user.last_name} up to #{options[:hours]}hrs using task '#{task.name}' in project '#{project.name}' between #{from} and #{to} inclusive"
109143
timeslips = from.step(to).map do |date|
110144
next if (date.saturday? || date.sunday?) && !options[:weekends]
111145
existing_hours = existing[date] || 0
112146
adding_hours = options[:hours] - existing_hours
113147
next if adding_hours <= 0
114148

115-
{'task' => task.url, 'project' => project.url, 'user' => user.url, 'dated_on' => date.to_s, 'hours' => adding_hours.to_s, 'comment' => options[:comment]}
149+
SnakyHash::StringKeyed.new(task: task.url, project: project.url, user: user.url, dated_on: date.to_s, hours: adding_hours.to_s, comment: options[:comment])
116150
end.reject(&:nil?)
117151
api.batch_create_timeslips timeslips
152+
timeslips.each do |timeslip|
153+
puts case options[:format].downcase
154+
when 'human'; human(timeslip)
155+
when 'json'; timeslip.to_json
156+
else raise ArgumentError, "unknown output format #{options[:format]}"
157+
end
158+
end
118159
end
119160

120161
desc 'delete USER PROJECT TASK [FROM [TO]]', 'Delete timeslips'
162+
method_option :format, type: :string, default: 'human', desc: 'Output format, one of human or json'
121163
def delete user, project, task, from = nil, to = nil
122164
from = from.nil? ? Date::today : Date::parse(from)
123165
to = to.nil? ? from : Date::parse(to)
124166
user = match :user, api.users, user, :first_name, :last_name, :email
125167
project = match :project, api.projects, project, :name
126168
task = match :task, api.tasks(project), task, :name
127169

128-
puts "Deleting timeslips for #{user.first_name} #{user.last_name} for task '#{task.name}' in project '#{project.name}' between #{from} and #{to} inclusive"
170+
users[user.url] = user unless user.nil?
171+
projects[project.url] = project unless project.nil?
172+
tasks[task.url] = task unless task.nil?
173+
129174
api.timeslips(user: user, project: project, task: task, from: from, to: to).each do |timeslip|
130175
api.delete_timeslip(timeslip)
176+
puts case options[:format].downcase
177+
when 'human'; human(timeslip)
178+
when 'json'; timeslip.to_json
179+
else raise ArgumentError, "unknown output format #{options[:format]}"
180+
end
131181
end
132182
end
133183
end

freeagent-cli.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Gem::Specification.new do |spec|
4848
spec.add_dependency 'webrick', '~> 1.9'
4949
spec.add_dependency 'thor', '~> 1.3'
5050
spec.add_dependency 'dotenv', '> 2.1.2'
51+
spec.add_dependency 'snaky_hash', '~> 2.0'
5152

5253
spec.add_development_dependency 'rake', '~> 13'
5354

0 commit comments

Comments
 (0)