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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e5079b49daa5deed838350e66235ce5301518e928a478f405423f268e431390
4
- data.tar.gz: e7ef91dd990022437605bdfe406387e1499b9d08de5f6c233211332d9cb5ea0d
3
+ metadata.gz: 5fb7549f94a072231980f81d1d931c14f98c2b7aa90c9cb22fc81ba361e40af9
4
+ data.tar.gz: a2c160d0dfc3132427b07aeac3a16310fc5f1c3d36e2a76f4166bd2a0cc153be
5
5
  SHA512:
6
- metadata.gz: a6f4a1db83a82e0a0e53787a1b8847b7ceaa2694e0e4fbe318271bfc00a33c078d525d832f3be0b136d11c9b4a580ff3648ef9a1064e8921be6d7e79cb59c910
7
- data.tar.gz: 7cd3c8513a161e3864501cec96317b833bdda14d937779b97d0feb680a894284f34d7427ba1197ad8ca0a721ede075b13b549489e5beaaa1cbfb751fa71f927f
6
+ metadata.gz: fd8aa958b7ecdfa53bd674a7ed66f619b5ccf0968ce36c62180026fb391ce9e31d5c1ce37d0c618f45ffd5a006d98735397c524e64ab9b3e1bb786c1e9aebec0
7
+ data.tar.gz: 54a9bb03b6e435e9120e8e07aaf1b37c5091834f95c36e312185a1eabae1454e6e9526f7a266f67e0693fbd8500d904bb68549e43bd6b40ec0cc53d605e9c8f5
data/.deepsource.toml CHANGED
@@ -11,3 +11,5 @@ enabled = true
11
11
  [coverage]
12
12
  reporter = "simplecov"
13
13
  paths = ["coverage/.resultset.json"]
14
+
15
+ ignore_issues = ["RB-RL1039", "RB-RL1058", "RB-E1003", "RB-RL1054"]
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]` |
@@ -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
- [time_scope] => [today (t), yesterday (y), week (w), month (m). => tt su yesterday
194
- [start_date]..[end_date]] => tt su 2024-10-03..2024-10-20
195
- [tag] => tt su Task1
196
- --csv=csv_filename => tt su month --csv=myfile
197
- --ics=ics_filename => tt su week --csv=mycalendar'
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
- Update notes => tt edit 12 notes "Update note"
218
- Update start time => tt edit 12 start 12:33'
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
- escaped_message = Shellwords.shellescape(show_message(tag))
115
- notification_command = "notify-send --icon=clock #{escaped_message}"
116
- command = "sleep #{time} && tput bel && tt stop 0 && #{notification_command} &"
117
- pid = Kernel.spawn(command)
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
- # Escape double quotes and backslashes for AppleScript, then shell-escape the entire AppleScript command
128
- escaped_message_for_applescript = show_message(tag).gsub('\\', '\\\\').gsub('"', '\"')
129
- escaped_applescript_command = Shellwords.shellescape("display notification \"#{escaped_message_for_applescript}\"")
130
- notification_command = "osascript -e #{escaped_applescript_command}"
131
- command = "sleep #{time} && afplay /System/Library/Sounds/Basso.aiff && tt stop 0 && #{notification_command} &"
132
- pid = Kernel.spawn(command)
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
 
@@ -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.
@@ -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
@@ -6,6 +6,6 @@ module Timet
6
6
  # @return [String] The version number in the format 'major.minor.patch'.
7
7
  #
8
8
  # @example Get the version of the Timet application
9
- # Timet::VERSION # => '1.5.9'
10
- VERSION = '1.5.9'
9
+ # Timet::VERSION # => '1.6.0'
10
+ VERSION = '1.6.0'
11
11
  end
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.5.9
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-07-30 00:00:00.000000000 Z
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://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
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://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
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