timet 0.8.2 → 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 +69 -20
- data/README.md +20 -1
- data/lib/timet/application.rb +135 -19
- data/lib/timet/application_helper.rb +43 -0
- data/lib/timet/database.rb +139 -29
- data/lib/timet/formatter.rb +34 -0
- data/lib/timet/status_helper.rb +19 -1
- data/lib/timet/time_helper.rb +88 -12
- data/lib/timet/time_report.rb +157 -5
- data/lib/timet/validation_edit_helper.rb +91 -0
- data/lib/timet/version.rb +7 -1
- data/lib/timet.rb +11 -0
- metadata +3 -2
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,16 +171,38 @@ 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]
|
94
188
|
filter_by_date_range(start_date, end_date, tag)
|
189
|
+
elsif valid_date_format?(filter)
|
190
|
+
start_date, end_date = filter.split('..').map { |x| Date.parse(x) }
|
191
|
+
filter_by_date_range(start_date, end_date, tag)
|
95
192
|
else
|
96
193
|
puts 'Invalid filter. Supported filters: today, yesterday, week, month'
|
97
194
|
[]
|
98
195
|
end
|
99
196
|
end
|
100
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'.
|
101
206
|
def date_ranges
|
102
207
|
today = Date.today
|
103
208
|
{
|
@@ -108,6 +213,18 @@ module Timet
|
|
108
213
|
}
|
109
214
|
end
|
110
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.
|
111
228
|
def filter_by_date_range(start_date, end_date = nil, tag = nil)
|
112
229
|
start_time = TimeHelper.date_to_timestamp(start_date)
|
113
230
|
end_time = TimeHelper.calculate_end_time(start_date, end_date)
|
@@ -117,13 +234,48 @@ module Timet
|
|
117
234
|
)
|
118
235
|
end
|
119
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.
|
120
247
|
def formatted_filter(filter)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
248
|
+
filter_map = {
|
249
|
+
'today' => %w[today t],
|
250
|
+
'yesterday' => %w[yesterday y],
|
251
|
+
'week' => %w[week w],
|
252
|
+
'month' => %w[month m]
|
253
|
+
}
|
254
|
+
|
255
|
+
filter_map.each do |key, values|
|
256
|
+
return key if values.include?(filter)
|
257
|
+
end
|
258
|
+
|
259
|
+
return filter if filter && valid_date_format?(filter)
|
125
260
|
|
126
261
|
'today'
|
127
262
|
end
|
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.
|
274
|
+
def valid_date_format?(date_string)
|
275
|
+
date_format_single = /^\d{4}-\d{2}-\d{2}$/
|
276
|
+
date_format_range = /^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/
|
277
|
+
|
278
|
+
date_string.match?(date_format_single) || date_string.match?(date_format_range)
|
279
|
+
end
|
128
280
|
end
|
129
281
|
end
|
@@ -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
|
|
@@ -18,10 +33,24 @@ module Timet
|
|
18
33
|
else
|
19
34
|
@db.update_item(id, field, new_value)
|
20
35
|
end
|
36
|
+
|
37
|
+
@db.find_item(id)
|
21
38
|
end
|
22
39
|
|
23
40
|
private
|
24
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.
|
25
54
|
def process_and_update_time_field(item, field, date_value, id)
|
26
55
|
formatted_date = TimeHelper.format_time_string(date_value)
|
27
56
|
|
@@ -37,10 +66,28 @@ module Timet
|
|
37
66
|
end
|
38
67
|
end
|
39
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')
|
40
77
|
def print_error(message)
|
41
78
|
puts "\u001b[31mInvalid date: #{message}\033[0m"
|
42
79
|
end
|
43
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')
|
44
91
|
def update_time_field(item, field, formatted_value)
|
45
92
|
field_index = Timet::Application::FIELD_INDEX[field]
|
46
93
|
timestamp = item[field_index]
|
@@ -49,6 +96,17 @@ module Timet
|
|
49
96
|
DateTime.strptime(current_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
|
50
97
|
end
|
51
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)
|
52
110
|
def valid_time_value?(item, field, new_value_epoch, id)
|
53
111
|
item_start = fetch_item_start(item)
|
54
112
|
item_end = fetch_item_end(item)
|
@@ -62,18 +120,51 @@ module Timet
|
|
62
120
|
end
|
63
121
|
end
|
64
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)
|
65
131
|
def fetch_item_start(item)
|
66
132
|
item[Timet::Application::FIELD_INDEX['start']]
|
67
133
|
end
|
68
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)
|
69
143
|
def fetch_item_end(item)
|
70
144
|
item[Timet::Application::FIELD_INDEX['end']] || TimeHelper.current_timestamp
|
71
145
|
end
|
72
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)
|
73
156
|
def fetch_item_before_end(id, item_start)
|
74
157
|
@db.find_item(id - 1)&.dig(Timet::Application::FIELD_INDEX['end']) || item_start
|
75
158
|
end
|
76
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)
|
77
168
|
def fetch_item_after_start(id)
|
78
169
|
@db.find_item(id + 1)&.dig(Timet::Application::FIELD_INDEX['start']) || TimeHelper.current_timestamp
|
79
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.
|
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
|
@@ -93,6 +93,7 @@ metadata:
|
|
93
93
|
homepage_uri: https://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
94
94
|
source_code_uri: https://github.com/frankvielma/timet
|
95
95
|
changelog_uri: https://github.com/frankvielma/timet/blob/main/CHANGELOG.md
|
96
|
+
documentation_uri: https://rubydoc.info/gems/timet
|
96
97
|
rubygems_mfa_required: 'true'
|
97
98
|
post_install_message:
|
98
99
|
rdoc_options: []
|