fewald-worklog 0 → 0.1.1

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.
data/lib/worklog.rb DELETED
@@ -1,216 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'optparse'
5
- require 'rainbow'
6
- require 'yaml'
7
-
8
- require 'hash'
9
- require 'daily_log'
10
- require 'date_parser'
11
- require 'log_entry'
12
- require 'storage'
13
- require 'worklogger'
14
- require 'string_helper'
15
- require 'printer'
16
- require 'statistics'
17
-
18
- # Main class providing all worklog functionality.
19
- # This class is the main entry point for the application.
20
- # It handles command line arguments, configuration, and logging.
21
- class Worklog
22
- include StringHelper
23
- attr_reader :config
24
-
25
- def initialize(config = nil)
26
- @config = config || Configuration.new
27
- @storage = Storage.new(@config)
28
-
29
- WorkLogger.level = @config.log_level == :debug ? Logger::Severity::DEBUG : Logger::Severity::INFO
30
- end
31
-
32
- def add(message, options = {})
33
- # Remove leading and trailing whitespaces
34
- # Raise an error if the message is empty
35
- message = message.strip
36
- raise ArgumentError, 'Message cannot be empty' if message.empty?
37
-
38
- date = Date.strptime(options[:date], '%Y-%m-%d')
39
- time = Time.strptime(options[:time], '%H:%M:%S')
40
- @storage.create_file_skeleton(date)
41
-
42
- daily_log = @storage.load_log!(@storage.filepath(date))
43
- daily_log.entries << LogEntry.new(time:, tags: options[:tags], ticket: options[:ticket], url: options[:url],
44
- epic: options[:epic], message:)
45
-
46
- # Sort by time in case an entry was added later out of order.
47
- daily_log.entries.sort_by!(&:time)
48
-
49
- @storage.write_log(@storage.filepath(options[:date]), daily_log)
50
-
51
- WorkLogger.info Rainbow("Added entry on #{options[:date]}: #{message}").green
52
- end
53
-
54
- def edit(options = {})
55
- date = Date.strptime(options[:date], '%Y-%m-%d')
56
-
57
- # Load existing log
58
- log = @storage.load_log(@storage.filepath(date))
59
- unless log
60
- WorkLogger.error "No work log found for #{options[:date]}. Aborting."
61
- exit 1
62
- end
63
-
64
- txt = Editor::EDITOR_PREAMBLE.result_with_hash(content: YAML.dump(log))
65
- return_val = Editor.open_editor(txt)
66
-
67
- @storage.write_log(@storage.filepath(date),
68
- YAML.load(return_val, permitted_classes: [Date, Time, DailyLog, LogEntry]))
69
- WorkLogger.info Rainbow("Updated work log for #{options[:date]}").green
70
- end
71
-
72
- def show(options = {})
73
- people = @storage.load_people!
74
- printer = Printer.new(people)
75
-
76
- start_date, end_date = start_end_date(options)
77
-
78
- entries = @storage.days_between(start_date, end_date)
79
- if entries.empty?
80
- printer.no_entries(start_date, end_date)
81
- else
82
- entries.each do |entry|
83
- printer.print_day(entry, entries.size > 1, options[:epics_only])
84
- end
85
- end
86
- end
87
-
88
- def people(person = nil, _options = {})
89
- all_people = @storage.load_people!
90
- people_map = all_people.to_h { |p| [p.handle, p] }
91
- all_logs = @storage.all_days
92
-
93
- if person
94
- unless people_map.key?(person)
95
- WorkLogger.error Rainbow("No person found with handle #{person}.").red
96
- return
97
- end
98
- person_detail(all_logs, all_people, people_map[person.strip])
99
- else
100
- puts 'People mentioned in the work log:'
101
-
102
- mentions = {}
103
-
104
- all_logs.map(&:people).each do |people|
105
- mentions.merge!(people) { |_key, oldval, newval| oldval + newval }
106
- end
107
-
108
- # Sort the mentions by handle
109
- mentions = mentions.to_a.sort_by { |handle, _| handle }
110
-
111
- mentions.each do |handle, v|
112
- if people_map.key?(handle)
113
- print "#{Rainbow(people_map[handle].name).gold} (#{handle})"
114
- else
115
- print handle
116
- end
117
- puts ": #{v} #{pluralize(v, 'occurrence')}"
118
- end
119
- end
120
- end
121
-
122
- def person_detail(all_logs, all_people, person)
123
- printer = Printer.new(all_people)
124
- puts "All interactions with #{Rainbow(person.name).gold}"
125
-
126
- if person.notes
127
- puts 'Notes:'
128
- person.notes.each do |note|
129
- puts "* #{note}"
130
- end
131
- end
132
-
133
- puts 'Interactions:'
134
- all_logs.each do |daily_log|
135
- daily_log.entries.each do |entry|
136
- printer.print_entry(daily_log, entry, true) if entry.people.include?(person.handle)
137
- end
138
- end
139
- end
140
-
141
- def tags(_options = {})
142
- all_logs = @storage.all_days
143
-
144
- puts Rainbow('Tags used in the work log:').gold
145
-
146
- # Count all tags used in the work log
147
- tags = all_logs.map(&:entries).flatten.map(&:tags).flatten.compact.tally
148
-
149
- # Determine length of longest tag for formatting
150
- # Add one additonal space for formatting
151
- max_len = tags.empty? ? 0 : tags.keys.map(&:length).max + 1
152
-
153
- tags.sort.each { |k, v| puts "#{Rainbow(k.ljust(max_len)).gold}: #{v} #{pluralize(v, 'occurrence')}" }
154
- end
155
-
156
- def stats(_options = {})
157
- stats = Statistics.new(@config).calculate
158
- puts "#{format_left('Total days')}: #{stats.total_days}"
159
- puts "#{format_left('Total entries')}: #{stats.total_entries}"
160
- puts "#{format_left('Total epics')}: #{stats.total_epics}"
161
- puts "#{format_left('Entries per day')}: #{format('%.2f', stats.avg_entries)}"
162
- puts "#{format_left('First entry')}: #{stats.first_entry}"
163
- puts "#{format_left('Last entry')}: #{stats.last_entry}"
164
- end
165
-
166
- def summary(options = {})
167
- start_date, end_date = start_end_date(options)
168
- entries = @storage.days_between(start_date, end_date).map(&:entries).flatten
169
-
170
- # Do nothing if no entries are found.
171
- if entries.empty?
172
- Printer.new.no_entries(start_date, end_date)
173
- return
174
- end
175
- puts Summary.new(@config).generate_summary(entries)
176
- end
177
-
178
- def remove(options = {})
179
- date = Date.strptime(options[:date], '%Y-%m-%d')
180
- unless File.exist?(@storage.filepath(date))
181
- WorkLogger.error Rainbow("No work log found for #{options[:date]}. Aborting.").red
182
- exit 1
183
- end
184
-
185
- daily_log = @storage.load_log!(@storage.filepath(options[:date]))
186
- if daily_log.entries.empty?
187
- WorkLogger.error Rainbow("No entries found for #{options[:date]}. Aborting.").red
188
- exit 1
189
- end
190
-
191
- removed_entry = daily_log.entries.pop
192
- @storage.write_log(@storage.filepath(date), daily_log)
193
- WorkLogger.info Rainbow("Removed entry: #{removed_entry.message}").green
194
- end
195
-
196
- # Parse the start and end date based on the options provided
197
- #
198
- # @param options [Hash] the options hash
199
- # @return [Array] the start and end date as an array
200
- def start_end_date(options)
201
- if options[:days]
202
- # Safeguard against negative days
203
- raise ArgumentError, 'Number of days cannot be negative' if options[:days].negative?
204
-
205
- start_date = Date.today - options[:days]
206
- end_date = Date.today
207
- elsif options[:from]
208
- start_date = DateParser.parse_date_string!(options[:from], true)
209
- end_date = DateParser.parse_date_string!(options[:to], false) if options[:to]
210
- else
211
- start_date = Date.strptime(options[:date], '%Y-%m-%d')
212
- end_date = start_date
213
- end
214
- [start_date, end_date]
215
- end
216
- end
File without changes
File without changes
File without changes
File without changes
File without changes