timet 0.2.2 → 0.8.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 +4 -4
- data/CHANGELOG.md +83 -0
- data/README.md +40 -53
- data/lib/timet/application.rb +65 -14
- data/lib/timet/application_helper.rb +29 -0
- data/lib/timet/database.rb +7 -1
- data/lib/timet/formatter.rb +34 -0
- data/lib/timet/status_helper.rb +9 -7
- data/lib/timet/time_helper.rb +86 -26
- data/lib/timet/time_report.rb +42 -61
- data/lib/timet/validation_edit_helper.rb +81 -0
- data/lib/timet/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85190a5b5ae6c446a96b2dda4c71efbe19ae0a1ec907dd5d699b1d831ecd0cd7
|
4
|
+
data.tar.gz: ecfd87a600e956b45703fc02865503dd739079f5d8b003770ee9dcb554338903
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2866257d4d685e76cbd32874922f7bd837f7a68d59bd152e9575935f63dd98ac1514e5e08a1c019bf661ded1ed409ad6733a235194e996c1d50d4dec9494226c
|
7
|
+
data.tar.gz: a7e80d8c5e6f1d433b1549f98351721f5b6d28da242062c149cf47289f81ccb8c7808b2a13a3338550770c7594ad26ca4c91862e0da667c7f50259b6a10841cf
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,88 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.8.1] - 2024-10-02
|
4
|
+
**Bug fixes:**
|
5
|
+
- Fixed a LoadError caused by the byebug gem being required after its removal.
|
6
|
+
|
7
|
+
## [0.8.0] - 2024-10-01
|
8
|
+
|
9
|
+
**Improvements:**
|
10
|
+
- Introduced TimeHelper module to encapsulate time-related functionalities.
|
11
|
+
- Replaced direct calls to Time.now.to_i with TimeHelper.current_timestamp for consistency and modularity.
|
12
|
+
- Moved current_timestamp method to TimeHelper to centralize time-related logic.
|
13
|
+
- Ensured all timestamps are handled in UTC to avoid timezone issues.
|
14
|
+
- Refactored ValidationEditHelper to use TimeHelper for time-related operations.
|
15
|
+
- Simplified the conditional logic in update_time_field by using a single assignment with the || operator.
|
16
|
+
- Introduced local variables field_index and timestamp to store intermediate results, reducing redundancy and improving readability.
|
17
|
+
- Added ApplicationHelper module to handle common application tasks.
|
18
|
+
- Moved display and prompt logic for editing items into helper methods.
|
19
|
+
- Removed redundant field_value method and integrated its functionality into the helper methods.
|
20
|
+
- Consolidated the validation logic for start and end fields into a single conditional statement.
|
21
|
+
- Removed redundant validate_start_field and validate_end_field methods.
|
22
|
+
- Added a check to ensure the CSV export only occurs if there are items to export.
|
23
|
+
- Added a message to inform the user if no items are found for export.
|
24
|
+
- Refactored the validate_and_update method from Timet::Application to a more modular approach.
|
25
|
+
- Created new helper methods in ValidationEditHelper for fetching item values and validating start/end times.
|
26
|
+
- Enhanced readability and maintainability of the code by isolating concerns and reusing logic.
|
27
|
+
- Renamed item to last_item for clarity in the context where it represents the last task.
|
28
|
+
- Added a new variable last_item_status to explicitly define the status of the last item.
|
29
|
+
- Extracted table formatting logic into a new Formatter module.
|
30
|
+
- Removed redundant methods (format_table_header, format_table_separator, format_table_row, format_notes) from the TimeReport class.
|
31
|
+
- Introduced current_timestamp method to encapsulate the logic for getting the current timestamp.
|
32
|
+
- Added insert_item_if_valid method to handle item insertion based on valid statuses.
|
33
|
+
- Introduced VALID_STATUSES_FOR_INSERTION constant to define valid statuses for item insertion.
|
34
|
+
- Updated StatusHelper to use :in_progress status instead of :incomplete.
|
35
|
+
- Ensured consistency in timestamp handling across methods.
|
36
|
+
- Added support for editing notes.
|
37
|
+
- Added the csv gem and moved the extract_date method to the TimeHelper module.
|
38
|
+
- Improved error handling by ensuring the CSV filename is correctly processed and validated.
|
39
|
+
- Added a confirmation message when the CSV file is successfully exported.
|
40
|
+
|
41
|
+
**Bug fixes:**
|
42
|
+
- Updated timestamp_to_time method to return nil if the input timestamp is nil.
|
43
|
+
- Removed unnecessary &.then syntax for clarity and consistency.
|
44
|
+
- Corrected the test expectations to use the correct Unix timestamp.
|
45
|
+
- Updated format_time and timestamp_to_date methods to return nil if the input timestamp is nil.
|
46
|
+
- Ensured that the formatted time string does not include an extra space at the end.
|
47
|
+
- Updated update_time_field to handle nil values for the time field by using a conditional assignment.
|
48
|
+
- Ensured that the method correctly handles cases where the time field is not set, defaulting to the current timestamp.
|
49
|
+
- Addressed the NilCheck warning and improved the robustness of the method.
|
50
|
+
- Added validation to prevent updating start or end times with nil values.
|
51
|
+
|
52
|
+
**Tasks:**
|
53
|
+
- Added ENV['TZ'] = 'UTC' to spec_helper.rb to ensure that all RSpec tests run in the UTC time zone.
|
54
|
+
- Updated tests to reflect the new structure and ensure items are returned for export.
|
55
|
+
- Updated and added tests to cover the new export logic.
|
56
|
+
- Updated the expectations to use the renamed variables, ensuring the code remains clear and maintainable.
|
57
|
+
- Included the Formatter module in the TimeReport class to utilize its formatted output.
|
58
|
+
- Updated README.md with alternative syntax for 'timet start' command.
|
59
|
+
- Update README.md with new command reference and edit functionality
|
60
|
+
- Refactored start method tests to use context blocks for better readability and organization.
|
61
|
+
- Added tests to verify the behavior of the start method when the database is empty, the last item is complete, or the last item is still in progress.
|
62
|
+
- Added tests to ensure the start method handles notes provided via the --notes option correctly.
|
63
|
+
- Refactored stop method tests to use context blocks and added tests to verify the behavior when the last item is in progress or complete.
|
64
|
+
- Refactored resume method tests to use context blocks and added tests to verify the behavior when a task is currently being tracked, when there is a last task, and when there are no items.
|
65
|
+
- Refactored summary method tests to use context blocks and added tests to verify the behavior for different combinations of arguments.
|
66
|
+
- Updated cancel method test to reflect the correct status for active time tracking.
|
67
|
+
- Updated database_spec.rb to reflect the correct status for in-progress items.
|
68
|
+
|
69
|
+
**Additional Considerations:**
|
70
|
+
- Ensured that all timestamps are handled in UTC to avoid timezone issues across different environments.
|
71
|
+
- Updated the test to reflect the correct UTC time zone.
|
72
|
+
- Ensured consistency in timestamp handling across methods.
|
73
|
+
- Improved overall structure, readability, and maintainability of the code.
|
74
|
+
- Made the application easier to maintain and extend in the future.
|
75
|
+
|
76
|
+
**Refactor and enhance time formatting methods**
|
77
|
+
- Refactored `format_time_string` method in `TimeHelper` to improve readability and maintainability.
|
78
|
+
- Added detailed documentation for the `format_time_string` method.
|
79
|
+
- Simplified the logic for parsing and validating time components.
|
80
|
+
- Updated `ValidationEditHelper` to use the refactored `format_time_string` method.
|
81
|
+
- Added comprehensive RSpec tests for the `format_time_string` method to cover various input scenarios, including edge cases.
|
82
|
+
- Fixed a bug where `nil` input was not handled correctly.
|
83
|
+
- Ensured that invalid time values return `nil` instead of an empty string.
|
84
|
+
- Refactored the CSV export logic in `TimeReport` to improve readability and maintainability.
|
85
|
+
|
3
86
|
## [0.2.2] - 2024-09-27
|
4
87
|
|
5
88
|
* **Improvements**:
|
data/README.md
CHANGED
@@ -35,7 +35,7 @@ Old versions of Ruby and Sqlite:
|
|
35
35
|
|
36
36
|
Install the gem by executing:
|
37
37
|
```bash
|
38
|
-
|
38
|
+
gem install timet
|
39
39
|
```
|
40
40
|
|
41
41
|
## Usage
|
@@ -43,6 +43,10 @@ $ gem install timet
|
|
43
43
|
- **timet start [tag] --notes='...'**: Starts tracking time for a task labeled with the provided [tag] and notes (optional). Example:
|
44
44
|
```bash
|
45
45
|
timet start task1 --notes='Meeting with client'
|
46
|
+
|
47
|
+
or
|
48
|
+
|
49
|
+
timet start task1 'Meeting with client'
|
46
50
|
```
|
47
51
|
|
48
52
|
```
|
@@ -86,61 +90,44 @@ $ gem install timet
|
|
86
90
|
+-------+------------+--------+----------+----------+----------+--------------------------+
|
87
91
|
```
|
88
92
|
|
89
|
-
- **timet summary today (t)**: Display a report of tracked time for today.
|
90
|
-
|
91
|
-
```bash
|
92
|
-
timet summary today
|
93
|
-
```
|
94
|
-
|
95
|
-
- **timet summary yesterday (y)**: Display a report of tracked time for yesterday.
|
96
|
-
|
97
|
-
```bash
|
98
|
-
timet summary yesterday
|
99
|
-
```
|
100
|
-
|
101
|
-
- **timet summary week (w)**: Display a report of tracked time for the week.
|
102
|
-
|
103
|
-
```bash
|
104
|
-
timet summary week
|
105
|
-
```
|
106
|
-
|
107
|
-
- **timet summary resume (r)**: Resume tracking the last task.
|
108
|
-
|
109
|
-
```bash
|
110
|
-
timet summary resume
|
111
|
-
```
|
112
|
-
|
113
|
-
- **timet summary resume (r)**: Resume tracking the last month.
|
114
|
-
|
115
|
-
```bash
|
116
|
-
timet summary month
|
117
|
-
```
|
118
|
-
|
119
|
-
- **timet su t --csv=[filename]**: Display a report of tracked time for today and export it to filename.csv
|
120
|
-
|
121
|
-
```bash
|
122
|
-
timet su t --csv=summary_today.csv
|
123
|
-
```
|
124
|
-
|
125
|
-
- **timet delete [id]**: Delete a task
|
126
|
-
|
127
|
-
```bash
|
128
|
-
timet delete [id]
|
129
|
-
|
130
|
-
or
|
131
|
-
|
132
|
-
timet d [id]
|
133
|
-
```
|
134
|
-
|
135
|
-
- **timet cancel**: Cancel active time tracking
|
136
93
|
|
137
|
-
|
138
|
-
|
94
|
+
- **timet edit**: It allows users update a task's notes, tag, start or end fields.
|
95
|
+
```bash
|
96
|
+
timet e 1
|
97
|
+
```
|
139
98
|
|
140
|
-
|
99
|
+
```
|
100
|
+
Tracked time report [today]:
|
101
|
+
+-------+------------+--------+----------+----------+----------+--------------------------+
|
102
|
+
| Id | Date | Tag | Start | End | Duration | Notes |
|
103
|
+
+-------+------------+--------+----------+----------+----------+--------------------------+
|
104
|
+
| 2 | 2024-08-09 | task1 | 16:15:07 | - | 00:00:00 | Meeting with client |
|
105
|
+
| 1 | | task1 | 14:55:07 | 15:55:07 | 01:00:00 | Meeting with client |
|
106
|
+
+-------+------------+--------+----------+----------+----------+--------------------------+
|
107
|
+
| Total: | 01:00:00 | |
|
108
|
+
+-------+------------+--------+----------+----------+----------+--------------------------+
|
109
|
+
Edit Field? (Press ↑/↓ arrow to move and Enter to select)
|
110
|
+
‣ Notes
|
111
|
+
Tag
|
112
|
+
Start
|
113
|
+
End
|
114
|
+
```
|
141
115
|
|
142
|
-
|
143
|
-
|
116
|
+
## Command Reference
|
117
|
+
|
118
|
+
| Command | Description | Example Usage |
|
119
|
+
|----------------------------------------------|-----------------------------------------------------------------------------|---------------------------------------------------|
|
120
|
+
| `timet start [tag] --notes='...'` | Start tracking time for a task labeled [tag] and notes (optional). | `timet start Task "My notes"` |
|
121
|
+
| `timet stop` | Stop tracking time. | `timet start Task "My notes"` |
|
122
|
+
| `timet summary today (t)` | Display a report of tracked time for today. | `timet su t` or `timet su` |
|
123
|
+
| `timet summary yesterday (y)` | Display a report of tracked time for yesterday. | `timet su y` |
|
124
|
+
| `timet summary week (w)` | Display a report of tracked time for the week. | `timet su w` |
|
125
|
+
| `timet summary month (m)` | Resume tracking the last month. | `timet su m` |
|
126
|
+
| `timet su t --csv=[filename]` | Display a report of tracked time for today and export it to `filename.csv`. | `timet su t --csv=file.csv` |
|
127
|
+
| `timet summary resume (r)` | Resume tracking the last task. | `timet su r` |
|
128
|
+
| `timet delete [id]` | Delete a task by its ID. | `timet d [id]` |
|
129
|
+
| `timet cancel` | Cancel active time tracking. | `timet c` |
|
130
|
+
| `timet edit [id]` | Update a task's notes, tag, start or end fields. | `timet e [1]` |
|
144
131
|
|
145
132
|
|
146
133
|
## Data
|
data/lib/timet/application.rb
CHANGED
@@ -3,29 +3,51 @@
|
|
3
3
|
require_relative 'version'
|
4
4
|
require 'thor'
|
5
5
|
require 'tty-prompt'
|
6
|
-
|
6
|
+
require_relative 'validation_edit_helper'
|
7
|
+
require_relative 'application_helper'
|
8
|
+
require_relative 'time_helper'
|
7
9
|
|
8
10
|
module Timet
|
9
|
-
#
|
11
|
+
# Application class that defines CLI commands for time tracking:
|
12
|
+
# - start: Start time tracking with optional notes
|
13
|
+
# - stop: Stop time tracking
|
14
|
+
# - resume: Resume the last task
|
15
|
+
# - summary: Display a summary of tracked time and export to CSV
|
16
|
+
# - edit: Edit a task
|
17
|
+
# - delete: Delete a task
|
18
|
+
# - cancel: Cancel active time tracking
|
10
19
|
class Application < Thor
|
20
|
+
include ValidationEditHelper
|
21
|
+
include ApplicationHelper
|
22
|
+
|
11
23
|
def initialize(*args)
|
12
24
|
super
|
13
25
|
@db = Timet::Database.new
|
14
26
|
end
|
15
27
|
|
28
|
+
FIELD_INDEX = {
|
29
|
+
'notes' => 4,
|
30
|
+
'tag' => 3,
|
31
|
+
'start' => 1,
|
32
|
+
'end' => 2
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
VALID_STATUSES_FOR_INSERTION = %i[no_items complete].freeze
|
36
|
+
|
16
37
|
desc "start [tag] --notes='...'", "start time tracking --notes='my notes...'"
|
17
38
|
option :notes, type: :string, desc: 'Add a note'
|
18
39
|
def start(tag, notes = nil)
|
19
|
-
|
40
|
+
start_time = TimeHelper.current_timestamp
|
20
41
|
notes = options[:notes] || notes
|
21
|
-
|
42
|
+
|
43
|
+
insert_item_if_valid(start_time, tag, notes)
|
22
44
|
summary
|
23
45
|
end
|
24
46
|
|
25
47
|
desc 'stop', 'stop time tracking'
|
26
48
|
def stop
|
27
|
-
stop =
|
28
|
-
@db.update(stop) if @db.last_item_status == :
|
49
|
+
stop = TimeHelper.current_timestamp
|
50
|
+
@db.update(stop) if @db.last_item_status == :in_progress
|
29
51
|
result = @db.last_item
|
30
52
|
|
31
53
|
return unless result
|
@@ -35,12 +57,18 @@ module Timet
|
|
35
57
|
|
36
58
|
desc 'resume (r)', 'resume last task'
|
37
59
|
def resume
|
38
|
-
|
60
|
+
status = @db.last_item_status
|
61
|
+
|
62
|
+
case status
|
63
|
+
when :in_progress
|
39
64
|
puts 'A task is currently being tracked.'
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
65
|
+
when :complete
|
66
|
+
last_item = @db.last_item
|
67
|
+
if last_item
|
68
|
+
tag = last_item[FIELD_INDEX['tag']]
|
69
|
+
notes = last_item[FIELD_INDEX['notes']]
|
70
|
+
start(tag, notes)
|
71
|
+
end
|
44
72
|
end
|
45
73
|
end
|
46
74
|
|
@@ -49,10 +77,27 @@ module Timet
|
|
49
77
|
and export to csv_filename"
|
50
78
|
option :csv, type: :string, desc: 'Export to CSV file'
|
51
79
|
def summary(filter = nil, tag = nil)
|
52
|
-
csv_filename = options[:csv]
|
80
|
+
csv_filename = options[:csv].split('.')[0] if options[:csv]
|
53
81
|
summary = TimeReport.new(@db, filter, tag, csv_filename)
|
54
82
|
summary.display
|
55
|
-
|
83
|
+
if csv_filename && summary.items.any?
|
84
|
+
summary.export_sheet
|
85
|
+
elsif summary.items.empty?
|
86
|
+
puts 'No items found to export'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
desc 'edit (e) [id]', 'edit a task'
|
91
|
+
def edit(id)
|
92
|
+
item = @db.find_item(id)
|
93
|
+
return puts "No tracked time found for id: #{id}" unless item
|
94
|
+
|
95
|
+
display_item(item)
|
96
|
+
field = select_field_to_edit
|
97
|
+
new_value = prompt_for_new_value(item, field)
|
98
|
+
validate_and_update(item, field, new_value)
|
99
|
+
|
100
|
+
summary.display
|
56
101
|
end
|
57
102
|
|
58
103
|
desc 'delete (d) [id]', 'delete a task'
|
@@ -60,7 +105,7 @@ module Timet
|
|
60
105
|
item = @db.find_item(id)
|
61
106
|
return puts "No tracked time found for id: #{id}" unless item
|
62
107
|
|
63
|
-
TimeReport.new(@db
|
108
|
+
TimeReport.new(@db).show_row(item)
|
64
109
|
return unless TTY::Prompt.new.yes?('Are you sure you want to delete this entry?')
|
65
110
|
|
66
111
|
delete_item_and_print_message(id, "Deleted #{id}")
|
@@ -80,6 +125,12 @@ module Timet
|
|
80
125
|
|
81
126
|
private
|
82
127
|
|
128
|
+
def insert_item_if_valid(start_time, tag, notes)
|
129
|
+
return unless VALID_STATUSES_FOR_INSERTION.include?(@db.last_item_status)
|
130
|
+
|
131
|
+
@db.insert_item(start_time, tag, notes)
|
132
|
+
end
|
133
|
+
|
83
134
|
def delete_item_and_print_message(id, message)
|
84
135
|
@db.delete_item(id)
|
85
136
|
puts message
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timet
|
4
|
+
# Provides helper methods for the Timet application.
|
5
|
+
module ApplicationHelper
|
6
|
+
def display_item(item)
|
7
|
+
TimeReport.new(@db).show_row(item)
|
8
|
+
end
|
9
|
+
|
10
|
+
def prompt_for_new_value(item, field)
|
11
|
+
current_value = field_value(item, field)
|
12
|
+
prompt = TTY::Prompt.new(active_color: :green)
|
13
|
+
prompt.ask("Update #{field} (#{current_value}):")
|
14
|
+
end
|
15
|
+
|
16
|
+
def select_field_to_edit
|
17
|
+
prompt = TTY::Prompt.new(active_color: :green)
|
18
|
+
prompt.select('Edit Field?', Timet::Application::FIELD_INDEX.keys.map(&:capitalize), active_color: :cyan).downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
def field_value(item, field)
|
22
|
+
index = Timet::Application::FIELD_INDEX[field]
|
23
|
+
value = item[index]
|
24
|
+
return TimeHelper.timestamp_to_time(value) if %w[start end].include?(field)
|
25
|
+
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/timet/database.rb
CHANGED
@@ -54,6 +54,12 @@ module Timet
|
|
54
54
|
execute_sql("DELETE FROM items WHERE id = #{id}")
|
55
55
|
end
|
56
56
|
|
57
|
+
def update_item(id, field, value)
|
58
|
+
return if %w[start end].include?(field) && value.nil?
|
59
|
+
|
60
|
+
execute_sql("UPDATE items SET #{field}='#{value}' WHERE id = #{id}")
|
61
|
+
end
|
62
|
+
|
57
63
|
# Fetches the ID of the last inserted item
|
58
64
|
def fetch_last_id
|
59
65
|
result = execute_sql('SELECT id FROM items ORDER BY id DESC LIMIT 1').first
|
@@ -81,7 +87,7 @@ module Timet
|
|
81
87
|
start_time = last_item[1]
|
82
88
|
end_time = last_item[2]
|
83
89
|
|
84
|
-
total_seconds = end_time ? end_time - start_time :
|
90
|
+
total_seconds = end_time ? end_time - start_time : TimeHelper.current_timestamp - start_time
|
85
91
|
seconds_to_hms(total_seconds)
|
86
92
|
end
|
87
93
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timet
|
4
|
+
# This module is responsible for formatting the output of the `timet` application.
|
5
|
+
# It provides methods for formatting the table header, separators, and rows.
|
6
|
+
module Formatter
|
7
|
+
def format_table_header
|
8
|
+
header = <<~TABLE
|
9
|
+
Tracked time report \u001b[31m[#{@filter}]\033[0m:
|
10
|
+
#{format_table_separator}
|
11
|
+
\033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
|
12
|
+
#{format_table_separator}
|
13
|
+
TABLE
|
14
|
+
puts header
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_table_separator
|
18
|
+
'+-------+------------+--------+----------+----------+----------+--------------------------+'
|
19
|
+
end
|
20
|
+
|
21
|
+
def format_table_row(*row)
|
22
|
+
id, tag, start_date, start_time, end_time, duration, notes = row
|
23
|
+
"| #{id.to_s.rjust(5)} | #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
|
24
|
+
"#{end_time.split[1].rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} |"
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_notes(notes)
|
28
|
+
return ' ' * 23 if notes.nil?
|
29
|
+
|
30
|
+
notes = "#{notes.slice(0, 20)}..." if notes.length > 20
|
31
|
+
notes.ljust(23)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/timet/status_helper.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Timet
|
4
|
+
# Determines the status of a time tracking result based on the presence and end time of items.
|
5
|
+
module StatusHelper
|
6
|
+
def self.determine_status(result)
|
7
|
+
return :no_items if result.empty?
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
last_item_end = result.first[1]
|
10
|
+
return :in_progress unless last_item_end
|
10
11
|
|
11
|
-
|
12
|
+
:complete
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/timet/time_helper.rb
CHANGED
@@ -1,36 +1,96 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# -
|
7
|
-
# -
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module Timet
|
4
|
+
# This module provides helper functions for working with time and dates.
|
5
|
+
# It includes methods for:
|
6
|
+
# - formatting timestamps into a specific format
|
7
|
+
# - calculating the duration between two timestamps
|
8
|
+
# - converting a Date object to a timestamp
|
9
|
+
module TimeHelper
|
10
|
+
def self.format_time(timestamp)
|
11
|
+
return nil if timestamp.nil?
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
13
|
+
Time.at(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
16
|
+
def self.timestamp_to_date(timestamp)
|
17
|
+
return nil if timestamp.nil?
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
end_time = end_time ? Time.at(end_time) : Time.now
|
19
|
+
Time.at(timestamp).strftime('%Y-%m-%d')
|
20
|
+
end
|
24
21
|
|
25
|
-
(
|
26
|
-
|
22
|
+
def self.timestamp_to_time(timestamp)
|
23
|
+
return nil if timestamp.nil?
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
Time.at(timestamp).strftime('%H:%M:%S')
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.calculate_duration(start_time, end_time)
|
29
|
+
end_time = end_time ? Time.at(end_time) : current_timestamp
|
30
|
+
(end_time - start_time).to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.date_to_timestamp(date)
|
34
|
+
date.to_time.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.calculate_end_time(start_date, end_date)
|
38
|
+
end_date ||= start_date + 1
|
39
|
+
date_to_timestamp(end_date)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.extract_date(items, idx)
|
43
|
+
current_start_date = items[idx][1]
|
44
|
+
date = TimeHelper.timestamp_to_date(current_start_date)
|
45
|
+
|
46
|
+
last_start_date = items[idx - 1][1] if idx.positive?
|
47
|
+
date if idx.zero? || date != TimeHelper.timestamp_to_date(last_start_date)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Formats a time string into a standard HH:MM:SS format.
|
51
|
+
#
|
52
|
+
# @param input [String] The input string to format.
|
53
|
+
# @return [String] The formatted time string in HH:MM:SS format, or nil if the input is invalid.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# TimeHelper.format_time_string('123456') # => "12:34:56"
|
57
|
+
# TimeHelper.format_time_string('1234567') # => "12:34:56"
|
58
|
+
# TimeHelper.format_time_string('1234') # => "12:34:00"
|
59
|
+
# TimeHelper.format_time_string('123') # => "12:30:00"
|
60
|
+
# TimeHelper.format_time_string('12') # => "12:00:00"
|
61
|
+
# TimeHelper.format_time_string('1') # => "01:00:00"
|
62
|
+
# TimeHelper.format_time_string('127122') # => nil
|
63
|
+
# TimeHelper.format_time_string('abc') # => nil
|
64
|
+
def self.format_time_string(input)
|
65
|
+
return nil if input.nil? || input.empty?
|
66
|
+
|
67
|
+
digits = input.gsub(/\D/, '')[0..5]
|
68
|
+
return nil if digits.empty?
|
69
|
+
|
70
|
+
hours, minutes, seconds = parse_time_components(digits)
|
71
|
+
return nil unless valid_time?(hours, minutes, seconds)
|
72
|
+
|
73
|
+
format('%<hours>02d:%<minutes>02d:%<seconds>02d', hours: hours, minutes: minutes, seconds: seconds)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.parse_time_components(digits)
|
77
|
+
padded_digits = case digits.size
|
78
|
+
when 1 then "0#{digits}0000"
|
79
|
+
when 2 then "#{digits}0000"
|
80
|
+
when 3 then "#{digits}000"
|
81
|
+
when 4 then "#{digits}00"
|
82
|
+
else digits.ljust(6, '0')
|
83
|
+
end
|
84
|
+
|
85
|
+
padded_digits.scan(/.{2}/).map(&:to_i)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.valid_time?(hours, minutes, seconds)
|
89
|
+
hours < 24 && minutes < 60 && seconds < 60
|
90
|
+
end
|
31
91
|
|
32
|
-
|
33
|
-
|
34
|
-
|
92
|
+
def self.current_timestamp
|
93
|
+
Time.now.utc.to_i
|
94
|
+
end
|
35
95
|
end
|
36
96
|
end
|
data/lib/timet/time_report.rb
CHANGED
@@ -2,17 +2,19 @@
|
|
2
2
|
|
3
3
|
require 'date'
|
4
4
|
require 'csv'
|
5
|
-
require_relative 'time_helper'
|
6
5
|
require_relative 'status_helper'
|
6
|
+
require_relative 'formatter'
|
7
7
|
|
8
8
|
module Timet
|
9
9
|
# The TimeReport class is responsible for displaying a report of tracked time
|
10
|
-
# entries. It allows filtering the report by time periods
|
11
|
-
#
|
10
|
+
# entries. It allows filtering the report by time periods and displays
|
11
|
+
# a formatted table with the relevant information.
|
12
12
|
class TimeReport
|
13
|
+
include Formatter
|
14
|
+
|
13
15
|
attr_reader :db, :items, :filename
|
14
16
|
|
15
|
-
def initialize(db, filter, tag, csv)
|
17
|
+
def initialize(db, filter = nil, tag = nil, csv = nil)
|
16
18
|
@db = db
|
17
19
|
@filename = csv
|
18
20
|
@filter = formatted_filter(filter)
|
@@ -24,7 +26,7 @@ module Timet
|
|
24
26
|
|
25
27
|
format_table_header
|
26
28
|
items.each_with_index do |item, idx|
|
27
|
-
date = extract_date(items, idx)
|
29
|
+
date = TimeHelper.extract_date(items, idx)
|
28
30
|
display_time_entry(item, date)
|
29
31
|
end
|
30
32
|
puts format_table_separator
|
@@ -39,29 +41,32 @@ module Timet
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def export_sheet
|
42
|
-
|
43
|
-
|
44
|
+
file_name = "#{filename}.csv"
|
45
|
+
write_csv(file_name)
|
44
46
|
|
45
|
-
|
46
|
-
csv << [
|
47
|
-
id,
|
48
|
-
TimeHelper.format_time(start_time),
|
49
|
-
TimeHelper.format_time(end_time),
|
50
|
-
tags,
|
51
|
-
notes
|
52
|
-
]
|
53
|
-
end
|
54
|
-
end
|
47
|
+
puts "The #{file_name} has been exported."
|
55
48
|
end
|
56
49
|
|
57
50
|
private
|
58
51
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
52
|
+
def write_csv(file_name)
|
53
|
+
CSV.open(file_name, 'w') do |csv|
|
54
|
+
csv << %w[ID Start End Tag Notes]
|
55
|
+
items.each do |item|
|
56
|
+
csv << format_item(item)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
62
60
|
|
63
|
-
|
64
|
-
|
61
|
+
def format_item(item)
|
62
|
+
id, start_time, end_time, tags, notes = item
|
63
|
+
[
|
64
|
+
id,
|
65
|
+
TimeHelper.format_time(start_time),
|
66
|
+
TimeHelper.format_time(end_time),
|
67
|
+
tags,
|
68
|
+
notes
|
69
|
+
]
|
65
70
|
end
|
66
71
|
|
67
72
|
def display_time_entry(item, date = nil)
|
@@ -71,7 +76,7 @@ module Timet
|
|
71
76
|
duration = TimeHelper.calculate_duration(start_time_value, end_time_value)
|
72
77
|
start_time = TimeHelper.format_time(start_time_value)
|
73
78
|
end_time = TimeHelper.format_time(end_time_value) || '- -'
|
74
|
-
start_date = date
|
79
|
+
start_date = date || (' ' * 10)
|
75
80
|
puts format_table_row(id, tag_name[0..5], start_date, start_time, end_time, duration, notes)
|
76
81
|
end
|
77
82
|
|
@@ -83,43 +88,26 @@ module Timet
|
|
83
88
|
puts format_table_separator
|
84
89
|
end
|
85
90
|
|
86
|
-
def format_table_header
|
87
|
-
header = <<~TABLE
|
88
|
-
Tracked time report \u001b[31m[#{@filter}]\033[0m:
|
89
|
-
#{format_table_separator}
|
90
|
-
\033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
|
91
|
-
#{format_table_separator}
|
92
|
-
TABLE
|
93
|
-
puts header
|
94
|
-
end
|
95
|
-
|
96
|
-
def format_table_separator
|
97
|
-
'+-------+------------+--------+----------+----------+----------+--------------------------+'
|
98
|
-
end
|
99
|
-
|
100
|
-
def format_table_row(*row)
|
101
|
-
id, tag, start_date, start_time, end_time, duration, notes = row
|
102
|
-
"| #{id.to_s.rjust(5)} | #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
|
103
|
-
"#{end_time.split[1].rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} |"
|
104
|
-
end
|
105
|
-
|
106
91
|
def filter_items(filter, tag)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
filter_by_date_range(today, nil, tag)
|
111
|
-
when 'yesterday'
|
112
|
-
filter_by_date_range(today - 1, nil, tag)
|
113
|
-
when 'week'
|
114
|
-
filter_by_date_range(today - 7, today + 1, tag)
|
115
|
-
when 'month'
|
116
|
-
filter_by_date_range(today - 30, today + 1, tag)
|
92
|
+
if date_ranges.key?(filter)
|
93
|
+
start_date, end_date = date_ranges[filter]
|
94
|
+
filter_by_date_range(start_date, end_date, tag)
|
117
95
|
else
|
118
|
-
puts 'Invalid filter. Supported filters: today, yesterday, week'
|
96
|
+
puts 'Invalid filter. Supported filters: today, yesterday, week, month'
|
119
97
|
[]
|
120
98
|
end
|
121
99
|
end
|
122
100
|
|
101
|
+
def date_ranges
|
102
|
+
today = Date.today
|
103
|
+
{
|
104
|
+
'today' => [today, nil],
|
105
|
+
'yesterday' => [today - 1, nil],
|
106
|
+
'week' => [today - 7, today + 1],
|
107
|
+
'month' => [today - 30, today + 1]
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
123
111
|
def filter_by_date_range(start_date, end_date = nil, tag = nil)
|
124
112
|
start_time = TimeHelper.date_to_timestamp(start_date)
|
125
113
|
end_time = TimeHelper.calculate_end_time(start_date, end_date)
|
@@ -129,13 +117,6 @@ module Timet
|
|
129
117
|
)
|
130
118
|
end
|
131
119
|
|
132
|
-
def format_notes(notes)
|
133
|
-
return ' ' * 23 if notes.nil?
|
134
|
-
|
135
|
-
notes = "#{notes.slice(0, 20)}..." if notes.length > 20
|
136
|
-
notes.ljust(23)
|
137
|
-
end
|
138
|
-
|
139
120
|
def formatted_filter(filter)
|
140
121
|
return 'today' if %w[today t].include?(filter)
|
141
122
|
return 'yesterday' if %w[yesterday y].include?(filter)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'time_helper'
|
4
|
+
module Timet
|
5
|
+
# Validates and updates a specific field of an item based on certain conditions.
|
6
|
+
# If the field is 'start' or 'end', it checks and updates the value accordingly.
|
7
|
+
# Otherwise, it directly updates the field with the new value.
|
8
|
+
module ValidationEditHelper
|
9
|
+
TIME_FIELDS = %w[start end].freeze
|
10
|
+
|
11
|
+
def validate_and_update(item, field, new_value)
|
12
|
+
return if new_value.nil?
|
13
|
+
|
14
|
+
id = item[0]
|
15
|
+
|
16
|
+
if TIME_FIELDS.include?(field)
|
17
|
+
process_and_update_time_field(item, field, new_value, id)
|
18
|
+
else
|
19
|
+
@db.update_item(id, field, new_value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def process_and_update_time_field(item, field, date_value, id)
|
26
|
+
formatted_date = TimeHelper.format_time_string(date_value)
|
27
|
+
|
28
|
+
return print_error(date_value) unless formatted_date
|
29
|
+
|
30
|
+
new_date = update_time_field(item, field, formatted_date)
|
31
|
+
new_value_epoch = new_date.to_i
|
32
|
+
|
33
|
+
if valid_time_value?(item, field, new_value_epoch, id)
|
34
|
+
@db.update_item(id, field, new_value_epoch)
|
35
|
+
else
|
36
|
+
print_error(new_date)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def print_error(message)
|
41
|
+
puts "\u001b[31mInvalid date: #{message}\033[0m"
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_time_field(item, field, formatted_value)
|
45
|
+
field_index = Timet::Application::FIELD_INDEX[field]
|
46
|
+
timestamp = item[field_index]
|
47
|
+
current_time = Time.at(timestamp || TimeHelper.current_timestamp).to_s.split
|
48
|
+
current_time[1] = formatted_value
|
49
|
+
DateTime.strptime(current_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_time_value?(item, field, new_value_epoch, id)
|
53
|
+
item_start = fetch_item_start(item)
|
54
|
+
item_end = fetch_item_end(item)
|
55
|
+
item_before_end = fetch_item_before_end(id, item_start)
|
56
|
+
item_after_start = fetch_item_after_start(id)
|
57
|
+
|
58
|
+
if field == 'start'
|
59
|
+
new_value_epoch >= item_before_end && new_value_epoch <= item_end
|
60
|
+
else
|
61
|
+
new_value_epoch >= item_start && new_value_epoch <= item_after_start
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_item_start(item)
|
66
|
+
item[Timet::Application::FIELD_INDEX['start']]
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch_item_end(item)
|
70
|
+
item[Timet::Application::FIELD_INDEX['end']] || TimeHelper.current_timestamp
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch_item_before_end(id, item_start)
|
74
|
+
@db.find_item(id - 1)&.dig(Timet::Application::FIELD_INDEX['end']) || item_start
|
75
|
+
end
|
76
|
+
|
77
|
+
def fetch_item_after_start(id)
|
78
|
+
@db.find_item(id + 1)&.dig(Timet::Application::FIELD_INDEX['start']) || TimeHelper.current_timestamp
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/timet/version.rb
CHANGED
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: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Vielma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -58,8 +58,7 @@ dependencies:
|
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '1.7'
|
61
|
-
description:
|
62
|
-
different things.
|
61
|
+
description: Timet is a command-line time tracker that keeps track of your activities.
|
63
62
|
email:
|
64
63
|
- frankvielma@gmail.com
|
65
64
|
executables:
|
@@ -78,10 +77,13 @@ files:
|
|
78
77
|
- bin/timet
|
79
78
|
- lib/timet.rb
|
80
79
|
- lib/timet/application.rb
|
80
|
+
- lib/timet/application_helper.rb
|
81
81
|
- lib/timet/database.rb
|
82
|
+
- lib/timet/formatter.rb
|
82
83
|
- lib/timet/status_helper.rb
|
83
84
|
- lib/timet/time_helper.rb
|
84
85
|
- lib/timet/time_report.rb
|
86
|
+
- lib/timet/validation_edit_helper.rb
|
85
87
|
- lib/timet/version.rb
|
86
88
|
- sig/timet.rbs
|
87
89
|
homepage: https://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
|
@@ -110,5 +112,5 @@ requirements: []
|
|
110
112
|
rubygems_version: 3.5.9
|
111
113
|
signing_key:
|
112
114
|
specification_version: 4
|
113
|
-
summary: Command line time
|
115
|
+
summary: Command line time tracker with reports
|
114
116
|
test_files: []
|