timet 1.5.7 → 1.5.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 488c0136f2b11a62046a2f507b054d346a5def75dc3a32609ea51872bb411e6b
4
- data.tar.gz: c28bbe32a4558ab6755e438ba7c5a53f879d6c036ae68706782b62d8ca26133c
3
+ metadata.gz: 9e5079b49daa5deed838350e66235ce5301518e928a478f405423f268e431390
4
+ data.tar.gz: e7ef91dd990022437605bdfe406387e1499b9d08de5f6c233211332d9cb5ea0d
5
5
  SHA512:
6
- metadata.gz: d1e74dfa8e0d6fdacf0fd8c458ade7667e2f2722376aea9307a5dab7b68b6a0a561b475432cf666564ba6664c18b6f80f94aed0bd88c3f6703ead36aa5d1cf84
7
- data.tar.gz: 70608773c6f504e38637b7d4b2d71a852d7ed20c5679170c082d302748e1e3e6e4be2d1d4b909ce49e2ae45052dc3ac2cd02ba9a51106aba723211d3efa3c66a
6
+ metadata.gz: a6f4a1db83a82e0a0e53787a1b8847b7ceaa2694e0e4fbe318271bfc00a33c078d525d832f3be0b136d11c9b4a580ff3648ef9a1064e8921be6d7e79cb59c910
7
+ data.tar.gz: 7cd3c8513a161e3864501cec96317b833bdda14d937779b97d0feb680a894284f34d7427ba1197ad8ca0a721ede075b13b549489e5beaaa1cbfb751fa71f927f
data/.deepsource.toml ADDED
@@ -0,0 +1,13 @@
1
+ version = 1
2
+
3
+ [[analyzers]]
4
+ name = "ruby"
5
+ enabled = true
6
+
7
+ [[analyzers]]
8
+ name = "test-coverage"
9
+ enabled = true
10
+
11
+ [coverage]
12
+ reporter = "simplecov"
13
+ paths = ["coverage/.resultset.json"]
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## [1.5.9] - 2025-07-30
2
+
3
+ **Improvements:**
4
+
5
+ - Replaced Code Climate with DeepSource for static analysis and test coverage reporting.
6
+ - Updated the CI workflow by removing Code Climate steps and adding a .deepsource.toml configuration file.
7
+ - Updated gem dependencies, including rubocop, thor, and various aws-sdk gems.
8
+ - Refactored application.rb to use abort for more idiomatic error handling on invalid arguments.
9
+ - Updated the README to replace the old Code Climate badges with a new DeepSource badge.
10
+
11
+ **Bug fixes:**
12
+
13
+ - Fixed a shell injection vulnerability in application_helper.rb by properly escaping notification messages using Shellwords.shellescape.
14
+ - Corrected a timezone-related issue in time_validation_helper.rb by using Time.now.getlocal for future date validation.
15
+
16
+ ## [1.5.8] - 2025-06-28
17
+
18
+ **Improvements:**
19
+
20
+ - Updated gem dependencies, including `aws-sdk-s3`, `rake`, `sqlite3`, and `rubocop`.
21
+ - Refactored `update_time_field` method into `TimeHelper` for better code organization.
22
+ - Simplified `TimeStatistics#average_by_tag` to use the more concise `(&:mean)` method in `TimeStatistics`.
23
+ - Improved robustness of `format_time_string` in `TimeHelper`.
24
+ - Enhanced code clarity in `DatabaseSyncHelper` and `WeekInfo`.
25
+ - Added `vendor/bundle` and a test calendar file to `.gitignore`.
26
+
27
+ **Bug Fixes:**
28
+
29
+ - Corrected time collision validation in `ValidationEditHelper` to properly handle exact time matches (`>=`).
30
+
1
31
  ## [1.5.7] - 2025-05-16
2
32
 
3
33
  **Improvements:**
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/timet.svg)](https://badge.fury.io/rb/timet)
2
2
  ![timet workflow](https://github.com/frankvielma/timet/actions/workflows/ci.yml/badge.svg)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/44d57b6c561b9be717f5/maintainability)](https://codeclimate.com/github/frankvielma/timet/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/44d57b6c561b9be717f5/test_coverage)](https://codeclimate.com/github/frankvielma/timet/test_coverage)
5
-
3
+ [![DeepSource](https://app.deepsource.com/gh/frankvielma/timet.svg/?label=active+issues&show_trend=true&token=RV8_VCNrXIfEU7NL9mk9MSuP)](https://app.deepsource.com/gh/frankvielma/timet/)
6
4
  # Timet
7
5
 
8
6
  ![Timet](timet.webp)
@@ -85,8 +85,7 @@ module Timet
85
85
  if VALID_ARGUMENTS.include?(command_name)
86
86
  @db = Database.new
87
87
  else
88
- warn 'Invalid arguments provided. Please check your input.'
89
- exit(1)
88
+ abort 'Invalid arguments provided. Please check your input.'
90
89
  end
91
90
  end
92
91
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'shellwords'
4
+
3
5
  module Timet
4
6
  # Provides helper methods for the Timet application.
5
7
  module ApplicationHelper
@@ -109,7 +111,8 @@ module Timet
109
111
  # @param tag [String] A tag or label for the session, used in the notification message.
110
112
  # @return [void]
111
113
  def run_linux_session(time, tag)
112
- notification_command = "notify-send --icon=clock '#{show_message(tag)}'"
114
+ escaped_message = Shellwords.shellescape(show_message(tag))
115
+ notification_command = "notify-send --icon=clock #{escaped_message}"
113
116
  command = "sleep #{time} && tput bel && tt stop 0 && #{notification_command} &"
114
117
  pid = Kernel.spawn(command)
115
118
  Process.detach(pid)
@@ -121,7 +124,10 @@ module Timet
121
124
  # @param _tag [String] A tag or label for the session, not used in the notification message on macOS.
122
125
  # @return [void]
123
126
  def run_mac_session(time, tag)
124
- notification_command = "osascript -e 'display notification \"#{show_message(tag)}\"'"
127
+ # Escape double quotes and backslashes for AppleScript, then shell-escape the entire AppleScript command
128
+ escaped_message_for_applescript = show_message(tag).gsub('\\', '\\\\').gsub('"', '\"')
129
+ escaped_applescript_command = Shellwords.shellescape("display notification \"#{escaped_message_for_applescript}\"")
130
+ notification_command = "osascript -e #{escaped_applescript_command}"
125
131
  command = "sleep #{time} && afplay /System/Library/Sounds/Basso.aiff && tt stop 0 && #{notification_command} &"
126
132
  pid = Kernel.spawn(command)
127
133
  Process.detach(pid)
@@ -40,12 +40,13 @@ module Timet
40
40
  # comparing it with the local database, and handling any differences found
41
41
  def self.process_remote_database(local_db, remote_storage, bucket, local_db_path)
42
42
  with_temp_file do |temp_file|
43
- remote_storage.download_file(bucket, 'timet.db', temp_file.path)
43
+ file_path = temp_file.path
44
+ remote_storage.download_file(bucket, 'timet.db', file_path)
44
45
 
45
- if databases_are_in_sync?(temp_file.path, local_db_path)
46
+ if databases_are_in_sync?(file_path, local_db_path)
46
47
  puts 'Local database is up to date'
47
48
  else
48
- handle_database_differences(local_db, remote_storage, bucket, local_db_path, temp_file.path)
49
+ handle_database_differences(local_db, remote_storage, bucket, local_db_path, file_path)
49
50
  end
50
51
  end
51
52
  end
@@ -115,7 +115,7 @@ module Timet
115
115
  # TimeHelper.format_time_string('127122') # => nil
116
116
  # TimeHelper.format_time_string('abc') # => nil
117
117
  def self.format_time_string(input)
118
- return nil if input.nil? || input.empty?
118
+ return nil if input.to_s.empty?
119
119
 
120
120
  digits = input.gsub(/\D/, '')[0..5]
121
121
  return nil if digits.empty?
@@ -264,5 +264,23 @@ module Timet
264
264
  parsed_time_component.sec
265
265
  )
266
266
  end
267
+
268
+ # Updates a time field (start or end) of a tracking item with a formatted date value.
269
+ #
270
+ # @param item [Array] The tracking item to be updated.
271
+ # @param field [String] The time field to be updated.
272
+ # @param new_time [String] The new time value.
273
+ #
274
+ # @return [Time] The updated time value.
275
+ #
276
+ # @example Update the 'start' field of a tracking item with a formatted date value
277
+ # update_time_field(item, 'start', '11:10:00')
278
+ def self.update_time_field(item, field, new_time)
279
+ field_index = Timet::Application::FIELD_INDEX[field]
280
+ timestamp = item[field_index]
281
+ edit_time = Time.at(timestamp || item[1]).to_s.split
282
+ edit_time[1] = new_time
283
+ DateTime.strptime(edit_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
284
+ end
267
285
  end
268
286
  end
@@ -72,7 +72,7 @@ module Timet
72
72
  #
73
73
  # @return [Hash<String, Float>] A hash mapping tags to their average durations.
74
74
  def average_by_tag
75
- @duration_by_tag.transform_values { |durations| durations.sum.to_f / durations.size }
75
+ @duration_by_tag.transform_values(&:mean)
76
76
  end
77
77
 
78
78
  # Returns a hash where keys are tags and values are the standard deviation of durations for each tag.
@@ -25,7 +25,7 @@ module Timet
25
25
 
26
26
  return print_error(date_value) unless formatted_date
27
27
 
28
- new_date = update_time_field(item, field, formatted_date)
28
+ new_date = TimeHelper.update_time_field(item, field, formatted_date)
29
29
  new_value_epoch = new_date.to_i
30
30
 
31
31
  if valid_time_value?(item, field, new_value_epoch, id)
@@ -47,24 +47,6 @@ module Timet
47
47
  puts "Invalid date: #{message}".red
48
48
  end
49
49
 
50
- # Updates a time field (start or end) of a tracking item with a formatted date value.
51
- #
52
- # @param item [Array] The tracking item to be updated.
53
- # @param field [String] The time field to be updated.
54
- # @param new_time [String] The new time value.
55
- #
56
- # @return [Time] The updated time value.
57
- #
58
- # @example Update the 'start' field of a tracking item with a formatted date value
59
- # update_time_field(item, 'start', '11:10:00')
60
- def update_time_field(item, field, new_time)
61
- field_index = Timet::Application::FIELD_INDEX[field]
62
- timestamp = item[field_index]
63
- edit_time = Time.at(timestamp || item[1]).to_s.split
64
- edit_time[1] = new_time
65
- DateTime.strptime(edit_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
66
- end
67
-
68
50
  # Validates if a new time value is valid for a specific time field (start or end).
69
51
  #
70
52
  # @param item [Array] The tracking item to be validated.
@@ -103,9 +103,10 @@ module Timet
103
103
  #
104
104
  # @raise [ArgumentError] If the new datetime is in the future.
105
105
  def validate_future_date(new_datetime)
106
- return unless new_datetime > Time.now
106
+ # Ensure the new datetime is not in the future relative to the current time.
107
+ return unless new_datetime > Time.now.getlocal
107
108
 
108
- raise ArgumentError, "Cannot set time to a future date: #{new_datetime.strftime('%Y-%m-%d %H:%M:%S')}"
109
+ raise ArgumentError, "Cannot set time to a future date or time: #{new_datetime.strftime('%Y-%m-%d %H:%M:%S')}"
109
110
  end
110
111
 
111
112
  # Validates that the difference between two timestamps is less than 24 hours.
@@ -106,7 +106,7 @@ module Timet
106
106
  def check_collision_with_next_item(field, new_epoch, next_item)
107
107
  return unless next_item
108
108
 
109
- if field == 'start' && new_epoch > next_item[1]
109
+ if field == 'start' && new_epoch >= next_item[1]
110
110
  raise ArgumentError,
111
111
  'New start time collides with next item (starts at ' \
112
112
  "#{Time.at(next_item[1]).strftime('%Y-%m-%d %H:%M:%S')})."
data/lib/timet/version.rb CHANGED
@@ -6,6 +6,6 @@ module Timet
6
6
  # @return [String] The version number in the format 'major.minor.patch'.
7
7
  #
8
8
  # @example Get the version of the Timet application
9
- # Timet::VERSION # => '1.5.7'
10
- VERSION = '1.5.7'
9
+ # Timet::VERSION # => '1.5.9'
10
+ VERSION = '1.5.9'
11
11
  end
@@ -22,15 +22,14 @@ module Timet
22
22
  @date_string = date_string_for_display # Use the passed string for display
23
23
  @current_cweek = date_object.cweek
24
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
25
+ is_first_entry = weeks_array_ref.empty?
26
+ is_new_week = is_first_entry || @current_cweek != weeks_array_ref.last
27
+
28
+ # A separator is needed if this entry starts a new week group, but it's not the very first one.
29
+ @print_separator_before_this = is_new_week && !is_first_entry
30
+
31
+ # The week number is underlined if it's the first time this week appears.
32
+ @week_display_string = if is_new_week
34
33
  format('%02d', @current_cweek).underline
35
34
  else
36
35
  ' '
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.7
4
+ version: 1.5.9
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-05-16 00:00:00.000000000 Z
11
+ date: 2025-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -123,6 +123,7 @@ executables:
123
123
  extensions: []
124
124
  extra_rdoc_files: []
125
125
  files:
126
+ - ".deepsource.toml"
126
127
  - ".reek.yml"
127
128
  - ".rspec"
128
129
  - ".rubocop.yml"