timet 1.5.5 → 1.5.7
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +57 -0
- data/lib/timet/application.rb +37 -9
- data/lib/timet/block_char_helper.rb +33 -0
- data/lib/timet/database.rb +11 -11
- data/lib/timet/item_data_helper.rb +59 -0
- data/lib/timet/table.rb +2 -1
- data/lib/timet/time_block_chart.rb +85 -111
- data/lib/timet/time_helper.rb +17 -0
- data/lib/timet/time_report.rb +9 -9
- data/lib/timet/time_report_helper.rb +1 -116
- data/lib/timet/time_update_helper.rb +93 -0
- data/lib/timet/time_validation_helper.rb +174 -0
- data/lib/timet/utils.rb +130 -0
- data/lib/timet/validation_edit_helper.rb +115 -129
- data/lib/timet/version.rb +2 -2
- data/lib/timet/week_info.rb +62 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 488c0136f2b11a62046a2f507b054d346a5def75dc3a32609ea51872bb411e6b
|
4
|
+
data.tar.gz: c28bbe32a4558ab6755e438ba7c5a53f879d6c036ae68706782b62d8ca26133c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1e74dfa8e0d6fdacf0fd8c458ade7667e2f2722376aea9307a5dab7b68b6a0a561b475432cf666564ba6664c18b6f80f94aed0bd88c3f6703ead36aa5d1cf84
|
7
|
+
data.tar.gz: 70608773c6f504e38637b7d4b2d71a852d7ed20c5679170c082d302748e1e3e6e4be2d1d4b909ce49e2ae45052dc3ac2cd02ba9a51106aba723211d3efa3c66a
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,60 @@
|
|
1
|
+
## [1.5.7] - 2025-05-16
|
2
|
+
|
3
|
+
**Improvements:**
|
4
|
+
|
5
|
+
- Extracted collision checks in [`ValidationEditHelper`](lib/timet/validation_edit_helper.rb) into dedicated private methods (`check_collision_with_previous_item`, `check_collision_with_next_item`).
|
6
|
+
- Enhanced time collision validation in [`ValidationEditHelper`](lib/timet/validation_edit_helper.rb): renamed `validate_collision` to `validate_time_collisions`, added a `field` argument for specific checks ('start'/'end'), implemented distinct collision logic, and updated `perform_validation` to use the new method for both fields.
|
7
|
+
- Improved time validation logic structure by extracting `determine_start_base_date_time` and `determine_end_base_date_time` from `determine_base_date_time`, and introducing `validate_time_order` to centralize start/end time order validation.
|
8
|
+
- Centralized datetime creation by introducing `create_datetime_from_components` in [`TimeHelper`](lib/timet/time_helper.rb).
|
9
|
+
- Improved time validation and determination logic in [`TimeValidationHelper`](lib/timet/time_validation_helper.rb) and [`ValidationEditHelper`](lib/timet/validation_edit_helper.rb): refactored `determine_base_date_time` to use a `case` statement, extracted `validate_time_difference` for 24-hour difference validation, and removed the unused `time_str` parameter from `determine_and_create_datetime`.
|
10
|
+
- Centralized time validation logic to the new [`TimeValidationHelper`](lib/timet/time_validation_helper.rb) module, moving relevant methods from [`ValidationEditHelper`](lib/timet/validation_edit_edit_helper.rb).
|
11
|
+
- Refactored time validation and item data helpers by moving time validation logic to [`TimeUpdateHelper`](lib/timet/time_update_helper.rb) and item data fetching methods to [`ItemDataHelper`](lib/timet/item_data_helper.rb).
|
12
|
+
- Extracted time parsing, base date determination, datetime creation, end time adjustment, and validation logic into dedicated private methods in [`lib/timet/validation_edit_helper.rb`](lib/timet/validation_edit_helper.rb).
|
13
|
+
- Improved time validation and editing logic in [`ValidationEditHelper`](lib/timet/validation_edit_helper.rb): anchored end time date to start time date, handled end time on the next day, enforced < 24 hour duration, and preserved original time on empty input.
|
14
|
+
- Centralized utility methods into a new [`Timet::Utils`](lib/timet/utils.rb) module.
|
15
|
+
- Improved [`WeekInfo`](lib/timet/week_info.rb) initialization to accept a `Date` object and display string, and refactored its responsibilities for clarity and Reek compliance.
|
16
|
+
- Improved [`TimeBlockChart`](lib/timet/time_block_chart.rb) readability by replacing magic numbers with constants and ensuring chronological date order in `print_blocks`.
|
17
|
+
- Refactored [`TimeBlockChart`](lib/timet/time_block_chart.rb) for clarity and Reek compliance, moving separator printing responsibility and the `CHAR_MAPPING` constant to appropriate locations/helpers.
|
18
|
+
- Refactored database initialization logic in [`Timet::Application`](lib/timet/application.rb) for clarity and testability, encapsulating `initialize_database` within a `no_commands` block.
|
19
|
+
- Updated gem dependencies, including `aws-sdk-s3`, `icalendar`, `rubocop`, and `rubocop-rspec`.
|
20
|
+
|
21
|
+
**Bug Fixes:**
|
22
|
+
|
23
|
+
- Corrected `perform_validation` in [`ValidationEditHelper`](lib/timet/validation_edit_helper.rb) to accept and utilize the `item` object for correct context in collision checks.
|
24
|
+
- Modified `create_new_datetime` to derive date components from the parsed time component, allowing users to specify a full date and time for 'start' or 'end' fields.
|
25
|
+
- Fixed a bug in [`WeekInfo#format_and_print_date_info`](lib/timet/week_info.rb) where weekend days were incorrectly checked using "Sa" and "Su".
|
26
|
+
- Handled empty `@time_block` in [`TimeBlockChart#initialize`](lib/timet/time_block_chart.rb) to prevent errors.
|
27
|
+
|
28
|
+
## [1.5.6] - 2025-04-07
|
29
|
+
|
30
|
+
**Improvements:**
|
31
|
+
|
32
|
+
- **Enhanced Edit Command:** The `edit` command now interactively prompts the user for both the field to edit and its new value if they are not provided as command-line arguments.
|
33
|
+
- **Improved Database Initialization:** Database initialization logic has been extracted into a separate `initialize_database` method within the `Application` class, improving code organization and readability.
|
34
|
+
- **Refactored `validate_and_update`:** The `validate_and_update` method in `ValidationEditHelper` has been refactored to handle both time fields and other fields more robustly. It now raises an `ArgumentError` for invalid fields and returns the updated item.
|
35
|
+
- **Enhanced Security:** The `update_item` method in the `Database` class now utilizes parameterized queries to prevent SQL injection vulnerabilities.
|
36
|
+
- **Improved Data Integrity:** The `update_time_columns` method in the `Database` class now correctly updates the `updated_at` and `created_at` columns to reflect the current time.
|
37
|
+
- **Code Clarity:** Increased the `Metrics/MethodLength` max length to 15 in `.rubocop.yml` to accommodate refactored code.
|
38
|
+
- **Dependency Updates:**
|
39
|
+
- Updated `aws-sdk-s3` to version 1.183.
|
40
|
+
- Updated `csv` to version 3.3.3.
|
41
|
+
- Updated `diff-lcs` to version 1.6.1.
|
42
|
+
- Updated `json` to version 2.10.2.
|
43
|
+
- Updated `parser` to version 3.3.7.4.
|
44
|
+
- Updated `rubocop` to version 1.75.2.
|
45
|
+
- Updated `rubocop-ast` to version 1.44.0.
|
46
|
+
- Added `prism` to version 1.4.0.
|
47
|
+
- Added `logger` to version 1.7.0.
|
48
|
+
- **Test Coverage:** Added new tests to `application_spec.rb` to cover the `edit` command, and to `database_spec.rb` to cover the `update_item` and `update_time_columns` methods. Refactored tests in `validation_edit_helper_spec.rb` to cover changes in `validate_and_update`.
|
49
|
+
- **Git Ignore:** Added `.qodo` to `.gitignore` to prevent it from being added to the repository.
|
50
|
+
|
51
|
+
**Bug Fixes:**
|
52
|
+
|
53
|
+
- **Edit Command:** The `edit` command now correctly updates the database with the new value.
|
54
|
+
- **`update_item`:** The `update_item` method is no longer vulnerable to SQL injection.
|
55
|
+
- **`update_time_columns`:** The `update_time_columns` method now correctly updates the `updated_at` and `created_at` columns.
|
56
|
+
- **.qodo file:** The .qodo file is now ignored by git.
|
57
|
+
|
1
58
|
## [1.5.5] - 2025-02-26
|
2
59
|
|
3
60
|
**Improvements:**
|
data/lib/timet/application.rb
CHANGED
@@ -51,16 +51,43 @@ module Timet
|
|
51
51
|
|
52
52
|
def initialize(*args)
|
53
53
|
super
|
54
|
+
config = args[2] || {} # Third argument is the config hash
|
55
|
+
initialize_database(config)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Initializes the database connection based on the provided options.
|
59
|
+
#
|
60
|
+
# This method determines how to initialize the database connection based on the options provided.
|
61
|
+
# It supports injecting a database instance for testing, using a fallback for RSpec, and initializing
|
62
|
+
# a production database based on valid command arguments.
|
63
|
+
#
|
64
|
+
# @param options [Hash] A hash of options that may include a :database key for injecting a database instance.
|
65
|
+
# @option options [Database] :database An instance of the Database class to be used directly.
|
66
|
+
# @option options [Hash] :current_command The current command being executed, used to validate production
|
67
|
+
# database initialization.
|
68
|
+
#
|
69
|
+
# @return [void] This method does not return a value; it initializes the `@db` instance variable.
|
70
|
+
#
|
71
|
+
# @raise [SystemExit] If invalid arguments are provided for production database initialization.
|
72
|
+
no_commands do
|
73
|
+
def initialize_database(options)
|
74
|
+
db_from_options = options[:database]
|
54
75
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
76
|
+
# Allow injecting a database instance, primarily for testing
|
77
|
+
if db_from_options
|
78
|
+
@db = db_from_options
|
79
|
+
elsif defined?(RSpec)
|
80
|
+
# Fallback for RSpec if not injected (though injection is preferred)
|
60
81
|
@db = Database.new
|
61
82
|
else
|
62
|
-
|
63
|
-
|
83
|
+
# Production database initialization
|
84
|
+
command_name = options.dig(:current_command, :name)
|
85
|
+
if VALID_ARGUMENTS.include?(command_name)
|
86
|
+
@db = Database.new
|
87
|
+
else
|
88
|
+
warn 'Invalid arguments provided. Please check your input.'
|
89
|
+
exit(1)
|
90
|
+
end
|
64
91
|
end
|
65
92
|
end
|
66
93
|
end
|
@@ -219,13 +246,14 @@ module Timet
|
|
219
246
|
return puts "No tracked time found for id: #{id}" unless item
|
220
247
|
|
221
248
|
display_item(item)
|
222
|
-
|
249
|
+
if field.nil? || new_value.nil?
|
223
250
|
field = select_field_to_edit
|
224
251
|
new_value = prompt_for_new_value(item, field)
|
225
252
|
end
|
226
253
|
|
227
254
|
updated_item = validate_and_update(item, field, new_value)
|
228
|
-
|
255
|
+
@db.update_item(id, field, updated_item[FIELD_INDEX[field]])
|
256
|
+
display_item(updated_item)
|
229
257
|
end
|
230
258
|
|
231
259
|
desc 'delete (d) [id]', 'Delete task => tt d 23'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timet
|
4
|
+
#
|
5
|
+
# The BlockCharHelper module provides a utility method for getting the block character
|
6
|
+
# based on a given value.
|
7
|
+
#
|
8
|
+
module BlockCharHelper
|
9
|
+
# Character mapping for different time ranges
|
10
|
+
CHAR_MAPPING = {
|
11
|
+
0..120 => '_',
|
12
|
+
121..450 => ' ',
|
13
|
+
451..900 => '▂',
|
14
|
+
901..1350 => '▃',
|
15
|
+
1351..1800 => '▄',
|
16
|
+
1801..2250 => '▅',
|
17
|
+
2251..2700 => '▆',
|
18
|
+
2701..3150 => '▇',
|
19
|
+
3151..3600 => '█'
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# Gets the block character for a given value
|
23
|
+
#
|
24
|
+
# @param [Integer, nil] value The value
|
25
|
+
# @return [String] The block character
|
26
|
+
def self.get_block_char(value)
|
27
|
+
return ' ' unless value
|
28
|
+
|
29
|
+
mapping = CHAR_MAPPING.find { |range, _| range.include?(value) }
|
30
|
+
mapping ? mapping.last : ' '
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/timet/database.rb
CHANGED
@@ -115,9 +115,7 @@ module Timet
|
|
115
115
|
#
|
116
116
|
# @note The method executes SQL to update the specified field of the item with the given ID.
|
117
117
|
def update_item(id, field, value)
|
118
|
-
|
119
|
-
|
120
|
-
execute_sql("UPDATE items SET #{field}='#{value}', updated_at=#{Time.now.utc.to_i} WHERE id = #{id}")
|
118
|
+
execute_sql("UPDATE items SET #{field} = ?, updated_at = ? WHERE id = ?", [value, Time.now.utc.to_i, id])
|
121
119
|
end
|
122
120
|
|
123
121
|
# Deletes an item from the items table.
|
@@ -162,19 +160,21 @@ module Timet
|
|
162
160
|
result.empty? ? nil : result[0]
|
163
161
|
end
|
164
162
|
|
165
|
-
# Finds an item
|
163
|
+
# Finds an item by its ID.
|
166
164
|
#
|
167
|
-
# @param id [Integer] The ID of the item to
|
165
|
+
# @param id [Integer] The ID of the item to find.
|
168
166
|
#
|
169
|
-
# @return [Array, nil] The item as an array
|
167
|
+
# @return [Array, nil] The item as an array if found, nil otherwise.
|
170
168
|
#
|
171
169
|
# @example Find an item with ID 1
|
172
|
-
# find_item(1)
|
170
|
+
# find_item(1) # => [1, 1678886400, 1678890000, 'work', 'notes', nil, 1678890000, 1678886400, nil]
|
173
171
|
#
|
174
|
-
# @note The method executes SQL to find the item
|
172
|
+
# @note The method executes a SQL query to find the item by its ID.
|
173
|
+
# @note If the item is found, it returns the item as an array.
|
174
|
+
# @note If the item is not found, it returns nil.
|
175
175
|
def find_item(id)
|
176
|
-
result = execute_sql('SELECT * FROM items WHERE id = ?
|
177
|
-
result.
|
176
|
+
result = execute_sql('SELECT * FROM items WHERE id = ?', [id])
|
177
|
+
result.first.dup if result.any? # Add .dup to create a copy
|
178
178
|
end
|
179
179
|
|
180
180
|
# Fetches all items from the items table that have a start time greater than or equal to today.
|
@@ -316,7 +316,7 @@ module Timet
|
|
316
316
|
result.each do |item|
|
317
317
|
id = item[0]
|
318
318
|
end_time = item[2]
|
319
|
-
execute_sql(
|
319
|
+
execute_sql('UPDATE items SET updated_at = ?, created_at = ? WHERE id = ?', [end_time, end_time, id])
|
320
320
|
end
|
321
321
|
end
|
322
322
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timet
|
4
|
+
# Helper methods for fetching item data.
|
5
|
+
module ItemDataHelper
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Fetches the start time of a tracking item.
|
9
|
+
#
|
10
|
+
# @param item [Array] The tracking item.
|
11
|
+
#
|
12
|
+
# @return [Integer] The start time in epoch format.
|
13
|
+
#
|
14
|
+
# @example Fetch the start time of a tracking item
|
15
|
+
# fetch_item_start(item)
|
16
|
+
def fetch_item_start(item)
|
17
|
+
item[Timet::Application::FIELD_INDEX['start']]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Fetches the end time of a tracking item.
|
21
|
+
#
|
22
|
+
# @param item [Array] The tracking item.
|
23
|
+
#
|
24
|
+
# @return [Integer] The end time in epoch format.
|
25
|
+
#
|
26
|
+
# @example Fetch the end time of a tracking item
|
27
|
+
# fetch_item_end(item)
|
28
|
+
def fetch_item_end(item)
|
29
|
+
item[Timet::Application::FIELD_INDEX['end']] || TimeHelper.current_timestamp
|
30
|
+
end
|
31
|
+
|
32
|
+
# Fetches the end time of the tracking item before the current one.
|
33
|
+
#
|
34
|
+
# @param db [Timet::Database] The database instance.
|
35
|
+
# @param id [Integer] The ID of the current tracking item.
|
36
|
+
# @param item_start [Integer] The start time of the current tracking item.
|
37
|
+
#
|
38
|
+
# @return [Integer] The end time of the previous tracking item in epoch format.
|
39
|
+
#
|
40
|
+
# @example Fetch the end time of the previous tracking item
|
41
|
+
# fetch_item_before_end(db, 1, 1633072800)
|
42
|
+
def fetch_item_before_end(db, id, item_start)
|
43
|
+
db.find_item(id - 1)&.dig(Timet::Application::FIELD_INDEX['end']) || item_start
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fetches the start time of the tracking item after the current one.
|
47
|
+
#
|
48
|
+
# @param db [Timet::Database] The database instance.
|
49
|
+
# @param id [Integer] The ID of the current tracking item.
|
50
|
+
#
|
51
|
+
# @return [Integer] The start time of the next tracking item in epoch format.
|
52
|
+
#
|
53
|
+
# @example Fetch the start time of the next tracking item
|
54
|
+
# fetch_item_after_start(db, id)
|
55
|
+
def fetch_item_after_start(db, id)
|
56
|
+
db.find_item(id + 1)&.dig(Timet::Application::FIELD_INDEX['start']) || TimeHelper.current_timestamp
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/timet/table.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# require_relative 'color_codes'
|
4
4
|
require 'timet/time_report_helper'
|
5
|
+
require_relative 'utils'
|
5
6
|
module Timet
|
6
7
|
# This class is responsible for formatting the output of the `timet` application.
|
7
8
|
# It provides methods for formatting the table header, separators, and rows.
|
@@ -123,7 +124,7 @@ module Timet
|
|
123
124
|
|
124
125
|
block_hour = TimeHelper.count_seconds_per_hour_block(start_time, end_time, tag)
|
125
126
|
date_line = TimeHelper.timestamp_to_date(start_time)
|
126
|
-
time_block[date_line] = add_hashes(time_block[date_line], block_hour)
|
127
|
+
time_block[date_line] = Timet::Utils.add_hashes(time_block[date_line], block_hour)
|
127
128
|
time_block
|
128
129
|
end
|
129
130
|
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'week_info'
|
4
|
+
require_relative 'block_char_helper'
|
5
|
+
|
3
6
|
module Timet
|
4
7
|
#
|
5
8
|
# The TimeBlockChart class is responsible for generating and printing a visual representation
|
@@ -18,22 +21,18 @@ module Timet
|
|
18
21
|
# @attr_reader [Integer] start_hour The starting hour of the time block
|
19
22
|
# @attr_reader [Integer] end_hour The ending hour of the time block
|
20
23
|
class TimeBlockChart
|
21
|
-
# Character mapping for different time ranges
|
22
|
-
CHAR_MAPPING = {
|
23
|
-
0..120 => '_',
|
24
|
-
121..450 => '▁',
|
25
|
-
451..900 => '▂',
|
26
|
-
901..1350 => '▃',
|
27
|
-
1351..1800 => '▄',
|
28
|
-
1801..2250 => '▅',
|
29
|
-
2251..2700 => '▆',
|
30
|
-
2701..3150 => '▇',
|
31
|
-
3151..3600 => '█'
|
32
|
-
}.freeze
|
33
|
-
|
34
24
|
# Separator character for the chart
|
35
25
|
SEPARATOR_CHAR = '░'
|
36
26
|
|
27
|
+
# Width of the date/week string content (e.g., "02 2023-10-01 Fri")
|
28
|
+
DATE_WEEK_CONTENT_WIDTH = 17
|
29
|
+
|
30
|
+
# Width of the '┆- ' part after the date/week string
|
31
|
+
DATE_WEEK_BORDER_WIDTH = 3
|
32
|
+
|
33
|
+
# Width of the total hours column including the border
|
34
|
+
TOTAL_HOURS_COLUMN_WIDTH = 4
|
35
|
+
|
37
36
|
# Initializes a new TimeBlockChart instance.
|
38
37
|
#
|
39
38
|
# This method sets up the time block chart by processing the time entries from the provided table
|
@@ -50,8 +49,13 @@ module Timet
|
|
50
49
|
# @see Table#process_time_entries
|
51
50
|
def initialize(table)
|
52
51
|
@time_block = table.process_time_entries(display: false)
|
53
|
-
|
54
|
-
|
52
|
+
hours = @time_block.values.map(&:keys).flatten.uniq
|
53
|
+
if hours.empty?
|
54
|
+
@start_hour = @end_hour = 0
|
55
|
+
return
|
56
|
+
end
|
57
|
+
@start_hour = hours.min.to_i
|
58
|
+
@end_hour = hours.max.to_i
|
55
59
|
end
|
56
60
|
|
57
61
|
# Prints the time block chart.
|
@@ -59,13 +63,14 @@ module Timet
|
|
59
63
|
# This method formats and prints the time block chart, including the header and the time blocks
|
60
64
|
# for each entry. The chart is color-coded based on the provided color mapping for different tags.
|
61
65
|
#
|
62
|
-
# @param table [Hash] The time block data to be displayed in the chart.
|
63
66
|
# @param colors [Hash] A mapping of tags to colors, used to color-code the time blocks.
|
64
67
|
# @return [void] This method does not return a value; it performs side effects such as printing the chart.
|
65
68
|
#
|
66
69
|
# @example Print a time block chart
|
70
|
+
# # Assuming 'table' is an instance of Timet::Table
|
67
71
|
# chart = TimeBlockChart.new(table)
|
68
|
-
#
|
72
|
+
# colors = { "work" => 0, "break" => 1 } # Example color mapping
|
73
|
+
# chart.print_time_block_chart(colors)
|
69
74
|
#
|
70
75
|
# @note
|
71
76
|
# - The method first prints the header of the chart, which includes the time range.
|
@@ -74,9 +79,11 @@ module Timet
|
|
74
79
|
#
|
75
80
|
# @see #print_header
|
76
81
|
# @see #print_blocks
|
77
|
-
def print_time_block_chart(
|
82
|
+
def print_time_block_chart(colors)
|
83
|
+
return puts 'No time-block data to display.' if @no_data
|
84
|
+
|
78
85
|
print_header
|
79
|
-
print_blocks(
|
86
|
+
print_blocks(colors)
|
80
87
|
end
|
81
88
|
|
82
89
|
private
|
@@ -86,48 +93,64 @@ module Timet
|
|
86
93
|
# @return [void]
|
87
94
|
def print_header
|
88
95
|
puts
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
96
|
+
print_hours_row
|
97
|
+
# Dynamically build header using width constants
|
98
|
+
left = "┌#{'╴' * (DATE_WEEK_CONTENT_WIDTH - 11)}"
|
99
|
+
week = 'W'.center(DATE_WEEK_BORDER_WIDTH, '╴')
|
100
|
+
clock = '⏰'.center(DATE_WEEK_BORDER_WIDTH + 4, '╴')
|
101
|
+
hours_width = (@end_hour - @start_hour + 1) * 4
|
102
|
+
hours = '╴' * hours_width
|
103
|
+
right = "#{'╴' * (TOTAL_HOURS_COLUMN_WIDTH - 1)}┼"
|
104
|
+
header = left.gray + week.gray + clock.gray + '┼'.gray + hours.gray + right.gray
|
105
|
+
puts header
|
93
106
|
end
|
94
107
|
|
95
|
-
# Prints the
|
108
|
+
# Prints the hours row in the header.
|
96
109
|
#
|
97
|
-
#
|
98
|
-
|
99
|
-
|
110
|
+
# @return [void]
|
111
|
+
def print_hours_row
|
112
|
+
left_margin = DATE_WEEK_CONTENT_WIDTH + DATE_WEEK_BORDER_WIDTH - 1
|
113
|
+
print ' ' * left_margin
|
114
|
+
(@start_hour..@end_hour).each { |hour| print format('%02d', hour).rjust(4) }
|
115
|
+
puts
|
116
|
+
end
|
117
|
+
|
118
|
+
# Prints the main body of the time block chart, including date rows and corresponding time blocks.
|
100
119
|
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
120
|
+
# This method iterates over each date present in the `@time_block` instance variable,
|
121
|
+
# which is expected to be a hash mapping date strings to hourly time block data.
|
122
|
+
# For each date, it:
|
123
|
+
# 1. Displays week and date information using `WeekInfo`.
|
124
|
+
# 2. Prints the visual time blocks for the hours of the day using `#print_time_blocks`.
|
125
|
+
# 3. Calculates and prints the total hours logged for that day using `#calculate_and_print_hours`.
|
126
|
+
# After processing all dates, it prints a chart footer using `#print_footer`.
|
105
127
|
#
|
106
|
-
# @
|
107
|
-
#
|
108
|
-
#
|
128
|
+
# @param colors [Hash] A mapping of tags to color indices. This is used by `#print_time_blocks`
|
129
|
+
# to color-code the visual time blocks. Example: `{ "work" => 0, "break" => 1 }`
|
130
|
+
# @return [void] This method does not return a value; it prints directly to the console.
|
109
131
|
#
|
110
132
|
# @note
|
111
|
-
# -
|
112
|
-
# -
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
# - A footer is printed at the end to provide a visual separation.
|
133
|
+
# - This is a private method, primarily called by `#print_time_block_chart`.
|
134
|
+
# - It returns early if `@time_block` is `nil`. If `@time_block` is an empty hash,
|
135
|
+
# no date-specific rows are printed, but the chart footer is still rendered.
|
136
|
+
# - Relies on `@start_hour` and `@end_hour` instance variables being correctly set
|
137
|
+
# during the `TimeBlockChart`'s initialization.
|
117
138
|
#
|
118
|
-
# @see #format_and_print_date_info
|
139
|
+
# @see WeekInfo#format_and_print_date_info
|
119
140
|
# @see #print_time_blocks
|
120
141
|
# @see #calculate_and_print_hours
|
121
142
|
# @see #print_footer
|
122
|
-
def print_blocks(
|
123
|
-
return unless
|
143
|
+
def print_blocks(colors)
|
144
|
+
return unless @time_block
|
124
145
|
|
125
146
|
weeks = []
|
126
|
-
@time_block.
|
127
|
-
|
128
|
-
day =
|
147
|
+
@time_block.keys.sort.each do |date_string| # ISO-date strings sort naturally
|
148
|
+
date_object = Date.parse(date_string)
|
149
|
+
day = date_object.strftime('%a')[0..2]
|
129
150
|
|
130
|
-
|
151
|
+
week_info = WeekInfo.new(date_object, date_string, weeks)
|
152
|
+
print_inter_week_separator if week_info.needs_inter_week_separator?
|
153
|
+
week_info.format_and_print_date_info(day)
|
131
154
|
|
132
155
|
time_block_initial = @time_block[date_string]
|
133
156
|
print_time_blocks(time_block_initial, colors)
|
@@ -148,62 +171,6 @@ module Timet
|
|
148
171
|
puts
|
149
172
|
end
|
150
173
|
|
151
|
-
# Formats and prints the date information
|
152
|
-
#
|
153
|
-
# @param [String] date_string The date string
|
154
|
-
# @param [String] day The day of the week
|
155
|
-
# @param [Array] weeks The list of weeks
|
156
|
-
# @return [void]
|
157
|
-
def format_and_print_date_info(date_string, day, weeks)
|
158
|
-
weekend = date_string
|
159
|
-
day = day.red if %w[Sa Su].include?(day)
|
160
|
-
weekend = weekend.red if %w[Sa Su].include?(day)
|
161
|
-
|
162
|
-
week = format_and_print_week(date_string, weeks)
|
163
|
-
|
164
|
-
print '┆'.gray + "#{week} #{weekend} #{day}" + '┆- '.gray
|
165
|
-
end
|
166
|
-
|
167
|
-
# Formats and prints the week information
|
168
|
-
#
|
169
|
-
# @param [String] date_string The date string
|
170
|
-
# @param [Array] weeks The list of weeks
|
171
|
-
# @return [String] The formatted week string
|
172
|
-
def format_and_print_week(date_string, weeks)
|
173
|
-
week, current_index = determine_week(date_string, weeks)
|
174
|
-
print_separator(week, current_index)
|
175
|
-
week
|
176
|
-
end
|
177
|
-
|
178
|
-
# Determines the week for a given date
|
179
|
-
#
|
180
|
-
# @param [String] date_string The date string
|
181
|
-
# @param [Array] weeks The list of weeks
|
182
|
-
# @return [Array] The week string and current index
|
183
|
-
def determine_week(date_string, weeks)
|
184
|
-
weeks << Date.parse(date_string).cweek
|
185
|
-
current_index = weeks.size - 1
|
186
|
-
current_week = weeks[current_index]
|
187
|
-
week = if current_week == weeks[current_index - 1] && current_index.positive?
|
188
|
-
' '
|
189
|
-
else
|
190
|
-
format('%02d', current_week).to_s.underline
|
191
|
-
end
|
192
|
-
[week, current_index]
|
193
|
-
end
|
194
|
-
|
195
|
-
# Prints the separator line
|
196
|
-
#
|
197
|
-
# @param [String] week The week string
|
198
|
-
# @param [Integer] current_index The current index
|
199
|
-
# @return [void]
|
200
|
-
def print_separator(week, current_index)
|
201
|
-
return unless week != ' ' && current_index.positive?
|
202
|
-
|
203
|
-
sep = SEPARATOR_CHAR
|
204
|
-
puts "┆#{sep * 17}┼#{sep * (@end_hour - @start_hour + 1) * 4}#{sep * 3}┼#{sep * 4}".gray
|
205
|
-
end
|
206
|
-
|
207
174
|
# Prints the footer of the chart
|
208
175
|
#
|
209
176
|
# @return [void]
|
@@ -234,7 +201,7 @@ module Timet
|
|
234
201
|
formatted_hour = format('%02d', hour)
|
235
202
|
hour_data = time_block_initial[formatted_hour]
|
236
203
|
tag = hour_data&.last
|
237
|
-
[tag, get_block_char(hour_data&.first)]
|
204
|
+
[tag, BlockCharHelper.get_block_char(hour_data&.first)]
|
238
205
|
end
|
239
206
|
|
240
207
|
# Prints the colored block character
|
@@ -250,14 +217,21 @@ module Timet
|
|
250
217
|
print colored_block.rjust(4)
|
251
218
|
end
|
252
219
|
|
253
|
-
#
|
220
|
+
# Prints the separator line between different weeks in the chart.
|
254
221
|
#
|
255
|
-
# @
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
222
|
+
# @return [void]
|
223
|
+
def print_inter_week_separator
|
224
|
+
sep = SEPARATOR_CHAR
|
225
|
+
line_parts = [
|
226
|
+
'┆',
|
227
|
+
sep * DATE_WEEK_CONTENT_WIDTH, # Date/Week column fill
|
228
|
+
'┼',
|
229
|
+
sep * (@end_hour - @start_hour + 1) * 4, # Hour blocks column fill
|
230
|
+
sep * DATE_WEEK_BORDER_WIDTH, # Additional fill for hour blocks column
|
231
|
+
'┼',
|
232
|
+
sep * TOTAL_HOURS_COLUMN_WIDTH # Total hours column fill
|
233
|
+
]
|
234
|
+
puts line_parts.join.gray
|
261
235
|
end
|
262
236
|
end
|
263
237
|
end
|
data/lib/timet/time_helper.rb
CHANGED
@@ -247,5 +247,22 @@ module Timet
|
|
247
247
|
def self.beginning_of_day(time = Time.now)
|
248
248
|
Time.new(time.year, time.month, time.day)
|
249
249
|
end
|
250
|
+
|
251
|
+
# Creates a new datetime object.
|
252
|
+
#
|
253
|
+
# @param base_date_time [Time] The base date and time.
|
254
|
+
# @param parsed_time_component [Time] The parsed time component.
|
255
|
+
#
|
256
|
+
# @return [Time] The new datetime object.
|
257
|
+
def self.create_new_datetime(base_date_time, parsed_time_component)
|
258
|
+
Time.new(
|
259
|
+
base_date_time.year,
|
260
|
+
base_date_time.month,
|
261
|
+
base_date_time.day,
|
262
|
+
parsed_time_component.hour,
|
263
|
+
parsed_time_component.min,
|
264
|
+
parsed_time_component.sec
|
265
|
+
)
|
266
|
+
end
|
250
267
|
end
|
251
268
|
end
|