timet 1.5.9 → 1.6.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 +4 -4
- data/.deepsource.toml +2 -0
- data/CHANGELOG.md +17 -0
- data/README.md +27 -1
- data/lib/timet/application.rb +31 -12
- data/lib/timet/application_helper.rb +20 -11
- data/lib/timet/color_codes.rb +9 -0
- data/lib/timet/discord_notifier.rb +100 -0
- data/lib/timet/tag_distribution.rb +66 -0
- data/lib/timet/time_report.rb +11 -0
- data/lib/timet/version.rb +2 -2
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fb7549f94a072231980f81d1d931c14f98c2b7aa90c9cb22fc81ba361e40af9
|
4
|
+
data.tar.gz: a2c160d0dfc3132427b07aeac3a16310fc5f1c3d36e2a76f4166bd2a0cc153be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd8aa958b7ecdfa53bd674a7ed66f619b5ccf0968ce36c62180026fb391ce9e31d5c1ce37d0c618f45ffd5a006d98735397c524e64ab9b3e1bb786c1e9aebec0
|
7
|
+
data.tar.gz: 54a9bb03b6e435e9120e8e07aaf1b37c5091834f95c36e312185a1eabae1454e6e9526f7a266f67e0693fbd8500d904bb68549e43bd6b40ec0cc53d605e9c8f5
|
data/.deepsource.toml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## [1.6.0] - 2025-09-24
|
2
|
+
|
3
|
+
**New Features:**
|
4
|
+
|
5
|
+
- **Enhanced Summary Report:** Added a new `--report` option to the `summary` command, providing a detailed, color-coded explanation of tag distribution, including percentages, total duration, average duration, and standard deviation.
|
6
|
+
- **Improved Edit Command:** The `edit` command now supports editing the last tracked item by simply running `timet edit` without an ID.
|
7
|
+
- **Resume Pomodoro Option:** The `resume` command now accepts a `--pomodoro=[min]` option to specify a pomodoro duration when resuming a task.
|
8
|
+
- **Bold Text Formatting:** Introduced bold text formatting for improved readability in terminal output.
|
9
|
+
|
10
|
+
**Improvements:**
|
11
|
+
|
12
|
+
- **Robust Session Management:** Refactored `run_linux_session` and `run_mac_session` to use `fork` and `system` calls, enhancing security and robustness by avoiding complex shell escaping.
|
13
|
+
- **Dependency Updates:** Updated `aws-sdk-s3`, `rubocop`, and numerous other gems to their latest versions for better performance and security.
|
14
|
+
- **README Updates:** Added new interactive mode examples for the `edit` command and updated the project homepage URL.
|
15
|
+
- **Timezone Consistency:** Improved time handling in `ValidationEditHelper` specs by consistently using `Time.parse().getlocal()` for better timezone accuracy.
|
16
|
+
|
17
|
+
|
1
18
|
## [1.5.9] - 2025-07-30
|
2
19
|
|
3
20
|
**Improvements:**
|
data/README.md
CHANGED
@@ -35,6 +35,7 @@
|
|
35
35
|
- **Block Time Plot:** Visualizes the distribution of tracked time across a specified range of dates. Each column represents the amount of time tracked during a specific hour, with a header showing the hours and a row for each date displaying the time blocks for each hour.
|
36
36
|
- **Tag Distribution Plot:** Illustrates the proportion of total tracked time allocated to each tag, showing the relative contribution of each tag to the overall time tracked.
|
37
37
|
- **Detailed Statistics:** Displays detailed statistics for each tag, including total duration, average duration, and standard deviation.
|
38
|
+
- **Enhanced Summary Report:** Provides a detailed, color-coded explanation of tag distribution, including percentages, total duration, average duration, and standard deviation, via the `--report` option in the `summary` command.
|
38
39
|
- **iCalendar Export:** Easily export your time tracking data to iCalendar format for integration with calendar applications.
|
39
40
|
- **S3 Cloud Backup:** Seamlessly backup and sync your time tracking data with S3-compatible storage services, providing an additional layer of data protection and accessibility.
|
40
41
|
|
@@ -97,7 +98,7 @@ gem install timet
|
|
97
98
|
|
98
99
|
### Pomodoro Integration
|
99
100
|
|
100
|
-
The `pomodoro` option in the `start` command enhances time tracking by integrating the Pomodoro Technique. Users can specify a Pomodoro session length in minutes, like `pomodoro=25`, to start a 25-minute work interval. The app automatically tracks time and notifies users when the interval ends, helping maintain focus.
|
101
|
+
The `pomodoro` option in the `start` command enhances time tracking by integrating the Pomodoro Technique. Users can specify a Pomodoro session length in minutes, like `pomodoro=25`, to start a 25-minute work interval. The app automatically tracks time and notifies users when the interval ends, helping maintain focus. This notification is now automatically triggered upon the completion of the Pomodoro timer, ensuring timely alerts.
|
101
102
|
|
102
103
|
**Benefits:**
|
103
104
|
|
@@ -105,6 +106,24 @@ gem install timet
|
|
105
106
|
- **Focus:** Encourages disciplined work practices.
|
106
107
|
- **Productivity:** Helps achieve higher productivity and better time management.
|
107
108
|
|
109
|
+
### Discord Integration
|
110
|
+
|
111
|
+
Timet now supports sending notifications to Discord channels upon Pomodoro timer completion. To enable this feature, you need to configure a webhook in your Discord channel and export the webhook URL as an environment variable.
|
112
|
+
|
113
|
+
#### Setup:
|
114
|
+
|
115
|
+
1. **Create a Discord Webhook:** In your Discord server, go to `Server Settings` -> `Integrations` -> `Webhooks` -> `New Webhook`. Copy the generated webhook URL.
|
116
|
+
2. **Export Environment Variable:** Before running `timet`, export the webhook URL as `DISCORD_WEBHOOK_URL`:
|
117
|
+
|
118
|
+
```bash
|
119
|
+
export DISCORD_WEBHOOK_URL="YOUR_DISCORD_WEBHOOK_URL_HERE"
|
120
|
+
```
|
121
|
+
|
122
|
+
Replace `"YOUR_DISCORD_WEBHOOK_URL_HERE"` with the actual webhook URL you copied from Discord.
|
123
|
+
|
124
|
+
Once configured, Timet will automatically send a notification to your Discord channel when a Pomodoro session ends.
|
125
|
+
|
126
|
+
---
|
108
127
|
---
|
109
128
|
|
110
129
|
- **`timet stop`:** Stops tracking the current task, records the elapsed time, and displays the total time spent on all tasks.
|
@@ -150,6 +169,12 @@ gem install timet
|
|
150
169
|
|
151
170
|
**Interactive Mode:**
|
152
171
|
|
172
|
+
```bash
|
173
|
+
timet edit
|
174
|
+
```
|
175
|
+
|
176
|
+
This will edit the last tracked item.
|
177
|
+
|
153
178
|
```bash
|
154
179
|
timet edit 1
|
155
180
|
```
|
@@ -202,6 +227,7 @@ gem install timet
|
|
202
227
|
| `timet summary month (m)` | Display a report of tracked time for the month. | `timet su m` |
|
203
228
|
| `timet su t --csv=[filename]` | Display a report of tracked time for today and export to CSV file | `timet su t --csv=file.csv` |
|
204
229
|
| `timet su w --ics=[filename]` | Display a report of tracked time for week and export to iCalendar file | `timet su w --ics=file.csv` |
|
230
|
+
| `timet su t --report` | Display a detailed report of tag distribution for today. | `timet su t --report` |
|
205
231
|
| `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
|
206
232
|
| `timet cancel` | Cancel active time tracking. | `timet c` |
|
207
233
|
| `timet edit [id]` | Update a task's notes, tag, start, or end fields. | `timet e [id]` |
|
data/lib/timet/application.rb
CHANGED
@@ -9,6 +9,7 @@ require_relative 'application_helper'
|
|
9
9
|
require_relative 'time_helper'
|
10
10
|
require_relative 'version'
|
11
11
|
require_relative 'database_sync_helper'
|
12
|
+
require_relative 'discord_notifier'
|
12
13
|
require 'tempfile'
|
13
14
|
require 'digest'
|
14
15
|
module Timet
|
@@ -89,6 +90,14 @@ module Timet
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
end
|
93
|
+
|
94
|
+
def resume_complete_task(id)
|
95
|
+
item = id ? @db.find_item(id) : @db.last_item
|
96
|
+
tag = item[3]
|
97
|
+
notes = item[4]
|
98
|
+
pomodoro = options[:pomodoro]
|
99
|
+
start(tag, notes, pomodoro)
|
100
|
+
end
|
92
101
|
end
|
93
102
|
|
94
103
|
desc "start [tag] --notes='' --pomodoro=[min]",
|
@@ -129,6 +138,7 @@ module Timet
|
|
129
138
|
return puts 'A task is currently being tracked.' unless VALID_STATUSES_FOR_INSERTION.include?(@db.item_status)
|
130
139
|
|
131
140
|
@db.insert_item(start_time, tag, notes, pomodoro, start_time, start_time)
|
141
|
+
DiscordNotifier.pomodoro_started(pomodoro) if pomodoro.positive? # Notify that a Pomodoro session has started
|
132
142
|
play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
|
133
143
|
summary
|
134
144
|
end
|
@@ -151,12 +161,13 @@ module Timet
|
|
151
161
|
return unless @db.item_status == :in_progress
|
152
162
|
|
153
163
|
last_id = @db.fetch_last_id
|
164
|
+
@db.find_item(last_id) # Fetch the item to get pomodoro duration
|
154
165
|
@db.update_item(last_id, 'end', TimeHelper.current_timestamp)
|
155
|
-
|
156
166
|
summary
|
157
167
|
end
|
158
168
|
|
159
|
-
desc 'resume (r) [id]', 'Resume last task (id is an optional parameter) => tt resume'
|
169
|
+
desc 'resume (r) [id] --pomodoro=[min]', 'Resume last task (id is an optional parameter) => tt resume'
|
170
|
+
option :pomodoro, type: :numeric, desc: 'Pomodoro time in minutes'
|
160
171
|
# Resumes the last tracking session if it was completed.
|
161
172
|
#
|
162
173
|
# @return [void] This method does not return a value; it performs side effects such as resuming a tracking session
|
@@ -188,15 +199,17 @@ module Timet
|
|
188
199
|
end
|
189
200
|
end
|
190
201
|
|
191
|
-
desc 'summary (su) [time_scope] [tag] --csv=csv_filename --ics=ics_filename',
|
202
|
+
desc 'summary (su) [time_scope] [tag] --csv=csv_filename --ics=ics_filename --report',
|
192
203
|
'Display a summary of tracked time and export to CSV.
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
204
|
+
|
205
|
+
Examples:
|
206
|
+
> tt su yesterday [today (t), yesterday (y), week (w), month (m)]
|
207
|
+
> tt su 2024-10-03..2024-10-20
|
208
|
+
> tt su month --csv=myfile
|
209
|
+
> tt su week --csv=mycalendar'
|
198
210
|
option :csv, type: :string, desc: 'Export to CSV'
|
199
211
|
option :ics, type: :string, desc: 'Export to iCalendar'
|
212
|
+
option :report, type: :string, desc: 'Display report'
|
200
213
|
# Generates a summary of tracking items based on the provided time_scope and tag, and optionally exports the summary
|
201
214
|
# to a CSV file and/or an iCalendar file.
|
202
215
|
#
|
@@ -210,12 +223,17 @@ module Timet
|
|
210
223
|
options = build_options(time_scope, tag)
|
211
224
|
report = TimeReport.new(@db, options)
|
212
225
|
display_and_export_report(report, options)
|
226
|
+
return unless options[:report]
|
227
|
+
|
228
|
+
report.print_tag_explanation_report
|
213
229
|
end
|
214
230
|
|
215
231
|
desc 'edit (e) [id] [field] [value]',
|
216
|
-
'Edit task, [field] (notes, tag, start or end) and [value] are optional parameters.
|
217
|
-
|
218
|
-
|
232
|
+
'Edit task, [id] [field] (notes, tag, start or end) and [value] are optional parameters. Examples:
|
233
|
+
|
234
|
+
Update last item => tt edit
|
235
|
+
Update notes => tt edit 12 notes "Update note"
|
236
|
+
Update start time => tt edit 12 start 12:33'
|
219
237
|
# Edits a specific tracking item by its ID, allowing the user to modify fields such as notes, tag, start time, or
|
220
238
|
# end time.
|
221
239
|
#
|
@@ -240,7 +258,8 @@ module Timet
|
|
240
258
|
# a new value.
|
241
259
|
# @note The method then validates and updates the item using `validate_and_update(item, field, new_value)`.
|
242
260
|
# @note Finally, it displays the updated item details using `display_item(updated_item)`.
|
243
|
-
def edit(id, field = nil, new_value = nil)
|
261
|
+
def edit(id = nil, field = nil, new_value = nil)
|
262
|
+
id = @db.fetch_last_id if id.nil?
|
244
263
|
item = @db.find_item(id)
|
245
264
|
return puts "No tracked time found for id: #{id}" unless item
|
246
265
|
|
@@ -111,10 +111,13 @@ module Timet
|
|
111
111
|
# @param tag [String] A tag or label for the session, used in the notification message.
|
112
112
|
# @return [void]
|
113
113
|
def run_linux_session(time, tag)
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
pid = fork do
|
115
|
+
sleep Integer(time)
|
116
|
+
system('tput', 'bel')
|
117
|
+
DiscordNotifier.pomodoro_ended(time / 60) # Notify that a Pomodoro session has ended
|
118
|
+
system('tt', 'stop')
|
119
|
+
system('notify-send', '--icon=clock', show_message(tag))
|
120
|
+
end
|
118
121
|
Process.detach(pid)
|
119
122
|
end
|
120
123
|
|
@@ -124,12 +127,17 @@ module Timet
|
|
124
127
|
# @param _tag [String] A tag or label for the session, not used in the notification message on macOS.
|
125
128
|
# @return [void]
|
126
129
|
def run_mac_session(time, tag)
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
130
|
+
pid = fork do
|
131
|
+
sleep Integer(time)
|
132
|
+
system('afplay', '/System/Library/Sounds/Basso.aiff')
|
133
|
+
DiscordNotifier.pomodoro_ended(time / 60) # Notify that a Pomodoro session has ended
|
134
|
+
system('tt', 'stop')
|
135
|
+
message = show_message(tag)
|
136
|
+
# Escape for AppleScript
|
137
|
+
escaped_message = message.gsub('\\', '\\\\').gsub('"', '\"')
|
138
|
+
applescript_command = "display notification \"#{escaped_message}\""
|
139
|
+
system('osascript', '-e', applescript_command)
|
140
|
+
end
|
133
141
|
Process.detach(pid)
|
134
142
|
end
|
135
143
|
|
@@ -208,7 +216,8 @@ module Timet
|
|
208
216
|
filter: time_scope,
|
209
217
|
tag: tag,
|
210
218
|
csv: csv_filename,
|
211
|
-
ics: ics_filename
|
219
|
+
ics: ics_filename,
|
220
|
+
report: options[:report]
|
212
221
|
}
|
213
222
|
end
|
214
223
|
|
data/lib/timet/color_codes.rb
CHANGED
@@ -6,6 +6,7 @@ module Timet
|
|
6
6
|
RESET = "\u001b[0m"
|
7
7
|
UNDERLINE = "\e[4m"
|
8
8
|
BLINK = "\e[5m"
|
9
|
+
BOLD = "\e[1m"
|
9
10
|
|
10
11
|
def self.reset
|
11
12
|
RESET
|
@@ -19,6 +20,10 @@ module Timet
|
|
19
20
|
BLINK
|
20
21
|
end
|
21
22
|
|
23
|
+
def self.bold
|
24
|
+
BOLD
|
25
|
+
end
|
26
|
+
|
22
27
|
def self.color(num)
|
23
28
|
"\u001b[38;5;#{num}m"
|
24
29
|
end
|
@@ -51,6 +56,10 @@ class String
|
|
51
56
|
"#{Timet::ColorCodes.blink}#{self}#{Timet::ColorCodes.reset}"
|
52
57
|
end
|
53
58
|
|
59
|
+
def bold
|
60
|
+
"#{Timet::ColorCodes.bold}#{self}#{Timet::ColorCodes.reset}"
|
61
|
+
end
|
62
|
+
|
54
63
|
def green
|
55
64
|
"#{Timet::ColorCodes.color(10)}#{self}#{Timet::ColorCodes.reset}"
|
56
65
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Timet
|
6
|
+
# Handles sending notifications to Discord via webhooks for Pomodoro events.
|
7
|
+
#
|
8
|
+
# This class provides methods to send structured messages to a Discord channel
|
9
|
+
# when a Pomodoro session starts, ends, or a break ends. It relies on a
|
10
|
+
# Discord webhook URL configured via the `DISCORD_WEBHOOK_URL` environment variable.
|
11
|
+
class DiscordNotifier
|
12
|
+
DISCORD_WEBHOOK_URL = ENV.fetch('DISCORD_WEBHOOK_URL', nil)
|
13
|
+
|
14
|
+
# Sends a notification to the configured Discord webhook.
|
15
|
+
#
|
16
|
+
# @param message_content [String] The main text content of the message.
|
17
|
+
# @param embed_data [Hash, nil] An optional hash representing a Discord embed object.
|
18
|
+
# See Discord API documentation for embed structure.
|
19
|
+
# @return [void]
|
20
|
+
def self.send_notification(message_content, embed_data = nil)
|
21
|
+
return unless DISCORD_WEBHOOK_URL
|
22
|
+
|
23
|
+
uri = URI.parse(DISCORD_WEBHOOK_URL)
|
24
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
25
|
+
http.use_ssl = true
|
26
|
+
|
27
|
+
request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
|
28
|
+
|
29
|
+
payload = {
|
30
|
+
content: message_content,
|
31
|
+
username: 'Timet Pomodoro',
|
32
|
+
avatar_url: 'https://gravatar.com/avatar/b4921f111e1d481e3f5f35101432bce5'
|
33
|
+
}
|
34
|
+
payload[:embeds] = [embed_data] if embed_data
|
35
|
+
|
36
|
+
request.body = payload.to_json
|
37
|
+
|
38
|
+
response = http.request(request)
|
39
|
+
unless response.is_a?(Net::HTTPSuccess)
|
40
|
+
puts "Failed to send Discord notification: #{response.code} - #{response.body}"
|
41
|
+
end
|
42
|
+
rescue StandardError => e
|
43
|
+
puts "Error sending Discord notification: #{e.message}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sends a notification indicating that a Pomodoro work session has started.
|
47
|
+
#
|
48
|
+
# The notification includes a title, description, color, and fields for duration
|
49
|
+
# and the next scheduled event (short break).
|
50
|
+
#
|
51
|
+
# Sends a notification indicating that a Pomodoro work session has started.
|
52
|
+
#
|
53
|
+
# The notification includes a title, description, color, and fields for duration
|
54
|
+
# and the next scheduled event (short break).
|
55
|
+
#
|
56
|
+
# @param duration [Integer] The duration of the Pomodoro session in minutes.
|
57
|
+
# @return [void]
|
58
|
+
def self.pomodoro_started(duration)
|
59
|
+
embed = {
|
60
|
+
title: 'Pomodoro Session Started! 🍅',
|
61
|
+
description: "Time to focus for #{duration} minutes!",
|
62
|
+
color: 0x00FF00, # Green
|
63
|
+
fields: [
|
64
|
+
{ name: 'Duration', value: "#{duration} minutes", inline: true },
|
65
|
+
{ name: 'Next Up', value: 'Short Break', inline: true }
|
66
|
+
],
|
67
|
+
timestamp: Time.now.utc.iso8601
|
68
|
+
}
|
69
|
+
send_notification('Focus time!', embed)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sends a notification indicating that a Pomodoro work session has ended.
|
73
|
+
#
|
74
|
+
# The notification includes a title, description, color, and fields for duration
|
75
|
+
# and the next scheduled event (work session).
|
76
|
+
#
|
77
|
+
# Sends a notification indicating that a Pomodoro work session has ended.
|
78
|
+
#
|
79
|
+
# The notification includes a title, description, color, and fields for duration
|
80
|
+
# and the next scheduled event (work session).
|
81
|
+
#
|
82
|
+
# @param duration [Integer] The duration of the Pomodoro session in minutes.
|
83
|
+
# @return [void]
|
84
|
+
def self.pomodoro_ended(duration)
|
85
|
+
break_duration = (duration / 5).to_i # Assuming a 1/5th break duration
|
86
|
+
break_duration = 5 if break_duration == 0 # Minimum 5 minute break
|
87
|
+
embed = {
|
88
|
+
title: 'Pomodoro Session Ended! 🎉',
|
89
|
+
description: "Time for a #{break_duration} minute break!",
|
90
|
+
color: 0xFFA500, # Orange
|
91
|
+
fields: [
|
92
|
+
{ name: 'Duration', value: "#{break_duration} minutes", inline: true },
|
93
|
+
{ name: 'Next Up', value: 'Work Session', inline: true }
|
94
|
+
],
|
95
|
+
timestamp: Time.now.utc.iso8601
|
96
|
+
}
|
97
|
+
send_notification('Break time!', embed)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'time_statistics'
|
4
|
+
require_relative 'color_codes' # Ensure color methods are available
|
4
5
|
module Timet
|
5
6
|
# The TagDistribution module provides functionality to format and display the distribution of tags based on their
|
6
7
|
# durations. This is particularly useful for visualizing how time is distributed across different tags in a project
|
@@ -54,6 +55,71 @@ module Timet
|
|
54
55
|
puts 'SD:'.rjust(4).red + 'The standard deviation of the durations'.gray
|
55
56
|
end
|
56
57
|
|
58
|
+
# Generates and prints an explanation of the time report based on tag distribution.
|
59
|
+
#
|
60
|
+
# @param time_stats [Object] An object containing the time statistics.
|
61
|
+
# @param total [Numeric] The total duration of all tags combined in seconds.
|
62
|
+
# @return [void] This method outputs the explanation to the standard output.
|
63
|
+
def print_explanation(time_stats, total)
|
64
|
+
explanations = []
|
65
|
+
high_sd_threshold = 0.5
|
66
|
+
moderate_sd_threshold = 0.2
|
67
|
+
|
68
|
+
# --- Introduction ---
|
69
|
+
total_duration_hours = (total / 3600.0).round(1)
|
70
|
+
explanations << "\n---"
|
71
|
+
explanations << 'Time Report Summary'.bold
|
72
|
+
explanations << "This report provides a detailed breakdown of time spent across various categories, totaling #{"#{total_duration_hours}h".bold} of tracked work.".white
|
73
|
+
explanations << "\n"
|
74
|
+
|
75
|
+
# --- Individual Category Explanations ---
|
76
|
+
explanations << 'Category Breakdown'.bold
|
77
|
+
time_stats.sorted_duration_by_tag.each do |tag, duration|
|
78
|
+
explanation = "#{"#{tag.capitalize}".bold}:"
|
79
|
+
|
80
|
+
# Percentage
|
81
|
+
percentage = (duration.to_f / total * 100).round(1)
|
82
|
+
explanation += " This category consumed #{"#{percentage}%".bold} of the total tracked time."
|
83
|
+
|
84
|
+
# Total Duration
|
85
|
+
total_hours = (duration / 3600.0).round(1)
|
86
|
+
explanation += " The cumulative time spent was #{"#{total_hours}h".bold}, indicating the overall effort dedicated to this area."
|
87
|
+
|
88
|
+
# Average Duration
|
89
|
+
avg_minutes = (time_stats.average_by_tag[tag] / 60.0).round(1)
|
90
|
+
explanation += " On average, each task took #{"#{avg_minutes}min".bold}, which helps in understanding the typical time commitment per task."
|
91
|
+
|
92
|
+
# Standard Deviation
|
93
|
+
sd_minutes = (time_stats.standard_deviation_by_tag[tag] / 60.0).round(1)
|
94
|
+
avg_duration_seconds = time_stats.average_by_tag[tag]
|
95
|
+
|
96
|
+
if sd_minutes > avg_duration_seconds / 60.0 * high_sd_threshold
|
97
|
+
explanation += " A high standard deviation of #{"#{sd_minutes}min".bold} relative to the average suggests significant variability in task durations. This could imply inconsistent task definitions, varying complexity, or frequent interruptions.".red
|
98
|
+
elsif sd_minutes > avg_duration_seconds / 60.0 * moderate_sd_threshold
|
99
|
+
explanation += " A moderate standard deviation of #{"#{sd_minutes}min".bold} indicates some variation in task durations.".blue
|
100
|
+
else
|
101
|
+
explanation += " A low standard deviation of #{"#{sd_minutes}min".bold} suggests that task durations were quite consistent and predictable.".green
|
102
|
+
end
|
103
|
+
|
104
|
+
explanations << explanation.white
|
105
|
+
end
|
106
|
+
|
107
|
+
# --- Overall Summary ---
|
108
|
+
if time_stats.sorted_duration_by_tag.any?
|
109
|
+
sorted_categories = time_stats.sorted_duration_by_tag.map do |tag, duration|
|
110
|
+
[tag, (duration.to_f / total * 100).round(1)]
|
111
|
+
end.sort_by { |_, percentage| -percentage }
|
112
|
+
|
113
|
+
major_categories = sorted_categories.select { |_, percentage| percentage > 10 }
|
114
|
+
if major_categories.size > 1
|
115
|
+
total_percentage = major_categories.sum { |_, percentage| percentage }
|
116
|
+
category_names = major_categories.map { |c, _| "'#{c.capitalize}'" }.join(' and ')
|
117
|
+
explanations << "\nTogether, #{category_names} dominate the time spent, accounting for nearly #{"#{total_percentage.round}%".bold} of the total.".white
|
118
|
+
end
|
119
|
+
end
|
120
|
+
puts explanations.join("\n")
|
121
|
+
end
|
122
|
+
|
57
123
|
# Prints the summary information including total duration, average duration, and standard deviation.
|
58
124
|
#
|
59
125
|
# @param time_stats [Object] An object containing the time statistics, including totals.
|
data/lib/timet/time_report.rb
CHANGED
@@ -94,6 +94,17 @@ module Timet
|
|
94
94
|
tag_distribution(colors)
|
95
95
|
end
|
96
96
|
|
97
|
+
# Prints the tag distribution explanation.
|
98
|
+
# This method is a public wrapper for the private `print_explanation` method
|
99
|
+
# from the `TagDistribution` module.
|
100
|
+
#
|
101
|
+
# @return [void] This method outputs the explanation to the standard output.
|
102
|
+
def print_tag_explanation_report
|
103
|
+
time_stats = TimeStatistics.new(@items)
|
104
|
+
total = time_stats.total_duration
|
105
|
+
print_explanation(time_stats, total) if total.positive?
|
106
|
+
end
|
107
|
+
|
97
108
|
# Displays a single row of the report.
|
98
109
|
#
|
99
110
|
# This method formats and prints a single row of the report, including the table header, the specified row,
|
data/lib/timet/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Vielma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -142,6 +142,7 @@ files:
|
|
142
142
|
- lib/timet/database.rb
|
143
143
|
- lib/timet/database_sync_helper.rb
|
144
144
|
- lib/timet/database_syncer.rb
|
145
|
+
- lib/timet/discord_notifier.rb
|
145
146
|
- lib/timet/item_data_helper.rb
|
146
147
|
- lib/timet/s3_supabase.rb
|
147
148
|
- lib/timet/table.rb
|
@@ -158,11 +159,11 @@ files:
|
|
158
159
|
- lib/timet/version.rb
|
159
160
|
- lib/timet/week_info.rb
|
160
161
|
- sig/timet.rbs
|
161
|
-
homepage: https://
|
162
|
+
homepage: https://frankv.top/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
162
163
|
licenses:
|
163
164
|
- MIT
|
164
165
|
metadata:
|
165
|
-
homepage_uri: https://
|
166
|
+
homepage_uri: https://frankv.top/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
166
167
|
source_code_uri: https://github.com/frankvielma/timet
|
167
168
|
changelog_uri: https://github.com/frankvielma/timet/blob/main/CHANGELOG.md
|
168
169
|
documentation_uri: https://rubydoc.info/gems/timet
|