timet 1.4.0 → 1.4.2

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: bec76070c29c2eab4a2a5792084a9980c1cd29c0ab3ac80dbae2c6310e74d78a
4
- data.tar.gz: 551fe37b00ac0ca015e28fcdd37810216c0ff00ea4b800c97bd0fb539af1df7b
3
+ metadata.gz: 9c57d8e7c61f25526dda2e7a5d1208814cadbc93192b7c60c342399c83f0b545
4
+ data.tar.gz: 625f4ba71e547e2ebb483d93f1de3812a8b075cacc7afaff7c80554267e5bcc1
5
5
  SHA512:
6
- metadata.gz: 290e56a65e8c9e163d1558efe87fb93c8d74522c9aa1410f77a21ea6fe81d4a8270ec03947deb959699940c1c40ff91b4749f9512f1de4e005ac822cb564934a
7
- data.tar.gz: 0e27f41ad71bca972355c70c235d900ae0c4ec571c47f9fd7324b979b3036b02f4cd6dd5aaa0464d4dd5ac12f2989ae38aed5e15613c496eb182dacb92f3bf11
6
+ metadata.gz: 99b2d1783c14024620865d826d11e16c42146ce220a50ae066cf762231129c42d683d88e4bb50086a5743cc923cdacd1d88f6fd4a4af4f5b5934017bfbb7b476
7
+ data.tar.gz: 7d3cb54c375f6fe2b86f0ffedb290d31c5d2348ec877a0848fc9c20d47c90422db6573a5ab0265ef4c0d8f6f89465dda7916f2f6dae59c2296511761b2f63705
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.4.2] - 2024-11-01
4
+
5
+ **Improvements:**
6
+
7
+ - Refactored the `table` method in `lib/timet/table.rb` to simplify its return value and remove unnecessary complexity.
8
+ - Updated the `process_time_entries` and `process_time_block_item` methods to streamline the processing of time entries and time block items.
9
+ - Integrated the `TimeStatistics` class into the `TagDistribution` module to calculate and display detailed statistics for each tag.
10
+ - Modified the `tag_distribution` method to use the `TimeStatistics` class for generating tag distribution information.
11
+ - Updated the `display` method in `lib/timet/time_report.rb` to use the refactored methods and integrate the new tag distribution logic.
12
+ - Added the `descriptive_statistics` gem to the Gemfile to support statistical calculations.
13
+ - Created a new `TimeStatistics` class in `lib/timet/time_statistics.rb` to analyze and summarize time duration data associated with various tags.
14
+ - Implemented methods in `TimeStatistics` to calculate total duration by tag, sorted duration by tag, average duration by tag, standard deviation by tag, and additional descriptive statistics by tag.
15
+ - Updated Gemfile.lock to reflect the new dependency.
16
+ - Update README.md
17
+
18
+ **Tasks:**
19
+
20
+ - Refactor time tracking report methods and integrate `TimeStatistics`.
21
+ - Add `descriptive_statistics` gem and create `TimeStatistics` class.
22
+ - Fix typo in `README.md`.
23
+
24
+ **Bug fixes:**
25
+
26
+ - Fixed a typo in `README.md`.
27
+
28
+ ## [1.4.1] - 2024-10-31
29
+
30
+ **Improvements:**
31
+
32
+ - Refactor `resume` method to accept an optional `id` parameter for resuming a specific tracking item.
33
+ - Renamed `last_item_status` method to `item_status` in the `Database` class for better clarity and flexibility.
34
+ - Updated `Application` class methods (`start`, `stop`, `resume`, `cancel`) to use the new `item_status` method.
35
+ - Introduced `determine_status` method within the `Database` class to encapsulate status determination logic.
36
+ - Updated documentation and test cases to reflect the changes and ensure consistent status handling.
37
+ - Bumped version to reflect the changes.
38
+ - Updated README.md to include new features and improvements.
39
+
40
+ **Tasks:**
41
+
42
+ - Refactor `resume` method.
43
+ - Bump version.
44
+ - Update README.md.
45
+ - Refactor `Database` class for improved status determination and encapsulation.
46
+
3
47
  ## [1.4.0] - 2024-10-29
4
48
 
5
49
  **Improvements:**
data/README.md CHANGED
@@ -7,23 +7,23 @@
7
7
 
8
8
  ![Timet](timet.webp)
9
9
 
10
- Timet refers to a command-line tool designed to track your activities by recording the time spent on each task, allowing you to monitor your work hours and productivity directly from your terminal without needing a graphical interface; essentially, it's a way to log your time spent on different projects or tasks using simple text commands
10
+ [Timet](https://rubygems.org/gems/timet) is a command-line tool designed to track your activities by recording the time spent on each task. This allows you to monitor your work hours and productivity directly from your terminal without needing a graphical interface. Essentially, it's a way to log your time spent on different projects or tasks using simple text commands.
11
11
 
12
12
  🔑 **Key Features:**
13
13
 
14
- - **Local Data Storage:** Timet utilizes SQLite to store your time tracking data locally, ensuring privacy and security.
14
+ - **Local Data Storage:** Timet uses SQLite to store your time tracking data locally, ensuring privacy and security.
15
15
  - **Lightweight and Fast:** Its efficient design and local data storage make Timet a speedy and responsive tool.
16
16
  - **Structured Data:** SQLite ensures your data is organized and easily accessible.
17
17
  - **Scalability:** Timet can handle growing time tracking needs.
18
18
  - **Data Integrity:** SQLite maintains the accuracy and consistency of your data.
19
19
  - **Querying and Reporting:** Generate detailed reports for specific periods.
20
20
  - **CSV Export:** Easily export your time tracking data to CSV format for further analysis or sharing.
21
- - **Pomodoro Integration:** The pomodoro option in the start command enhances time tracking by integrating the Pomodoro Technique.
22
- - **Block Time Plot:** Visualizes the distribution of tracked time across a specified range of dates, with bars in each column representing the amount of time tracked during that specific hour. The plot includes a header showing the hours and a row for each date, displaying the time blocks for each hour.
21
+ - **Pomodoro Integration:** The `pomodoro` option in the `start` command enhances time tracking by integrating the Pomodoro Technique.
22
+ - **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.
23
23
  - **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.
24
+ - **Detailed Statistics:** Displays detailed statistics for each tag, including total duration, average duration, and standard deviation.
24
25
 
25
-
26
- Examples:
26
+ **Examples:**
27
27
 
28
28
  ![Timet1 demo](timet1.gif)
29
29
 
@@ -32,10 +32,9 @@ Examples:
32
32
  - Ruby version: >= 3.0.0
33
33
  - sqlite3: > 1.7
34
34
 
35
- Old versions of Ruby and Sqlite:
35
+ For older versions of Ruby and Sqlite:
36
36
 
37
37
  - [Ruby >= 2.7](https://github.com/frankvielma/timet/tree/ruby-2.7.0)
38
-
39
38
  - [Ruby >= 2.4](https://github.com/frankvielma/timet/tree/ruby-2.4.0)
40
39
 
41
40
  ## 💾 Installation
@@ -54,7 +53,8 @@ gem install timet
54
53
  - `tt`: An alias for the `timet` command, providing a shorter alternative.
55
54
 
56
55
  ---
57
- - **timet start [tag] --notes="" --pomodoro=[minutes]**: Starts tracking time for a task labeled with the provided [tag], notes and "pomodoro time" in minutes (optional). Example:
56
+
57
+ - **`timet start [tag] --notes="" --pomodoro=[minutes]`:** Starts tracking time for a task labeled with the provided [tag], notes, and "pomodoro time" in minutes (optional). Example:
58
58
 
59
59
  ```bash
60
60
  timet start task1 --notes="Meeting with client" --pomodoro=25
@@ -81,15 +81,15 @@ gem install timet
81
81
 
82
82
  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.
83
83
 
84
- **Benefits**
84
+ **Benefits:**
85
85
 
86
- - **Flexibility**: Supports various productivity strategies.
87
- - **Focus**: Encourages disciplined work practices.
88
- - **Productivity**: Helps achieve higher productivity and better time management.
86
+ - **Flexibility:** Supports various productivity strategies.
87
+ - **Focus:** Encourages disciplined work practices.
88
+ - **Productivity:** Helps achieve higher productivity and better time management.
89
89
 
90
90
  ---
91
91
 
92
- - **timet stop**: Stops tracking the current task, records the elapsed time, and displays the total time spent on all tasks.
92
+ - **`timet stop`:** Stops tracking the current task, records the elapsed time, and displays the total time spent on all tasks.
93
93
 
94
94
  ```bash
95
95
  timet stop
@@ -105,8 +105,14 @@ gem install timet
105
105
  | Total: | 01:00:00 | |
106
106
  +-------+------------+--------+----------+----------+----------+--------------------------+
107
107
  ```
108
+
108
109
  ---
109
- - **timet resume**: It allows users to quickly resume tracking a task that was previously in progress.
110
+
111
+ - **`timet resume [id]`:** This command allows users to quickly resume tracking a task that was previously in progress. If an ID is provided, it resumes the tracking session for the specified task. If no ID is provided, it resumes the last completed task.
112
+
113
+ ```bash
114
+ timet resume 1
115
+ ```
110
116
 
111
117
  ```
112
118
  Tracked time report [today]:
@@ -119,10 +125,12 @@ gem install timet
119
125
  | Total: | 01:00:00 | |
120
126
  +-------+------------+--------+----------+----------+----------+--------------------------+
121
127
  ```
128
+
122
129
  ---
123
- - **timet edit**: It allows users to update a task's notes, tag, start, or end fields. Users can either interactively select the field and provide a new value or specify them directly in the command.
124
130
 
125
- - **Interactive Mode:**
131
+ - **`timet edit`:** Allows users to update a task's notes, tag, start, or end fields. Users can either interactively select the field and provide a new value or specify them directly in the command.
132
+
133
+ **Interactive Mode:**
126
134
 
127
135
  ```bash
128
136
  timet edit 1
@@ -145,7 +153,7 @@ gem install timet
145
153
  End
146
154
  ```
147
155
 
148
- - **Direct Specification Mode:**
156
+ **Direct Specification Mode:**
149
157
 
150
158
  ```bash
151
159
  timet e 1 notes "New Meeting Notes"
@@ -163,23 +171,23 @@ gem install timet
163
171
  +-------+------------+--------+----------+----------+----------+--------------------------+
164
172
  ```
165
173
 
166
- ## 📋 Command Reference
174
+ ## 📋 Command Reference
167
175
 
168
- | Command | Description | Example Usage |
169
- | ----------------------------------- | --------------------------------------------------------------------------- | --------------------------------- |
170
- | `timet start [tag] --notes='' --pomodoro=[time]` | Start tracking time for a task labeled [tag] and notes (optional). | `timet start Task "My notes" 25` |
171
- | `timet stop` | Stop tracking time. | `timet start Task "My notes"` |
172
- | `timet summary today (t)` | Display a report of tracked time for today. | `timet su t` or `timet su` |
173
- | `timet summary yesterday (y)` | Display a report of tracked time for yesterday. | `timet su y` |
174
- | `timet summary week (w)` | Display a report of tracked time for the week. | `timet su w` |
175
- | `timet summary month (m)` | Resume tracking the last month. | `timet su m` |
176
- | `timet su t --csv=[filename]` | Display a report of tracked time for today and export it to `filename.csv`. | `timet su t --csv=file.csv` |
177
- | `timet summary resume (r)` | Resume tracking the last task. | `timet su r` |
178
- | `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
179
- | `timet cancel` | Cancel active time tracking. | `timet c` |
180
- | `timet edit [id]` | Update a task's notes, tag, start or end fields. | `timet e [1]` |
181
- | `timet su [date]` | Display a report of tracked time for a specific date. | `timet su 2024-01-03` |
182
- | `timet su [start_date]..[end_date]` | Display a report of tracked time for a date range. | `timet su 2024-01-02..2024-01-03` |
176
+ | Command | Description | Example Usage |
177
+ | -------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------- |
178
+ | `timet start [tag] --notes='' --pomodoro=[time]` | Start tracking time for a task labeled [tag] and notes (optional). | `timet start Task "My notes" 25` |
179
+ | `timet stop` | Stop tracking time. | `timet stop` |
180
+ | `timet summary today (t)` | Display a report of tracked time for today. | `timet su t` or `timet su` |
181
+ | `timet summary yesterday (y)` | Display a report of tracked time for yesterday. | `timet su y` |
182
+ | `timet summary week (w)` | Display a report of tracked time for the week. | `timet su w` |
183
+ | `timet summary month (m)` | Display a report of tracked time for the month. | `timet su m` |
184
+ | `timet su t --csv=[filename]` | Display a report of tracked time for today and export it to `filename.csv`. | `timet su t --csv=file.csv` |
185
+ | `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
186
+ | `timet cancel` | Cancel active time tracking. | `timet c` |
187
+ | `timet edit [id]` | Update a task's notes, tag, start, or end fields. | `timet e [id]` |
188
+ | `timet su [date]` | Display a report of tracked time for a specific date. | `timet su 2024-01-03` |
189
+ | `timet su [start_date]..[end_date]` | Display a report of tracked time for a date range. | `timet su 2024-01-02..2024-01-03` |
190
+ | `timet resume (r) [id]` | Resume tracking a task by ID or the last completed task. | `timet resume [id]` |
183
191
 
184
192
  ### Date Range in Summary
185
193
 
@@ -187,20 +195,21 @@ The `timet summary` command now supports specifying a date range for generating
187
195
 
188
196
  #### Examples:
189
197
 
190
- - **Single Date**: Display a report for a specific date.
198
+ - **Single Date:** Display a report for a specific date.
191
199
 
192
200
  ```sh
193
201
  timet su 2024-01-03
194
202
  ```
195
203
 
196
- - **Date Range**: Display a report for a date range.
204
+ - **Date Range:** Display a report for a date range.
205
+
197
206
  ```sh
198
207
  timet su 2024-01-02..2024-01-03
199
208
  ```
200
209
 
201
210
  ## Data
202
211
 
203
- Timet's data is stored in ~/.timet.db
212
+ Timet's data is stored in `~/.timet.db`.
204
213
 
205
214
  ## Development
206
215
 
@@ -210,13 +219,14 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
210
219
 
211
220
  ## Contributing
212
221
 
213
- Bug reports and pull requests are welcome on GitHub at https://github.com/frankvielma/timet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/frankvielma/timet/blob/master/CODE_OF_CONDUCT.md).
222
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/frankvielma/timet](https://github.com/frankvielma/timet). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/frankvielma/timet/blob/master/CODE_OF_CONDUCT.md).
214
223
 
215
224
  ## Buy Me A Coffee! ☕
216
225
 
217
226
  Many people have contacted me asking how to contribute. Any contribution, from a virtual coffee to a kind word, is greatly appreciated and helps me continue my work. Please only donate if you're able, as there are no refunds. Your support is entirely voluntary, and I thank you for your consideration.
218
227
 
219
228
  **Bitcoin Address:**
229
+
220
230
  ```sh
221
231
  bc1qkg9me2jsuhpzu2hp9kkpxagwtf9ewnyfl4kszl
222
232
  ```
@@ -231,4 +241,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
231
241
 
232
242
  ## Code of Conduct
233
243
 
234
- Everyone interacting in the Timet project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/frankvielma/timet/blob/master/CODE_OF_CONDUCT.md).
244
+ Everyone interacting in the Timet project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/frankvielma/timet/blob/master/CODE_OF_CONDUCT.md).
@@ -69,9 +69,7 @@ module Timet
69
69
  notes = options[:notes] || notes
70
70
  pomodoro = (options[:pomodoro] || pomodoro).to_i
71
71
 
72
- unless VALID_STATUSES_FOR_INSERTION.include?(@db.last_item_status)
73
- return puts 'A task is currently being tracked.'
74
- end
72
+ return puts 'A task is currently being tracked.' unless VALID_STATUSES_FOR_INSERTION.include?(@db.item_status)
75
73
 
76
74
  @db.insert_item(start_time, tag, notes, pomodoro)
77
75
  play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
@@ -87,13 +85,13 @@ module Timet
87
85
  # @example Stop the current tracking session
88
86
  # stop
89
87
  #
90
- # @note The method checks if the last tracking item is in progress by calling `@db.last_item_status`.
88
+ # @note The method checks if the last tracking item is in progress by calling `@db.item_status`.
91
89
  # @note If the last item is in progress, it fetches the last item's ID using `@db.fetch_last_id` and updates it
92
90
  # with the current timestamp.
93
91
  # @note The method then fetches the last item using `@db.last_item` and generates a summary if the result
94
92
  # is not nil.
95
93
  def stop(display = nil)
96
- return unless @db.last_item_status == :in_progress
94
+ return unless @db.item_status == :in_progress
97
95
 
98
96
  last_id = @db.fetch_last_id
99
97
  @db.update_item(last_id, 'end', TimeHelper.current_timestamp)
@@ -101,7 +99,7 @@ module Timet
101
99
  summary unless display
102
100
  end
103
101
 
104
- desc 'resume (r)', 'resume last task'
102
+ desc 'resume (r) [id]', 'resume last task'
105
103
  # Resumes the last tracking session if it was completed.
106
104
  #
107
105
  # @return [void] This method does not return a value; it performs side effects such as resuming a tracking session
@@ -110,23 +108,26 @@ module Timet
110
108
  # @example Resume the last tracking session
111
109
  # resume
112
110
  #
113
- # @note The method checks the status of the last tracking item using `@db.last_item_status`.
111
+ # @example Resume a specific task by ID
112
+ # resume(123)
113
+ #
114
+ # @note The method checks the status of the last tracking item using `@db.item_status`.
114
115
  # @note If the last item is in progress, it prints a message indicating that a task is currently being tracked.
115
- # @note If the last item is complete, it fetches the last item using `@db.last_item`, retrieves the tag and notes,
116
+ # @note If the last item is complete, it fetches the last item using `@db.find_item` or `@db.last_item`,
117
+ # retrieves the tag and notes,
116
118
  # and calls the `start` method to resume the tracking session.
117
- def resume
118
- status = @db.last_item_status
119
-
120
- case status
119
+ #
120
+ # @param id [Integer, nil] The ID of the tracking item to resume. If nil, the last item is used.
121
+ #
122
+ # @see Database#item_status
123
+ # @see Database#find_item
124
+ # @see #start
125
+ def resume(id = nil)
126
+ case @db.item_status(id)
121
127
  when :in_progress
122
128
  puts 'A task is currently being tracked.'
123
129
  when :complete
124
- last_item = @db.last_item
125
- if last_item
126
- tag = last_item[FIELD_INDEX['tag']]
127
- notes = last_item[FIELD_INDEX['notes']]
128
- start(tag, notes)
129
- end
130
+ resume_complete_task(id)
130
131
  end
131
132
  end
132
133
 
@@ -247,13 +248,13 @@ module Timet
247
248
  # cancel
248
249
  #
249
250
  # @note The method fetches the ID of the last tracking item using `@db.fetch_last_id`.
250
- # @note It checks if the last item is in progress by comparing `@db.last_item_status` with `:complete`.
251
+ # @note It checks if the last item is in progress by comparing `@db.item_status` with `:complete`.
251
252
  # @note If the last item is in progress, it deletes the item and prints a confirmation message using
252
253
  # `delete_item_and_print_message(id, "Canceled active time tracking #{id}")`.
253
254
  # @note If there is no active time tracking, it prints a message indicating that there is no active time tracking.
254
255
  def cancel
255
256
  id = @db.fetch_last_id
256
- return puts 'There is no active time tracking' if @db.last_item_status == :complete
257
+ return puts 'There is no active time tracking' if @db.item_status == :complete
257
258
 
258
259
  delete_item_and_print_message(id, "Canceled active time tracking #{id}")
259
260
  end
@@ -157,5 +157,32 @@ module Timet
157
157
  @db.delete_item(id)
158
158
  puts message
159
159
  end
160
+
161
+ # Resumes a tracking session for a completed task.
162
+ #
163
+ # @param id [Integer, nil] The ID of the tracking item to resume. If nil, the last completed item is used.
164
+ #
165
+ # @return [void] This method does not return a value; it performs side effects such as resuming a tracking session.
166
+ #
167
+ # @example Resume the last completed task
168
+ # resume_complete_task
169
+ #
170
+ # @example Resume a specific task by ID
171
+ # resume_complete_task(123)
172
+ #
173
+ # @note The method fetches the specified or last completed item using `@db.find_item` or `@db.last_item`.
174
+ # @note If the item is found, it retrieves the tag and notes and calls the `start` method to resume the
175
+ # tracking session.
176
+ #
177
+ # @see Database#find_item
178
+ # @see Database#last_item
179
+ # @see #start
180
+ def resume_complete_task(id)
181
+ item = id ? @db.find_item(id) : @db.last_item
182
+ return unless item
183
+
184
+ tag, notes = item.values_at(Application::FIELD_INDEX['tag'], Application::FIELD_INDEX['notes'])
185
+ start(tag, notes)
186
+ end
160
187
  end
161
188
  end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sqlite3'
4
- require_relative 'status_helper'
5
4
  module Timet
6
5
  # Provides database access for managing time tracking data.
7
6
  class Database
8
- include StatusHelper
9
-
10
7
  # The default path to the SQLite database file.
11
8
  DEFAULT_DATABASE_PATH = File.join(Dir.home, '.timet.db')
12
9
 
@@ -158,12 +155,15 @@ module Timet
158
155
  # @return [Symbol] The status of the last item. Possible values are :no_items, :in_progress, or :complete.
159
156
  #
160
157
  # @example Determine the status of the last item
161
- # last_item_status
158
+ # item_status
162
159
  #
163
160
  # @note The method executes SQL to fetch the last item and determines its status using the `StatusHelper` module.
164
- def last_item_status
165
- result = execute_sql('SELECT id, end FROM items ORDER BY id DESC LIMIT 1')
166
- StatusHelper.determine_status(result)
161
+ #
162
+ # @param id [Integer, nil] The ID of the item to check. If nil, the last item in the table is used.
163
+ #
164
+ def item_status(id = nil)
165
+ id = fetch_last_id if id.nil?
166
+ determine_status(find_item(id))
167
167
  end
168
168
 
169
169
  # Finds an item in the items table by its ID.
@@ -241,5 +241,33 @@ module Timet
241
241
 
242
242
  format '%<hours>02d:%<minutes>02d:%<seconds>02d', hours: hours, minutes: minutes, seconds: seconds
243
243
  end
244
+
245
+ # Determines the status of a time tracking result based on the presence and end time of items.
246
+ #
247
+ # @param result [Array] The result set containing time tracking items.
248
+ #
249
+ # @return [Symbol] The status of the time tracking result. Possible values are
250
+ # :no_items, :in_progress, or :complete.
251
+ #
252
+ # @example Determine the status of an empty result set
253
+ # StatusHelper.determine_status([]) # => :no_items
254
+ #
255
+ # @example Determine the status of a result set with an in-progress item
256
+ # StatusHelper.determine_status([[1, nil]]) # => :in_progress
257
+ #
258
+ # @example Determine the status of a result set with a completed item
259
+ # StatusHelper.determine_status([[1, 1633072800]]) # => :complete
260
+ #
261
+ # @note The method checks if the result set is empty and returns :no_items if true.
262
+ # @note If the last item in the result set has no end time, it returns :in_progress.
263
+ # @note If the last item in the result set has an end time, it returns :complete.
264
+ def determine_status(result)
265
+ return :no_items if result.nil?
266
+
267
+ last_item_end = result[2]
268
+ return :in_progress unless last_item_end
269
+
270
+ :complete
271
+ end
244
272
  end
245
273
  end
data/lib/timet/table.rb CHANGED
@@ -9,12 +9,12 @@ module Timet
9
9
  # @example
10
10
  # table
11
11
  #
12
- # @return [Array<(String, Hash)>] An array containing the time block string and a hash of durations by tag.
12
+ # @return [String] The time block string.
13
13
  #
14
14
  # @note
15
15
  # - The method relies on the `header`, `process_time_entries`, `separator`, and `total` methods.
16
16
  # - The `header` method is responsible for printing the table header.
17
- # - The `process_time_entries` method processes the time entries and returns the time block and duration by tag.
17
+ # - The `process_time_entries` method processes the time entries and returns the time block.
18
18
  # - The `separator` method returns a string representing the separator line.
19
19
  # - The `total` method prints the total duration.
20
20
  #
@@ -24,10 +24,10 @@ module Timet
24
24
  # @see #total
25
25
  def table
26
26
  header
27
- time_block, duration_by_tag = process_time_entries
27
+ time_block = process_time_entries
28
28
  puts separator
29
29
  total
30
- [time_block, duration_by_tag]
30
+ time_block
31
31
  end
32
32
 
33
33
  # Formats the header of the time tracking report table.
@@ -62,73 +62,53 @@ module Timet
62
62
  '+-------+------------+--------+----------+----------+----------+--------------------+'
63
63
  end
64
64
 
65
- # Processes each time entry in the `items` array and updates the time block and duration by tag.
65
+ # Processes time entries and generates a time block structure.
66
66
  #
67
- # @return [Array<(Hash, Hash)>] An array containing the updated time block and duration by tag.
68
- #
69
- # @example
70
- # items = [
71
- # [start_time1, end_time1, tag1],
72
- # [start_time2, end_time2, tag2]
73
- # ]
74
- # process_time_entries
75
- # #=> [{ '2024-10-21' => { 8 => [duration1, tag1], 9 => [duration2, tag2] } },
76
- # { tag1 => total_duration1, tag2 => total_duration2 }]
67
+ # @return [Hash] A nested hash representing the time block structure.
77
68
  #
78
69
  # @note
79
- # - The method relies on the `items` instance variable, which should be an array of arrays.
80
- # - Each sub-array in `items` is expected to contain a start time, end time, and a tag.
81
- # - The `display_time_entry` method is used to display each time entry.
82
- # - The `process_time_block_item` method processes each time entry and updates the time block and duration by tag.
70
+ # - The method iterates over each item in the `items` array.
71
+ # - For each item, it calls `display_time_entry` to display the time entry.
72
+ # - It then processes the time block item using `process_time_block_item`.
73
+ # - The `TimeHelper.extract_date` method is used to extract the date from the items.
83
74
  #
84
- # @see #items
85
75
  # @see #display_time_entry
86
76
  # @see #process_time_block_item
77
+ # @see TimeHelper#extract_date
87
78
  def process_time_entries
88
- duration_by_tag = Hash.new(0)
89
79
  time_block = Hash.new { |hash, key| hash[key] = {} }
90
80
 
91
81
  items.each_with_index do |item, idx|
92
82
  display_time_entry(item, TimeHelper.extract_date(items, idx))
93
- time_block, duration_by_tag = process_time_block_item(item, time_block, duration_by_tag)
83
+ time_block = process_time_block_item(item, time_block)
94
84
  end
95
- [time_block, duration_by_tag]
85
+
86
+ time_block
96
87
  end
97
88
 
98
- # Processes a time block item and updates the time block hash.
89
+ # Processes a single time block item and updates the time block structure.
99
90
  #
100
- # @param item [Array] The time entry to process, containing the start time, end time, and tag.
101
- # @param time_block [Hash] A hash containing time block data, where keys are dates and values are hashes of time
102
- # slots and their corresponding values.
103
- # @param duration_by_tag [Hash] A hash containing the total duration by tag.
91
+ # @param item [Array] An array containing the item details, including start time, end time, and tag.
92
+ # @param time_block [Hash] The current time block structure.
104
93
  #
105
- # @return [Array<(Hash, Hash)>] An array containing the updated time block hash and the updated duration
106
- # by tag hash.
107
- #
108
- # @example
109
- # item = [nil, Time.new(2024, 10, 21, 8, 0, 0), Time.new(2024, 10, 21, 9, 0, 0), 'work']
110
- # time_block = {}
111
- # duration_by_tag = {}
112
- # process_time_block_item(item, time_block, duration_by_tag)
113
- # #=> [{ '2024-10-21' => { 8 => [3600, 'work'] } }, { 'work' => 3600 }]
94
+ # @return [Hash] The updated time block structure.
114
95
  #
115
96
  # @note
116
- # - The method relies on the `TimeHelper` module for time-related calculations.
117
- # - The `add_hashes` method is used to merge the new time block data into the existing time block hash.
118
- # - The `calculate_duration` method calculates the duration between the start and end times.
97
+ # - The method extracts the start time, end time, and tag from the item.
98
+ # - It calculates the number of seconds per hour block using `TimeHelper.count_seconds_per_hour_block`.
99
+ # - It converts the start time to a date using `TimeHelper.timestamp_to_date`.
100
+ # - It updates the time block structure by adding the new block hour to the existing structure.
119
101
  #
120
102
  # @see TimeHelper#count_seconds_per_hour_block
121
103
  # @see TimeHelper#timestamp_to_date
122
- # @see TimeHelper#calculate_duration
123
104
  # @see #add_hashes
124
- def process_time_block_item(item, time_block, duration_by_tag)
105
+ def process_time_block_item(item, time_block)
125
106
  _, start_time, end_time, tag = item
126
107
 
127
108
  block_hour = TimeHelper.count_seconds_per_hour_block(start_time, end_time, tag)
128
109
  date_line = TimeHelper.timestamp_to_date(start_time)
129
110
  time_block[date_line] = add_hashes(time_block[date_line], block_hour)
130
- duration_by_tag[tag] += TimeHelper.calculate_duration(start_time, end_time)
131
- [time_block, duration_by_tag]
111
+ time_block
132
112
  end
133
113
 
134
114
  # Displays a single time entry in the report.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'time_statistics'
3
4
  module Timet
4
5
  # The TagDistribution module provides functionality to format and display the distribution of tags based on their
5
6
  # durations. This is particularly useful for visualizing how time is distributed across different tags in a project
@@ -12,20 +13,23 @@ module Timet
12
13
  # Formats and displays the tag distribution.
13
14
  #
14
15
  # @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
16
+ # @param colors [Hash<String, String>] A hash where keys are tags and values are color codes for display.
15
17
  # @return [void] This method outputs the formatted tag distribution to the console.
16
18
  #
17
19
  # @example
18
20
  # duration_by_tag = { "timet" => 3600, "nextjs" => 1800 }
19
- # Formatter.format_tag_distribution(duration_by_tag)
21
+ # colors = { "timet" => "\e[31m", "nextjs" => "\e[32m" }
22
+ # Formatter.format_tag_distribution(duration_by_tag, colors)
20
23
  # # Output:
21
- # # timet: 66.67% ====================
22
- # # nextjs: 33.33% ==========
23
- def tag_distribution(duration_by_tag, colors)
24
- total = duration_by_tag.values.sum
24
+ # # \e[31m timet: 66.67% ==================== \e[0m
25
+ # # \e[32m nextjs: 33.33% ========== \e[0m
26
+ def tag_distribution(colors)
27
+ time_stats = TimeStatistics.new(@items)
28
+ total = time_stats.total_duration
29
+
25
30
  return unless total.positive?
26
31
 
27
- sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
28
- process_and_print_tags(sorted_duration_by_tag, total, colors)
32
+ process_and_print_tags(time_stats, total, colors)
29
33
  end
30
34
 
31
35
  # Processes and prints the tag distribution information.
@@ -34,15 +38,50 @@ module Timet
34
38
  # tag and its corresponding duration, sorted by duration in descending order.
35
39
  # @param total [Numeric] The total duration of all tags combined.
36
40
  # @return [void] This method outputs the tag distribution information to the standard output.
37
- def process_and_print_tags(sorted_duration_by_tag, total, colors)
38
- sorted_duration_by_tag.each do |tag, duration|
39
- value, bar_length = calculate_value_and_bar_length(duration, total)
40
- horizontal_bar = (BLOCK_CHAR * bar_length).to_s.color(colors[tag] + 1)
41
- tag = tag[0..TAG_SIZE - 1] if tag.size >= TAG_SIZE
42
- puts "#{tag.rjust(TAG_SIZE)}: #{value.to_s.rjust(5)}% #{horizontal_bar}"
41
+ def process_and_print_tags(time_stats, total, colors)
42
+ time_stats.sorted_duration_by_tag.each do |tag, duration|
43
+ print_tag_info(tag, duration, total, time_stats, colors)
43
44
  end
44
45
  end
45
46
 
47
+ # Prints the detailed information for a specific tag.
48
+ #
49
+ # @param tag [String] The tag for which to print the information.
50
+ # @param duration [Numeric] The duration associated with the tag.
51
+ # @param total [Numeric] The total duration of all tags combined.
52
+ # @param time_stats [Object] An object containing time statistics for the tags.
53
+ # @param colors [Hash] A hash mapping tags to color indices for display.
54
+ # @return [void] This method outputs the tag information to the standard output.
55
+ def print_tag_info(tag, duration, total, time_stats, colors)
56
+ value, bar_length = calculate_value_and_bar_length(duration, total)
57
+ horizontal_bar = generate_horizontal_bar(bar_length, colors[tag])
58
+ formatted_tag = tag[0...TAG_SIZE].rjust(TAG_SIZE)
59
+ stats = generate_stats(tag, time_stats)
60
+
61
+ puts "#{formatted_tag}: #{value.to_s.rjust(5)}% #{horizontal_bar} [#{stats}]"
62
+ end
63
+
64
+ # Generates a horizontal bar for display based on the bar length and color index.
65
+ #
66
+ # @param bar_length [Numeric] The length of the bar to generate.
67
+ # @param color_index [Numeric] The color index to use for the bar.
68
+ # @return [String] The generated horizontal bar string.
69
+ def generate_horizontal_bar(bar_length, color_index)
70
+ (BLOCK_CHAR * bar_length).to_s.color(color_index + 1)
71
+ end
72
+
73
+ # Generates the statistics string for a given tag.
74
+ #
75
+ # @param tag [String] The tag for which to generate the statistics.
76
+ # @param time_stats [Object] An object containing time statistics for the tags.
77
+ # @return [String] The generated statistics string.
78
+ def generate_stats(tag, time_stats)
79
+ total_hours = (time_stats.total_duration_by_tag[tag] / 3600.0).round(1)
80
+ avg_minutes = (time_stats.average_by_tag[tag] / 60.0).round(1)
81
+ sd_minutes = (time_stats.standard_deviation_by_tag[tag] / 60).round(1)
82
+ "T: #{total_hours}h, AVG: #{avg_minutes}min SD: #{sd_minutes}min".gray
83
+ end
84
+
46
85
  # Calculates the percentage value and bar length for a given duration and total duration.
47
86
  #
48
87
  # @param duration [Numeric] The duration for the current tag.
@@ -53,7 +92,7 @@ module Timet
53
92
  # calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
54
93
  def calculate_value_and_bar_length(duration, total)
55
94
  value = duration.to_f / total
56
- percentage_value = (duration.to_f / total * 100).round(2)
95
+ percentage_value = (duration.to_f / total * 100).round(1)
57
96
  bar_length = (value * MAX_BAR_LENGTH).round
58
97
  [percentage_value, bar_length]
59
98
  end
@@ -54,15 +54,22 @@ module Timet
54
54
  # time_report.display
55
55
  #
56
56
  # @note The method formats and prints the table header, rows, and total duration.
57
+ #
58
+ # @param items [Array<Hash>] The list of time entries to be displayed.
59
+ # @param options [Hash] Additional options for customizing the display (e.g., color scheme).
60
+ #
61
+ # @see #table
62
+ # @see #print_time_block_chart
63
+ # @see #tag_distribution
57
64
  def display
58
- return puts 'No tracked time found for the specified filter.' if items.empty?
65
+ return puts 'No tracked time found for the specified filter.' if @items.empty?
59
66
 
60
- time_block, duration_by_tag = table
67
+ time_block = table
61
68
 
62
- colors = duration_by_tag.map { |x| x[0] }.sort.each_with_index.to_h
69
+ colors = @items.map { |x| x[3] }.uniq.each_with_index.to_h
63
70
  print_time_block_chart(time_block, colors)
64
71
 
65
- tag_distribution(duration_by_tag, colors)
72
+ tag_distribution(colors)
66
73
  end
67
74
 
68
75
  # Displays a single row of the report.
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'descriptive_statistics'
4
+ require_relative 'time_helper'
5
+
6
+ module Timet
7
+ # @!attribute [r] duration_by_tag
8
+ # @return [Hash] A hash where keys are tags and values are arrays of durations (in seconds) associated
9
+ # with each tag.
10
+ # @!attribute [r] total_duration
11
+ # @return [Integer] The total duration (in seconds) of all time intervals across all tags.
12
+ class TimeStatistics
13
+ attr_reader :duration_by_tag, :total_duration
14
+
15
+ # Initializes a new instance of TimeStatistics.
16
+ #
17
+ # @param data [Array<Array>] An array of arrays where each sub-array contains:
18
+ # - [0] An identifier (not used in calculations)
19
+ # - [1] The start time (in seconds since the epoch)
20
+ # - [2] The end time (in seconds since the epoch), or nil if the interval is ongoing
21
+ # - [3] The tag associated with the time interval
22
+ # @return [TimeStatistics] A new instance of TimeStatistics.
23
+ def initialize(data)
24
+ @data = data
25
+ @duration_by_tag = Hash.new { |hash, key| hash[key] = [] }
26
+ @total_duration = 0
27
+ calculate_durations_by_tag
28
+ end
29
+
30
+ # Calculates the duration for each tag and updates the @duration_by_tag and @total_duration attributes.
31
+ #
32
+ # @return [void]
33
+ def calculate_durations_by_tag
34
+ @data.each do |row|
35
+ start_time = row[1]
36
+ end_time = row[2] || Time.now.to_i
37
+ tag = row[3]
38
+
39
+ duration = end_time - start_time
40
+ @duration_by_tag[tag] << duration
41
+ @total_duration += duration
42
+ end
43
+ end
44
+
45
+ # Returns a hash where keys are tags and values are the total duration (in seconds) for each tag.
46
+ #
47
+ # @return [Hash<String, Integer>] A hash mapping tags to their total durations.
48
+ def total_duration_by_tag
49
+ @duration_by_tag.transform_values(&:sum)
50
+ end
51
+
52
+ # Returns an array of arrays where each sub-array contains a tag and its total duration, sorted by duration in
53
+ # descending order.
54
+ #
55
+ # @return [Array<Array>] An array of [tag, total_duration] pairs sorted by total_duration in descending order.
56
+ def sorted_duration_by_tag
57
+ @duration_by_tag.map { |tag, durations| [tag, durations.sum] }.sort_by { |_, sum| -sum }
58
+ end
59
+
60
+ # Returns a hash where keys are tags and values are the average duration (in seconds) for each tag.
61
+ #
62
+ # @return [Hash<String, Float>] A hash mapping tags to their average durations.
63
+ def average_by_tag
64
+ @duration_by_tag.transform_values { |durations| durations.sum.to_f / durations.size }
65
+ end
66
+
67
+ # Returns a hash where keys are tags and values are the standard deviation of durations for each tag.
68
+ #
69
+ # @return [Hash<String, Float>] A hash mapping tags to their standard deviations.
70
+ def standard_deviation_by_tag
71
+ @duration_by_tag.transform_values(&:standard_deviation)
72
+ end
73
+
74
+ # Returns a hash where keys are tags and values are additional descriptive statistics for the durations of each tag.
75
+ #
76
+ # @return [Hash<String, Hash>] A hash mapping tags to a hash of descriptive statistics
77
+ # (e.g., min, max, median, etc.).
78
+ def additional_stats_by_tag
79
+ @duration_by_tag.transform_values(&:descriptive_statistics)
80
+ end
81
+ end
82
+ end
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.4.0'
10
- VERSION = '1.4.0'
9
+ # Timet::VERSION # => '1.4.2'
10
+ VERSION = '1.4.2'
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.4.0
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Vielma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-29 00:00:00.000000000 Z
11
+ date: 2024-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -82,13 +82,13 @@ files:
82
82
  - lib/timet/application_helper.rb
83
83
  - lib/timet/color_codes.rb
84
84
  - lib/timet/database.rb
85
- - lib/timet/status_helper.rb
86
85
  - lib/timet/table.rb
87
86
  - lib/timet/tag_distribution.rb
88
87
  - lib/timet/time_block_chart.rb
89
88
  - lib/timet/time_helper.rb
90
89
  - lib/timet/time_report.rb
91
90
  - lib/timet/time_report_helper.rb
91
+ - lib/timet/time_statistics.rb
92
92
  - lib/timet/validation_edit_helper.rb
93
93
  - lib/timet/version.rb
94
94
  - sig/timet.rbs
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Timet
4
- # Provides helper methods to determine the status of time tracking results.
5
- module StatusHelper
6
- # Determines the status of a time tracking result based on the presence and end time of items.
7
- #
8
- # @param result [Array] The result set containing time tracking items.
9
- #
10
- # @return [Symbol] The status of the time tracking result. Possible values are
11
- # :no_items, :in_progress, or :complete.
12
- #
13
- # @example Determine the status of an empty result set
14
- # StatusHelper.determine_status([]) # => :no_items
15
- #
16
- # @example Determine the status of a result set with an in-progress item
17
- # StatusHelper.determine_status([[1, nil]]) # => :in_progress
18
- #
19
- # @example Determine the status of a result set with a completed item
20
- # StatusHelper.determine_status([[1, 1633072800]]) # => :complete
21
- #
22
- # @note The method checks if the result set is empty and returns :no_items if true.
23
- # @note If the last item in the result set has no end time, it returns :in_progress.
24
- # @note If the last item in the result set has an end time, it returns :complete.
25
- def self.determine_status(result)
26
- return :no_items if result.empty?
27
-
28
- last_item_end = result.first[1]
29
- return :in_progress unless last_item_end
30
-
31
- :complete
32
- end
33
- end
34
- end