timet 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +2 -2
- data/lib/timet/application.rb +121 -7
- data/lib/timet/application_helper.rb +43 -0
- data/lib/timet/database.rb +136 -6
- data/lib/timet/formatter.rb +34 -0
- data/lib/timet/status_helper.rb +19 -1
- data/lib/timet/time_helper.rb +87 -11
- data/lib/timet/time_report.rb +135 -1
- data/lib/timet/validation_edit_helper.rb +89 -0
- data/lib/timet/version.rb +7 -1
- data/lib/timet.rb +11 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b90eeedf6f1653e70f0d90fc2181ebeec88c3788bbc88dbcd072f5a6f2fe821
|
4
|
+
data.tar.gz: c2fbc67fd45bb9d3923a91f737f03fc2a74599193cbe932fbeaadbb44a2d21be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 475f77e1febf29ccdb1e17bfdd309a829b3d309d4d670afe95ede0926ab897173006ab89d165de4f87c3c3663c5114a08e54ada2f6835453fc4d99fb86134c62
|
7
|
+
data.tar.gz: ca865940ccafb9e60eb682d9085202e3946e1029f5df01b0ece8e998d9dcbe3746f1a196d4638e9a94cb587425198cba4fd93182e2934185d4e849407d34f81c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.9.1] - 2024-10-04
|
4
|
+
|
5
|
+
**Improvements:**
|
6
|
+
|
7
|
+
- Added YARD documentation
|
8
|
+
- Refactored the `start` method to use `@db.insert_item` directly if the last item status is valid for insertion.
|
9
|
+
- Removed the `insert_item_if_valid` private method as it is no longer needed.
|
10
|
+
- Updated the README.md to reflect the latest changes and improvements.
|
11
|
+
- Added a badge displaying the current gem version in the README.md.
|
12
|
+
|
3
13
|
## [0.9.0] - 2024-10-03
|
4
14
|
|
5
15
|
**Improvements:**
|
data/README.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
[](https://badge.fury.io/rb/timet)
|
1
2
|

|
2
3
|
[](https://codeclimate.com/github/frankvielma/timet/maintainability)
|
3
4
|
[](https://codeclimate.com/github/frankvielma/timet/test_coverage)
|
4
5
|
|
5
6
|
# Timet
|
6
7
|
|
7
|
-
Timet
|
8
|
+
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
|
8
9
|
|
9
10
|
Timet utilizes SQLite to store your time tracking data. This means your data is stored locally and securely, with no need for external databases or cloud storage. This makes Timet lightweight, fast, and perfect for users who value privacy and control over their data.
|
10
11
|
|
@@ -154,7 +155,6 @@ gem install timet
|
|
154
155
|
| `timet su [start_date]..[end_date]` | Display a report of tracked time for a date range. | `timet su 2024-01-02..2024-01-03` |
|
155
156
|
|
156
157
|
|
157
|
-
|
158
158
|
### Date Range in Summary
|
159
159
|
|
160
160
|
The `timet summary` command now supports specifying a date range for generating reports. This allows users to filter and summarize data within specific date intervals. The date format is in ISO 8601 format (YYYY-MM-DD).
|
data/lib/timet/application.rb
CHANGED
@@ -19,6 +19,7 @@ module Timet
|
|
19
19
|
class Application < Thor
|
20
20
|
include ValidationEditHelper
|
21
21
|
include ApplicationHelper
|
22
|
+
include TimeHelper
|
22
23
|
|
23
24
|
def initialize(*args)
|
24
25
|
super
|
@@ -36,15 +37,40 @@ module Timet
|
|
36
37
|
|
37
38
|
desc "start [tag] --notes=''", "start time tracking --notes='my notes...'"
|
38
39
|
option :notes, type: :string, desc: 'Add a note'
|
40
|
+
# Starts a new tracking session with the given tag and optional notes.
|
41
|
+
#
|
42
|
+
# @param tag [String] The tag associated with the tracking session. This is a required parameter.
|
43
|
+
# @param notes [String, nil] Optional notes to be associated with the tracking session. If not provided, it defaults to the value in `options[:notes]`.
|
44
|
+
#
|
45
|
+
# @return [void] This method does not return a value; it performs side effects such as inserting a tracking item and generating a summary.
|
46
|
+
#
|
47
|
+
# @example Start a tracking session with a tag and notes
|
48
|
+
# start('work', 'Starting work on project X')
|
49
|
+
#
|
50
|
+
# @example Start a tracking session with only a tag
|
51
|
+
# start('break')
|
52
|
+
#
|
53
|
+
# @note The method uses `TimeHelper.current_timestamp` to get the current timestamp for the start time.
|
54
|
+
# @note The method calls `summary` to generate a summary after inserting the tracking item.
|
39
55
|
def start(tag, notes = nil)
|
40
56
|
start_time = TimeHelper.current_timestamp
|
41
57
|
notes = options[:notes] || notes
|
42
58
|
|
43
|
-
|
59
|
+
@db.insert_item(start_time, tag, notes) if VALID_STATUSES_FOR_INSERTION.include?(@db.last_item_status)
|
44
60
|
summary
|
45
61
|
end
|
46
62
|
|
47
63
|
desc 'stop', 'stop time tracking'
|
64
|
+
# Stops the current tracking session if there is one in progress.
|
65
|
+
#
|
66
|
+
# @return [void] This method does not return a value; it performs side effects such as updating the tracking item and generating a summary.
|
67
|
+
#
|
68
|
+
# @example Stop the current tracking session
|
69
|
+
# stop
|
70
|
+
#
|
71
|
+
# @note The method checks if the last tracking item is in progress by calling `@db.last_item_status`.
|
72
|
+
# @note If the last item is in progress, it fetches the last item's ID using `@db.fetch_last_id` and updates it with the current timestamp.
|
73
|
+
# @note The method then fetches the last item using `@db.last_item` and generates a summary if the result is not nil.
|
48
74
|
def stop
|
49
75
|
if @db.last_item_status == :in_progress
|
50
76
|
last_id = @db.fetch_last_id
|
@@ -58,6 +84,16 @@ module Timet
|
|
58
84
|
end
|
59
85
|
|
60
86
|
desc 'resume (r)', 'resume last task'
|
87
|
+
# Resumes the last tracking session if it was completed.
|
88
|
+
#
|
89
|
+
# @return [void] This method does not return a value; it performs side effects such as resuming a tracking session or providing feedback.
|
90
|
+
#
|
91
|
+
# @example Resume the last tracking session
|
92
|
+
# resume
|
93
|
+
#
|
94
|
+
# @note The method checks the status of the last tracking item using `@db.last_item_status`.
|
95
|
+
# @note If the last item is in progress, it prints a message indicating that a task is currently being tracked.
|
96
|
+
# @note If the last item is complete, it fetches the last item using `@db.last_item`, retrieves the tag and notes, and calls the `start` method to resume the tracking session.
|
61
97
|
def resume
|
62
98
|
status = @db.last_item_status
|
63
99
|
|
@@ -77,6 +113,26 @@ module Timet
|
|
77
113
|
desc 'summary (su) [filter] [tag] --csv=csv_filename',
|
78
114
|
' [filter] => [today (t), yesterday (y), week (w), month (m), [start_date]..[end_date]] [tag]'
|
79
115
|
option :csv, type: :string, desc: 'Export to CSV file'
|
116
|
+
# Generates a summary of tracking items based on the provided filter and tag, and optionally exports the summary to a CSV file.
|
117
|
+
#
|
118
|
+
# @param filter [String, nil] The filter to apply when generating the summary. Possible values include 'today', 'yesterday', 'week', 'month', or a date range in the format '[start_date]..[end_date]'.
|
119
|
+
# @param tag [String, nil] The tag to filter the tracking items by.
|
120
|
+
#
|
121
|
+
# @return [void] This method does not return a value; it performs side effects such as displaying the summary and exporting to CSV if specified.
|
122
|
+
#
|
123
|
+
# @example Generate a summary for today
|
124
|
+
# summary('today')
|
125
|
+
#
|
126
|
+
# @example Generate a summary for a specific tag
|
127
|
+
# summary(nil, 'work')
|
128
|
+
#
|
129
|
+
# @example Generate a summary for a date range and export to CSV
|
130
|
+
# summary('2023-01-01..2023-01-31', nil, csv: 'summary.csv')
|
131
|
+
#
|
132
|
+
# @note The method initializes a `TimeReport` object with the database, filter, tag, and optional CSV filename.
|
133
|
+
# @note The method calls `display` on the `TimeReport` object to show the summary.
|
134
|
+
# @note If a CSV filename is provided and there are items to export, the method calls `export_sheet` to export the summary to a CSV file.
|
135
|
+
# @note If no items are found to export, it prints a message indicating that no items were found.
|
80
136
|
def summary(filter = nil, tag = nil)
|
81
137
|
csv_filename = options[:csv]&.split('.')&.first
|
82
138
|
summary = TimeReport.new(@db, filter, tag, csv_filename)
|
@@ -92,6 +148,25 @@ module Timet
|
|
92
148
|
|
93
149
|
desc 'edit (e) [id] [field] [value]',
|
94
150
|
'edit a task, [field] (notes, tag, start or end) and [value] are optional parameters'
|
151
|
+
# Edits a specific tracking item by its ID, allowing the user to modify fields such as notes, tag, start time, or end time.
|
152
|
+
#
|
153
|
+
# @param id [Integer] The ID of the tracking item to be edited.
|
154
|
+
# @param field [String, nil] The field to be edited. Possible values include 'notes', 'tag', 'start', or 'end'. If not provided, the user will be prompted to select a field.
|
155
|
+
# @param new_value [String, nil] The new value to be set for the specified field. If not provided, the user will be prompted to enter a new value.
|
156
|
+
#
|
157
|
+
# @return [void] This method does not return a value; it performs side effects such as updating the tracking item and displaying the updated item.
|
158
|
+
#
|
159
|
+
# @example Edit the notes of a tracking item with ID 1
|
160
|
+
# edit(1, 'notes', 'Updated notes')
|
161
|
+
#
|
162
|
+
# @example Edit a tracking item with ID 2, prompting for the field and new value
|
163
|
+
# edit(2)
|
164
|
+
#
|
165
|
+
# @note The method first attempts to find the tracking item by its ID using `@db.find_item(id)`.
|
166
|
+
# @note If the item is found, it displays the current item details using `display_item(item)`.
|
167
|
+
# @note If the field or new value is not provided, the user is prompted to select a field to edit and enter a new value.
|
168
|
+
# @note The method then validates and updates the item using `validate_and_update(item, field, new_value)`.
|
169
|
+
# @note Finally, it displays the updated item details using `display_item(updated_item)`.
|
95
170
|
def edit(id, field = nil, new_value = nil)
|
96
171
|
item = @db.find_item(id)
|
97
172
|
return puts "No tracked time found for id: #{id}" unless item
|
@@ -107,6 +182,19 @@ module Timet
|
|
107
182
|
end
|
108
183
|
|
109
184
|
desc 'delete (d) [id]', 'delete a task'
|
185
|
+
# Deletes a specific tracking item by its ID after confirming with the user.
|
186
|
+
#
|
187
|
+
# @param id [Integer] The ID of the tracking item to be deleted.
|
188
|
+
#
|
189
|
+
# @return [void] This method does not return a value; it performs side effects such as deleting the tracking item and displaying a confirmation message.
|
190
|
+
#
|
191
|
+
# @example Delete a tracking item with ID 1
|
192
|
+
# delete(1)
|
193
|
+
#
|
194
|
+
# @note The method first attempts to find the tracking item by its ID using `@db.find_item(id)`.
|
195
|
+
# @note If the item is found, it displays the item details using `TimeReport.new(@db).show_row(item)`.
|
196
|
+
# @note The method then prompts the user for confirmation using `TTY::Prompt.new.yes?('Are you sure you want to delete this entry?')`.
|
197
|
+
# @note If the user confirms, the method deletes the item and prints a confirmation message using `delete_item_and_print_message(id, "Deleted #{id}")`.
|
110
198
|
def delete(id)
|
111
199
|
item = @db.find_item(id)
|
112
200
|
return puts "No tracked time found for id: #{id}" unless item
|
@@ -118,6 +206,17 @@ module Timet
|
|
118
206
|
end
|
119
207
|
|
120
208
|
desc 'cancel (c)', 'cancel active time tracking'
|
209
|
+
# Cancels the active time tracking session by deleting the last tracking item.
|
210
|
+
#
|
211
|
+
# @return [void] This method does not return a value; it performs side effects such as deleting the active tracking item and displaying a confirmation message.
|
212
|
+
#
|
213
|
+
# @example Cancel the active time tracking session
|
214
|
+
# cancel
|
215
|
+
#
|
216
|
+
# @note The method fetches the ID of the last tracking item using `@db.fetch_last_id`.
|
217
|
+
# @note It checks if the last item is in progress by comparing `@db.last_item_status` with `:complete`.
|
218
|
+
# @note If the last item is in progress, it deletes the item and prints a confirmation message using `delete_item_and_print_message(id, "Canceled active time tracking #{id}")`.
|
219
|
+
# @note If there is no active time tracking, it prints a message indicating that there is no active time tracking.
|
121
220
|
def cancel
|
122
221
|
id = @db.fetch_last_id
|
123
222
|
return puts 'There is no active time tracking' if @db.last_item_status == :complete
|
@@ -125,18 +224,33 @@ module Timet
|
|
125
224
|
delete_item_and_print_message(id, "Canceled active time tracking #{id}")
|
126
225
|
end
|
127
226
|
|
227
|
+
# Determines whether the application should exit when a command fails.
|
228
|
+
#
|
229
|
+
# @return [Boolean] Returns `true`, indicating that the application should exit when a command fails.
|
230
|
+
#
|
231
|
+
# @example Check if the application should exit on failure
|
232
|
+
# MyClass.exit_on_failure? # => true
|
233
|
+
#
|
234
|
+
# @note This method is typically used in command-line applications to control the behavior when a command fails.
|
235
|
+
# @note Returning `true` means that the application will exit immediately if a command fails, which is useful for ensuring that errors are handled gracefully.
|
128
236
|
def self.exit_on_failure?
|
129
237
|
true
|
130
238
|
end
|
131
239
|
|
132
240
|
private
|
133
241
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
242
|
+
# Deletes a tracking item from the database by its ID and prints a confirmation message.
|
243
|
+
#
|
244
|
+
# @param id [Integer] The ID of the tracking item to be deleted.
|
245
|
+
# @param message [String] The message to be printed after the item is deleted.
|
246
|
+
#
|
247
|
+
# @return [void] This method does not return a value; it performs side effects such as deleting the tracking item and printing a message.
|
248
|
+
#
|
249
|
+
# @example Delete a tracking item with ID 1 and print a confirmation message
|
250
|
+
# delete_item_and_print_message(1, 'Deleted item 1')
|
251
|
+
#
|
252
|
+
# @note The method deletes the tracking item from the database using `@db.delete_item(id)`.
|
253
|
+
# @note After deleting the item, the method prints the provided message using `puts message`.
|
140
254
|
def delete_item_and_print_message(id, message)
|
141
255
|
@db.delete_item(id)
|
142
256
|
puts message
|
@@ -3,21 +3,64 @@
|
|
3
3
|
module Timet
|
4
4
|
# Provides helper methods for the Timet application.
|
5
5
|
module ApplicationHelper
|
6
|
+
# Displays the details of a tracking item.
|
7
|
+
#
|
8
|
+
# @param item [Hash] The tracking item to be displayed.
|
9
|
+
#
|
10
|
+
# @return [void] This method does not return a value; it performs side effects such as displaying the item details.
|
11
|
+
#
|
12
|
+
# @example Display the details of a tracking item
|
13
|
+
# display_item(item)
|
14
|
+
#
|
15
|
+
# @note The method initializes a `TimeReport` object with the database and calls `show_row` to display the item details.
|
6
16
|
def display_item(item)
|
7
17
|
TimeReport.new(@db).show_row(item)
|
8
18
|
end
|
9
19
|
|
20
|
+
# Prompts the user to enter a new value for a specific field of a tracking item.
|
21
|
+
#
|
22
|
+
# @param item [Hash] The tracking item to be edited.
|
23
|
+
# @param field [String] The field to be updated.
|
24
|
+
#
|
25
|
+
# @return [String] The new value entered by the user.
|
26
|
+
#
|
27
|
+
# @example Prompt for a new value for the 'notes' field
|
28
|
+
# prompt_for_new_value(item, 'notes')
|
29
|
+
#
|
30
|
+
# @note The method retrieves the current value of the field using `field_value`.
|
31
|
+
# @note The method uses `TTY::Prompt.new` to prompt the user for a new value, displaying the current value in the prompt.
|
10
32
|
def prompt_for_new_value(item, field)
|
11
33
|
current_value = field_value(item, field)
|
12
34
|
prompt = TTY::Prompt.new(active_color: :green)
|
13
35
|
prompt.ask("Update #{field} (#{current_value}):")
|
14
36
|
end
|
15
37
|
|
38
|
+
# Prompts the user to select a field to edit from a list of available fields.
|
39
|
+
#
|
40
|
+
# @return [String] The selected field in lowercase.
|
41
|
+
#
|
42
|
+
# @example Prompt for a field to edit
|
43
|
+
# select_field_to_edit
|
44
|
+
#
|
45
|
+
# @note The method uses `TTY::Prompt.new` to display a list of available fields for the user to select from.
|
46
|
+
# @note The method returns the selected field in lowercase.
|
16
47
|
def select_field_to_edit
|
17
48
|
prompt = TTY::Prompt.new(active_color: :green)
|
18
49
|
prompt.select('Edit Field?', Timet::Application::FIELD_INDEX.keys.map(&:capitalize), active_color: :cyan).downcase
|
19
50
|
end
|
20
51
|
|
52
|
+
# Retrieves the value of a specific field from a tracking item.
|
53
|
+
#
|
54
|
+
# @param item [Hash] The tracking item.
|
55
|
+
# @param field [String] The field to retrieve the value for.
|
56
|
+
#
|
57
|
+
# @return [String, Time] The value of the specified field. If the field is 'start' or 'end', it returns the value as a Time object.
|
58
|
+
#
|
59
|
+
# @example Retrieve the value of the 'notes' field
|
60
|
+
# field_value(item, 'notes')
|
61
|
+
#
|
62
|
+
# @note The method retrieves the index of the field from `Timet::Application::FIELD_INDEX`.
|
63
|
+
# @note If the field is 'start' or 'end', the method converts the value to a Time object using `TimeHelper.timestamp_to_time`.
|
21
64
|
def field_value(item, field)
|
22
65
|
index = Timet::Application::FIELD_INDEX[field]
|
23
66
|
value = item[index]
|
data/lib/timet/database.rb
CHANGED
@@ -1,19 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'sqlite3'
|
4
|
-
|
4
|
+
require_relative 'status_helper'
|
5
5
|
module Timet
|
6
6
|
# Provides database access for managing time tracking data.
|
7
7
|
class Database
|
8
|
+
include StatusHelper
|
9
|
+
|
10
|
+
# The default path to the SQLite database file.
|
8
11
|
DEFAULT_DATABASE_PATH = File.join(Dir.home, '.timet.db')
|
9
12
|
|
13
|
+
# Initializes a new instance of the Database class.
|
14
|
+
#
|
15
|
+
# @param database_path [String] The path to the SQLite database file. Defaults to DEFAULT_DATABASE_PATH.
|
16
|
+
#
|
17
|
+
# @return [void] This method does not return a value; it performs side effects such as initializing the database connection and creating the necessary tables.
|
18
|
+
#
|
19
|
+
# @example Initialize a new Database instance with the default path
|
20
|
+
# Database.new
|
21
|
+
#
|
22
|
+
# @example Initialize a new Database instance with a custom path
|
23
|
+
# Database.new('/path/to/custom.db')
|
24
|
+
#
|
25
|
+
# @note The method creates a new SQLite3 database connection and initializes the necessary tables if they do not already exist.
|
10
26
|
def initialize(database_path = DEFAULT_DATABASE_PATH)
|
11
27
|
@db = SQLite3::Database.new(database_path)
|
12
28
|
create_table
|
13
29
|
add_notes
|
14
30
|
end
|
15
31
|
|
16
|
-
# Creates the items table if it doesn't already exist
|
32
|
+
# Creates the items table if it doesn't already exist.
|
33
|
+
#
|
34
|
+
# @return [void] This method does not return a value; it performs side effects such as executing SQL to create the table.
|
35
|
+
#
|
36
|
+
# @example Create the items table
|
37
|
+
# create_table
|
38
|
+
#
|
39
|
+
# @note The method executes SQL to create the 'items' table with columns for id, start, end, and tag.
|
17
40
|
def create_table
|
18
41
|
execute_sql(<<-SQL)
|
19
42
|
CREATE TABLE IF NOT EXISTS items (
|
@@ -26,6 +49,13 @@ module Timet
|
|
26
49
|
end
|
27
50
|
|
28
51
|
# Adds a new column named "notes" to the "items" table if it doesn't exist.
|
52
|
+
#
|
53
|
+
# @return [void] This method does not return a value; it performs side effects such as executing SQL to add the column.
|
54
|
+
#
|
55
|
+
# @example Add the notes column to the items table
|
56
|
+
# add_notes
|
57
|
+
#
|
58
|
+
# @note The method checks if the 'notes' column already exists and adds it if it does not.
|
29
59
|
def add_notes
|
30
60
|
table_name = 'items'
|
31
61
|
new_column_name = 'notes'
|
@@ -37,45 +67,129 @@ module Timet
|
|
37
67
|
puts "Column '#{new_column_name}' added to table '#{table_name}'."
|
38
68
|
end
|
39
69
|
|
40
|
-
# Inserts a new item into the items table
|
70
|
+
# Inserts a new item into the items table.
|
71
|
+
#
|
72
|
+
# @param start [Integer] The start time of the item.
|
73
|
+
# @param tag [String] The tag associated with the item.
|
74
|
+
# @param notes [String] The notes associated with the item.
|
75
|
+
#
|
76
|
+
# @return [void] This method does not return a value; it performs side effects such as executing SQL to insert the item.
|
77
|
+
#
|
78
|
+
# @example Insert a new item into the items table
|
79
|
+
# insert_item(1633072800, 'work', 'Completed task X')
|
80
|
+
#
|
81
|
+
# @note The method executes SQL to insert a new row into the 'items' table.
|
41
82
|
def insert_item(start, tag, notes)
|
42
83
|
execute_sql('INSERT INTO items (start, tag, notes) VALUES (?, ?, ?)', [start, tag, notes])
|
43
84
|
end
|
44
85
|
|
86
|
+
# Updates an existing item in the items table.
|
87
|
+
#
|
88
|
+
# @param id [Integer] The ID of the item to be updated.
|
89
|
+
# @param field [String] The field to be updated.
|
90
|
+
# @param value [String, Integer, nil] The new value for the specified field.
|
91
|
+
#
|
92
|
+
# @return [void] This method does not return a value; it performs side effects such as executing SQL to update the item.
|
93
|
+
#
|
94
|
+
# @example Update the tag of an item with ID 1
|
95
|
+
# update_item(1, 'tag', 'updated_work')
|
96
|
+
#
|
97
|
+
# @note The method executes SQL to update the specified field of the item with the given ID.
|
45
98
|
def update_item(id, field, value)
|
46
99
|
return if %w[start end].include?(field) && value.nil?
|
47
100
|
|
48
101
|
execute_sql("UPDATE items SET #{field}='#{value}' WHERE id = #{id}")
|
49
102
|
end
|
50
103
|
|
104
|
+
# Deletes an item from the items table.
|
105
|
+
#
|
106
|
+
# @param id [Integer] The ID of the item to be deleted.
|
107
|
+
#
|
108
|
+
# @return [void] This method does not return a value; it performs side effects such as executing SQL to delete the item.
|
109
|
+
#
|
110
|
+
# @example Delete an item with ID 1
|
111
|
+
# delete_item(1)
|
112
|
+
#
|
113
|
+
# @note The method executes SQL to delete the item with the given ID from the 'items' table.
|
51
114
|
def delete_item(id)
|
52
115
|
execute_sql("DELETE FROM items WHERE id = #{id}")
|
53
116
|
end
|
54
117
|
|
55
|
-
# Fetches the ID of the last inserted item
|
118
|
+
# Fetches the ID of the last inserted item.
|
119
|
+
#
|
120
|
+
# @return [Integer, nil] The ID of the last inserted item, or nil if no items exist.
|
121
|
+
#
|
122
|
+
# @example Fetch the last inserted item ID
|
123
|
+
# fetch_last_id
|
124
|
+
#
|
125
|
+
# @note The method executes SQL to fetch the ID of the last inserted item.
|
56
126
|
def fetch_last_id
|
57
127
|
result = execute_sql('SELECT id FROM items ORDER BY id DESC LIMIT 1').first
|
58
128
|
result ? result[0] : nil
|
59
129
|
end
|
60
130
|
|
131
|
+
# Fetches the last item from the items table.
|
132
|
+
#
|
133
|
+
# @return [Array, nil] The last item as an array, or nil if no items exist.
|
134
|
+
#
|
135
|
+
# @example Fetch the last item
|
136
|
+
# last_item
|
137
|
+
#
|
138
|
+
# @note The method executes SQL to fetch the last item from the 'items' table.
|
61
139
|
def last_item
|
62
140
|
execute_sql('SELECT * FROM items ORDER BY id DESC LIMIT 1').first
|
63
141
|
end
|
64
142
|
|
143
|
+
# Determines the status of the last item in the items table.
|
144
|
+
#
|
145
|
+
# @return [Symbol] The status of the last item. Possible values are :no_items, :in_progress, or :complete.
|
146
|
+
#
|
147
|
+
# @example Determine the status of the last item
|
148
|
+
# last_item_status
|
149
|
+
#
|
150
|
+
# @note The method executes SQL to fetch the last item and determines its status using the `StatusHelper` module.
|
65
151
|
def last_item_status
|
66
152
|
result = execute_sql('SELECT id, end FROM items ORDER BY id DESC LIMIT 1')
|
67
153
|
StatusHelper.determine_status(result)
|
68
154
|
end
|
69
155
|
|
156
|
+
# Finds an item in the items table by its ID.
|
157
|
+
#
|
158
|
+
# @param id [Integer] The ID of the item to be found.
|
159
|
+
#
|
160
|
+
# @return [Array, nil] The item as an array, or nil if the item does not exist.
|
161
|
+
#
|
162
|
+
# @example Find an item with ID 1
|
163
|
+
# find_item(1)
|
164
|
+
#
|
165
|
+
# @note The method executes SQL to find the item with the given ID in the 'items' table.
|
70
166
|
def find_item(id)
|
71
167
|
execute_sql("select * from items where id=#{id}").first
|
72
168
|
end
|
73
169
|
|
170
|
+
# Fetches all items from the items table that have a start time greater than or equal to today.
|
171
|
+
#
|
172
|
+
# @return [Array] An array of items.
|
173
|
+
#
|
174
|
+
# @example Fetch all items from today
|
175
|
+
# all_items
|
176
|
+
#
|
177
|
+
# @note The method executes SQL to fetch all items from the 'items' table that have a start time greater than or equal to today.
|
74
178
|
def all_items
|
75
179
|
execute_sql("SELECT * FROM items where start >= '#{Date.today.to_time.to_i}' ORDER BY id DESC")
|
76
180
|
end
|
77
181
|
|
78
|
-
# Executes a SQL query and returns the result
|
182
|
+
# Executes a SQL query and returns the result.
|
183
|
+
#
|
184
|
+
# @param sql [String] The SQL query to execute.
|
185
|
+
# @param params [Array] The parameters to bind to the SQL query.
|
186
|
+
#
|
187
|
+
# @return [Array] The result of the SQL query.
|
188
|
+
#
|
189
|
+
# @example Execute a SQL query
|
190
|
+
# execute_sql('SELECT * FROM items WHERE id = ?', [1])
|
191
|
+
#
|
192
|
+
# @note The method executes the given SQL query with the provided parameters and returns the result.
|
79
193
|
def execute_sql(sql, params = [])
|
80
194
|
@db.execute(sql, params)
|
81
195
|
rescue SQLite3::SQLException => e
|
@@ -83,12 +197,28 @@ module Timet
|
|
83
197
|
[]
|
84
198
|
end
|
85
199
|
|
86
|
-
# Closes the database connection
|
200
|
+
# Closes the database connection.
|
201
|
+
#
|
202
|
+
# @return [void] This method does not return a value; it performs side effects such as closing the database connection.
|
203
|
+
#
|
204
|
+
# @example Close the database connection
|
205
|
+
# close
|
206
|
+
#
|
207
|
+
# @note The method closes the SQLite3 database connection.
|
87
208
|
def close
|
88
209
|
@db&.close
|
89
210
|
end
|
90
211
|
|
91
212
|
# Converts a given number of seconds into a human-readable HH:MM:SS format.
|
213
|
+
#
|
214
|
+
# @param seconds [Integer] The number of seconds to convert.
|
215
|
+
#
|
216
|
+
# @return [String] The formatted time in HH:MM:SS format.
|
217
|
+
#
|
218
|
+
# @example Convert 3661 seconds to HH:MM:SS format
|
219
|
+
# seconds_to_hms(3661) # => '01:01:01'
|
220
|
+
#
|
221
|
+
# @note The method converts the given number of seconds into hours, minutes, and seconds, and formats them as HH:MM:SS.
|
92
222
|
def seconds_to_hms(seconds)
|
93
223
|
hours, remainder = seconds.divmod(3600)
|
94
224
|
minutes, seconds = remainder.divmod(60)
|
data/lib/timet/formatter.rb
CHANGED
@@ -4,6 +4,14 @@ module Timet
|
|
4
4
|
# This module is responsible for formatting the output of the `timet` application.
|
5
5
|
# It provides methods for formatting the table header, separators, and rows.
|
6
6
|
module Formatter
|
7
|
+
# Formats the header of the time tracking report table.
|
8
|
+
#
|
9
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the formatted header.
|
10
|
+
#
|
11
|
+
# @example Format and print the table header
|
12
|
+
# format_table_header
|
13
|
+
#
|
14
|
+
# @note The method constructs a string representing the table header and prints it.
|
7
15
|
def format_table_header
|
8
16
|
header = <<~TABLE
|
9
17
|
Tracked time report \u001b[31m[#{@filter}]\033[0m:
|
@@ -14,16 +22,42 @@ module Timet
|
|
14
22
|
puts header
|
15
23
|
end
|
16
24
|
|
25
|
+
# Formats the separator line for the time tracking report table.
|
26
|
+
#
|
27
|
+
# @return [String] The formatted separator line.
|
28
|
+
#
|
29
|
+
# @example Get the formatted table separator
|
30
|
+
# format_table_separator # => '+-------+------------+--------+----------+----------+----------+--------------------------+'
|
31
|
+
#
|
32
|
+
# @note The method returns a string representing the separator line for the table.
|
17
33
|
def format_table_separator
|
18
34
|
'+-------+------------+--------+----------+----------+----------+--------------------------+'
|
19
35
|
end
|
20
36
|
|
37
|
+
# Formats a row of the time tracking report table.
|
38
|
+
#
|
39
|
+
# @param row [Array] The row data to be formatted.
|
40
|
+
# @return [String] The formatted row.
|
41
|
+
#
|
42
|
+
# @example Format a table row
|
43
|
+
# format_table_row(1, 'work', '2023-10-01', '12:00:00', '14:00:00', 7200, 'Completed task X')
|
44
|
+
#
|
45
|
+
# @note The method formats each element of the row and constructs a string representing the formatted row.
|
21
46
|
def format_table_row(*row)
|
22
47
|
id, tag, start_date, start_time, end_time, duration, notes = row
|
23
48
|
"| #{id.to_s.rjust(5)} | #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
|
24
49
|
"#{end_time.split[1].rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} |"
|
25
50
|
end
|
26
51
|
|
52
|
+
# Formats the notes column of the time tracking report table.
|
53
|
+
#
|
54
|
+
# @param notes [String, nil] The notes to be formatted.
|
55
|
+
# @return [String] The formatted notes.
|
56
|
+
#
|
57
|
+
# @example Format notes
|
58
|
+
# format_notes('This is a long note that needs to be truncated')
|
59
|
+
#
|
60
|
+
# @note The method truncates the notes to a maximum of 20 characters and pads them to a fixed width.
|
27
61
|
def format_notes(notes)
|
28
62
|
return ' ' * 23 if notes.nil?
|
29
63
|
|
data/lib/timet/status_helper.rb
CHANGED
@@ -1,8 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Timet
|
4
|
-
#
|
4
|
+
# Provides helper methods to determine the status of time tracking results.
|
5
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 :no_items, :in_progress, or :complete.
|
11
|
+
#
|
12
|
+
# @example Determine the status of an empty result set
|
13
|
+
# StatusHelper.determine_status([]) # => :no_items
|
14
|
+
#
|
15
|
+
# @example Determine the status of a result set with an in-progress item
|
16
|
+
# StatusHelper.determine_status([[1, nil]]) # => :in_progress
|
17
|
+
#
|
18
|
+
# @example Determine the status of a result set with a completed item
|
19
|
+
# StatusHelper.determine_status([[1, 1633072800]]) # => :complete
|
20
|
+
#
|
21
|
+
# @note The method checks if the result set is empty and returns :no_items if true.
|
22
|
+
# @note If the last item in the result set has no end time, it returns :in_progress.
|
23
|
+
# @note If the last item in the result set has an end time, it returns :complete.
|
6
24
|
def self.determine_status(result)
|
7
25
|
return :no_items if result.empty?
|
8
26
|
|
data/lib/timet/time_helper.rb
CHANGED
@@ -7,38 +7,91 @@ module Timet
|
|
7
7
|
# - calculating the duration between two timestamps
|
8
8
|
# - converting a Date object to a timestamp
|
9
9
|
module TimeHelper
|
10
|
+
# Formats a timestamp into a specific format.
|
11
|
+
#
|
12
|
+
# @param timestamp [Integer] The timestamp to format.
|
13
|
+
# @return [String, nil] The formatted time string in 'YYYY-MM-DD HH:MM:SS' format, or nil if the timestamp is nil.
|
14
|
+
#
|
15
|
+
# @example Format a timestamp
|
16
|
+
# TimeHelper.format_time(1633072800) # => '2021-10-01 12:00:00'
|
10
17
|
def self.format_time(timestamp)
|
11
18
|
return nil if timestamp.nil?
|
12
19
|
|
13
20
|
Time.at(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
14
21
|
end
|
15
22
|
|
23
|
+
# Converts a timestamp to a date string.
|
24
|
+
#
|
25
|
+
# @param timestamp [Integer] The timestamp to convert.
|
26
|
+
# @return [String, nil] The date string in 'YYYY-MM-DD' format, or nil if the timestamp is nil.
|
27
|
+
#
|
28
|
+
# @example Convert a timestamp to a date string
|
29
|
+
# TimeHelper.timestamp_to_date(1633072800) # => '2021-10-01'
|
16
30
|
def self.timestamp_to_date(timestamp)
|
17
31
|
return nil if timestamp.nil?
|
18
32
|
|
19
33
|
Time.at(timestamp).strftime('%Y-%m-%d')
|
20
34
|
end
|
21
35
|
|
36
|
+
# Converts a timestamp to a time string.
|
37
|
+
#
|
38
|
+
# @param timestamp [Integer] The timestamp to convert.
|
39
|
+
# @return [String, nil] The time string in 'HH:MM:SS' format, or nil if the timestamp is nil.
|
40
|
+
#
|
41
|
+
# @example Convert a timestamp to a time string
|
42
|
+
# TimeHelper.timestamp_to_time(1633072800) # => '12:00:00'
|
22
43
|
def self.timestamp_to_time(timestamp)
|
23
44
|
return nil if timestamp.nil?
|
24
45
|
|
25
46
|
Time.at(timestamp).strftime('%H:%M:%S')
|
26
47
|
end
|
27
48
|
|
49
|
+
# Calculates the duration between two timestamps.
|
50
|
+
#
|
51
|
+
# @param start_time [Integer] The start timestamp.
|
52
|
+
# @param end_time [Integer, nil] The end timestamp. If nil, the current timestamp is used.
|
53
|
+
# @return [Integer] The duration in seconds.
|
54
|
+
#
|
55
|
+
# @example Calculate the duration between two timestamps
|
56
|
+
# TimeHelper.calculate_duration(1633072800, 1633076400) # => 3600
|
28
57
|
def self.calculate_duration(start_time, end_time)
|
29
58
|
end_time = end_time ? Time.at(end_time) : current_timestamp
|
30
59
|
(end_time - start_time).to_i
|
31
60
|
end
|
32
61
|
|
62
|
+
# Converts a Date object to a timestamp.
|
63
|
+
#
|
64
|
+
# @param date [Date] The Date object to convert.
|
65
|
+
# @return [Integer] The timestamp.
|
66
|
+
#
|
67
|
+
# @example Convert a Date object to a timestamp
|
68
|
+
# TimeHelper.date_to_timestamp(Date.new(2021, 10, 1)) # => 1633072800
|
33
69
|
def self.date_to_timestamp(date)
|
34
70
|
date.to_time.to_i
|
35
71
|
end
|
36
72
|
|
73
|
+
# Calculates the end time based on the start date and end date.
|
74
|
+
#
|
75
|
+
# @param start_date [Date] The start date.
|
76
|
+
# @param end_date [Date, nil] The end date. If nil, the start date + 1 day is used.
|
77
|
+
# @return [Integer] The end timestamp.
|
78
|
+
#
|
79
|
+
# @example Calculate the end time
|
80
|
+
# TimeHelper.calculate_end_time(Date.new(2021, 10, 1), Date.new(2021, 10, 2)) # => 1633159200
|
37
81
|
def self.calculate_end_time(start_date, end_date)
|
38
82
|
end_date = end_date ? end_date + 1 : start_date + 1
|
39
83
|
date_to_timestamp(end_date)
|
40
84
|
end
|
41
85
|
|
86
|
+
# Extracts the date from a list of items based on the index.
|
87
|
+
#
|
88
|
+
# @param items [Array] The list of items.
|
89
|
+
# @param idx [Integer] The index of the current item.
|
90
|
+
# @return [String, nil] The date string in 'YYYY-MM-DD' format, or nil if the date is the same as the previous item.
|
91
|
+
#
|
92
|
+
# @example Extract the date from a list of items
|
93
|
+
# items = [[1, 1633072800], [2, 1633159200]]
|
94
|
+
# TimeHelper.extract_date(items, 1) # => '2021-10-02'
|
42
95
|
def self.extract_date(items, idx)
|
43
96
|
current_start_date = items[idx][1]
|
44
97
|
date = TimeHelper.timestamp_to_date(current_start_date)
|
@@ -50,17 +103,17 @@ module Timet
|
|
50
103
|
# Formats a time string into a standard HH:MM:SS format.
|
51
104
|
#
|
52
105
|
# @param input [String] The input string to format.
|
53
|
-
# @return [String] The formatted time string in HH:MM:SS format, or nil if the input is invalid.
|
54
|
-
#
|
55
|
-
# @example
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
106
|
+
# @return [String, nil] The formatted time string in HH:MM:SS format, or nil if the input is invalid.
|
107
|
+
#
|
108
|
+
# @example Format a time string
|
109
|
+
# TimeHelper.format_time_string('123456') # => "12:34:56"
|
110
|
+
# TimeHelper.format_time_string('1234567') # => "12:34:56"
|
111
|
+
# TimeHelper.format_time_string('1234') # => "12:34:00"
|
112
|
+
# TimeHelper.format_time_string('123') # => "12:30:00"
|
113
|
+
# TimeHelper.format_time_string('12') # => "12:00:00"
|
114
|
+
# TimeHelper.format_time_string('1') # => "01:00:00"
|
115
|
+
# TimeHelper.format_time_string('127122') # => nil
|
116
|
+
# TimeHelper.format_time_string('abc') # => nil
|
64
117
|
def self.format_time_string(input)
|
65
118
|
return nil if input.nil? || input.empty?
|
66
119
|
|
@@ -73,6 +126,13 @@ module Timet
|
|
73
126
|
format('%<hours>02d:%<minutes>02d:%<seconds>02d', hours: hours, minutes: minutes, seconds: seconds)
|
74
127
|
end
|
75
128
|
|
129
|
+
# Parses time components from a string of digits.
|
130
|
+
#
|
131
|
+
# @param digits [String] The string of digits to parse.
|
132
|
+
# @return [Array] An array containing the hours, minutes, and seconds.
|
133
|
+
#
|
134
|
+
# @example Parse time components
|
135
|
+
# TimeHelper.parse_time_components('123456') # => [12, 34, 56]
|
76
136
|
def self.parse_time_components(digits)
|
77
137
|
padded_digits = case digits.size
|
78
138
|
when 1 then "0#{digits}0000"
|
@@ -85,10 +145,26 @@ module Timet
|
|
85
145
|
padded_digits.scan(/.{2}/).map(&:to_i)
|
86
146
|
end
|
87
147
|
|
148
|
+
# Validates the time components.
|
149
|
+
#
|
150
|
+
# @param hours [Integer] The hours component.
|
151
|
+
# @param minutes [Integer] The minutes component.
|
152
|
+
# @param seconds [Integer] The seconds component.
|
153
|
+
# @return [Boolean] True if the time components are valid, otherwise false.
|
154
|
+
#
|
155
|
+
# @example Validate time components
|
156
|
+
# TimeHelper.valid_time?(12, 34, 56) # => true
|
157
|
+
# TimeHelper.valid_time?(25, 34, 56) # => false
|
88
158
|
def self.valid_time?(hours, minutes, seconds)
|
89
159
|
hours < 24 && minutes < 60 && seconds < 60
|
90
160
|
end
|
91
161
|
|
162
|
+
# Returns the current timestamp.
|
163
|
+
#
|
164
|
+
# @return [Integer] The current timestamp.
|
165
|
+
#
|
166
|
+
# @example Get the current timestamp
|
167
|
+
# TimeHelper.current_timestamp
|
92
168
|
def self.current_timestamp
|
93
169
|
Time.now.utc.to_i
|
94
170
|
end
|
data/lib/timet/time_report.rb
CHANGED
@@ -12,8 +12,26 @@ module Timet
|
|
12
12
|
class TimeReport
|
13
13
|
include Formatter
|
14
14
|
|
15
|
-
|
15
|
+
# Provides access to the database instance.
|
16
|
+
attr_reader :db
|
16
17
|
|
18
|
+
# Provides access to the filtered items.
|
19
|
+
attr_reader :items
|
20
|
+
|
21
|
+
# Provides access to the CSV filename.
|
22
|
+
attr_reader :filename
|
23
|
+
|
24
|
+
# Initializes a new instance of the TimeReport class.
|
25
|
+
#
|
26
|
+
# @param db [Database] The database instance to use for fetching data.
|
27
|
+
# @param filter [String, nil] The filter to apply when fetching items. Possible values include 'today', 'yesterday', 'week', 'month', or a date range in the format 'YYYY-MM-DD..YYYY-MM-DD'.
|
28
|
+
# @param tag [String, nil] The tag to filter the items by.
|
29
|
+
# @param csv [String, nil] The filename to use when exporting the report to CSV.
|
30
|
+
#
|
31
|
+
# @return [void] This method does not return a value; it performs side effects such as initializing the instance variables.
|
32
|
+
#
|
33
|
+
# @example Initialize a new TimeReport instance with a filter and tag
|
34
|
+
# TimeReport.new(db, 'today', 'work', 'report.csv')
|
17
35
|
def initialize(db, filter = nil, tag = nil, csv = nil)
|
18
36
|
@db = db
|
19
37
|
@filename = csv
|
@@ -21,6 +39,14 @@ module Timet
|
|
21
39
|
@items = filter ? filter_items(@filter, tag) : @db.all_items
|
22
40
|
end
|
23
41
|
|
42
|
+
# Displays the report of tracked time entries.
|
43
|
+
#
|
44
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the report.
|
45
|
+
#
|
46
|
+
# @example Display the report
|
47
|
+
# time_report.display
|
48
|
+
#
|
49
|
+
# @note The method formats and prints the table header, rows, and total duration.
|
24
50
|
def display
|
25
51
|
return puts 'No tracked time found for the specified filter.' if items.empty?
|
26
52
|
|
@@ -33,6 +59,16 @@ module Timet
|
|
33
59
|
total
|
34
60
|
end
|
35
61
|
|
62
|
+
# Displays a single row of the report.
|
63
|
+
#
|
64
|
+
# @param item [Array] The item to display.
|
65
|
+
#
|
66
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the row.
|
67
|
+
#
|
68
|
+
# @example Display a single row
|
69
|
+
# time_report.show_row(item)
|
70
|
+
#
|
71
|
+
# @note The method formats and prints the table header, row, and total duration.
|
36
72
|
def show_row(item)
|
37
73
|
format_table_header
|
38
74
|
display_time_entry(item)
|
@@ -40,6 +76,14 @@ module Timet
|
|
40
76
|
total
|
41
77
|
end
|
42
78
|
|
79
|
+
# Exports the report to a CSV file.
|
80
|
+
#
|
81
|
+
# @return [void] This method does not return a value; it performs side effects such as writing the CSV file.
|
82
|
+
#
|
83
|
+
# @example Export the report to a CSV file
|
84
|
+
# time_report.export_sheet
|
85
|
+
#
|
86
|
+
# @note The method writes the items to a CSV file and prints a confirmation message.
|
43
87
|
def export_sheet
|
44
88
|
file_name = "#{filename}.csv"
|
45
89
|
write_csv(file_name)
|
@@ -49,6 +93,16 @@ module Timet
|
|
49
93
|
|
50
94
|
private
|
51
95
|
|
96
|
+
# Writes the items to a CSV file.
|
97
|
+
#
|
98
|
+
# @param file_name [String] The name of the CSV file to write.
|
99
|
+
#
|
100
|
+
# @return [void] This method does not return a value; it performs side effects such as writing the CSV file.
|
101
|
+
#
|
102
|
+
# @example Write items to a CSV file
|
103
|
+
# write_csv('report.csv')
|
104
|
+
#
|
105
|
+
# @note The method writes the items to the specified CSV file.
|
52
106
|
def write_csv(file_name)
|
53
107
|
CSV.open(file_name, 'w') do |csv|
|
54
108
|
csv << %w[ID Start End Tag Notes]
|
@@ -58,6 +112,16 @@ module Timet
|
|
58
112
|
end
|
59
113
|
end
|
60
114
|
|
115
|
+
# Formats an item for CSV export.
|
116
|
+
#
|
117
|
+
# @param item [Array] The item to format.
|
118
|
+
#
|
119
|
+
# @return [Array] The formatted item.
|
120
|
+
#
|
121
|
+
# @example Format an item for CSV export
|
122
|
+
# format_item(item)
|
123
|
+
#
|
124
|
+
# @note The method formats the item's ID, start time, end time, tag, and notes.
|
61
125
|
def format_item(item)
|
62
126
|
id, start_time, end_time, tags, notes = item
|
63
127
|
[
|
@@ -69,6 +133,17 @@ module Timet
|
|
69
133
|
]
|
70
134
|
end
|
71
135
|
|
136
|
+
# Displays a single time entry in the report.
|
137
|
+
#
|
138
|
+
# @param item [Array] The item to display.
|
139
|
+
# @param date [String, nil] The date to display. If nil, the date is not displayed.
|
140
|
+
#
|
141
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the row.
|
142
|
+
#
|
143
|
+
# @example Display a time entry
|
144
|
+
# display_time_entry(item, '2021-10-01')
|
145
|
+
#
|
146
|
+
# @note The method formats and prints the row for the time entry.
|
72
147
|
def display_time_entry(item, date = nil)
|
73
148
|
return puts 'Missing time entry data.' unless item
|
74
149
|
|
@@ -80,6 +155,14 @@ module Timet
|
|
80
155
|
puts format_table_row(id, tag_name[0..5], start_date, start_time, end_time, duration, notes)
|
81
156
|
end
|
82
157
|
|
158
|
+
# Displays the total duration of the tracked time entries.
|
159
|
+
#
|
160
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the total duration.
|
161
|
+
#
|
162
|
+
# @example Display the total duration
|
163
|
+
# total
|
164
|
+
#
|
165
|
+
# @note The method calculates and prints the total duration of the tracked time entries.
|
83
166
|
def total
|
84
167
|
total = @items.map do |item|
|
85
168
|
TimeHelper.calculate_duration(item[1], item[2])
|
@@ -88,6 +171,17 @@ module Timet
|
|
88
171
|
puts format_table_separator
|
89
172
|
end
|
90
173
|
|
174
|
+
# Filters the items based on the specified filter and tag.
|
175
|
+
#
|
176
|
+
# @param filter [String] The filter to apply.
|
177
|
+
# @param tag [String, nil] The tag to filter the items by.
|
178
|
+
#
|
179
|
+
# @return [Array] The filtered items.
|
180
|
+
#
|
181
|
+
# @example Filter items by date range and tag
|
182
|
+
# filter_items('2021-10-01..2021-10-31', 'work')
|
183
|
+
#
|
184
|
+
# @note The method filters the items based on the specified date range and tag.
|
91
185
|
def filter_items(filter, tag)
|
92
186
|
if date_ranges.key?(filter)
|
93
187
|
start_date, end_date = date_ranges[filter]
|
@@ -101,6 +195,14 @@ module Timet
|
|
101
195
|
end
|
102
196
|
end
|
103
197
|
|
198
|
+
# Provides predefined date ranges for filtering.
|
199
|
+
#
|
200
|
+
# @return [Hash] A hash containing predefined date ranges.
|
201
|
+
#
|
202
|
+
# @example Get the predefined date ranges
|
203
|
+
# date_ranges
|
204
|
+
#
|
205
|
+
# @note The method returns a hash with predefined date ranges for 'today', 'yesterday', 'week', and 'month'.
|
104
206
|
def date_ranges
|
105
207
|
today = Date.today
|
106
208
|
{
|
@@ -111,6 +213,18 @@ module Timet
|
|
111
213
|
}
|
112
214
|
end
|
113
215
|
|
216
|
+
# Filters the items by date range and tag.
|
217
|
+
#
|
218
|
+
# @param start_date [Date] The start date of the range.
|
219
|
+
# @param end_date [Date, nil] The end date of the range. If nil, the end date is the start date + 1 day.
|
220
|
+
# @param tag [String, nil] The tag to filter the items by.
|
221
|
+
#
|
222
|
+
# @return [Array] The filtered items.
|
223
|
+
#
|
224
|
+
# @example Filter items by date range and tag
|
225
|
+
# filter_by_date_range(Date.new(2021, 10, 1), Date.new(2021, 10, 31), 'work')
|
226
|
+
#
|
227
|
+
# @note The method filters the items based on the specified date range and tag.
|
114
228
|
def filter_by_date_range(start_date, end_date = nil, tag = nil)
|
115
229
|
start_time = TimeHelper.date_to_timestamp(start_date)
|
116
230
|
end_time = TimeHelper.calculate_end_time(start_date, end_date)
|
@@ -120,6 +234,16 @@ module Timet
|
|
120
234
|
)
|
121
235
|
end
|
122
236
|
|
237
|
+
# Formats the filter string.
|
238
|
+
#
|
239
|
+
# @param filter [String, nil] The filter string to format.
|
240
|
+
#
|
241
|
+
# @return [String] The formatted filter string.
|
242
|
+
#
|
243
|
+
# @example Format the filter string
|
244
|
+
# formatted_filter('t') # => 'today'
|
245
|
+
#
|
246
|
+
# @note The method maps shorthand filters to their full names and validates date formats.
|
123
247
|
def formatted_filter(filter)
|
124
248
|
filter_map = {
|
125
249
|
'today' => %w[today t],
|
@@ -137,6 +261,16 @@ module Timet
|
|
137
261
|
'today'
|
138
262
|
end
|
139
263
|
|
264
|
+
# Validates the date format.
|
265
|
+
#
|
266
|
+
# @param date_string [String] The date string to validate.
|
267
|
+
#
|
268
|
+
# @return [Boolean] True if the date format is valid, otherwise false.
|
269
|
+
#
|
270
|
+
# @example Validate the date format
|
271
|
+
# valid_date_format?('2021-10-01') # => true
|
272
|
+
#
|
273
|
+
# @note The method validates the date format for single dates and date ranges.
|
140
274
|
def valid_date_format?(date_string)
|
141
275
|
date_format_single = /^\d{4}-\d{2}-\d{2}$/
|
142
276
|
date_format_range = /^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/
|
@@ -6,8 +6,23 @@ module Timet
|
|
6
6
|
# If the field is 'start' or 'end', it checks and updates the value accordingly.
|
7
7
|
# Otherwise, it directly updates the field with the new value.
|
8
8
|
module ValidationEditHelper
|
9
|
+
# Constants for time fields.
|
9
10
|
TIME_FIELDS = %w[start end].freeze
|
10
11
|
|
12
|
+
# Validates and updates a tracking item's field with a new value.
|
13
|
+
#
|
14
|
+
# @param item [Array] The tracking item to be updated.
|
15
|
+
# @param field [String] The field to be updated.
|
16
|
+
# @param new_value [String, nil] The new value to be set for the specified field.
|
17
|
+
#
|
18
|
+
# @return [Array, nil] The updated tracking item if the update was successful, otherwise nil.
|
19
|
+
#
|
20
|
+
# @example Validate and update the 'notes' field of a tracking item
|
21
|
+
# validate_and_update(item, 'notes', 'Updated notes')
|
22
|
+
#
|
23
|
+
# @note The method checks if the field is a time field (start or end) and processes it accordingly.
|
24
|
+
# @note If the field is not a time field, it directly updates the field with the new value.
|
25
|
+
# @note The method returns the updated tracking item if the update was successful.
|
11
26
|
def validate_and_update(item, field, new_value)
|
12
27
|
return if new_value.nil?
|
13
28
|
|
@@ -24,6 +39,18 @@ module Timet
|
|
24
39
|
|
25
40
|
private
|
26
41
|
|
42
|
+
# Processes and updates a time field (start or end) of a tracking item.
|
43
|
+
#
|
44
|
+
# @param item [Array] The tracking item to be updated.
|
45
|
+
# @param field [String] The time field to be updated.
|
46
|
+
# @param date_value [String] The new value for the time field.
|
47
|
+
# @param id [Integer] The ID of the tracking item.
|
48
|
+
#
|
49
|
+
# @return [void] This method does not return a value; it performs side effects such as updating the time field.
|
50
|
+
#
|
51
|
+
# @note The method formats the date value and checks if it is valid.
|
52
|
+
# @note If the date value is valid, it updates the time field with the new value.
|
53
|
+
# @note If the date value is invalid, it prints an error message.
|
27
54
|
def process_and_update_time_field(item, field, date_value, id)
|
28
55
|
formatted_date = TimeHelper.format_time_string(date_value)
|
29
56
|
|
@@ -39,10 +66,28 @@ module Timet
|
|
39
66
|
end
|
40
67
|
end
|
41
68
|
|
69
|
+
# Prints an error message for an invalid date.
|
70
|
+
#
|
71
|
+
# @param message [String] The error message to be printed.
|
72
|
+
#
|
73
|
+
# @return [void] This method does not return a value; it performs side effects such as printing an error message.
|
74
|
+
#
|
75
|
+
# @example Print an error message for an invalid date
|
76
|
+
# print_error('Invalid date: 2023-13-32')
|
42
77
|
def print_error(message)
|
43
78
|
puts "\u001b[31mInvalid date: #{message}\033[0m"
|
44
79
|
end
|
45
80
|
|
81
|
+
# Updates a time field (start or end) of a tracking item with a formatted date value.
|
82
|
+
#
|
83
|
+
# @param item [Array] The tracking item to be updated.
|
84
|
+
# @param field [String] The time field to be updated.
|
85
|
+
# @param formatted_value [String] The formatted date value.
|
86
|
+
#
|
87
|
+
# @return [Time] The updated time value.
|
88
|
+
#
|
89
|
+
# @example Update the 'start' field of a tracking item with a formatted date value
|
90
|
+
# update_time_field(item, 'start', '2023-10-01 12:00:00')
|
46
91
|
def update_time_field(item, field, formatted_value)
|
47
92
|
field_index = Timet::Application::FIELD_INDEX[field]
|
48
93
|
timestamp = item[field_index]
|
@@ -51,6 +96,17 @@ module Timet
|
|
51
96
|
DateTime.strptime(current_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
|
52
97
|
end
|
53
98
|
|
99
|
+
# Validates if a new time value is valid for a specific time field (start or end).
|
100
|
+
#
|
101
|
+
# @param item [Array] The tracking item to be validated.
|
102
|
+
# @param field [String] The time field to be validated.
|
103
|
+
# @param new_value_epoch [Integer] The new time value in epoch format.
|
104
|
+
# @param id [Integer] The ID of the tracking item.
|
105
|
+
#
|
106
|
+
# @return [Boolean] Returns true if the new time value is valid, otherwise false.
|
107
|
+
#
|
108
|
+
# @example Validate a new 'start' time value
|
109
|
+
# valid_time_value?(item, 'start', 1633072800, 1)
|
54
110
|
def valid_time_value?(item, field, new_value_epoch, id)
|
55
111
|
item_start = fetch_item_start(item)
|
56
112
|
item_end = fetch_item_end(item)
|
@@ -64,18 +120,51 @@ module Timet
|
|
64
120
|
end
|
65
121
|
end
|
66
122
|
|
123
|
+
# Fetches the start time of a tracking item.
|
124
|
+
#
|
125
|
+
# @param item [Array] The tracking item.
|
126
|
+
#
|
127
|
+
# @return [Integer] The start time in epoch format.
|
128
|
+
#
|
129
|
+
# @example Fetch the start time of a tracking item
|
130
|
+
# fetch_item_start(item)
|
67
131
|
def fetch_item_start(item)
|
68
132
|
item[Timet::Application::FIELD_INDEX['start']]
|
69
133
|
end
|
70
134
|
|
135
|
+
# Fetches the end time of a tracking item.
|
136
|
+
#
|
137
|
+
# @param item [Array] The tracking item.
|
138
|
+
#
|
139
|
+
# @return [Integer] The end time in epoch format.
|
140
|
+
#
|
141
|
+
# @example Fetch the end time of a tracking item
|
142
|
+
# fetch_item_end(item)
|
71
143
|
def fetch_item_end(item)
|
72
144
|
item[Timet::Application::FIELD_INDEX['end']] || TimeHelper.current_timestamp
|
73
145
|
end
|
74
146
|
|
147
|
+
# Fetches the end time of the tracking item before the current one.
|
148
|
+
#
|
149
|
+
# @param id [Integer] The ID of the current tracking item.
|
150
|
+
# @param item_start [Integer] The start time of the current tracking item.
|
151
|
+
#
|
152
|
+
# @return [Integer] The end time of the previous tracking item in epoch format.
|
153
|
+
#
|
154
|
+
# @example Fetch the end time of the previous tracking item
|
155
|
+
# fetch_item_before_end(1, 1633072800)
|
75
156
|
def fetch_item_before_end(id, item_start)
|
76
157
|
@db.find_item(id - 1)&.dig(Timet::Application::FIELD_INDEX['end']) || item_start
|
77
158
|
end
|
78
159
|
|
160
|
+
# Fetches the start time of the tracking item after the current one.
|
161
|
+
#
|
162
|
+
# @param id [Integer] The ID of the current tracking item.
|
163
|
+
#
|
164
|
+
# @return [Integer] The start time of the next tracking item in epoch format.
|
165
|
+
#
|
166
|
+
# @example Fetch the start time of the next tracking item
|
167
|
+
# fetch_item_after_start(1)
|
79
168
|
def fetch_item_after_start(id)
|
80
169
|
@db.find_item(id + 1)&.dig(Timet::Application::FIELD_INDEX['start']) || TimeHelper.current_timestamp
|
81
170
|
end
|
data/lib/timet/version.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Timet
|
4
|
-
|
4
|
+
# The version of the Timet application.
|
5
|
+
#
|
6
|
+
# @return [String] The version number in the format 'major.minor.patch'.
|
7
|
+
#
|
8
|
+
# @example Get the version of the Timet application
|
9
|
+
# Timet::VERSION # => '0.9.1'
|
10
|
+
VERSION = '0.9.1'
|
5
11
|
end
|
data/lib/timet.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Require the Timet::Application class from the 'timet/application' file.
|
4
|
+
#
|
5
|
+
# @note This statement loads the Timet::Application class, which is responsible for handling the command-line interface and user commands.
|
3
6
|
require_relative 'timet/application'
|
7
|
+
|
8
|
+
# Require the Timet::Database class from the 'timet/database' file.
|
9
|
+
#
|
10
|
+
# @note This statement loads the Timet::Database class, which provides database access for managing time tracking data.
|
4
11
|
require_relative 'timet/database'
|
12
|
+
|
13
|
+
# Require the Timet::TimeReport class from the 'timet/time_report' file.
|
14
|
+
#
|
15
|
+
# @note This statement loads the Timet::TimeReport class, which is responsible for displaying a report of tracked time entries.
|
5
16
|
require_relative 'timet/time_report'
|
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: 0.9.
|
4
|
+
version: 0.9.1
|
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-
|
11
|
+
date: 2024-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|