timet 1.4.1 → 1.4.3

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: 5e9f44f4ee2f1ae5aab382b308364ffeaffa5e09bf21d1578e8f5267ff715f0f
4
- data.tar.gz: 13becd17288c93925e91a3c49dcdc7f4f7e7afe41d1469a10a7f931930b2d26d
3
+ metadata.gz: 7754392fd614ebc62c53293166db637a9593f8497b4cc22ea3ea602bc51546f7
4
+ data.tar.gz: e4f699cf2725be5cf6849a5946323270d078ab1d6fa13892877f17ce161b1d1b
5
5
  SHA512:
6
- metadata.gz: 27fca2e9adef3a4695a497b21106a400e7448caff946efccba034f9bbfcb29962822a632f17e3ab51856de764826257f5d6b33458d69fd9b56eebeb91946e5b8
7
- data.tar.gz: df46d99862c3366f63f4ad007fbece638f88ca997fa2085070a78c065306b23888ecbbd5b610ba99582898395201dd00a1a8871de0ef8983d24c9ec3edefe32b
6
+ metadata.gz: 2a8be74697ff40c179a0546662fc7b067d0ceddf4da159ea3107c87fef68b3f6cb7a7c3e61ff9c9115bcc799d5d5278fe209629a892077783aa56c77c772c81f
7
+ data.tar.gz: bd9131bccca060ea7d8a7710d9a96b6c784cfeca7312a9146f8fa5c31d8bd0e40b3a58e82ec670328baf5e92d0d2f8b3fa8e80a47a25a0f771c2cb1a6c5e667b
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,24 @@
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.
25
+ - **iCalendar Export:** Easily export your time tracking data to iCalendar format for integration with calendar applications.
24
26
 
25
-
26
- Examples:
27
+ **Examples:**
27
28
 
28
29
  ![Timet1 demo](timet1.gif)
29
30
 
@@ -32,10 +33,9 @@ Examples:
32
33
  - Ruby version: >= 3.0.0
33
34
  - sqlite3: > 1.7
34
35
 
35
- Old versions of Ruby and Sqlite:
36
+ For older versions of Ruby and Sqlite:
36
37
 
37
38
  - [Ruby >= 2.7](https://github.com/frankvielma/timet/tree/ruby-2.7.0)
38
-
39
39
  - [Ruby >= 2.4](https://github.com/frankvielma/timet/tree/ruby-2.4.0)
40
40
 
41
41
  ## 💾 Installation
@@ -54,7 +54,8 @@ gem install timet
54
54
  - `tt`: An alias for the `timet` command, providing a shorter alternative.
55
55
 
56
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:
57
+
58
+ - **`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
59
 
59
60
  ```bash
60
61
  timet start task1 --notes="Meeting with client" --pomodoro=25
@@ -81,15 +82,15 @@ gem install timet
81
82
 
82
83
  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
84
 
84
- **Benefits**
85
+ **Benefits:**
85
86
 
86
- - **Flexibility**: Supports various productivity strategies.
87
- - **Focus**: Encourages disciplined work practices.
88
- - **Productivity**: Helps achieve higher productivity and better time management.
87
+ - **Flexibility:** Supports various productivity strategies.
88
+ - **Focus:** Encourages disciplined work practices.
89
+ - **Productivity:** Helps achieve higher productivity and better time management.
89
90
 
90
91
  ---
91
92
 
92
- - **timet stop**: Stops tracking the current task, records the elapsed time, and displays the total time spent on all tasks.
93
+ - **`timet stop`:** Stops tracking the current task, records the elapsed time, and displays the total time spent on all tasks.
93
94
 
94
95
  ```bash
95
96
  timet stop
@@ -105,8 +106,14 @@ gem install timet
105
106
  | Total: | 01:00:00 | |
106
107
  +-------+------------+--------+----------+----------+----------+--------------------------+
107
108
  ```
109
+
108
110
  ---
109
- - **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.
111
+
112
+ - **`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.
113
+
114
+ ```bash
115
+ timet resume 1
116
+ ```
110
117
 
111
118
  ```
112
119
  Tracked time report [today]:
@@ -119,10 +126,12 @@ gem install timet
119
126
  | Total: | 01:00:00 | |
120
127
  +-------+------------+--------+----------+----------+----------+--------------------------+
121
128
  ```
129
+
122
130
  ---
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
131
 
125
- - **Interactive Mode:**
132
+ - **`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.
133
+
134
+ **Interactive Mode:**
126
135
 
127
136
  ```bash
128
137
  timet edit 1
@@ -145,7 +154,7 @@ gem install timet
145
154
  End
146
155
  ```
147
156
 
148
- - **Direct Specification Mode:**
157
+ **Direct Specification Mode:**
149
158
 
150
159
  ```bash
151
160
  timet e 1 notes "New Meeting Notes"
@@ -163,24 +172,24 @@ gem install timet
163
172
  +-------+------------+--------+----------+----------+----------+--------------------------+
164
173
  ```
165
174
 
166
- ## 📋 Command Reference
175
+ ## 📋 Command Reference
167
176
 
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 [id]` |
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` |
183
- | `timet resume [id]` | Resume tracking a task by ID or the last completed task. | `timet resume [id]` |
177
+ | Command | Description | Example Usage |
178
+ | -------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------- |
179
+ | `timet start [tag] --notes='' --pomodoro=[time]` | Start tracking time for a task labeled [tag] and notes (optional). | `timet start Task "My notes" 25` |
180
+ | `timet stop` | Stop tracking time. | `timet stop` |
181
+ | `timet summary today (t)` | Display a report of tracked time for today. | `timet su t` or `timet su` |
182
+ | `timet summary yesterday (y)` | Display a report of tracked time for yesterday. | `timet su y` |
183
+ | `timet summary week (w)` | Display a report of tracked time for the week. | `timet su w` |
184
+ | `timet summary month (m)` | Display a report of tracked time for the month. | `timet su m` |
185
+ | `timet su t --csv=[filename]` | Display a report of tracked time for today and export to CSV file | `timet su t --csv=file.csv` |
186
+ | `timet su w --ics=[filename]` | Display a report of tracked time for week and export to iCalendar file | `timet su w --ics=file.csv` |
187
+ | `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
188
+ | `timet cancel` | Cancel active time tracking. | `timet c` |
189
+ | `timet edit [id]` | Update a task's notes, tag, start, or end fields. | `timet e [id]` |
190
+ | `timet su [date]` | Display a report of tracked time for a specific date. | `timet su 2024-01-03` |
191
+ | `timet su [start_date]..[end_date]` | Display a report of tracked time for a date range. | `timet su 2024-01-02..2024-01-03` |
192
+ | `timet resume (r) [id]` | Resume tracking a task by ID or the last completed task. | `timet resume [id]` |
184
193
 
185
194
  ### Date Range in Summary
186
195
 
@@ -188,20 +197,21 @@ The `timet summary` command now supports specifying a date range for generating
188
197
 
189
198
  #### Examples:
190
199
 
191
- - **Single Date**: Display a report for a specific date.
200
+ - **Single Date:** Display a report for a specific date.
192
201
 
193
202
  ```sh
194
203
  timet su 2024-01-03
195
204
  ```
196
205
 
197
- - **Date Range**: Display a report for a date range.
206
+ - **Date Range:** Display a report for a date range.
207
+
198
208
  ```sh
199
209
  timet su 2024-01-02..2024-01-03
200
210
  ```
201
211
 
202
212
  ## Data
203
213
 
204
- Timet's data is stored in ~/.timet.db
214
+ Timet's data is stored in `~/.timet.db`.
205
215
 
206
216
  ## Development
207
217
 
@@ -211,13 +221,14 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
211
221
 
212
222
  ## Contributing
213
223
 
214
- 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).
224
+ 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).
215
225
 
216
226
  ## Buy Me A Coffee! ☕
217
227
 
218
228
  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.
219
229
 
220
230
  **Bitcoin Address:**
231
+
221
232
  ```sh
222
233
  bc1qkg9me2jsuhpzu2hp9kkpxagwtf9ewnyfl4kszl
223
234
  ```
@@ -232,4 +243,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
232
243
 
233
244
  ## Code of Conduct
234
245
 
235
- 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).
246
+ 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).
@@ -36,8 +36,9 @@ module Timet
36
36
  VALID_STATUSES_FOR_INSERTION = %i[no_items complete].freeze
37
37
 
38
38
  desc "start [tag] --notes='' --pomodoro=[min]",
39
- 'Starts tracking time for a task labeled with the provided [tag], notes and "pomodoro time" in minutes
40
- (optional).'
39
+ 'Start time tracking for a task labeled with the provided [tag], notes and "pomodoro time"
40
+ in minutes (optional).
41
+ tt start project1 "Starting project1" --pomodoro=25'
41
42
  option :notes, type: :string, desc: 'Add a note'
42
43
  option :pomodoro, type: :numeric, desc: 'Pomodoro time in minutes'
43
44
  # Starts a new tracking session with the given tag and optional notes.
@@ -76,7 +77,7 @@ module Timet
76
77
  summary
77
78
  end
78
79
 
79
- desc 'stop', 'stop time tracking'
80
+ desc 'stop', 'Stop time tracking'
80
81
  # Stops the current tracking session if there is one in progress.
81
82
  #
82
83
  # @return [void] This method does not return a value; it performs side effects such as updating the tracking item
@@ -99,7 +100,7 @@ module Timet
99
100
  summary unless display
100
101
  end
101
102
 
102
- desc 'resume (r) [id]', 'resume last task'
103
+ desc 'resume (r) [id]', 'Resume last task (id is an optional parameter) => tt resume'
103
104
  # Resumes the last tracking session if it was completed.
104
105
  #
105
106
  # @return [void] This method does not return a value; it performs side effects such as resuming a tracking session
@@ -131,48 +132,34 @@ module Timet
131
132
  end
132
133
  end
133
134
 
134
- desc 'summary (su) [time_scope] [tag] --csv=csv_filename',
135
- '[time_scope] => [today (t), yesterday (y), week (w), month (m), [start_date]..[end_date]] [tag]'
136
- option :csv, type: :string, desc: 'Export to CSV file'
135
+ desc 'summary (su) [time_scope] [tag] --csv=csv_filename --ics=ics_filename',
136
+ 'Display a summary of tracked time and export to CSV.
137
+ [time_scope] => [today (t), yesterday (y), week (w), month (m). => tt su yesterday
138
+ [start_date]..[end_date]] => tt su 2024-10-03..2024-10-20
139
+ [tag] => tt su Task1
140
+ --csv=csv_filename => tt su month --csv=myfile
141
+ --ics=ics_filename => tt su week --csv=mycalendar'
142
+ option :csv, type: :string, desc: 'Export to CSV'
143
+ option :ics, type: :string, desc: 'Export to iCalendar'
137
144
  # Generates a summary of tracking items based on the provided time_scope and tag, and optionally exports the summary
138
- # to a CSV file.
145
+ # to a CSV file and/or an iCalendar file.
139
146
  #
140
- # @param time_scope [String, nil] The time_scope to apply when generating the summary. Possible values include
141
- # 'today', 'yesterday', 'week', 'month', or a date range in the format '[start_date]..[end_date]'.
142
- # @param tag [String, nil] The tag to time_scope the tracking items by.
147
+ # @param time_scope [String, nil] The filter to apply when fetching items. Possible values include 'today',
148
+ # 'yesterday', 'week', 'month', or a date range in the format 'YYYY-MM-DD..YYYY-MM-DD'.
149
+ # @param tag [String, nil] The tag to filter the items by.
143
150
  #
144
- # @return [void] This method does not return a value; it performs side effects such as displaying the summary and
145
- # exporting to CSV if specified.
146
- #
147
- # @example Generate a summary for today
148
- # summary('today')
149
- #
150
- # @example Generate a summary for a specific tag
151
- # summary(nil, 'work')
152
- #
153
- # @example Generate a summary for a date range and export to CSV
154
- # summary('2023-01-01..2023-01-31', nil, csv: 'summary.csv')
155
- #
156
- # @note The method initializes a `TimeReport` object with the database, time_scope, tag, and optional CSV filename.
157
- # @note The method calls `display` on the `TimeReport` object to show the summary.
158
- # @note If a CSV filename is provided and there are items to export, the method calls `export_sheet` to export the
159
- # summary to a CSV file.
160
- # @note If no items are found to export, it prints a message indicating that no items were found.
151
+ # @return [void] This method does not return a value; it performs side effects such as displaying
152
+ # and exporting the report.
161
153
  def summary(time_scope = nil, tag = nil)
162
- csv_filename = options[:csv]&.split('.')&.first
163
- report = TimeReport.new(@db, time_scope, tag, csv_filename)
164
-
165
- report.display
166
- items = report.items
167
- if csv_filename && items.any?
168
- report.export_sheet
169
- elsif items.empty?
170
- puts 'No items found to export'
171
- end
154
+ options = build_options(time_scope, tag)
155
+ report = TimeReport.new(@db, options)
156
+ display_and_export_report(report, options)
172
157
  end
173
158
 
174
159
  desc 'edit (e) [id] [field] [value]',
175
- 'edit a task, [field] (notes, tag, start or end) and [value] are optional parameters'
160
+ 'Edit task, [field] (notes, tag, start or end) and [value] are optional parameters.
161
+ Update notes => tt edit 12 notes "Update note"
162
+ Update start time => tt edit 12 start 12:33'
176
163
  # Edits a specific tracking item by its ID, allowing the user to modify fields such as notes, tag, start time, or
177
164
  # end time.
178
165
  #
@@ -211,7 +198,7 @@ module Timet
211
198
  display_item(updated_item || item)
212
199
  end
213
200
 
214
- desc 'delete (d) [id]', 'delete a task'
201
+ desc 'delete (d) [id]', 'Delete task => tt d 23'
215
202
  # Deletes a specific tracking item by its ID after confirming with the user.
216
203
  #
217
204
  # @param id [Integer] The ID of the tracking item to be deleted.
@@ -238,7 +225,7 @@ module Timet
238
225
  delete_item_and_print_message(id, "Deleted #{id}")
239
226
  end
240
227
 
241
- desc 'cancel (c)', 'cancel active time tracking'
228
+ desc 'cancel (c)', 'Cancel active time tracking => tt c'
242
229
  # Cancels the active time tracking session by deleting the last tracking item.
243
230
  #
244
231
  # @return [void] This method does not return a value; it performs side effects such as deleting the active tracking
@@ -184,5 +184,81 @@ module Timet
184
184
  tag, notes = item.values_at(Application::FIELD_INDEX['tag'], Application::FIELD_INDEX['notes'])
185
185
  start(tag, notes)
186
186
  end
187
+
188
+ # Builds a hash of options to be used when initializing a TimeReport instance.
189
+ #
190
+ # @param time_scope [String, nil] The filter to apply when fetching items. Possible values include 'today',
191
+ # 'yesterday', 'week', 'month', or a date range in the format 'YYYY-MM-DD..YYYY-MM-DD'.
192
+ # @param tag [String, nil] The tag to filter the items by.
193
+ #
194
+ # @return [Hash] A hash containing the filter, tag, CSV filename, and iCalendar filename.
195
+ #
196
+ # @example Build options with a filter and tag
197
+ # build_options('today', 'work') # => { filter: 'today', tag: 'work', csv: nil, ics: nil }
198
+ def build_options(time_scope, tag)
199
+ csv_filename = options[:csv]&.split('.')&.first
200
+ ics_filename = options[:ics]&.split('.')&.first
201
+ {
202
+ filter: time_scope,
203
+ tag: tag,
204
+ csv: csv_filename,
205
+ ics: ics_filename
206
+ }
207
+ end
208
+
209
+ # @note This class is responsible for exporting reports to CSV and iCalendar formats.
210
+ class ReportExporter
211
+ # Exports the report to a CSV file if the `csv` option is provided.
212
+ #
213
+ # @param report [TimeReport] The report object to export.
214
+ # @param options [Hash] The options hash containing export settings.
215
+ # @option options [String] :csv The filename to use when exporting the report to CSV.
216
+ # @return [void]
217
+ def self.export_csv_report(report, options)
218
+ report.export_csv if options[:csv]
219
+ end
220
+
221
+ # Exports the report to an iCalendar file if the `ics` option is provided.
222
+ #
223
+ # @param report [TimeReport] The report object to export.
224
+ # @param options [Hash] The options hash containing export settings.
225
+ # @option options [String] :ics The filename to use when exporting the report to iCalendar.
226
+ # @return [void]
227
+ def self.export_icalendar_report(report, options)
228
+ report.export_icalendar if options[:ics]
229
+ end
230
+ end
231
+
232
+ # Displays the report and exports it to a CSV file and/or an iCalendar file if specified.
233
+ #
234
+ # @param report [TimeReport] The TimeReport instance to display and export.
235
+ # @param options [Hash] A hash containing the options for exporting the report.
236
+ # @option options [String, nil] :csv The filename to use when exporting the report to CSV.
237
+ # @option options [String, nil] :ics The filename to use when exporting the report to iCalendar.
238
+ #
239
+ # @return [void] This method does not return a value; it performs side effects such as displaying
240
+ # and exporting the report.
241
+ #
242
+ # @example Display and export the report to CSV and iCalendar
243
+ # display_and_export_report(report, { csv: 'report.csv', ics: 'icalendar.ics' })
244
+ def display_and_export_report(report, options)
245
+ report.display
246
+ export_report(report, options)
247
+ end
248
+
249
+ # Exports the given report in CSV and iCalendar formats if there are items, otherwise prints a message.
250
+ #
251
+ # @param report [Report] The report to be exported.
252
+ # @param options [Hash] The options to pass to the exporter.
253
+ # @return [void]
254
+ def export_report(report, options)
255
+ items = report.items
256
+ if items.any?
257
+ ReportExporter.export_csv_report(report, options)
258
+ ReportExporter.export_icalendar_report(report, options)
259
+ else
260
+ puts 'No items found to export'
261
+ end
262
+ end
187
263
  end
188
264
  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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'date'
4
4
  require 'csv'
5
+ require 'icalendar'
5
6
  require_relative 'time_report_helper'
6
7
  require_relative 'table'
7
8
  require_relative 'time_block_chart'
@@ -24,26 +25,32 @@ module Timet
24
25
  attr_reader :items
25
26
 
26
27
  # Provides access to the CSV filename.
27
- attr_reader :filename
28
+ attr_reader :csv_filename
29
+
30
+ # Provides access to the ICS filename.
31
+ attr_reader :ics_filename
28
32
 
29
33
  # Initializes a new instance of the TimeReport class.
30
34
  #
31
35
  # @param db [Database] The database instance to use for fetching data.
32
- # @param filter [String, nil] The filter to apply when fetching items. Possible values include 'today',
33
- # 'yesterday', 'week', 'month', or a date range in the format 'YYYY-MM-DD..YYYY-MM-DD'.
34
- # @param tag [String, nil] The tag to filter the items by.
35
- # @param csv [String, nil] The filename to use when exporting the report to CSV.
36
+ # @param options [Hash] A hash containing optional parameters.
37
+ # @option options [String, nil] :filter The filter to apply when fetching items. Possible values include 'today',
38
+ # 'yesterday', 'week', 'month', or a date range in the format 'YYYY-MM-DD..YYYY-MM-DD'.
39
+ # @option options [String, nil] :tag The tag to filter the items by.
40
+ # @option options [String, nil] :csv The filename to use when exporting the report to CSV.
41
+ # @option options [String, nil] :ics The filename to use when exporting the report to iCalendar.
36
42
  #
37
43
  # @return [void] This method does not return a value; it performs side effects such as initializing the
38
44
  # instance variables.
39
45
  #
40
46
  # @example Initialize a new TimeReport instance with a filter and tag
41
- # TimeReport.new(db, 'today', 'work', 'report.csv')
42
- def initialize(db, filter = nil, tag = nil, csv = nil)
47
+ # TimeReport.new(db, filter: 'today', tag: 'work', csv: 'report.csv', ics: 'icalendar.ics')
48
+ def initialize(db, options = {})
43
49
  @db = db
44
- @filename = csv
45
- @filter = formatted_filter(filter)
46
- @items = filter ? filter_items(@filter, tag) : @db.all_items
50
+ @csv_filename = options[:csv]
51
+ @ics_filename = options[:ics]
52
+ @filter = formatted_filter(options[:filter])
53
+ @items = options[:filter] ? filter_items(@filter, options[:tag]) : @db.all_items
47
54
  end
48
55
 
49
56
  # Displays the report of tracked time entries.
@@ -54,15 +61,22 @@ module Timet
54
61
  # time_report.display
55
62
  #
56
63
  # @note The method formats and prints the table header, rows, and total duration.
64
+ #
65
+ # @param items [Array<Hash>] The list of time entries to be displayed.
66
+ # @param options [Hash] Additional options for customizing the display (e.g., color scheme).
67
+ #
68
+ # @see #table
69
+ # @see #print_time_block_chart
70
+ # @see #tag_distribution
57
71
  def display
58
- return puts 'No tracked time found for the specified filter.' if items.empty?
72
+ return puts 'No tracked time found for the specified filter.' if @items.empty?
59
73
 
60
- time_block, duration_by_tag = table
74
+ time_block = table
61
75
 
62
- colors = duration_by_tag.map { |x| x[0] }.sort.each_with_index.to_h
76
+ colors = @items.map { |x| x[3] }.uniq.each_with_index.to_h
63
77
  print_time_block_chart(time_block, colors)
64
78
 
65
- tag_distribution(duration_by_tag, colors)
79
+ tag_distribution(colors)
66
80
  end
67
81
 
68
82
  # Displays a single row of the report.
@@ -87,16 +101,42 @@ module Timet
87
101
  # @return [void] This method does not return a value; it performs side effects such as writing the CSV file.
88
102
  #
89
103
  # @example Export the report to a CSV file
90
- # time_report.export_sheet
104
+ # time_report.export_csv
91
105
  #
92
106
  # @note The method writes the items to a CSV file and prints a confirmation message.
93
- def export_sheet
94
- file_name = "#{filename}.csv"
107
+ def export_csv
108
+ file_name = "#{csv_filename}.csv"
95
109
  write_csv(file_name)
96
110
 
97
111
  puts "The #{file_name} has been exported."
98
112
  end
99
113
 
114
+ def export_icalendar
115
+ file_name = "#{ics_filename}.ics"
116
+
117
+ cal = Icalendar::Calendar.new
118
+ items.each do |item|
119
+ dtstart = Time.at(item[1]).to_datetime
120
+ end_time = item[2] || TimeHelper.current_timestamp
121
+ dtend = Time.at(end_time).to_datetime
122
+
123
+ tag = item[3]
124
+ notes = item[4]
125
+ cal.event do |e|
126
+ e.dtstart = dtstart
127
+ e.dtend = dtend
128
+ e.summary = tag
129
+ e.description = notes
130
+ e.ip_class = 'PRIVATE'
131
+ end
132
+ end
133
+ cal.publish
134
+
135
+ File.write(file_name, cal.to_ical)
136
+
137
+ puts "The #{file_name} has been generated."
138
+ end
139
+
100
140
  private
101
141
 
102
142
  # Writes the items to a CSV file.
@@ -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.1'
10
- VERSION = '1.4.1'
9
+ # Timet::VERSION # => '1.4.3'
10
+ VERSION = '1.4.3'
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.1
4
+ version: 1.4.3
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-31 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -88,6 +88,7 @@ files:
88
88
  - lib/timet/time_helper.rb
89
89
  - lib/timet/time_report.rb
90
90
  - lib/timet/time_report_helper.rb
91
+ - lib/timet/time_statistics.rb
91
92
  - lib/timet/validation_edit_helper.rb
92
93
  - lib/timet/version.rb
93
94
  - sig/timet.rbs