timet 1.5.6 → 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/CHANGELOG.md +27 -0
- data/lib/timet/application.rb +20 -20
- data/lib/timet/block_char_helper.rb +33 -0
- 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 +97 -121
- data/lib/timet/version.rb +2 -2
- data/lib/timet/week_info.rb +62 -0
- metadata +8 -2
@@ -1,11 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'time_helper'
|
4
|
+
require_relative 'item_data_helper'
|
5
|
+
require_relative 'time_update_helper'
|
6
|
+
require_relative 'time_validation_helper'
|
7
|
+
|
4
8
|
module Timet
|
5
9
|
# Validates and updates a specific field of an item based on certain conditions.
|
6
10
|
# If the field is 'start' or 'end', it checks and updates the value accordingly.
|
7
11
|
# Otherwise, it directly updates the field with the new value.
|
8
12
|
module ValidationEditHelper
|
13
|
+
include TimeValidationHelper
|
14
|
+
|
9
15
|
# Constants for time fields.
|
10
16
|
TIME_FIELDS = %w[start end].freeze
|
11
17
|
|
@@ -25,9 +31,9 @@ module Timet
|
|
25
31
|
when 'tag'
|
26
32
|
item[3] = new_value
|
27
33
|
when 'start'
|
28
|
-
item[1] = validate_time(new_value)
|
34
|
+
item[1] = validate_time(item, 'start', new_value)
|
29
35
|
when 'end'
|
30
|
-
item[2] = validate_time(new_value)
|
36
|
+
item[2] = validate_time(item, 'end', new_value)
|
31
37
|
else
|
32
38
|
raise ArgumentError, "Invalid field: #{field}"
|
33
39
|
end
|
@@ -36,149 +42,119 @@ module Timet
|
|
36
42
|
|
37
43
|
# Validates if a given time string is in a valid format.
|
38
44
|
#
|
39
|
-
# @param
|
45
|
+
# @param item [Array] The item being modified.
|
46
|
+
# @param field [String] The field being validated ('start' or 'end').
|
47
|
+
# @param time_str [String, nil] The new time string (e.g., "HH:MM" or "HH:MM:SS").
|
48
|
+
# If nil or empty, it signifies that the original time should be kept.
|
40
49
|
#
|
41
|
-
# @return [Integer] The validated time as an integer.
|
50
|
+
# @return [Integer, nil] The validated time as an integer epoch.
|
51
|
+
# Returns the original timestamp for the field if time_str is nil/empty.
|
52
|
+
# Returns nil if the original field was nil and time_str is nil/empty.
|
42
53
|
#
|
43
54
|
# @raise [ArgumentError] If the time string is not in a valid format.
|
44
|
-
def validate_time(time_str)
|
45
|
-
|
46
|
-
|
47
|
-
raise ArgumentError, "Invalid time format: #{time_str}"
|
48
|
-
end
|
55
|
+
def validate_time(item, field, time_str)
|
56
|
+
# If time_str is nil or empty, user pressed Enter, meaning no change to this field.
|
57
|
+
return field == 'start' ? item[1] : item[2] if time_str.nil? || time_str.strip.empty?
|
49
58
|
|
50
|
-
|
59
|
+
parsed_time_component = parse_time_string(time_str)
|
51
60
|
|
52
|
-
|
53
|
-
|
54
|
-
# @param item [Array] The tracking item to be updated.
|
55
|
-
# @param field [String] The time field to be updated.
|
56
|
-
# @param date_value [String] The new value for the time field.
|
57
|
-
# @param id [Integer] The ID of the tracking item.
|
58
|
-
#
|
59
|
-
# @return [void] This method does not return a value; it performs side effects such as updating the time field.
|
60
|
-
#
|
61
|
-
# @note The method formats the date value and checks if it is valid.
|
62
|
-
# @note If the date value is valid, it updates the time field with the new value.
|
63
|
-
# @note If the date value is invalid, it prints an error message.
|
64
|
-
def process_and_update_time_field(*args)
|
65
|
-
item, field, date_value, id = args
|
66
|
-
formatted_date = TimeHelper.format_time_string(date_value)
|
61
|
+
start_timestamp = item[1]
|
62
|
+
end_timestamp = item[2]
|
67
63
|
|
68
|
-
|
64
|
+
new_datetime = determine_and_create_datetime(item, field, start_timestamp, parsed_time_component)
|
69
65
|
|
70
|
-
|
71
|
-
new_value_epoch = new_date.to_i
|
66
|
+
new_epoch = new_datetime.to_i
|
72
67
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
68
|
+
perform_validation(
|
69
|
+
item: item,
|
70
|
+
field: field,
|
71
|
+
new_epoch: new_epoch,
|
72
|
+
start_timestamp: start_timestamp,
|
73
|
+
end_timestamp: end_timestamp,
|
74
|
+
new_datetime: new_datetime
|
75
|
+
)
|
79
76
|
|
80
|
-
|
81
|
-
#
|
82
|
-
# @param message [String] The error message to be printed.
|
83
|
-
#
|
84
|
-
# @return [void] This method does not return a value; it performs side effects such as printing an error message.
|
85
|
-
#
|
86
|
-
# @example Print an error message for an invalid date
|
87
|
-
# print_error('Invalid date: 2023-13-32')
|
88
|
-
def print_error(message)
|
89
|
-
puts "Invalid date: #{message}".red
|
77
|
+
new_epoch
|
90
78
|
end
|
91
79
|
|
92
|
-
#
|
80
|
+
# Validates that the new start or end time does not collide with existing entries.
|
93
81
|
#
|
94
|
-
# @param item [Array] The
|
95
|
-
# @param field [String] The
|
96
|
-
# @param
|
82
|
+
# @param item [Array] The item being modified.
|
83
|
+
# @param field [String] The field being validated ('start' or 'end').
|
84
|
+
# @param new_epoch [Integer] The new time in epoch format.
|
97
85
|
#
|
98
|
-
# @
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
field_index = Timet::Application::FIELD_INDEX[field]
|
104
|
-
timestamp = item[field_index]
|
105
|
-
edit_time = Time.at(timestamp || item[1]).to_s.split
|
106
|
-
edit_time[1] = new_time
|
107
|
-
DateTime.strptime(edit_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
|
108
|
-
end
|
86
|
+
# @raise [ArgumentError] If the new time collides with a previous or next item.
|
87
|
+
def validate_time_collisions(item, field, new_epoch)
|
88
|
+
item_id = item[0]
|
89
|
+
prev_item = @db.find_item(item_id - 1)
|
90
|
+
next_item = @db.find_item(item_id + 1)
|
109
91
|
|
110
|
-
|
111
|
-
|
112
|
-
# @param item [Array] The tracking item to be validated.
|
113
|
-
# @param field [String] The time field to be validated.
|
114
|
-
# @param new_value_epoch [Integer] The new time value in epoch format.
|
115
|
-
# @param id [Integer] The ID of the tracking item.
|
116
|
-
#
|
117
|
-
# @return [Boolean] Returns true if the new time value is valid, otherwise false.
|
118
|
-
#
|
119
|
-
# @example Validate a new 'start' time value
|
120
|
-
# valid_time_value?(item, 'start', 1633072800, 1)
|
121
|
-
def valid_time_value?(*args)
|
122
|
-
item, field, new_value_epoch, id = args
|
123
|
-
item_start = fetch_item_start(item)
|
124
|
-
item_end = fetch_item_end(item)
|
125
|
-
item_before_end = fetch_item_before_end(id, item_start)
|
126
|
-
item_after_start = fetch_item_after_start(id)
|
127
|
-
|
128
|
-
if field == 'start'
|
129
|
-
new_value_epoch.between?(item_before_end, item_end)
|
130
|
-
else
|
131
|
-
new_value_epoch.between?(item_start, item_after_start)
|
132
|
-
end
|
92
|
+
check_collision_with_previous_item(field, new_epoch, prev_item)
|
93
|
+
check_collision_with_next_item(field, new_epoch, next_item)
|
133
94
|
end
|
134
95
|
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
# fetch_item_start(item)
|
143
|
-
def fetch_item_start(item)
|
144
|
-
item[Timet::Application::FIELD_INDEX['start']]
|
96
|
+
# Checks for collision with the previous item.
|
97
|
+
def check_collision_with_previous_item(field, new_epoch, prev_item)
|
98
|
+
return unless prev_item && field == 'start' && new_epoch < prev_item[2]
|
99
|
+
|
100
|
+
raise ArgumentError,
|
101
|
+
'New start time collides with previous item (ends at ' \
|
102
|
+
"#{Time.at(prev_item[2]).strftime('%Y-%m-%d %H:%M:%S')})."
|
145
103
|
end
|
146
104
|
|
147
|
-
#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
105
|
+
# Checks for collision with the next item.
|
106
|
+
def check_collision_with_next_item(field, new_epoch, next_item)
|
107
|
+
return unless next_item
|
108
|
+
|
109
|
+
if field == 'start' && new_epoch > next_item[1]
|
110
|
+
raise ArgumentError,
|
111
|
+
'New start time collides with next item (starts at ' \
|
112
|
+
"#{Time.at(next_item[1]).strftime('%Y-%m-%d %H:%M:%S')})."
|
113
|
+
elsif field == 'end' && new_epoch > next_item[1]
|
114
|
+
raise ArgumentError,
|
115
|
+
'New end time collides with next item (starts at ' \
|
116
|
+
"#{Time.at(next_item[1]).strftime('%Y-%m-%d %H:%M:%S')})."
|
117
|
+
end
|
157
118
|
end
|
158
119
|
|
159
|
-
|
160
|
-
|
161
|
-
#
|
162
|
-
# @param item_start [Integer] The start time of the current tracking item.
|
120
|
+
private
|
121
|
+
|
122
|
+
# Determines the base date and time, creates a new datetime object, and adjusts it if necessary.
|
163
123
|
#
|
164
|
-
# @
|
124
|
+
# @param item [Array] The item being modified.
|
125
|
+
# @param field [String] The field being validated ('start' or 'end').
|
126
|
+
# @param start_timestamp [Integer, nil] The start timestamp of the item.
|
127
|
+
# @param parsed_time_component [Time] The parsed time component.
|
165
128
|
#
|
166
|
-
# @
|
167
|
-
|
168
|
-
|
169
|
-
|
129
|
+
# @return [Time] The new datetime object.
|
130
|
+
def determine_and_create_datetime(item, field, start_timestamp, parsed_time_component)
|
131
|
+
base_date_time = determine_base_date_time(item, field, start_timestamp)
|
132
|
+
new_datetime = create_new_datetime(base_date_time, parsed_time_component)
|
133
|
+
adjust_end_datetime(field, start_timestamp, new_datetime)
|
170
134
|
end
|
171
135
|
|
172
|
-
#
|
173
|
-
#
|
174
|
-
# @param
|
175
|
-
#
|
176
|
-
# @
|
177
|
-
#
|
178
|
-
# @
|
179
|
-
#
|
180
|
-
|
181
|
-
|
136
|
+
# Performs the appropriate validation based on the field.
|
137
|
+
#
|
138
|
+
# @param options [Hash] A hash containing the parameters for validation.
|
139
|
+
# @option options [Array] :item The item being modified.
|
140
|
+
# @option options [String] :field The field being validated ('start' or 'end').
|
141
|
+
# @option options [Integer] :new_epoch The new time in epoch format.
|
142
|
+
# @option options [Integer, nil] :start_timestamp The start timestamp of the item.
|
143
|
+
# @option options [Integer, nil] :end_timestamp The end timestamp of the item.
|
144
|
+
# @option options [Time] :new_datetime The new datetime object.
|
145
|
+
def perform_validation(options)
|
146
|
+
item = options[:item]
|
147
|
+
field = options[:field]
|
148
|
+
new_epoch = options[:new_epoch]
|
149
|
+
start_timestamp = options[:start_timestamp]
|
150
|
+
end_timestamp = options[:end_timestamp]
|
151
|
+
new_datetime = options[:new_datetime]
|
152
|
+
|
153
|
+
validate_future_date(new_datetime)
|
154
|
+
|
155
|
+
validate_time_collisions(item, field, new_epoch) # Call the new method for both start and end
|
156
|
+
validate_start_time(new_epoch, end_timestamp, new_datetime) if field == 'start' && end_timestamp
|
157
|
+
validate_end_time(new_epoch, start_timestamp, new_datetime) if field == 'end'
|
182
158
|
end
|
183
159
|
end
|
184
160
|
end
|
data/lib/timet/version.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Timet
|
6
|
+
#
|
7
|
+
# The WeekInfo class encapsulates the date string and weeks array
|
8
|
+
# and provides methods for formatting and determining week information.
|
9
|
+
#
|
10
|
+
# It is instantiated for each date entry in the TimeBlockChart and helps decide
|
11
|
+
# how the week number is displayed and whether a separator line is needed before the entry.
|
12
|
+
class WeekInfo
|
13
|
+
# Initializes a new WeekInfo instance.
|
14
|
+
#
|
15
|
+
# @param date_object [Date] The Date object for the current entry.
|
16
|
+
# @param date_string_for_display [String] The original date string for display (e.g., "2023-10-01").
|
17
|
+
# @param weeks_array_ref [Array<Integer>] A reference to an array that accumulates the
|
18
|
+
# ISO 8601 week numbers of dates already processed. This array is mutated.
|
19
|
+
|
20
|
+
WEEKEND_DAYS = %w[Sat Sun].freeze
|
21
|
+
def initialize(date_object, date_string_for_display, weeks_array_ref)
|
22
|
+
@date_string = date_string_for_display # Use the passed string for display
|
23
|
+
@current_cweek = date_object.cweek
|
24
|
+
|
25
|
+
# Determine if a separator line should be printed *before* this entry.
|
26
|
+
# A separator is needed if this entry starts a new week group,
|
27
|
+
# and it's not the very first week group in the chart.
|
28
|
+
@print_separator_before_this = !weeks_array_ref.empty? && @current_cweek != weeks_array_ref.last
|
29
|
+
|
30
|
+
# Determine how the week number string should be displayed for this entry.
|
31
|
+
# It's underlined if it's the first time this cweek appears, otherwise blank.
|
32
|
+
is_first_display_of_this_cweek = weeks_array_ref.empty? || @current_cweek != weeks_array_ref.last
|
33
|
+
@week_display_string = if is_first_display_of_this_cweek
|
34
|
+
format('%02d', @current_cweek).underline
|
35
|
+
else
|
36
|
+
' '
|
37
|
+
end
|
38
|
+
|
39
|
+
weeks_array_ref << @current_cweek # Record this week as processed
|
40
|
+
end
|
41
|
+
|
42
|
+
# Indicates whether an inter-week separator line should be printed before this date's entry.
|
43
|
+
#
|
44
|
+
# @return [Boolean] True if a separator is needed, false otherwise.
|
45
|
+
def needs_inter_week_separator?
|
46
|
+
@print_separator_before_this
|
47
|
+
end
|
48
|
+
|
49
|
+
# Formats and prints the date information
|
50
|
+
#
|
51
|
+
# @param [String] day The day of the week
|
52
|
+
# @return [void]
|
53
|
+
def format_and_print_date_info(day)
|
54
|
+
weekend_str = @date_string # Use the original date string for display
|
55
|
+
is_weekend_day = WEEKEND_DAYS.include?(day)
|
56
|
+
day_str = is_weekend_day ? day.red : day
|
57
|
+
weekend_str = weekend_str.red if is_weekend_day
|
58
|
+
|
59
|
+
print '┆'.gray + "#{@week_display_string} #{weekend_str} #{day_str}" + '┆- '.gray
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Vielma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -136,10 +136,12 @@ files:
|
|
136
136
|
- lib/timet.rb
|
137
137
|
- lib/timet/application.rb
|
138
138
|
- lib/timet/application_helper.rb
|
139
|
+
- lib/timet/block_char_helper.rb
|
139
140
|
- lib/timet/color_codes.rb
|
140
141
|
- lib/timet/database.rb
|
141
142
|
- lib/timet/database_sync_helper.rb
|
142
143
|
- lib/timet/database_syncer.rb
|
144
|
+
- lib/timet/item_data_helper.rb
|
143
145
|
- lib/timet/s3_supabase.rb
|
144
146
|
- lib/timet/table.rb
|
145
147
|
- lib/timet/tag_distribution.rb
|
@@ -148,8 +150,12 @@ files:
|
|
148
150
|
- lib/timet/time_report.rb
|
149
151
|
- lib/timet/time_report_helper.rb
|
150
152
|
- lib/timet/time_statistics.rb
|
153
|
+
- lib/timet/time_update_helper.rb
|
154
|
+
- lib/timet/time_validation_helper.rb
|
155
|
+
- lib/timet/utils.rb
|
151
156
|
- lib/timet/validation_edit_helper.rb
|
152
157
|
- lib/timet/version.rb
|
158
|
+
- lib/timet/week_info.rb
|
153
159
|
- sig/timet.rbs
|
154
160
|
homepage: https://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
155
161
|
licenses:
|