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
@@ -1,174 +1,160 @@
|
|
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
|
|
12
|
-
# Validates and updates
|
18
|
+
# Validates and updates an item's attribute based on the provided field and new value.
|
13
19
|
#
|
14
|
-
# @param item [Array] The
|
20
|
+
# @param item [Array] The item to be updated.
|
15
21
|
# @param field [String] The field to be updated.
|
16
|
-
# @param new_value [String
|
17
|
-
#
|
18
|
-
# @return [Array, nil] The updated tracking item if the update was successful, otherwise nil.
|
22
|
+
# @param new_value [String] The new value for the field.
|
19
23
|
#
|
20
|
-
# @
|
21
|
-
# validate_and_update(item, 'notes', 'Updated notes')
|
24
|
+
# @return [Array] The updated item.
|
22
25
|
#
|
23
|
-
# @
|
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.
|
26
|
+
# @raise [ArgumentError] If the field is invalid or the new value is invalid.
|
26
27
|
def validate_and_update(item, field, new_value)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
case field
|
29
|
+
when 'notes'
|
30
|
+
item[4] = new_value
|
31
|
+
when 'tag'
|
32
|
+
item[3] = new_value
|
33
|
+
when 'start'
|
34
|
+
item[1] = validate_time(item, 'start', new_value)
|
35
|
+
when 'end'
|
36
|
+
item[2] = validate_time(item, 'end', new_value)
|
33
37
|
else
|
34
|
-
|
38
|
+
raise ArgumentError, "Invalid field: #{field}"
|
35
39
|
end
|
36
|
-
|
37
|
-
@db.find_item(id)
|
40
|
+
item
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
# Processes and updates a time field (start or end) of a tracking item.
|
43
|
+
# Validates if a given time string is in a valid format.
|
43
44
|
#
|
44
|
-
# @param item [Array] The
|
45
|
-
# @param field [String] The
|
46
|
-
# @param
|
47
|
-
#
|
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.
|
48
49
|
#
|
49
|
-
# @return [
|
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.
|
50
53
|
#
|
51
|
-
# @
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
item, field, date_value, id = args
|
56
|
-
formatted_date = TimeHelper.format_time_string(date_value)
|
54
|
+
# @raise [ArgumentError] If the time string is not in a valid format.
|
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?
|
57
58
|
|
58
|
-
|
59
|
+
parsed_time_component = parse_time_string(time_str)
|
59
60
|
|
60
|
-
|
61
|
-
|
61
|
+
start_timestamp = item[1]
|
62
|
+
end_timestamp = item[2]
|
62
63
|
|
63
|
-
|
64
|
-
@db.update_item(id, field, new_value_epoch)
|
65
|
-
else
|
66
|
-
print_error(new_date)
|
67
|
-
end
|
68
|
-
end
|
64
|
+
new_datetime = determine_and_create_datetime(item, field, start_timestamp, parsed_time_component)
|
69
65
|
|
70
|
-
|
71
|
-
#
|
72
|
-
# @param message [String] The error message to be printed.
|
73
|
-
#
|
74
|
-
# @return [void] This method does not return a value; it performs side effects such as printing an error message.
|
75
|
-
#
|
76
|
-
# @example Print an error message for an invalid date
|
77
|
-
# print_error('Invalid date: 2023-13-32')
|
78
|
-
def print_error(message)
|
79
|
-
puts "Invalid date: #{message}".red
|
80
|
-
end
|
66
|
+
new_epoch = new_datetime.to_i
|
81
67
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
def update_time_field(item, field, new_time)
|
93
|
-
field_index = Timet::Application::FIELD_INDEX[field]
|
94
|
-
timestamp = item[field_index]
|
95
|
-
edit_time = Time.at(timestamp || item[1]).to_s.split
|
96
|
-
edit_time[1] = new_time
|
97
|
-
DateTime.strptime(edit_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
|
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
|
+
)
|
76
|
+
|
77
|
+
new_epoch
|
98
78
|
end
|
99
79
|
|
100
|
-
# Validates
|
80
|
+
# Validates that the new start or end time does not collide with existing entries.
|
101
81
|
#
|
102
|
-
# @param item [Array] The
|
103
|
-
# @param field [String] The
|
104
|
-
# @param
|
105
|
-
# @param id [Integer] The ID of the tracking item.
|
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.
|
106
85
|
#
|
107
|
-
# @
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
item_before_end = fetch_item_before_end(id, item_start)
|
116
|
-
item_after_start = fetch_item_after_start(id)
|
117
|
-
|
118
|
-
if field == 'start'
|
119
|
-
new_value_epoch >= item_before_end && new_value_epoch <= item_end
|
120
|
-
else
|
121
|
-
new_value_epoch >= item_start && new_value_epoch <= item_after_start
|
122
|
-
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)
|
91
|
+
|
92
|
+
check_collision_with_previous_item(field, new_epoch, prev_item)
|
93
|
+
check_collision_with_next_item(field, new_epoch, next_item)
|
123
94
|
end
|
124
95
|
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
# fetch_item_start(item)
|
133
|
-
def fetch_item_start(item)
|
134
|
-
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')})."
|
135
103
|
end
|
136
104
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
147
118
|
end
|
148
119
|
|
149
|
-
|
150
|
-
|
151
|
-
#
|
152
|
-
# @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.
|
153
123
|
#
|
154
|
-
# @
|
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.
|
155
128
|
#
|
156
|
-
# @
|
157
|
-
|
158
|
-
|
159
|
-
|
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)
|
160
134
|
end
|
161
135
|
|
162
|
-
#
|
163
|
-
#
|
164
|
-
# @param
|
165
|
-
#
|
166
|
-
# @
|
167
|
-
#
|
168
|
-
# @
|
169
|
-
#
|
170
|
-
|
171
|
-
|
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'
|
172
158
|
end
|
173
159
|
end
|
174
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:
|