timet 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +4 -2
- data/lib/timet/formatter.rb +137 -54
- data/lib/timet/time_helper.rb +35 -37
- data/lib/timet/time_report.rb +8 -73
- data/lib/timet/time_report_helper.rb +150 -0
- data/lib/timet/version.rb +2 -2
- data/monthly_report.webp +0 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 822542486f30f170aa48dccd0577637caed0010047b085022653f3ae4d0cb848
|
4
|
+
data.tar.gz: 96b166cdd9ac9fe69ee546fec4d68e47d113df05f986b4aff61cc71358c1c9ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c950e2ff135ba0f665a70960c0389a55ac83ccf863dbdcee219d83d4fbc1939b79411c6e549e082f8b0abc1e9df223079dc5f784bf466f18a8ec9a7d52be039
|
7
|
+
data.tar.gz: 4d76b845a5b3af8ad142e8a1edb65309f9b637b755af129401f7fdcfcc6635c1deabb37665302649fc3eaa3f4cf73f2212114111bc594dbdf6e62b5d1956c7ed
|
data/CHANGELOG.md
CHANGED
@@ -1,18 +1,66 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.3.0] - 2024-10-22
|
4
|
+
|
5
|
+
**Improvements:**
|
6
|
+
|
7
|
+
- **Refactor `TimeReport` to use `TimeReportHelper` module for utility methods:**
|
8
|
+
|
9
|
+
- Extracted utility methods (`add_hashes`, `date_ranges`, `format_item`, `valid_date_format?`) into a new `TimeReportHelper` module.
|
10
|
+
- Updated `TimeReport` class to include `TimeReportHelper` module.
|
11
|
+
- Removed redundant utility methods from `TimeReport` class.
|
12
|
+
- Updated `display` method to use `process_time_entries` from `TimeReportHelper`.
|
13
|
+
- Updated `write_csv` method to use `write_csv_rows` from `TimeReportHelper`.
|
14
|
+
- Updated `print_time_block_chart` method to pass `colors` parameter to `format_tag_distribution`.
|
15
|
+
- Adjusted formatting in `total` method for better alignment.
|
16
|
+
|
17
|
+
- **Refactor `Timet::Formatter` to improve readability and modularity:**
|
18
|
+
|
19
|
+
- Introduced a constant `CHAR_MAPPING` to store block characters for different value ranges.
|
20
|
+
- Refactored `format_notes` method to use a more descriptive variable name for the maximum length.
|
21
|
+
- Updated `format_tag_distribution` method to accept `colors` parameter and pass it to `process_and_print_tags`.
|
22
|
+
- Extracted the logic for calculating `value` and `bar_length` into a separate method `calculate_value_and_bar_length`.
|
23
|
+
- Refactored `process_and_print_tags` to accept `colors` parameter and use the new `calculate_value_and_bar_length` method.
|
24
|
+
- Updated `print_time_block_chart` method to accept `colors` parameter and pass it to `print_blocks`.
|
25
|
+
- Refactored `print_blocks` method to accept `colors` and `start_time` parameters and use the new `print_time_blocks` method.
|
26
|
+
- Introduced `print_time_blocks` method to handle the printing of time blocks for each hour from the start time to 23.
|
27
|
+
- Introduced `get_formatted_block_char` method to retrieve the formatted block character and its associated tag for a given hour.
|
28
|
+
- Refactored `print_colored_block` method to use the `block` variable for clarity.
|
29
|
+
- Updated `get_block_char` method to use the `CHAR_MAPPING` constant for determining the block character.
|
30
|
+
|
31
|
+
- **Refactor `TimeHelper` methods and add new functionality:**
|
32
|
+
- Simplified nil checks in `format_time`, `timestamp_to_date`, and `timestamp_to_time` methods by using `unless` instead of `if`.
|
33
|
+
- Extracted the logic for calculating block end time and seconds into a new method `calculate_block_end_time_and_seconds`.
|
34
|
+
- Updated `count_seconds_per_hour_block` to use the new `calculate_block_end_time_and_seconds` method.
|
35
|
+
- Added a new method `append_tag_to_hour_blocks` to append a tag to each value in the `hour_blocks` hash.
|
36
|
+
- Removed the `aggregate_hash_values` method as it is no longer needed.
|
37
|
+
- Updated YARD documentation for all methods to reflect the changes.
|
38
|
+
|
39
|
+
**Bug fixes:**
|
40
|
+
|
41
|
+
- [ ] No bug fixes in this PR.
|
42
|
+
|
43
|
+
**Tasks:**
|
44
|
+
|
45
|
+
- Update `README.md` to reflect the changes.
|
46
|
+
- Update `Gemfile` and version to reflect the latest changes.
|
47
|
+
|
3
48
|
## [1.2.1] - 2024-10-18
|
4
49
|
|
5
50
|
**Improvements:**
|
51
|
+
|
6
52
|
- Updated the time block chart formatting to use square brackets for better visual representation.
|
7
53
|
- Refactored the `play_sound_and_notify` method to avoid redundant platform checks and introduced platform-specific session runners.
|
8
54
|
- Improved readability and maintainability of the `format_tag_distribution` method by extracting logic into a new private method.
|
9
55
|
- Updated the `rubocop` gem from `~> 1.65` to `~> 1.67`.
|
10
56
|
|
11
57
|
### Bug fixes:
|
58
|
+
|
12
59
|
- Fixed a `NoMethodError` caused by an undefined method `process_and_print_tags` in the `format_tag_distribution` method.
|
13
60
|
- Fixed line length violations in several files to comply with `rubocop` rules.
|
14
61
|
|
15
62
|
### Additional Considerations:
|
63
|
+
|
16
64
|
- The changes in this pull request should be thoroughly tested to ensure that they do not introduce any regressions.
|
17
65
|
- Future improvements could include further refactoring to extract more logic into separate methods or classes, depending on the complexity and requirements of the application.
|
18
66
|
|
data/README.md
CHANGED
@@ -19,7 +19,7 @@ Timet refers to a command-line tool designed to track your activities by recordi
|
|
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
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
|
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.
|
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
24
|
|
25
25
|
Example:
|
@@ -42,6 +42,8 @@ Tracked time report [today]:
|
|
42
42
|
Tag3: 50.0% ▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅
|
43
43
|
```
|
44
44
|
|
45
|
+

|
46
|
+
|
45
47
|
## Requirements
|
46
48
|
|
47
49
|
- Ruby version: >= 3.0.0
|
@@ -233,7 +235,7 @@ Many people have contacted me asking how to contribute. Any contribution, from a
|
|
233
235
|
|
234
236
|
**Bitcoin Address:**
|
235
237
|
```sh
|
236
|
-
bc1qkg9me2jsuhpzu2hp9kkpxagwtf9ewnyfl4kszl
|
238
|
+
bc1qkg9me2jsuhpzu2hp9kkpxagwtf9ewnyfl4kszl
|
237
239
|
```
|
238
240
|
|
239
241
|

|
data/lib/timet/formatter.rb
CHANGED
@@ -4,6 +4,18 @@ 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
|
+
CHAR_MAPPING = {
|
8
|
+
0..120 => '_',
|
9
|
+
121..450 => '▁',
|
10
|
+
451..900 => '▂',
|
11
|
+
901..1350 => '▃',
|
12
|
+
1351..1800 => '▄',
|
13
|
+
1801..2250 => '▅',
|
14
|
+
2251..2700 => '▆',
|
15
|
+
2701..3150 => '▇',
|
16
|
+
3151..3600 => '█'
|
17
|
+
}.freeze
|
18
|
+
|
7
19
|
# Formats the header of the time tracking report table.
|
8
20
|
#
|
9
21
|
# @return [void] This method does not return a value; it performs side effects such as printing
|
@@ -61,9 +73,10 @@ module Timet
|
|
61
73
|
# @note The method truncates the notes to a maximum of 20 characters and pads them to a fixed width.
|
62
74
|
def format_notes(notes)
|
63
75
|
spaces = 17
|
64
|
-
return ' ' * spaces
|
76
|
+
return ' ' * spaces unless notes
|
65
77
|
|
66
|
-
|
78
|
+
max_length = spaces - 3
|
79
|
+
notes = "#{notes.slice(0, max_length)}..." if notes.length > max_length
|
67
80
|
notes.ljust(spaces)
|
68
81
|
end
|
69
82
|
|
@@ -79,13 +92,13 @@ module Timet
|
|
79
92
|
#
|
80
93
|
# @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
|
81
94
|
# @return [void] This method outputs the formatted tag distribution to the console.
|
82
|
-
def format_tag_distribution(duration_by_tag)
|
95
|
+
def format_tag_distribution(duration_by_tag, colors)
|
83
96
|
total = duration_by_tag.values.sum
|
84
97
|
return unless total.positive?
|
85
98
|
|
86
99
|
factor = duration_by_tag.size < 3 ? 2 : 1
|
87
100
|
sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
|
88
|
-
process_and_print_tags(sorted_duration_by_tag, factor, total)
|
101
|
+
process_and_print_tags(sorted_duration_by_tag, factor, total, colors)
|
89
102
|
end
|
90
103
|
|
91
104
|
# Processes and prints the tag distribution information.
|
@@ -95,55 +108,78 @@ module Timet
|
|
95
108
|
# @param factor [Numeric] The factor used to adjust the bar length.
|
96
109
|
# @param total [Numeric] The total duration of all tags combined.
|
97
110
|
# @return [void] This method outputs the tag distribution information to the standard output.
|
98
|
-
def process_and_print_tags(
|
111
|
+
def process_and_print_tags(*args)
|
112
|
+
sorted_duration_by_tag, factor, total, colors = args
|
99
113
|
block = '▅'
|
100
114
|
sorted_duration_by_tag.each do |tag, duration|
|
101
|
-
value = (duration
|
102
|
-
|
103
|
-
color = rand(256)
|
104
|
-
puts "#{tag.rjust(8)}: #{value.to_s.rjust(7)}% \u001b[38;5;#{color}m#{block * bar_length}\u001b[0m"
|
115
|
+
value, bar_length = calculate_value_and_bar_length(duration, total, factor)
|
116
|
+
puts "#{tag.rjust(8)}: #{value.to_s.rjust(7)}% \u001b[38;5;#{colors[tag] + 1}m#{block * bar_length}\u001b[0m"
|
105
117
|
end
|
106
118
|
end
|
107
119
|
|
108
|
-
#
|
120
|
+
# Calculates the value and bar length for a given duration, total duration, and factor.
|
109
121
|
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
122
|
+
# @param duration [Numeric] The duration for the current tag.
|
123
|
+
# @param total [Numeric] The total duration.
|
124
|
+
# @param factor [Numeric] A factor to adjust the formatting.
|
125
|
+
# @return [Array<(Float, Integer)>] An array containing the calculated value and bar length.
|
114
126
|
#
|
115
|
-
# @param time_block [Hash] A hash where the keys are formatted hour strings
|
116
|
-
# (e.g., "00", "01") and the values are the corresponding
|
117
|
-
# values to determine the block character.
|
118
127
|
# @example
|
119
|
-
#
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
128
|
+
# calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
|
129
|
+
def calculate_value_and_bar_length(duration, total, factor)
|
130
|
+
value = (duration.to_f / total * 100).round(2)
|
131
|
+
bar_length = (value / factor).round
|
132
|
+
[value, bar_length]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Prints a time block chart based on the provided time block and colors.
|
136
|
+
#
|
137
|
+
# @param time_block [Hash] A hash where the keys are time blocks and the values are hashes of time slots and their
|
138
|
+
# corresponding values.
|
139
|
+
# Example: { "block1" => { 10 => "value1", 11 => "value2" }, "block2" => { 12 => "value3" } }
|
140
|
+
# @param colors [Hash] A hash where the keys are time slots and the values are the colors to be used
|
141
|
+
# for those slots.
|
142
|
+
# Example: { 10 => "red", 11 => "blue", 12 => "green" }
|
143
|
+
#
|
144
|
+
# @return [void] This method does not return a value; it prints the chart directly to the output.
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# time_block = { "block1" => { 10 => "value1", 11 => "value2" }, "block2" => { 12 => "value3" } }
|
148
|
+
# colors = { 10 => "red", 11 => "blue", 12 => "green" }
|
149
|
+
# print_time_block_chart(time_block, colors)
|
126
150
|
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
151
|
+
# @note This method relies on two helper methods: `print_header` and `print_blocks`.
|
152
|
+
# Ensure these methods are defined and available in the scope where `print_time_block_chart` is called.
|
153
|
+
#
|
154
|
+
# @see #print_header
|
155
|
+
# @see #print_blocks
|
156
|
+
def print_time_block_chart(time_block, colors)
|
157
|
+
start_time = time_block.values.map(&:keys).flatten.uniq.min.to_i
|
158
|
+
print_header(start_time)
|
159
|
+
print_blocks(time_block, colors, start_time)
|
131
160
|
end
|
132
161
|
|
133
162
|
# Prints the header of the time block chart.
|
134
163
|
#
|
135
|
-
#
|
136
|
-
#
|
164
|
+
# The header includes a visual representation of the time slots from the given start time to 23.
|
165
|
+
# Each time slot is formatted as a two-digit number and aligned to the right within a fixed width.
|
166
|
+
#
|
167
|
+
# @param start_time [Integer] The starting time for the chart. This should be an integer between 0 and 23.
|
168
|
+
#
|
169
|
+
# @return [void] This method does not return a value; it prints the header directly to the output.
|
137
170
|
#
|
138
171
|
# @example
|
139
|
-
# print_header
|
172
|
+
# print_header(10)
|
140
173
|
# # Output:
|
141
|
-
# #
|
174
|
+
# #
|
175
|
+
# # ⏳ ↦ [ 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
|
142
176
|
#
|
143
|
-
|
177
|
+
# @note The method assumes that the start_time is within the valid range of 0 to 23.
|
178
|
+
# If the start_time is outside this range, the output may not be as expected.
|
179
|
+
def print_header(start_time)
|
144
180
|
puts
|
145
|
-
print '⏳ ↦ [ '
|
146
|
-
(
|
181
|
+
print ' ⏳ ↦ [ '
|
182
|
+
(start_time..23).each { |hour| print format('%02d', hour).ljust(4) }
|
147
183
|
print ']'
|
148
184
|
puts
|
149
185
|
end
|
@@ -165,15 +201,72 @@ module Timet
|
|
165
201
|
# #
|
166
202
|
# # (followed by two newlines)
|
167
203
|
#
|
168
|
-
def print_blocks(time_block)
|
204
|
+
def print_blocks(time_block, colors, start_time)
|
169
205
|
return unless time_block
|
170
206
|
|
171
|
-
|
172
|
-
|
173
|
-
|
207
|
+
time_block.each_key do |item|
|
208
|
+
print "#{item} "
|
209
|
+
time_block_initial = time_block[item]
|
210
|
+
print_time_blocks(start_time, time_block_initial, colors)
|
211
|
+
puts
|
174
212
|
end
|
175
|
-
|
176
|
-
|
213
|
+
puts "\n"
|
214
|
+
end
|
215
|
+
|
216
|
+
# Prints time blocks for each hour from the start time to 23.
|
217
|
+
#
|
218
|
+
# @param start_time [Integer] The starting hour for printing time blocks.
|
219
|
+
# @param time_block_initial [Hash] A hash containing time block data, where keys are formatted hours and values
|
220
|
+
# are arrays containing block data.
|
221
|
+
# @param colors [Hash] A hash mapping tags to color codes.
|
222
|
+
# @return [void]
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
# time_block_initial = {
|
226
|
+
# '01' => ['block_char_data', 'tag']
|
227
|
+
# }
|
228
|
+
# colors = { 'tag' => 1 }
|
229
|
+
# print_time_blocks(1, time_block_initial, colors) # Prints time blocks for hours 1 to 23
|
230
|
+
def print_time_blocks(start_time, time_block_initial, colors)
|
231
|
+
(start_time..23).each do |hour|
|
232
|
+
tag, block_char = get_formatted_block_char(hour, time_block_initial)
|
233
|
+
print_colored_block(block_char, tag, colors)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns the formatted block character and its associated tag for a given hour.
|
238
|
+
#
|
239
|
+
# @param hour [Integer] The hour for which to retrieve the block character.
|
240
|
+
# @param time_block_initial [Hash] A hash containing time block data, where keys are formatted hours and values
|
241
|
+
# are arrays containing block data.
|
242
|
+
# @return [Array<(String, String)>] An array containing the tag and the block character.
|
243
|
+
#
|
244
|
+
# @example
|
245
|
+
# time_block_initial = {
|
246
|
+
# '01' => ['block_char_data', 'tag']
|
247
|
+
# }
|
248
|
+
# get_formatted_block_char(1, time_block_initial) #=> ['tag', 'block_char']
|
249
|
+
def get_formatted_block_char(hour, time_block_initial)
|
250
|
+
formatted_hour = format('%02d', hour)
|
251
|
+
hour_data = time_block_initial[formatted_hour]
|
252
|
+
tag = hour_data&.last
|
253
|
+
[tag, get_block_char(hour_data&.first)]
|
254
|
+
end
|
255
|
+
|
256
|
+
# Prints a colored block character based on the provided tag and block character.
|
257
|
+
#
|
258
|
+
# @param block_char [String] The block character to be printed.
|
259
|
+
# @param tag [String] The tag associated with the block character, used to determine the color.
|
260
|
+
# @param colors [Hash] A hash mapping tags to color codes.
|
261
|
+
#
|
262
|
+
# @example
|
263
|
+
# colors = { 'tag' => 1 }
|
264
|
+
# print_colored_block('X', 'tag', colors) # Prints a colored block character 'XX'
|
265
|
+
def print_colored_block(block_char, tag, colors)
|
266
|
+
color_code = colors[tag]
|
267
|
+
block = block_char * 2
|
268
|
+
colored_block = color_code ? "\u001b[38;5;#{color_code + 1}m#{block}\u001b[0m " : block
|
269
|
+
print colored_block.ljust(4)
|
177
270
|
end
|
178
271
|
|
179
272
|
# Determines the block character based on the value.
|
@@ -181,19 +274,9 @@ module Timet
|
|
181
274
|
# @param value [Integer] The value to determine the block character for.
|
182
275
|
# @return [String] The block character corresponding to the value.
|
183
276
|
def get_block_char(value)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
451..900 => '▂',
|
188
|
-
901..1350 => '▃',
|
189
|
-
1351..1800 => '▄',
|
190
|
-
1801..2250 => '▅',
|
191
|
-
2251..2700 => '▆',
|
192
|
-
2701..3150 => '▇',
|
193
|
-
3151..3600 => '█'
|
194
|
-
}
|
195
|
-
|
196
|
-
range_to_char.find { |range, _| range.include?(value) }&.last || ' '
|
277
|
+
return ' ' unless value
|
278
|
+
|
279
|
+
CHAR_MAPPING.find { |range, _| range.include?(value) }&.last || ' '
|
197
280
|
end
|
198
281
|
end
|
199
282
|
end
|
data/lib/timet/time_helper.rb
CHANGED
@@ -15,7 +15,7 @@ module Timet
|
|
15
15
|
# @example Format a timestamp
|
16
16
|
# TimeHelper.format_time(1633072800) # => '2021-10-01 12:00:00'
|
17
17
|
def self.format_time(timestamp)
|
18
|
-
return nil
|
18
|
+
return nil unless timestamp
|
19
19
|
|
20
20
|
Time.at(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
21
21
|
end
|
@@ -28,7 +28,7 @@ module Timet
|
|
28
28
|
# @example Convert a timestamp to a date string
|
29
29
|
# TimeHelper.timestamp_to_date(1633072800) # => '2021-10-01'
|
30
30
|
def self.timestamp_to_date(timestamp)
|
31
|
-
return nil
|
31
|
+
return nil unless timestamp
|
32
32
|
|
33
33
|
Time.at(timestamp).strftime('%Y-%m-%d')
|
34
34
|
end
|
@@ -41,7 +41,7 @@ module Timet
|
|
41
41
|
# @example Convert a timestamp to a time string
|
42
42
|
# TimeHelper.timestamp_to_time(1633072800) # => '12:00:00'
|
43
43
|
def self.timestamp_to_time(timestamp)
|
44
|
-
return nil
|
44
|
+
return nil unless timestamp
|
45
45
|
|
46
46
|
Time.at(timestamp).strftime('%H:%M:%S')
|
47
47
|
end
|
@@ -186,53 +186,51 @@ module Timet
|
|
186
186
|
# result = count_seconds_per_hour_block(start_time, end_time)
|
187
187
|
# # Output: {"08"=>1800, "09"=>1800, "10"=>3600, "11"=>1200}
|
188
188
|
#
|
189
|
-
def self.count_seconds_per_hour_block(start_time, end_time)
|
189
|
+
def self.count_seconds_per_hour_block(start_time, end_time, tag)
|
190
190
|
hour_blocks = Hash.new(0)
|
191
191
|
|
192
192
|
current_time = Time.at(start_time)
|
193
193
|
end_time = Time.at(end_time || current_timestamp)
|
194
194
|
|
195
195
|
while current_time < end_time
|
196
|
-
|
197
|
-
next_hour_boundary = Time.new(current_time.year, current_time.month, current_time.day, current_hour + 1)
|
198
|
-
|
199
|
-
block_end_time = [next_hour_boundary, end_time].min
|
200
|
-
seconds_in_block = (block_end_time - current_time).to_i
|
201
|
-
|
202
|
-
hour_block = current_time.strftime('%H')
|
203
|
-
hour_blocks[hour_block] += seconds_in_block
|
196
|
+
block_end_time, hour_blocks = calculate_block_end_time_and_seconds(current_time, end_time, hour_blocks)
|
204
197
|
|
205
198
|
current_time = block_end_time
|
206
199
|
end
|
207
200
|
|
208
|
-
hour_blocks
|
201
|
+
append_tag_to_hour_blocks(hour_blocks, tag)
|
209
202
|
end
|
210
203
|
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
214
|
-
#
|
215
|
-
#
|
216
|
-
#
|
217
|
-
#
|
218
|
-
# @return [Hash]
|
219
|
-
#
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
204
|
+
# Calculates the end time of the current block and the number of seconds in the block.
|
205
|
+
# Additionally, it updates the `hour_blocks` hash with the number of seconds for the current hour block.
|
206
|
+
#
|
207
|
+
# @param current_time [Time] The current time.
|
208
|
+
# @param end_time [Time] The end time of the overall period.
|
209
|
+
# @param hour_blocks [Hash] A hash where each key represents an hour block and the value is the number of seconds
|
210
|
+
# in that block.
|
211
|
+
# @return [Array<(Time, Hash)>] An array containing the end time of the current block and the updated
|
212
|
+
# `hour_blocks` hash.
|
213
|
+
def self.calculate_block_end_time_and_seconds(current_time, end_time, hour_blocks)
|
214
|
+
current_hour = current_time.hour
|
215
|
+
next_hour_boundary = Time.new(current_time.year, current_time.month, current_time.day, current_hour + 1)
|
216
|
+
|
217
|
+
block_end_time = [next_hour_boundary, end_time].min
|
218
|
+
seconds_in_block = (block_end_time - current_time).to_i
|
219
|
+
hour_block = current_time.strftime('%H')
|
220
|
+
hour_blocks[hour_block] += seconds_in_block
|
221
|
+
|
222
|
+
[block_end_time, hour_blocks]
|
223
|
+
end
|
224
|
+
|
225
|
+
# @param hour_blocks [Hash] A hash where each key represents an hour block and the value is some data associated
|
226
|
+
# with that hour block.
|
227
|
+
# @param tag [Object] The tag to append to each value in the hash.
|
228
|
+
# @return [Hash] The modified hash with the tag appended to each value.
|
229
|
+
def self.append_tag_to_hour_blocks(hour_blocks, tag)
|
230
|
+
hour_blocks.each do |key, value|
|
231
|
+
hour_blocks[key] = [value, tag]
|
235
232
|
end
|
233
|
+
hour_blocks
|
236
234
|
end
|
237
235
|
end
|
238
236
|
end
|
data/lib/timet/time_report.rb
CHANGED
@@ -4,6 +4,7 @@ require 'date'
|
|
4
4
|
require 'csv'
|
5
5
|
require_relative 'status_helper'
|
6
6
|
require_relative 'formatter'
|
7
|
+
require_relative 'time_report_helper'
|
7
8
|
|
8
9
|
module Timet
|
9
10
|
# The TimeReport class is responsible for displaying a report of tracked time
|
@@ -11,6 +12,7 @@ module Timet
|
|
11
12
|
# a formatted table with the relevant information.
|
12
13
|
class TimeReport
|
13
14
|
include Formatter
|
15
|
+
include TimeReportHelper
|
14
16
|
|
15
17
|
# Provides access to the database instance.
|
16
18
|
attr_reader :db
|
@@ -53,23 +55,14 @@ module Timet
|
|
53
55
|
return puts 'No tracked time found for the specified filter.' if items.empty?
|
54
56
|
|
55
57
|
format_table_header
|
56
|
-
duration_by_tag =
|
57
|
-
time_block = []
|
58
|
-
items.each_with_index do |item, idx|
|
59
|
-
date = TimeHelper.extract_date(items, idx)
|
60
|
-
display_time_entry(item, date)
|
61
|
-
time_block << TimeHelper.count_seconds_per_hour_block(item[1], item[2])
|
62
|
-
duration_by_tag[item[3]] += TimeHelper.calculate_duration(item[1], item[2])
|
63
|
-
end
|
58
|
+
time_block, duration_by_tag = process_time_entries
|
64
59
|
puts format_table_separator
|
65
60
|
total
|
66
61
|
|
67
|
-
|
68
|
-
|
69
|
-
print_time_block_chart(time_block_reverse)
|
70
|
-
end
|
62
|
+
colors = duration_by_tag.map { |x| x[0] }.sort.each_with_index.to_h
|
63
|
+
print_time_block_chart(time_block, colors)
|
71
64
|
|
72
|
-
format_tag_distribution(duration_by_tag)
|
65
|
+
format_tag_distribution(duration_by_tag, colors)
|
73
66
|
end
|
74
67
|
|
75
68
|
# Displays a single row of the report.
|
@@ -119,33 +112,10 @@ module Timet
|
|
119
112
|
def write_csv(file_name)
|
120
113
|
CSV.open(file_name, 'w') do |csv|
|
121
114
|
csv << %w[ID Start End Tag Notes]
|
122
|
-
|
123
|
-
csv << format_item(item)
|
124
|
-
end
|
115
|
+
write_csv_rows(csv)
|
125
116
|
end
|
126
117
|
end
|
127
118
|
|
128
|
-
# Formats an item for CSV export.
|
129
|
-
#
|
130
|
-
# @param item [Array] The item to format.
|
131
|
-
#
|
132
|
-
# @return [Array] The formatted item.
|
133
|
-
#
|
134
|
-
# @example Format an item for CSV export
|
135
|
-
# format_item(item)
|
136
|
-
#
|
137
|
-
# @note The method formats the item's ID, start time, end time, tag, and notes.
|
138
|
-
def format_item(item)
|
139
|
-
id, start_time, end_time, tags, notes = item
|
140
|
-
[
|
141
|
-
id,
|
142
|
-
TimeHelper.format_time(start_time),
|
143
|
-
TimeHelper.format_time(end_time),
|
144
|
-
tags,
|
145
|
-
notes
|
146
|
-
]
|
147
|
-
end
|
148
|
-
|
149
119
|
# Displays a single time entry in the report.
|
150
120
|
#
|
151
121
|
# @param item [Array] The item to display.
|
@@ -180,7 +150,7 @@ module Timet
|
|
180
150
|
total = @items.map do |item|
|
181
151
|
TimeHelper.calculate_duration(item[1], item[2])
|
182
152
|
end.sum
|
183
|
-
puts "|#{' ' *
|
153
|
+
puts "|#{' ' * 43}\033[94mTotal: | #{@db.seconds_to_hms(total).rjust(8)} |\033[0m |"
|
184
154
|
puts format_table_separator
|
185
155
|
end
|
186
156
|
|
@@ -208,24 +178,6 @@ module Timet
|
|
208
178
|
end
|
209
179
|
end
|
210
180
|
|
211
|
-
# Provides predefined date ranges for filtering.
|
212
|
-
#
|
213
|
-
# @return [Hash] A hash containing predefined date ranges.
|
214
|
-
#
|
215
|
-
# @example Get the predefined date ranges
|
216
|
-
# date_ranges
|
217
|
-
#
|
218
|
-
# @note The method returns a hash with predefined date ranges for 'today', 'yesterday', 'week', and 'month'.
|
219
|
-
def date_ranges
|
220
|
-
today = Date.today
|
221
|
-
{
|
222
|
-
'today' => [today, nil],
|
223
|
-
'yesterday' => [today - 1, nil],
|
224
|
-
'week' => [today - 7, today + 1],
|
225
|
-
'month' => [today - 30, today + 1]
|
226
|
-
}
|
227
|
-
end
|
228
|
-
|
229
181
|
# Filters the items by date range and tag.
|
230
182
|
#
|
231
183
|
# @param start_date [Date] The start date of the range.
|
@@ -273,22 +225,5 @@ module Timet
|
|
273
225
|
|
274
226
|
'today'
|
275
227
|
end
|
276
|
-
|
277
|
-
# Validates the date format.
|
278
|
-
#
|
279
|
-
# @param date_string [String] The date string to validate.
|
280
|
-
#
|
281
|
-
# @return [Boolean] True if the date format is valid, otherwise false.
|
282
|
-
#
|
283
|
-
# @example Validate the date format
|
284
|
-
# valid_date_format?('2021-10-01') # => true
|
285
|
-
#
|
286
|
-
# @note The method validates the date format for single dates and date ranges.
|
287
|
-
def valid_date_format?(date_string)
|
288
|
-
date_format_single = /^\d{4}-\d{2}-\d{2}$/
|
289
|
-
date_format_range = /^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/
|
290
|
-
|
291
|
-
date_string.match?(date_format_single) || date_string.match?(date_format_range)
|
292
|
-
end
|
293
228
|
end
|
294
229
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timet
|
4
|
+
# The TimeReportHelper module provides a collection of utility methods for processing and formatting time report data.
|
5
|
+
# It includes methods for processing time entries, handling time blocks, formatting items for CSV export,
|
6
|
+
# and validating date formats.
|
7
|
+
# This module is designed to be included in classes that require time report processing functionalities.
|
8
|
+
module TimeReportHelper
|
9
|
+
# Processes each time entry in the items array and updates the time block and duration by tag.
|
10
|
+
#
|
11
|
+
# @return [Array<(Hash, Hash)>] An array containing the updated time block and duration by tag.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# items = [
|
15
|
+
# [start_time1, end_time1, tag1],
|
16
|
+
# [start_time2, end_time2, tag2]
|
17
|
+
# ]
|
18
|
+
# process_time_entries
|
19
|
+
# #=> [{ '2024-10-21' => { 8 => [duration1, tag1], 9 => [duration2, tag2] } }, { tag1 => total_duration1,
|
20
|
+
# tag2 => total_duration2 }]
|
21
|
+
def process_time_entries
|
22
|
+
duration_by_tag = Hash.new(0)
|
23
|
+
time_block = Hash.new { |hash, key| hash[key] = {} }
|
24
|
+
|
25
|
+
items.each_with_index do |item, idx|
|
26
|
+
display_time_entry(item, TimeHelper.extract_date(items, idx))
|
27
|
+
start_time = item[1]
|
28
|
+
end_time = item[2]
|
29
|
+
tag = item[3]
|
30
|
+
time_block = process_time_block_item(start_time, end_time, tag, time_block)
|
31
|
+
|
32
|
+
duration_by_tag[tag] += TimeHelper.calculate_duration(start_time, end_time)
|
33
|
+
end
|
34
|
+
[time_block, duration_by_tag]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Processes a time block item and updates the time block hash.
|
38
|
+
#
|
39
|
+
# @param start_time [Time] The start time of the time block.
|
40
|
+
# @param end_time [Time] The end time of the time block.
|
41
|
+
# @param tag [String] The tag associated with the time block.
|
42
|
+
# @param time_block [Hash] A hash containing time block data, where keys are dates and values are hashes of time
|
43
|
+
# slots and their corresponding values.
|
44
|
+
# @return [Hash] The updated time block hash.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# start_time = Time.new(2024, 10, 21, 8, 0, 0)
|
48
|
+
# end_time = Time.new(2024, 10, 21, 9, 0, 0)
|
49
|
+
# tag = 'work'
|
50
|
+
# time_block = {}
|
51
|
+
# process_time_block_item(start_time, end_time, tag, time_block)
|
52
|
+
# #=> { '2024-10-21' => { 8 => [duration, 'work'] } }
|
53
|
+
def process_time_block_item(*args)
|
54
|
+
start_time, end_time, tag, time_block = args
|
55
|
+
block_hour = TimeHelper.count_seconds_per_hour_block(start_time, end_time, tag)
|
56
|
+
date_line = TimeHelper.timestamp_to_date(start_time)
|
57
|
+
time_block[date_line] = add_hashes(time_block[date_line], block_hour)
|
58
|
+
time_block
|
59
|
+
end
|
60
|
+
|
61
|
+
# Provides predefined date ranges for filtering.
|
62
|
+
#
|
63
|
+
# @return [Hash] A hash containing predefined date ranges.
|
64
|
+
#
|
65
|
+
# @example Get the predefined date ranges
|
66
|
+
# date_ranges
|
67
|
+
#
|
68
|
+
# @note The method returns a hash with predefined date ranges for 'today', 'yesterday', 'week', and 'month'.
|
69
|
+
def date_ranges
|
70
|
+
today = Date.today
|
71
|
+
tomorrow = today + 1
|
72
|
+
{
|
73
|
+
'today' => [today, nil],
|
74
|
+
'yesterday' => [today - 1, nil],
|
75
|
+
'week' => [today - 7, tomorrow],
|
76
|
+
'month' => [today - 30, tomorrow]
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Formats an item for CSV export.
|
81
|
+
#
|
82
|
+
# @param item [Array] The item to format.
|
83
|
+
#
|
84
|
+
# @return [Array] The formatted item.
|
85
|
+
#
|
86
|
+
# @example Format an item for CSV export
|
87
|
+
# format_item(item)
|
88
|
+
#
|
89
|
+
# @note The method formats the item's ID, start time, end time, tag, and notes.
|
90
|
+
def format_item(item)
|
91
|
+
id, start_time, end_time, tags, notes = item
|
92
|
+
[
|
93
|
+
id,
|
94
|
+
TimeHelper.format_time(start_time),
|
95
|
+
TimeHelper.format_time(end_time),
|
96
|
+
tags,
|
97
|
+
notes
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Validates the date format.
|
102
|
+
#
|
103
|
+
# @param date_string [String] The date string to validate.
|
104
|
+
#
|
105
|
+
# @return [Boolean] True if the date format is valid, otherwise false.
|
106
|
+
#
|
107
|
+
# @example Validate the date format
|
108
|
+
# valid_date_format?('2021-10-01') # => true
|
109
|
+
#
|
110
|
+
# @note The method validates the date format for single dates and date ranges.
|
111
|
+
def valid_date_format?(date_string)
|
112
|
+
date_format_single = /^\d{4}-\d{2}-\d{2}$/
|
113
|
+
date_format_range = /^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/
|
114
|
+
|
115
|
+
date_string.match?(date_format_single) || date_string.match?(date_format_range)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Merges two hashes, summing the numeric values of corresponding keys.
|
119
|
+
#
|
120
|
+
# @param base_hash [Hash] The base hash to which the additional hash will be merged.
|
121
|
+
# @param additional_hash [Hash] The additional hash whose values will be added to the base hash.
|
122
|
+
# @return [Hash] A new hash with the summed values.
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# base_hash = { 'key1' => [10, 'tag1'], 'key2' => [20, 'tag2'] }
|
126
|
+
# additional_hash = { 'key1' => [5, 'tag1'], 'key3' => [15, 'tag3'] }
|
127
|
+
# add_hashes(base_hash, additional_hash)
|
128
|
+
# #=> { 'key1' => [15, 'tag1'], 'key2' => [20, 'tag2'], 'key3' => [15, 'tag3'] }
|
129
|
+
def add_hashes(base_hash, additional_hash)
|
130
|
+
base_hash.merge(additional_hash) do |_key, old_value, new_value|
|
131
|
+
summed_number = old_value[0] + new_value[0]
|
132
|
+
[summed_number, old_value[1]]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Writes the CSV rows for the time report.
|
137
|
+
#
|
138
|
+
# @param csv [CSV] The CSV object to which the rows will be written.
|
139
|
+
# @return [void]
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# csv = CSV.new(file)
|
143
|
+
# write_csv_rows(csv)
|
144
|
+
def write_csv_rows(csv)
|
145
|
+
items.each do |item|
|
146
|
+
csv << format_item(item)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/timet/version.rb
CHANGED
data/monthly_report.webp
ADDED
Binary file
|
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
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Vielma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -86,8 +86,10 @@ files:
|
|
86
86
|
- lib/timet/status_helper.rb
|
87
87
|
- lib/timet/time_helper.rb
|
88
88
|
- lib/timet/time_report.rb
|
89
|
+
- lib/timet/time_report_helper.rb
|
89
90
|
- lib/timet/validation_edit_helper.rb
|
90
91
|
- lib/timet/version.rb
|
92
|
+
- monthly_report.webp
|
91
93
|
- sig/timet.rbs
|
92
94
|
- timet.webp
|
93
95
|
homepage: https://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|