timet 1.3.2 → 1.4.1

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: d469231d2981219dd87a4f3e7a456f1e5d047656186343798cc6a5edd4990fdc
4
- data.tar.gz: 17c5ff8e151fc7ac540aa85f84ba3e93b6678d50d544e3a8489411235dfdd391
3
+ metadata.gz: 5e9f44f4ee2f1ae5aab382b308364ffeaffa5e09bf21d1578e8f5267ff715f0f
4
+ data.tar.gz: 13becd17288c93925e91a3c49dcdc7f4f7e7afe41d1469a10a7f931930b2d26d
5
5
  SHA512:
6
- metadata.gz: 4915bac3ff784ebc128e4dc4ba97c9427cc673c04ee7cb14b72f01bc7366d4b8ce741eb060f4caa6d33126ebee6cece04a2ccb3d37aaa8483c8ce36a0f2be540
7
- data.tar.gz: cb97abad6c3be282f2ce0d46df2f500da6fbb4ccecdd89574b81110f1da1475a3d7ccf677634b4691a7332880b2ab9cc5e5e32ad9e25291f6210a0958ae48253
6
+ metadata.gz: 27fca2e9adef3a4695a497b21106a400e7448caff946efccba034f9bbfcb29962822a632f17e3ab51856de764826257f5d6b33458d69fd9b56eebeb91946e5b8
7
+ data.tar.gz: df46d99862c3366f63f4ad007fbece638f88ca997fa2085070a78c065306b23888ecbbd5b610ba99582898395201dd00a1a8871de0ef8983d24c9ec3edefe32b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.4.0] - 2024-10-29
4
+
5
+ **Improvements:**
6
+
7
+ - Introduced constants for fixed tag size and block character in the `TagDistribution` module.
8
+ - Refactored `process_and_print_tags` to use constants directly and ensure tags are truncated to fit within the defined size.
9
+ - Simplified `calculate_value_and_bar_length` method to directly calculate percentage value and bar length using `MAX_BAR_LENGTH`.
10
+ - Renamed `summary` to `report` in `application.rb` for clarity.
11
+ - Integrated `Formatter` functionality into `Table` and `TimeBlockChart` modules.
12
+ - Added new methods in `Table` for formatting specific parts of the table row.
13
+ - Enhanced `TimeBlockChart` with new methods for formatting and printing date information.
14
+ - Integrated `Table` and `TimeBlockChart` into `TimeReport` for a more modular structure.
15
+ - Removed redundant methods from `TimeReportHelper` and ensured all necessary methods are included in the appropriate modules.
16
+ - Added a `blue` method to the `String` class to apply blue color to text and applied it to the total time display in `TimeReport`.
17
+ - Refactored database initialization and column addition logic to improve reusability and maintainability.
18
+ - Refactored Pomodoro session handling and table formatting to improve readability and functionality.
19
+ - Refactored `summary` method to use `time_scope` instead of `filter` for clarity.
20
+ - Refactored insertion logic to improve clarity and prevent redundant checks.
21
+ - Refactored tag distribution formatting into a separate `TagDistribution` module for better code organization.
22
+
23
+ **Bug fixes:**
24
+
25
+ - Fixed a typo in the table header title.
26
+
3
27
  ## [1.3.2] - 2024-10-25
4
28
 
5
29
  **Improvements:**
data/README.md CHANGED
@@ -106,7 +106,7 @@ gem install timet
106
106
  +-------+------------+--------+----------+----------+----------+--------------------------+
107
107
  ```
108
108
  ---
109
- - **timet resume**: It allows users to quickly resume tracking a task that was previously in progress.
109
+ - **timet resume [id]**: This command allows users to quickly resume tracking a task that was previously in progress. If an id is provided, it resumes the tracking session for the specified task. If no id is provided, it resumes the last completed task.
110
110
 
111
111
  ```
112
112
  Tracked time report [today]:
@@ -177,9 +177,10 @@ gem install timet
177
177
  | `timet summary resume (r)` | Resume tracking the last task. | `timet su r` |
178
178
  | `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
179
179
  | `timet cancel` | Cancel active time tracking. | `timet c` |
180
- | `timet edit [id]` | Update a task's notes, tag, start or end fields. | `timet e [1]` |
180
+ | `timet edit [id]` | Update a task's notes, tag, start or end fields. | `timet e [id]` |
181
181
  | `timet su [date]` | Display a report of tracked time for a specific date. | `timet su 2024-01-03` |
182
182
  | `timet su [start_date]..[end_date]` | Display a report of tracked time for a date range. | `timet su 2024-01-02..2024-01-03` |
183
+ | `timet resume [id]` | Resume tracking a task by ID or the last completed task. | `timet resume [id]` |
183
184
 
184
185
  ### Date Range in Summary
185
186
 
@@ -69,10 +69,10 @@ module Timet
69
69
  notes = options[:notes] || notes
70
70
  pomodoro = (options[:pomodoro] || pomodoro).to_i
71
71
 
72
- if VALID_STATUSES_FOR_INSERTION.include?(@db.last_item_status)
73
- @db.insert_item(start_time, tag, notes)
74
- play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
75
- end
72
+ return puts 'A task is currently being tracked.' unless VALID_STATUSES_FOR_INSERTION.include?(@db.item_status)
73
+
74
+ @db.insert_item(start_time, tag, notes, pomodoro)
75
+ play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
76
76
  summary
77
77
  end
78
78
 
@@ -85,13 +85,13 @@ module Timet
85
85
  # @example Stop the current tracking session
86
86
  # stop
87
87
  #
88
- # @note The method checks if the last tracking item is in progress by calling `@db.last_item_status`.
88
+ # @note The method checks if the last tracking item is in progress by calling `@db.item_status`.
89
89
  # @note If the last item is in progress, it fetches the last item's ID using `@db.fetch_last_id` and updates it
90
90
  # with the current timestamp.
91
91
  # @note The method then fetches the last item using `@db.last_item` and generates a summary if the result
92
92
  # is not nil.
93
93
  def stop(display = nil)
94
- return unless @db.last_item_status == :in_progress
94
+ return unless @db.item_status == :in_progress
95
95
 
96
96
  last_id = @db.fetch_last_id
97
97
  @db.update_item(last_id, 'end', TimeHelper.current_timestamp)
@@ -99,7 +99,7 @@ module Timet
99
99
  summary unless display
100
100
  end
101
101
 
102
- desc 'resume (r)', 'resume last task'
102
+ desc 'resume (r) [id]', 'resume last task'
103
103
  # Resumes the last tracking session if it was completed.
104
104
  #
105
105
  # @return [void] This method does not return a value; it performs side effects such as resuming a tracking session
@@ -108,35 +108,38 @@ module Timet
108
108
  # @example Resume the last tracking session
109
109
  # resume
110
110
  #
111
- # @note The method checks the status of the last tracking item using `@db.last_item_status`.
111
+ # @example Resume a specific task by ID
112
+ # resume(123)
113
+ #
114
+ # @note The method checks the status of the last tracking item using `@db.item_status`.
112
115
  # @note If the last item is in progress, it prints a message indicating that a task is currently being tracked.
113
- # @note If the last item is complete, it fetches the last item using `@db.last_item`, retrieves the tag and notes,
116
+ # @note If the last item is complete, it fetches the last item using `@db.find_item` or `@db.last_item`,
117
+ # retrieves the tag and notes,
114
118
  # and calls the `start` method to resume the tracking session.
115
- def resume
116
- status = @db.last_item_status
117
-
118
- case status
119
+ #
120
+ # @param id [Integer, nil] The ID of the tracking item to resume. If nil, the last item is used.
121
+ #
122
+ # @see Database#item_status
123
+ # @see Database#find_item
124
+ # @see #start
125
+ def resume(id = nil)
126
+ case @db.item_status(id)
119
127
  when :in_progress
120
128
  puts 'A task is currently being tracked.'
121
129
  when :complete
122
- last_item = @db.last_item
123
- if last_item
124
- tag = last_item[FIELD_INDEX['tag']]
125
- notes = last_item[FIELD_INDEX['notes']]
126
- start(tag, notes)
127
- end
130
+ resume_complete_task(id)
128
131
  end
129
132
  end
130
133
 
131
- desc 'summary (su) [filter] [tag] --csv=csv_filename',
132
- '[filter] => [today (t), yesterday (y), week (w), month (m), [start_date]..[end_date]] [tag]'
134
+ desc 'summary (su) [time_scope] [tag] --csv=csv_filename',
135
+ '[time_scope] => [today (t), yesterday (y), week (w), month (m), [start_date]..[end_date]] [tag]'
133
136
  option :csv, type: :string, desc: 'Export to CSV file'
134
- # Generates a summary of tracking items based on the provided filter and tag, and optionally exports the summary
137
+ # Generates a summary of tracking items based on the provided time_scope and tag, and optionally exports the summary
135
138
  # to a CSV file.
136
139
  #
137
- # @param filter [String, nil] The filter to apply when generating the summary. Possible values include 'today',
138
- # 'yesterday', 'week', 'month', or a date range in the format '[start_date]..[end_date]'.
139
- # @param tag [String, nil] The tag to filter the tracking items by.
140
+ # @param time_scope [String, nil] The time_scope to apply when generating the summary. Possible values include
141
+ # 'today', 'yesterday', 'week', 'month', or a date range in the format '[start_date]..[end_date]'.
142
+ # @param tag [String, nil] The tag to time_scope the tracking items by.
140
143
  #
141
144
  # @return [void] This method does not return a value; it performs side effects such as displaying the summary and
142
145
  # exporting to CSV if specified.
@@ -150,19 +153,19 @@ module Timet
150
153
  # @example Generate a summary for a date range and export to CSV
151
154
  # summary('2023-01-01..2023-01-31', nil, csv: 'summary.csv')
152
155
  #
153
- # @note The method initializes a `TimeReport` object with the database, filter, tag, and optional CSV filename.
156
+ # @note The method initializes a `TimeReport` object with the database, time_scope, tag, and optional CSV filename.
154
157
  # @note The method calls `display` on the `TimeReport` object to show the summary.
155
158
  # @note If a CSV filename is provided and there are items to export, the method calls `export_sheet` to export the
156
159
  # summary to a CSV file.
157
160
  # @note If no items are found to export, it prints a message indicating that no items were found.
158
- def summary(filter = nil, tag = nil)
161
+ def summary(time_scope = nil, tag = nil)
159
162
  csv_filename = options[:csv]&.split('.')&.first
160
- summary = TimeReport.new(@db, filter, tag, csv_filename)
163
+ report = TimeReport.new(@db, time_scope, tag, csv_filename)
161
164
 
162
- summary.display
163
- items = summary.items
165
+ report.display
166
+ items = report.items
164
167
  if csv_filename && items.any?
165
- summary.export_sheet
168
+ report.export_sheet
166
169
  elsif items.empty?
167
170
  puts 'No items found to export'
168
171
  end
@@ -245,13 +248,13 @@ module Timet
245
248
  # cancel
246
249
  #
247
250
  # @note The method fetches the ID of the last tracking item using `@db.fetch_last_id`.
248
- # @note It checks if the last item is in progress by comparing `@db.last_item_status` with `:complete`.
251
+ # @note It checks if the last item is in progress by comparing `@db.item_status` with `:complete`.
249
252
  # @note If the last item is in progress, it deletes the item and prints a confirmation message using
250
253
  # `delete_item_and_print_message(id, "Canceled active time tracking #{id}")`.
251
254
  # @note If there is no active time tracking, it prints a message indicating that there is no active time tracking.
252
255
  def cancel
253
256
  id = @db.fetch_last_id
254
- return puts 'There is no active time tracking' if @db.last_item_status == :complete
257
+ return puts 'There is no active time tracking' if @db.item_status == :complete
255
258
 
256
259
  delete_item_and_print_message(id, "Canceled active time tracking #{id}")
257
260
  end
@@ -281,25 +284,5 @@ module Timet
281
284
  def version
282
285
  puts Timet::VERSION
283
286
  end
284
-
285
- private
286
-
287
- # Deletes a tracking item from the database by its ID and prints a confirmation message.
288
- #
289
- # @param id [Integer] The ID of the tracking item to be deleted.
290
- # @param message [String] The message to be printed after the item is deleted.
291
- #
292
- # @return [void] This method does not return a value; it performs side effects such as deleting the tracking item
293
- # and printing a message.
294
- #
295
- # @example Delete a tracking item with ID 1 and print a confirmation message
296
- # delete_item_and_print_message(1, 'Deleted item 1')
297
- #
298
- # @note The method deletes the tracking item from the database using `@db.delete_item(id)`.
299
- # @note After deleting the item, the method prints the provided message using `puts message`.
300
- def delete_item_and_print_message(id, message)
301
- @db.delete_item(id)
302
- puts message
303
- end
304
287
  end
305
288
  end
@@ -109,10 +109,10 @@ module Timet
109
109
  # @param tag [String] A tag or label for the session, used in the notification message.
110
110
  # @return [void]
111
111
  def run_linux_session(time, tag)
112
- notification_command = "notify-send --icon=clock 'Pomodoro session complete! (tag: #{tag}) Time for a break.'"
112
+ notification_command = "notify-send --icon=clock '#{show_message(tag)}'"
113
113
  command = "sleep #{time} && tput bel && tt stop 0 && #{notification_command} &"
114
114
  pid = spawn(command)
115
- Process.wait(pid)
115
+ Process.detach(pid)
116
116
  end
117
117
 
118
118
  # Runs a Pomodoro session on a macOS system.
@@ -120,11 +120,69 @@ module Timet
120
120
  # @param time [Integer] The duration of the Pomodoro session in seconds.
121
121
  # @param _tag [String] A tag or label for the session, not used in the notification message on macOS.
122
122
  # @return [void]
123
- def run_mac_session(time, _tag)
124
- notification_command = "osascript -e 'display notification \"Pomodoro session complete! Time for a break.\"'"
123
+ def run_mac_session(time, tag)
124
+ notification_command = "osascript -e 'display notification \"#{show_message(tag)}\"'"
125
125
  command = "sleep #{time} && afplay /System/Library/Sounds/Basso.aiff && tt stop 0 && #{notification_command} &"
126
126
  pid = spawn(command)
127
- Process.wait(pid)
127
+ Process.detach(pid)
128
+ end
129
+
130
+ # Generates a message indicating that a Pomodoro session is complete and it's time for a break.
131
+ #
132
+ # @param tag [String] The tag associated with the completed Pomodoro session.
133
+ # @return [String] A message indicating the completion of the Pomodoro session and suggesting a break.
134
+ #
135
+ # @example
136
+ # show_message("work")
137
+ # # => "Pomodoro session complete (work). Time for a break."
138
+ #
139
+ def show_message(tag)
140
+ "Pomodoro session complete (#{tag}). Time for a break."
141
+ end
142
+
143
+ # Deletes a tracking item from the database by its ID and prints a confirmation message.
144
+ #
145
+ # @param id [Integer] The ID of the tracking item to be deleted.
146
+ # @param message [String] The message to be printed after the item is deleted.
147
+ #
148
+ # @return [void] This method does not return a value; it performs side effects such as deleting the tracking item
149
+ # and printing a message.
150
+ #
151
+ # @example Delete a tracking item with ID 1 and print a confirmation message
152
+ # delete_item_and_print_message(1, 'Deleted item 1')
153
+ #
154
+ # @note The method deletes the tracking item from the database using `@db.delete_item(id)`.
155
+ # @note After deleting the item, the method prints the provided message using `puts message`.
156
+ def delete_item_and_print_message(id, message)
157
+ @db.delete_item(id)
158
+ puts message
159
+ end
160
+
161
+ # Resumes a tracking session for a completed task.
162
+ #
163
+ # @param id [Integer, nil] The ID of the tracking item to resume. If nil, the last completed item is used.
164
+ #
165
+ # @return [void] This method does not return a value; it performs side effects such as resuming a tracking session.
166
+ #
167
+ # @example Resume the last completed task
168
+ # resume_complete_task
169
+ #
170
+ # @example Resume a specific task by ID
171
+ # resume_complete_task(123)
172
+ #
173
+ # @note The method fetches the specified or last completed item using `@db.find_item` or `@db.last_item`.
174
+ # @note If the item is found, it retrieves the tag and notes and calls the `start` method to resume the
175
+ # tracking session.
176
+ #
177
+ # @see Database#find_item
178
+ # @see Database#last_item
179
+ # @see #start
180
+ def resume_complete_task(id)
181
+ item = id ? @db.find_item(id) : @db.last_item
182
+ return unless item
183
+
184
+ tag, notes = item.values_at(Application::FIELD_INDEX['tag'], Application::FIELD_INDEX['notes'])
185
+ start(tag, notes)
128
186
  end
129
187
  end
130
188
  end
@@ -5,7 +5,7 @@ module Timet
5
5
  class ColorCodes
6
6
  RESET = "\u001b[0m"
7
7
  UNDERLINE = "\e[4m"
8
- BLINK = "\e[5m["
8
+ BLINK = "\e[5m"
9
9
 
10
10
  def self.reset
11
11
  RESET
@@ -27,6 +27,10 @@ end
27
27
 
28
28
  # Extend String class globally
29
29
  class String
30
+ def white
31
+ "#{Timet::ColorCodes.color(246)}#{self}#{Timet::ColorCodes.reset}"
32
+ end
33
+
30
34
  def gray
31
35
  "#{Timet::ColorCodes.color(242)}#{self}#{Timet::ColorCodes.reset}"
32
36
  end
@@ -35,6 +39,10 @@ class String
35
39
  "#{Timet::ColorCodes.color(1)}#{self}#{Timet::ColorCodes.reset}"
36
40
  end
37
41
 
42
+ def blue
43
+ "#{Timet::ColorCodes.color(12)}#{self}#{Timet::ColorCodes.reset}"
44
+ end
45
+
38
46
  def underline
39
47
  "#{Timet::ColorCodes.underline}#{self}#{Timet::ColorCodes.reset}"
40
48
  end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sqlite3'
4
- require_relative 'status_helper'
5
4
  module Timet
6
5
  # Provides database access for managing time tracking data.
7
6
  class Database
8
- include StatusHelper
9
-
10
7
  # The default path to the SQLite database file.
11
8
  DEFAULT_DATABASE_PATH = File.join(Dir.home, '.timet.db')
12
9
 
@@ -28,7 +25,9 @@ module Timet
28
25
  def initialize(database_path = DEFAULT_DATABASE_PATH)
29
26
  @db = SQLite3::Database.new(database_path)
30
27
  create_table
31
- add_notes
28
+
29
+ add_column('items', 'notes', 'TEXT')
30
+ add_column('items', 'pomodoro', 'INTEGER')
32
31
  end
33
32
 
34
33
  # Creates the items table if it doesn't already exist.
@@ -51,23 +50,27 @@ module Timet
51
50
  SQL
52
51
  end
53
52
 
54
- # Adds a new column named "notes" to the "items" table if it doesn't exist.
55
- #
56
- # @return [void] This method does not return a value; it performs side effects such as executing SQL to add
57
- # the column.
58
- #
59
- # @example Add the notes column to the items table
60
- # add_notes
61
- #
62
- # @note The method checks if the 'notes' column already exists and adds it if it does not.
63
- def add_notes
64
- table_name = 'items'
65
- new_column_name = 'notes'
53
+ # Adds a new column to the specified table if it does not already exist.
54
+ #
55
+ # @param table_name [String] The name of the table to which the column will be added.
56
+ # @param new_column_name [String] The name of the new column to be added.
57
+ # @param date_type [String] The data type of the new column (e.g., 'INTEGER', 'TEXT', 'BOOLEAN').
58
+ # @return [void] This method does not return a value; it performs side effects such as adding the column and
59
+ # printing a message.
60
+ #
61
+ # @example Add a new 'completed' column to the 'tasks' table
62
+ # add_column('tasks', 'completed', 'INTEGER')
63
+ #
64
+ # @note The method first checks if the column already exists in the table using `pragma_table_info`.
65
+ # @note If the column exists, the method returns without making any changes.
66
+ # @note If the column does not exist, the method executes an SQL `ALTER TABLE` statement to add the column.
67
+ # @note The method prints a message indicating that the column has been added.
68
+ def add_column(table_name, new_column_name, date_type)
66
69
  result = execute_sql("SELECT count(*) FROM pragma_table_info('items') where name='#{new_column_name}'")
67
70
  column_exists = result[0][0].positive?
68
71
  return if column_exists
69
72
 
70
- execute_sql("ALTER TABLE #{table_name} ADD COLUMN #{new_column_name} TEXT")
73
+ execute_sql("ALTER TABLE #{table_name} ADD COLUMN #{new_column_name} #{date_type}")
71
74
  puts "Column '#{new_column_name}' added to table '#{table_name}'."
72
75
  end
73
76
 
@@ -84,8 +87,8 @@ module Timet
84
87
  # insert_item(1633072800, 'work', 'Completed task X')
85
88
  #
86
89
  # @note The method executes SQL to insert a new row into the 'items' table.
87
- def insert_item(start, tag, notes)
88
- execute_sql('INSERT INTO items (start, tag, notes) VALUES (?, ?, ?)', [start, tag, notes])
90
+ def insert_item(start, tag, notes, pomodoro = nil)
91
+ execute_sql('INSERT INTO items (start, tag, notes, pomodoro) VALUES (?, ?, ?, ?)', [start, tag, notes, pomodoro])
89
92
  end
90
93
 
91
94
  # Updates an existing item in the items table.
@@ -152,12 +155,15 @@ module Timet
152
155
  # @return [Symbol] The status of the last item. Possible values are :no_items, :in_progress, or :complete.
153
156
  #
154
157
  # @example Determine the status of the last item
155
- # last_item_status
158
+ # item_status
156
159
  #
157
160
  # @note The method executes SQL to fetch the last item and determines its status using the `StatusHelper` module.
158
- def last_item_status
159
- result = execute_sql('SELECT id, end FROM items ORDER BY id DESC LIMIT 1')
160
- StatusHelper.determine_status(result)
161
+ #
162
+ # @param id [Integer, nil] The ID of the item to check. If nil, the last item in the table is used.
163
+ #
164
+ def item_status(id = nil)
165
+ id = fetch_last_id if id.nil?
166
+ determine_status(find_item(id))
161
167
  end
162
168
 
163
169
  # Finds an item in the items table by its ID.
@@ -235,5 +241,33 @@ module Timet
235
241
 
236
242
  format '%<hours>02d:%<minutes>02d:%<seconds>02d', hours: hours, minutes: minutes, seconds: seconds
237
243
  end
244
+
245
+ # Determines the status of a time tracking result based on the presence and end time of items.
246
+ #
247
+ # @param result [Array] The result set containing time tracking items.
248
+ #
249
+ # @return [Symbol] The status of the time tracking result. Possible values are
250
+ # :no_items, :in_progress, or :complete.
251
+ #
252
+ # @example Determine the status of an empty result set
253
+ # StatusHelper.determine_status([]) # => :no_items
254
+ #
255
+ # @example Determine the status of a result set with an in-progress item
256
+ # StatusHelper.determine_status([[1, nil]]) # => :in_progress
257
+ #
258
+ # @example Determine the status of a result set with a completed item
259
+ # StatusHelper.determine_status([[1, 1633072800]]) # => :complete
260
+ #
261
+ # @note The method checks if the result set is empty and returns :no_items if true.
262
+ # @note If the last item in the result set has no end time, it returns :in_progress.
263
+ # @note If the last item in the result set has an end time, it returns :complete.
264
+ def determine_status(result)
265
+ return :no_items if result.nil?
266
+
267
+ last_item_end = result[2]
268
+ return :in_progress unless last_item_end
269
+
270
+ :complete
271
+ end
238
272
  end
239
273
  end