timet 1.5.8 → 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 +15 -0
- data/CHANGELOG.md +32 -0
- data/README.md +28 -4
- data/lib/timet/application.rb +32 -14
- data/lib/timet/application_helper.rb +22 -7
- 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/time_validation_helper.rb +3 -2
- data/lib/timet/version.rb +2 -2
- metadata +6 -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
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
version = 1
|
2
|
+
|
3
|
+
[[analyzers]]
|
4
|
+
name = "ruby"
|
5
|
+
enabled = true
|
6
|
+
|
7
|
+
[[analyzers]]
|
8
|
+
name = "test-coverage"
|
9
|
+
enabled = true
|
10
|
+
|
11
|
+
[coverage]
|
12
|
+
reporter = "simplecov"
|
13
|
+
paths = ["coverage/.resultset.json"]
|
14
|
+
|
15
|
+
ignore_issues = ["RB-RL1039", "RB-RL1058", "RB-E1003", "RB-RL1054"]
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,35 @@
|
|
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
|
+
|
18
|
+
## [1.5.9] - 2025-07-30
|
19
|
+
|
20
|
+
**Improvements:**
|
21
|
+
|
22
|
+
- Replaced Code Climate with DeepSource for static analysis and test coverage reporting.
|
23
|
+
- Updated the CI workflow by removing Code Climate steps and adding a .deepsource.toml configuration file.
|
24
|
+
- Updated gem dependencies, including rubocop, thor, and various aws-sdk gems.
|
25
|
+
- Refactored application.rb to use abort for more idiomatic error handling on invalid arguments.
|
26
|
+
- Updated the README to replace the old Code Climate badges with a new DeepSource badge.
|
27
|
+
|
28
|
+
**Bug fixes:**
|
29
|
+
|
30
|
+
- Fixed a shell injection vulnerability in application_helper.rb by properly escaping notification messages using Shellwords.shellescape.
|
31
|
+
- Corrected a timezone-related issue in time_validation_helper.rb by using Time.now.getlocal for future date validation.
|
32
|
+
|
1
33
|
## [1.5.8] - 2025-06-28
|
2
34
|
|
3
35
|
**Improvements:**
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
[](https://badge.fury.io/rb/timet)
|
2
2
|

|
3
|
-
[](https://codeclimate.com/github/frankvielma/timet/test_coverage)
|
5
|
-
|
3
|
+
[](https://app.deepsource.com/gh/frankvielma/timet/)
|
6
4
|
# Timet
|
7
5
|
|
8
6
|

|
@@ -37,6 +35,7 @@
|
|
37
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.
|
38
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.
|
39
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.
|
40
39
|
- **iCalendar Export:** Easily export your time tracking data to iCalendar format for integration with calendar applications.
|
41
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.
|
42
41
|
|
@@ -99,7 +98,7 @@ gem install timet
|
|
99
98
|
|
100
99
|
### Pomodoro Integration
|
101
100
|
|
102
|
-
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.
|
103
102
|
|
104
103
|
**Benefits:**
|
105
104
|
|
@@ -107,6 +106,24 @@ gem install timet
|
|
107
106
|
- **Focus:** Encourages disciplined work practices.
|
108
107
|
- **Productivity:** Helps achieve higher productivity and better time management.
|
109
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
|
+
---
|
110
127
|
---
|
111
128
|
|
112
129
|
- **`timet stop`:** Stops tracking the current task, records the elapsed time, and displays the total time spent on all tasks.
|
@@ -152,6 +169,12 @@ gem install timet
|
|
152
169
|
|
153
170
|
**Interactive Mode:**
|
154
171
|
|
172
|
+
```bash
|
173
|
+
timet edit
|
174
|
+
```
|
175
|
+
|
176
|
+
This will edit the last tracked item.
|
177
|
+
|
155
178
|
```bash
|
156
179
|
timet edit 1
|
157
180
|
```
|
@@ -204,6 +227,7 @@ gem install timet
|
|
204
227
|
| `timet summary month (m)` | Display a report of tracked time for the month. | `timet su m` |
|
205
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` |
|
206
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` |
|
207
231
|
| `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
|
208
232
|
| `timet cancel` | Cancel active time tracking. | `timet c` |
|
209
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
|
@@ -85,11 +86,18 @@ module Timet
|
|
85
86
|
if VALID_ARGUMENTS.include?(command_name)
|
86
87
|
@db = Database.new
|
87
88
|
else
|
88
|
-
|
89
|
-
exit(1)
|
89
|
+
abort 'Invalid arguments provided. Please check your input.'
|
90
90
|
end
|
91
91
|
end
|
92
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
|
93
101
|
end
|
94
102
|
|
95
103
|
desc "start [tag] --notes='' --pomodoro=[min]",
|
@@ -130,6 +138,7 @@ module Timet
|
|
130
138
|
return puts 'A task is currently being tracked.' unless VALID_STATUSES_FOR_INSERTION.include?(@db.item_status)
|
131
139
|
|
132
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
|
133
142
|
play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
|
134
143
|
summary
|
135
144
|
end
|
@@ -152,12 +161,13 @@ module Timet
|
|
152
161
|
return unless @db.item_status == :in_progress
|
153
162
|
|
154
163
|
last_id = @db.fetch_last_id
|
164
|
+
@db.find_item(last_id) # Fetch the item to get pomodoro duration
|
155
165
|
@db.update_item(last_id, 'end', TimeHelper.current_timestamp)
|
156
|
-
|
157
166
|
summary
|
158
167
|
end
|
159
168
|
|
160
|
-
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'
|
161
171
|
# Resumes the last tracking session if it was completed.
|
162
172
|
#
|
163
173
|
# @return [void] This method does not return a value; it performs side effects such as resuming a tracking session
|
@@ -189,15 +199,17 @@ module Timet
|
|
189
199
|
end
|
190
200
|
end
|
191
201
|
|
192
|
-
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',
|
193
203
|
'Display a summary of tracked time and export to CSV.
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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'
|
199
210
|
option :csv, type: :string, desc: 'Export to CSV'
|
200
211
|
option :ics, type: :string, desc: 'Export to iCalendar'
|
212
|
+
option :report, type: :string, desc: 'Display report'
|
201
213
|
# Generates a summary of tracking items based on the provided time_scope and tag, and optionally exports the summary
|
202
214
|
# to a CSV file and/or an iCalendar file.
|
203
215
|
#
|
@@ -211,12 +223,17 @@ module Timet
|
|
211
223
|
options = build_options(time_scope, tag)
|
212
224
|
report = TimeReport.new(@db, options)
|
213
225
|
display_and_export_report(report, options)
|
226
|
+
return unless options[:report]
|
227
|
+
|
228
|
+
report.print_tag_explanation_report
|
214
229
|
end
|
215
230
|
|
216
231
|
desc 'edit (e) [id] [field] [value]',
|
217
|
-
'Edit task, [field] (notes, tag, start or end) and [value] are optional parameters.
|
218
|
-
|
219
|
-
|
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'
|
220
237
|
# Edits a specific tracking item by its ID, allowing the user to modify fields such as notes, tag, start time, or
|
221
238
|
# end time.
|
222
239
|
#
|
@@ -241,7 +258,8 @@ module Timet
|
|
241
258
|
# a new value.
|
242
259
|
# @note The method then validates and updates the item using `validate_and_update(item, field, new_value)`.
|
243
260
|
# @note Finally, it displays the updated item details using `display_item(updated_item)`.
|
244
|
-
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?
|
245
263
|
item = @db.find_item(id)
|
246
264
|
return puts "No tracked time found for id: #{id}" unless item
|
247
265
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'shellwords'
|
4
|
+
|
3
5
|
module Timet
|
4
6
|
# Provides helper methods for the Timet application.
|
5
7
|
module ApplicationHelper
|
@@ -109,9 +111,13 @@ module Timet
|
|
109
111
|
# @param tag [String] A tag or label for the session, used in the notification message.
|
110
112
|
# @return [void]
|
111
113
|
def run_linux_session(time, tag)
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
115
121
|
Process.detach(pid)
|
116
122
|
end
|
117
123
|
|
@@ -121,9 +127,17 @@ module Timet
|
|
121
127
|
# @param _tag [String] A tag or label for the session, not used in the notification message on macOS.
|
122
128
|
# @return [void]
|
123
129
|
def run_mac_session(time, tag)
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
127
141
|
Process.detach(pid)
|
128
142
|
end
|
129
143
|
|
@@ -202,7 +216,8 @@ module Timet
|
|
202
216
|
filter: time_scope,
|
203
217
|
tag: tag,
|
204
218
|
csv: csv_filename,
|
205
|
-
ics: ics_filename
|
219
|
+
ics: ics_filename,
|
220
|
+
report: options[:report]
|
206
221
|
}
|
207
222
|
end
|
208
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,
|
@@ -103,9 +103,10 @@ module Timet
|
|
103
103
|
#
|
104
104
|
# @raise [ArgumentError] If the new datetime is in the future.
|
105
105
|
def validate_future_date(new_datetime)
|
106
|
-
|
106
|
+
# Ensure the new datetime is not in the future relative to the current time.
|
107
|
+
return unless new_datetime > Time.now.getlocal
|
107
108
|
|
108
|
-
raise ArgumentError, "Cannot set time to a future date: #{new_datetime.strftime('%Y-%m-%d %H:%M:%S')}"
|
109
|
+
raise ArgumentError, "Cannot set time to a future date or time: #{new_datetime.strftime('%Y-%m-%d %H:%M:%S')}"
|
109
110
|
end
|
110
111
|
|
111
112
|
# Validates that the difference between two timestamps is less than 24 hours.
|
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
|
@@ -123,6 +123,7 @@ executables:
|
|
123
123
|
extensions: []
|
124
124
|
extra_rdoc_files: []
|
125
125
|
files:
|
126
|
+
- ".deepsource.toml"
|
126
127
|
- ".reek.yml"
|
127
128
|
- ".rspec"
|
128
129
|
- ".rubocop.yml"
|
@@ -141,6 +142,7 @@ files:
|
|
141
142
|
- lib/timet/database.rb
|
142
143
|
- lib/timet/database_sync_helper.rb
|
143
144
|
- lib/timet/database_syncer.rb
|
145
|
+
- lib/timet/discord_notifier.rb
|
144
146
|
- lib/timet/item_data_helper.rb
|
145
147
|
- lib/timet/s3_supabase.rb
|
146
148
|
- lib/timet/table.rb
|
@@ -157,11 +159,11 @@ files:
|
|
157
159
|
- lib/timet/version.rb
|
158
160
|
- lib/timet/week_info.rb
|
159
161
|
- sig/timet.rbs
|
160
|
-
homepage: https://
|
162
|
+
homepage: https://frankv.top/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
161
163
|
licenses:
|
162
164
|
- MIT
|
163
165
|
metadata:
|
164
|
-
homepage_uri: https://
|
166
|
+
homepage_uri: https://frankv.top/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
165
167
|
source_code_uri: https://github.com/frankvielma/timet
|
166
168
|
changelog_uri: https://github.com/frankvielma/timet/blob/main/CHANGELOG.md
|
167
169
|
documentation_uri: https://rubydoc.info/gems/timet
|