timet 1.3.2 → 1.4.0
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 +24 -0
- data/lib/timet/application.rb +17 -35
- data/lib/timet/application_helper.rb +36 -5
- data/lib/timet/color_codes.rb +9 -1
- data/lib/timet/database.rb +19 -13
- data/lib/timet/table.rb +300 -0
- data/lib/timet/tag_distribution.rb +61 -0
- data/lib/timet/{formatter.rb → time_block_chart.rb} +76 -152
- data/lib/timet/time_report.rb +20 -44
- data/lib/timet/time_report_helper.rb +0 -66
- data/lib/timet/version.rb +2 -2
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bec76070c29c2eab4a2a5792084a9980c1cd29c0ab3ac80dbae2c6310e74d78a
|
4
|
+
data.tar.gz: 551fe37b00ac0ca015e28fcdd37810216c0ff00ea4b800c97bd0fb539af1df7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 290e56a65e8c9e163d1558efe87fb93c8d74522c9aa1410f77a21ea6fe81d4a8270ec03947deb959699940c1c40ff91b4749f9512f1de4e005ac822cb564934a
|
7
|
+
data.tar.gz: 0e27f41ad71bca972355c70c235d900ae0c4ec571c47f9fd7324b979b3036b02f4cd6dd5aaa0464d4dd5ac12f2989ae38aed5e15613c496eb182dacb92f3bf11
|
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/lib/timet/application.rb
CHANGED
@@ -69,10 +69,12 @@ module Timet
|
|
69
69
|
notes = options[:notes] || notes
|
70
70
|
pomodoro = (options[:pomodoro] || pomodoro).to_i
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
|
72
|
+
unless VALID_STATUSES_FOR_INSERTION.include?(@db.last_item_status)
|
73
|
+
return puts 'A task is currently being tracked.'
|
75
74
|
end
|
75
|
+
|
76
|
+
@db.insert_item(start_time, tag, notes, pomodoro)
|
77
|
+
play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
|
76
78
|
summary
|
77
79
|
end
|
78
80
|
|
@@ -128,15 +130,15 @@ module Timet
|
|
128
130
|
end
|
129
131
|
end
|
130
132
|
|
131
|
-
desc 'summary (su) [
|
132
|
-
'[
|
133
|
+
desc 'summary (su) [time_scope] [tag] --csv=csv_filename',
|
134
|
+
'[time_scope] => [today (t), yesterday (y), week (w), month (m), [start_date]..[end_date]] [tag]'
|
133
135
|
option :csv, type: :string, desc: 'Export to CSV file'
|
134
|
-
# Generates a summary of tracking items based on the provided
|
136
|
+
# Generates a summary of tracking items based on the provided time_scope and tag, and optionally exports the summary
|
135
137
|
# to a CSV file.
|
136
138
|
#
|
137
|
-
# @param
|
138
|
-
# 'yesterday', 'week', 'month', or a date range in the format '[start_date]..[end_date]'.
|
139
|
-
# @param tag [String, nil] The tag to
|
139
|
+
# @param time_scope [String, nil] The time_scope to apply when generating the summary. Possible values include
|
140
|
+
# 'today', 'yesterday', 'week', 'month', or a date range in the format '[start_date]..[end_date]'.
|
141
|
+
# @param tag [String, nil] The tag to time_scope the tracking items by.
|
140
142
|
#
|
141
143
|
# @return [void] This method does not return a value; it performs side effects such as displaying the summary and
|
142
144
|
# exporting to CSV if specified.
|
@@ -150,19 +152,19 @@ module Timet
|
|
150
152
|
# @example Generate a summary for a date range and export to CSV
|
151
153
|
# summary('2023-01-01..2023-01-31', nil, csv: 'summary.csv')
|
152
154
|
#
|
153
|
-
# @note The method initializes a `TimeReport` object with the database,
|
155
|
+
# @note The method initializes a `TimeReport` object with the database, time_scope, tag, and optional CSV filename.
|
154
156
|
# @note The method calls `display` on the `TimeReport` object to show the summary.
|
155
157
|
# @note If a CSV filename is provided and there are items to export, the method calls `export_sheet` to export the
|
156
158
|
# summary to a CSV file.
|
157
159
|
# @note If no items are found to export, it prints a message indicating that no items were found.
|
158
|
-
def summary(
|
160
|
+
def summary(time_scope = nil, tag = nil)
|
159
161
|
csv_filename = options[:csv]&.split('.')&.first
|
160
|
-
|
162
|
+
report = TimeReport.new(@db, time_scope, tag, csv_filename)
|
161
163
|
|
162
|
-
|
163
|
-
items =
|
164
|
+
report.display
|
165
|
+
items = report.items
|
164
166
|
if csv_filename && items.any?
|
165
|
-
|
167
|
+
report.export_sheet
|
166
168
|
elsif items.empty?
|
167
169
|
puts 'No items found to export'
|
168
170
|
end
|
@@ -281,25 +283,5 @@ module Timet
|
|
281
283
|
def version
|
282
284
|
puts Timet::VERSION
|
283
285
|
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
286
|
end
|
305
287
|
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 '
|
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.
|
115
|
+
Process.detach(pid)
|
116
116
|
end
|
117
117
|
|
118
118
|
# Runs a Pomodoro session on a macOS system.
|
@@ -120,11 +120,42 @@ 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,
|
124
|
-
notification_command = "osascript -e 'display notification \"
|
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.
|
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
|
128
159
|
end
|
129
160
|
end
|
130
161
|
end
|
data/lib/timet/color_codes.rb
CHANGED
@@ -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
|
data/lib/timet/database.rb
CHANGED
@@ -28,7 +28,9 @@ module Timet
|
|
28
28
|
def initialize(database_path = DEFAULT_DATABASE_PATH)
|
29
29
|
@db = SQLite3::Database.new(database_path)
|
30
30
|
create_table
|
31
|
-
|
31
|
+
|
32
|
+
add_column('items', 'notes', 'TEXT')
|
33
|
+
add_column('items', 'pomodoro', 'INTEGER')
|
32
34
|
end
|
33
35
|
|
34
36
|
# Creates the items table if it doesn't already exist.
|
@@ -51,23 +53,27 @@ module Timet
|
|
51
53
|
SQL
|
52
54
|
end
|
53
55
|
|
54
|
-
# Adds a new column
|
56
|
+
# Adds a new column to the specified table if it does not already exist.
|
55
57
|
#
|
56
|
-
# @
|
57
|
-
# the column.
|
58
|
+
# @param table_name [String] The name of the table to which the column will be added.
|
59
|
+
# @param new_column_name [String] The name of the new column to be added.
|
60
|
+
# @param date_type [String] The data type of the new column (e.g., 'INTEGER', 'TEXT', 'BOOLEAN').
|
61
|
+
# @return [void] This method does not return a value; it performs side effects such as adding the column and
|
62
|
+
# printing a message.
|
58
63
|
#
|
59
|
-
# @example Add
|
60
|
-
#
|
64
|
+
# @example Add a new 'completed' column to the 'tasks' table
|
65
|
+
# add_column('tasks', 'completed', 'INTEGER')
|
61
66
|
#
|
62
|
-
# @note The method checks if the
|
63
|
-
|
64
|
-
|
65
|
-
|
67
|
+
# @note The method first checks if the column already exists in the table using `pragma_table_info`.
|
68
|
+
# @note If the column exists, the method returns without making any changes.
|
69
|
+
# @note If the column does not exist, the method executes an SQL `ALTER TABLE` statement to add the column.
|
70
|
+
# @note The method prints a message indicating that the column has been added.
|
71
|
+
def add_column(table_name, new_column_name, date_type)
|
66
72
|
result = execute_sql("SELECT count(*) FROM pragma_table_info('items') where name='#{new_column_name}'")
|
67
73
|
column_exists = result[0][0].positive?
|
68
74
|
return if column_exists
|
69
75
|
|
70
|
-
execute_sql("ALTER TABLE #{table_name} ADD COLUMN #{new_column_name}
|
76
|
+
execute_sql("ALTER TABLE #{table_name} ADD COLUMN #{new_column_name} #{date_type}")
|
71
77
|
puts "Column '#{new_column_name}' added to table '#{table_name}'."
|
72
78
|
end
|
73
79
|
|
@@ -84,8 +90,8 @@ module Timet
|
|
84
90
|
# insert_item(1633072800, 'work', 'Completed task X')
|
85
91
|
#
|
86
92
|
# @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])
|
93
|
+
def insert_item(start, tag, notes, pomodoro = nil)
|
94
|
+
execute_sql('INSERT INTO items (start, tag, notes, pomodoro) VALUES (?, ?, ?, ?)', [start, tag, notes, pomodoro])
|
89
95
|
end
|
90
96
|
|
91
97
|
# Updates an existing item in the items table.
|
data/lib/timet/table.rb
ADDED
@@ -0,0 +1,300 @@
|
|
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 Table
|
7
|
+
# Generates and displays a table summarizing time entries, including headers, time blocks, and total durations.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# table
|
11
|
+
#
|
12
|
+
# @return [Array<(String, Hash)>] An array containing the time block string and a hash of durations by tag.
|
13
|
+
#
|
14
|
+
# @note
|
15
|
+
# - The method relies on the `header`, `process_time_entries`, `separator`, and `total` methods.
|
16
|
+
# - The `header` method is responsible for printing the table header.
|
17
|
+
# - The `process_time_entries` method processes the time entries and returns the time block and duration by tag.
|
18
|
+
# - The `separator` method returns a string representing the separator line.
|
19
|
+
# - The `total` method prints the total duration.
|
20
|
+
#
|
21
|
+
# @see #header
|
22
|
+
# @see #process_time_entries
|
23
|
+
# @see #separator
|
24
|
+
# @see #total
|
25
|
+
def table
|
26
|
+
header
|
27
|
+
time_block, duration_by_tag = process_time_entries
|
28
|
+
puts separator
|
29
|
+
total
|
30
|
+
[time_block, duration_by_tag]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Formats the header of the time tracking report table.
|
34
|
+
#
|
35
|
+
# @return [void] This method does not return a value; it performs side effects such as printing
|
36
|
+
# the formatted header.
|
37
|
+
#
|
38
|
+
# @example Format and print the table header
|
39
|
+
# header
|
40
|
+
#
|
41
|
+
# @note The method constructs a string representing the table header and prints it.
|
42
|
+
def header
|
43
|
+
title = "Tracked time report [#{@filter.blink.red}]:"
|
44
|
+
header = <<~TABLE
|
45
|
+
#{title}
|
46
|
+
#{separator}
|
47
|
+
\033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
|
48
|
+
#{separator}
|
49
|
+
TABLE
|
50
|
+
puts header
|
51
|
+
end
|
52
|
+
|
53
|
+
# Formats the separator line for the time tracking report table.
|
54
|
+
#
|
55
|
+
# @return [String] The formatted separator line.
|
56
|
+
#
|
57
|
+
# @example Get the formatted table separator
|
58
|
+
# separator # => '+-------+------------+--------+----------+----------+----------+------------+'
|
59
|
+
#
|
60
|
+
# @note The method returns a string representing the separator line for the table.
|
61
|
+
def separator
|
62
|
+
'+-------+------------+--------+----------+----------+----------+--------------------+'
|
63
|
+
end
|
64
|
+
|
65
|
+
# Processes each time entry in the `items` array and updates the time block and duration by tag.
|
66
|
+
#
|
67
|
+
# @return [Array<(Hash, Hash)>] An array containing the updated time block and duration by tag.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# items = [
|
71
|
+
# [start_time1, end_time1, tag1],
|
72
|
+
# [start_time2, end_time2, tag2]
|
73
|
+
# ]
|
74
|
+
# process_time_entries
|
75
|
+
# #=> [{ '2024-10-21' => { 8 => [duration1, tag1], 9 => [duration2, tag2] } },
|
76
|
+
# { tag1 => total_duration1, tag2 => total_duration2 }]
|
77
|
+
#
|
78
|
+
# @note
|
79
|
+
# - The method relies on the `items` instance variable, which should be an array of arrays.
|
80
|
+
# - Each sub-array in `items` is expected to contain a start time, end time, and a tag.
|
81
|
+
# - The `display_time_entry` method is used to display each time entry.
|
82
|
+
# - The `process_time_block_item` method processes each time entry and updates the time block and duration by tag.
|
83
|
+
#
|
84
|
+
# @see #items
|
85
|
+
# @see #display_time_entry
|
86
|
+
# @see #process_time_block_item
|
87
|
+
def process_time_entries
|
88
|
+
duration_by_tag = Hash.new(0)
|
89
|
+
time_block = Hash.new { |hash, key| hash[key] = {} }
|
90
|
+
|
91
|
+
items.each_with_index do |item, idx|
|
92
|
+
display_time_entry(item, TimeHelper.extract_date(items, idx))
|
93
|
+
time_block, duration_by_tag = process_time_block_item(item, time_block, duration_by_tag)
|
94
|
+
end
|
95
|
+
[time_block, duration_by_tag]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Processes a time block item and updates the time block hash.
|
99
|
+
#
|
100
|
+
# @param item [Array] The time entry to process, containing the start time, end time, and tag.
|
101
|
+
# @param time_block [Hash] A hash containing time block data, where keys are dates and values are hashes of time
|
102
|
+
# slots and their corresponding values.
|
103
|
+
# @param duration_by_tag [Hash] A hash containing the total duration by tag.
|
104
|
+
#
|
105
|
+
# @return [Array<(Hash, Hash)>] An array containing the updated time block hash and the updated duration
|
106
|
+
# by tag hash.
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# item = [nil, Time.new(2024, 10, 21, 8, 0, 0), Time.new(2024, 10, 21, 9, 0, 0), 'work']
|
110
|
+
# time_block = {}
|
111
|
+
# duration_by_tag = {}
|
112
|
+
# process_time_block_item(item, time_block, duration_by_tag)
|
113
|
+
# #=> [{ '2024-10-21' => { 8 => [3600, 'work'] } }, { 'work' => 3600 }]
|
114
|
+
#
|
115
|
+
# @note
|
116
|
+
# - The method relies on the `TimeHelper` module for time-related calculations.
|
117
|
+
# - The `add_hashes` method is used to merge the new time block data into the existing time block hash.
|
118
|
+
# - The `calculate_duration` method calculates the duration between the start and end times.
|
119
|
+
#
|
120
|
+
# @see TimeHelper#count_seconds_per_hour_block
|
121
|
+
# @see TimeHelper#timestamp_to_date
|
122
|
+
# @see TimeHelper#calculate_duration
|
123
|
+
# @see #add_hashes
|
124
|
+
def process_time_block_item(item, time_block, duration_by_tag)
|
125
|
+
_, start_time, end_time, tag = item
|
126
|
+
|
127
|
+
block_hour = TimeHelper.count_seconds_per_hour_block(start_time, end_time, tag)
|
128
|
+
date_line = TimeHelper.timestamp_to_date(start_time)
|
129
|
+
time_block[date_line] = add_hashes(time_block[date_line], block_hour)
|
130
|
+
duration_by_tag[tag] += TimeHelper.calculate_duration(start_time, end_time)
|
131
|
+
[time_block, duration_by_tag]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Displays a single time entry in the report.
|
135
|
+
#
|
136
|
+
# @param item [Array] The item to display.
|
137
|
+
# @param date [String, nil] The date to display. If nil, the date is not displayed.
|
138
|
+
#
|
139
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the row.
|
140
|
+
#
|
141
|
+
# @example Display a time entry
|
142
|
+
# display_time_entry(item, '2021-10-01')
|
143
|
+
#
|
144
|
+
# @note The method formats and prints the row for the time entry.
|
145
|
+
def display_time_entry(item, date = nil)
|
146
|
+
return puts 'Missing time entry data.' unless item
|
147
|
+
|
148
|
+
id, start_time_value, end_time_value, tag_name, notes = item
|
149
|
+
duration = TimeHelper.calculate_duration(start_time_value, end_time_value)
|
150
|
+
start_time = TimeHelper.format_time(start_time_value)
|
151
|
+
end_time = TimeHelper.format_time(end_time_value)
|
152
|
+
start_date = date || (' ' * 10)
|
153
|
+
puts format_table_row(id, tag_name[0..5], start_date, start_time, end_time, duration, notes)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Formats a table row with the given row data.
|
157
|
+
#
|
158
|
+
# @param row [Array] The row data to format, containing the following elements:
|
159
|
+
# - id [Integer] The ID of the time entry.
|
160
|
+
# - tag [String] The tag associated with the time entry.
|
161
|
+
# - start_date [String] The start date of the time entry.
|
162
|
+
# - start_time [String] The start time of the time entry.
|
163
|
+
# - end_time [String] The end time of the time entry.
|
164
|
+
# - duration [Integer] The duration of the time entry in seconds.
|
165
|
+
# - notes [String] Any notes associated with the time entry.
|
166
|
+
#
|
167
|
+
# @return [String] The formatted table row.
|
168
|
+
#
|
169
|
+
# @example
|
170
|
+
# row = [1, 'work', '2024-10-21', '08:00:00', '09:00:00', 3600, 'Completed task A']
|
171
|
+
# format_table_row(*row)
|
172
|
+
# #=> "| 1| 2024-10-21 | work | 08:00:00 | 09:00:00 | 1:00:00 | Completed task A |"
|
173
|
+
#
|
174
|
+
# @note
|
175
|
+
# - The method relies on the `@db` instance variable, which should be an object with `find_item`
|
176
|
+
# and `seconds_to_hms` methods.
|
177
|
+
# - The `format_end_time`, `format_mark`, and `format_notes` methods are used to format specific parts of the row.
|
178
|
+
#
|
179
|
+
# @see #format_end_time
|
180
|
+
# @see #format_mark
|
181
|
+
# @see #format_notes
|
182
|
+
def format_table_row(*row)
|
183
|
+
id, tag, start_date, start_time, end_time, duration, notes = row
|
184
|
+
end_time = format_end_time(end_time, id, duration)
|
185
|
+
mark = format_mark(id)
|
186
|
+
|
187
|
+
"| #{id.to_s.rjust(6)}| #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
|
188
|
+
"#{end_time.rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} #{mark}"
|
189
|
+
end
|
190
|
+
|
191
|
+
# Formats the end time of the time entry.
|
192
|
+
#
|
193
|
+
# @param end_time [String] The end time of the time entry.
|
194
|
+
# @param id [Integer] The ID of the time entry.
|
195
|
+
# @param duration [Integer] The duration of the time entry in seconds.
|
196
|
+
#
|
197
|
+
# @return [String] The formatted end time.
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# format_end_time('09:00:00', 1, 3600)
|
201
|
+
# #=> "09:00:00"
|
202
|
+
#
|
203
|
+
# @note
|
204
|
+
# - The method relies on the `@db` instance variable, which should be an object with a `find_item` method.
|
205
|
+
# - If the `pomodoro` value is positive and the end time is not set, a blinking `timet` is added.
|
206
|
+
#
|
207
|
+
# @see #format_table_row
|
208
|
+
def format_end_time(end_time, id, duration)
|
209
|
+
end_time = end_time ? end_time.split[1] : '-'
|
210
|
+
pomodoro = @db.find_item(id)[5] || 0
|
211
|
+
|
212
|
+
if pomodoro.positive? && end_time == '-'
|
213
|
+
delta = (@db.find_item(id)[5] - (duration / 60.0)).round(1)
|
214
|
+
timet = "\e]8;;Session ends\a#{delta} min\e]8;;\a".green
|
215
|
+
end_time = " #{timet}".blink
|
216
|
+
end
|
217
|
+
|
218
|
+
end_time
|
219
|
+
end
|
220
|
+
|
221
|
+
# Formats the mark for the time entry.
|
222
|
+
#
|
223
|
+
# @param id [Integer] The ID of the time entry.
|
224
|
+
#
|
225
|
+
# @return [String] The formatted mark.
|
226
|
+
#
|
227
|
+
# @example
|
228
|
+
# format_mark(1)
|
229
|
+
# #=> "|"
|
230
|
+
#
|
231
|
+
# @note
|
232
|
+
# - The method relies on the `@db` instance variable, which should be an object with a `find_item` method.
|
233
|
+
# - If the `pomodoro` value is positive, a special mark is added.
|
234
|
+
#
|
235
|
+
# @see #format_table_row
|
236
|
+
def format_mark(id)
|
237
|
+
pomodoro = @db.find_item(id)[5] || 0
|
238
|
+
mark = '|'
|
239
|
+
mark = "#{'├'.white} #{'P'.blue.blink}" if pomodoro.positive?
|
240
|
+
mark
|
241
|
+
end
|
242
|
+
|
243
|
+
# Formats the notes column of the time tracking report table.
|
244
|
+
#
|
245
|
+
# @param notes [String, nil] The notes to be formatted.
|
246
|
+
# @return [String] The formatted notes.
|
247
|
+
#
|
248
|
+
# @example Format notes
|
249
|
+
# format_notes('This is a long note that needs to be truncated')
|
250
|
+
#
|
251
|
+
# @note The method truncates the notes to a maximum of 20 characters and pads them to a fixed width.
|
252
|
+
def format_notes(notes)
|
253
|
+
spaces = 17
|
254
|
+
return ' ' * spaces unless notes
|
255
|
+
|
256
|
+
max_length = spaces - 3
|
257
|
+
notes = "#{notes.slice(0, max_length)}..." if notes.length > max_length
|
258
|
+
notes.ljust(spaces)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Displays the total duration of the tracked time entries.
|
262
|
+
#
|
263
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the total duration.
|
264
|
+
#
|
265
|
+
# @example Display the total duration
|
266
|
+
# total
|
267
|
+
#
|
268
|
+
# @note The method calculates and prints the total duration of the tracked time entries.
|
269
|
+
def total
|
270
|
+
total = @items.map do |item|
|
271
|
+
TimeHelper.calculate_duration(item[1], item[2])
|
272
|
+
end.sum
|
273
|
+
puts "|#{' ' * 43}#{'Total:'.blue} | #{@db.seconds_to_hms(total).rjust(8).blue} |#{' ' * 20}|"
|
274
|
+
puts separator
|
275
|
+
display_pomodoro_label
|
276
|
+
end
|
277
|
+
|
278
|
+
# Displays a blinking "Pomodoro" label if the sum of the compacted values in the 6th column of @items is positive.
|
279
|
+
#
|
280
|
+
# @example
|
281
|
+
# display_pomodoro_label
|
282
|
+
#
|
283
|
+
# @return [void] This method returns nothing.
|
284
|
+
#
|
285
|
+
# @note
|
286
|
+
# - The method relies on the `@items` instance variable, which should be an array of arrays.
|
287
|
+
# - The 6th column of each sub-array in `@items` is expected to contain numeric values.
|
288
|
+
# - The method uses the `blue.blink` color formatting, which assumes the presence of a `String` extension or
|
289
|
+
# gem that supports color formatting.
|
290
|
+
#
|
291
|
+
# @see #@items
|
292
|
+
# @see String#blue
|
293
|
+
# @see String#blink
|
294
|
+
def display_pomodoro_label
|
295
|
+
return unless @items.map { |x| x[5] }.compact.sum.positive?
|
296
|
+
|
297
|
+
puts "#{'P'.blue.blink}omodoro"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timet
|
4
|
+
# The TagDistribution module provides functionality to format and display the distribution of tags based on their
|
5
|
+
# durations. This is particularly useful for visualizing how time is distributed across different tags in a project
|
6
|
+
# or task management system.
|
7
|
+
module TagDistribution
|
8
|
+
MAX_BAR_LENGTH = 70
|
9
|
+
BLOCK_CHAR = '▅'
|
10
|
+
TAG_SIZE = 12
|
11
|
+
|
12
|
+
# Formats and displays the tag distribution.
|
13
|
+
#
|
14
|
+
# @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
|
15
|
+
# @return [void] This method outputs the formatted tag distribution to the console.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# duration_by_tag = { "timet" => 3600, "nextjs" => 1800 }
|
19
|
+
# Formatter.format_tag_distribution(duration_by_tag)
|
20
|
+
# # Output:
|
21
|
+
# # timet: 66.67% ====================
|
22
|
+
# # nextjs: 33.33% ==========
|
23
|
+
def tag_distribution(duration_by_tag, colors)
|
24
|
+
total = duration_by_tag.values.sum
|
25
|
+
return unless total.positive?
|
26
|
+
|
27
|
+
sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
|
28
|
+
process_and_print_tags(sorted_duration_by_tag, total, colors)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Processes and prints the tag distribution information.
|
32
|
+
#
|
33
|
+
# @param sorted_duration_by_tag [Array<Array(String, Numeric)>] An array of arrays where each inner array contains a
|
34
|
+
# tag and its corresponding duration, sorted by duration in descending order.
|
35
|
+
# @param total [Numeric] The total duration of all tags combined.
|
36
|
+
# @return [void] This method outputs the tag distribution information to the standard output.
|
37
|
+
def process_and_print_tags(sorted_duration_by_tag, total, colors)
|
38
|
+
sorted_duration_by_tag.each do |tag, duration|
|
39
|
+
value, bar_length = calculate_value_and_bar_length(duration, total)
|
40
|
+
horizontal_bar = (BLOCK_CHAR * bar_length).to_s.color(colors[tag] + 1)
|
41
|
+
tag = tag[0..TAG_SIZE - 1] if tag.size >= TAG_SIZE
|
42
|
+
puts "#{tag.rjust(TAG_SIZE)}: #{value.to_s.rjust(5)}% #{horizontal_bar}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Calculates the percentage value and bar length for a given duration and total duration.
|
47
|
+
#
|
48
|
+
# @param duration [Numeric] The duration for the current tag.
|
49
|
+
# @param total [Numeric] The total duration.
|
50
|
+
# @return [Array<(Float, Integer)>] An array containing the calculated value and bar length.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
|
54
|
+
def calculate_value_and_bar_length(duration, total)
|
55
|
+
value = duration.to_f / total
|
56
|
+
percentage_value = (duration.to_f / total * 100).round(2)
|
57
|
+
bar_length = (value * MAX_BAR_LENGTH).round
|
58
|
+
[percentage_value, bar_length]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Timet
|
4
4
|
# This module is responsible for formatting the output of the `timet` application.
|
5
5
|
# It provides methods for formatting the table header, separators, and rows.
|
6
|
-
module
|
6
|
+
module TimeBlockChart
|
7
7
|
CHAR_MAPPING = {
|
8
8
|
0..120 => '_',
|
9
9
|
121..450 => '▁',
|
@@ -16,123 +16,7 @@ module Timet
|
|
16
16
|
3151..3600 => '█'
|
17
17
|
}.freeze
|
18
18
|
|
19
|
-
|
20
|
-
#
|
21
|
-
# @return [void] This method does not return a value; it performs side effects such as printing
|
22
|
-
# the formatted header.
|
23
|
-
#
|
24
|
-
# @example Format and print the table header
|
25
|
-
# format_table_header
|
26
|
-
#
|
27
|
-
# @note The method constructs a string representing the table header and prints it.
|
28
|
-
def format_table_header
|
29
|
-
title = "Tracked time report #{@filter.blink.red}]:"
|
30
|
-
header = <<~TABLE
|
31
|
-
#{title}
|
32
|
-
#{format_table_separator}
|
33
|
-
\033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
|
34
|
-
#{format_table_separator}
|
35
|
-
TABLE
|
36
|
-
puts header
|
37
|
-
end
|
38
|
-
|
39
|
-
# Formats the separator line for the time tracking report table.
|
40
|
-
#
|
41
|
-
# @return [String] The formatted separator line.
|
42
|
-
#
|
43
|
-
# @example Get the formatted table separator
|
44
|
-
# format_table_separator # => '+-------+------------+--------+----------+----------+----------+------------+'
|
45
|
-
#
|
46
|
-
# @note The method returns a string representing the separator line for the table.
|
47
|
-
def format_table_separator
|
48
|
-
'+-------+------------+--------+----------+----------+----------+--------------------+'
|
49
|
-
end
|
50
|
-
|
51
|
-
# Formats a row of the time tracking report table.
|
52
|
-
#
|
53
|
-
# @param row [Array] The row data to be formatted.
|
54
|
-
# @return [String] The formatted row.
|
55
|
-
#
|
56
|
-
# @example Format a table row
|
57
|
-
# format_table_row(1, 'work', '2023-10-01', '12:00:00', '14:00:00', 7200, 'Completed task X')
|
58
|
-
#
|
59
|
-
# @note The method formats each element of the row and constructs a string representing the formatted row.
|
60
|
-
def format_table_row(*row)
|
61
|
-
id, tag, start_date, start_time, end_time, duration, notes = row
|
62
|
-
"| #{id.to_s.rjust(5)} | #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
|
63
|
-
"#{end_time.split[1].rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} |"
|
64
|
-
end
|
65
|
-
|
66
|
-
# Formats the notes column of the time tracking report table.
|
67
|
-
#
|
68
|
-
# @param notes [String, nil] The notes to be formatted.
|
69
|
-
# @return [String] The formatted notes.
|
70
|
-
#
|
71
|
-
# @example Format notes
|
72
|
-
# format_notes('This is a long note that needs to be truncated')
|
73
|
-
#
|
74
|
-
# @note The method truncates the notes to a maximum of 20 characters and pads them to a fixed width.
|
75
|
-
def format_notes(notes)
|
76
|
-
spaces = 17
|
77
|
-
return ' ' * spaces unless notes
|
78
|
-
|
79
|
-
max_length = spaces - 3
|
80
|
-
notes = "#{notes.slice(0, max_length)}..." if notes.length > max_length
|
81
|
-
notes.ljust(spaces)
|
82
|
-
end
|
83
|
-
|
84
|
-
# @!method format_tag_distribution(duration_by_tag)
|
85
|
-
# Formats and displays the tag distribution.
|
86
|
-
#
|
87
|
-
# @example
|
88
|
-
# duration_by_tag = { "timet" => 3600, "nextjs" => 1800 }
|
89
|
-
# Formatter.format_tag_distribution(duration_by_tag)
|
90
|
-
# # Output:
|
91
|
-
# # timet: 66.67% ====================
|
92
|
-
# # nextjs: 33.33% ==========
|
93
|
-
#
|
94
|
-
# @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
|
95
|
-
# @return [void] This method outputs the formatted tag distribution to the console.
|
96
|
-
def format_tag_distribution(duration_by_tag, colors)
|
97
|
-
total = duration_by_tag.values.sum
|
98
|
-
return unless total.positive?
|
99
|
-
|
100
|
-
factor = duration_by_tag.size < 3 ? 2 : 1
|
101
|
-
sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
|
102
|
-
process_and_print_tags(sorted_duration_by_tag, factor, total, colors)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Processes and prints the tag distribution information.
|
106
|
-
#
|
107
|
-
# @param sorted_duration_by_tag [Array<Array(String, Numeric)>] An array of arrays where each inner array contains a
|
108
|
-
# tag and its corresponding duration, sorted by duration in descending order.
|
109
|
-
# @param factor [Numeric] The factor used to adjust the bar length.
|
110
|
-
# @param total [Numeric] The total duration of all tags combined.
|
111
|
-
# @return [void] This method outputs the tag distribution information to the standard output.
|
112
|
-
def process_and_print_tags(*args)
|
113
|
-
sorted_duration_by_tag, factor, total, colors = args
|
114
|
-
block = '▅'
|
115
|
-
sorted_duration_by_tag.each do |tag, duration|
|
116
|
-
value, bar_length = calculate_value_and_bar_length(duration, total, factor)
|
117
|
-
horizontal_bar = (block * bar_length).to_s.color(colors[tag] + 1)
|
118
|
-
puts "#{tag.rjust(8)}: #{value.to_s.rjust(7)}% #{horizontal_bar}"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Calculates the value and bar length for a given duration, total duration, and factor.
|
123
|
-
#
|
124
|
-
# @param duration [Numeric] The duration for the current tag.
|
125
|
-
# @param total [Numeric] The total duration.
|
126
|
-
# @param factor [Numeric] A factor to adjust the formatting.
|
127
|
-
# @return [Array<(Float, Integer)>] An array containing the calculated value and bar length.
|
128
|
-
#
|
129
|
-
# @example
|
130
|
-
# calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
|
131
|
-
def calculate_value_and_bar_length(duration, total, factor)
|
132
|
-
value = (duration.to_f / total * 100).round(2)
|
133
|
-
bar_length = (value / factor).round
|
134
|
-
[value, bar_length]
|
135
|
-
end
|
19
|
+
SEPARATOR_CHAR = '░'
|
136
20
|
|
137
21
|
# Prints a time block chart based on the provided time block and colors.
|
138
22
|
#
|
@@ -186,23 +70,12 @@ module Timet
|
|
186
70
|
puts '┌╴W ╴╴╴╴╴╴⏰╴╴╴╴╴╴┼'.gray + "#{'╴' * (24 - start_hour) * 4}╴╴╴┼".gray
|
187
71
|
end
|
188
72
|
|
189
|
-
# Prints the
|
190
|
-
#
|
191
|
-
# This method iterates over each hour from 0 to 23, retrieves the corresponding
|
192
|
-
# block character using the `get_block_char` method, and prints it aligned for
|
193
|
-
# readability. It also adds a double newline at the end for separation.
|
194
|
-
#
|
195
|
-
# @param time_block [Hash] A hash where the keys are formatted hour strings
|
196
|
-
# (e.g., "00", "01") and the values are the corresponding
|
197
|
-
# values to determine the block character.
|
198
|
-
# @example
|
199
|
-
# time_block = { "00" => 100, "01" => 200, ..., "23" => 300 }
|
200
|
-
# print_blocks(time_block)
|
201
|
-
# # Output:
|
202
|
-
# # ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █
|
203
|
-
# #
|
204
|
-
# # (followed by two newlines)
|
73
|
+
# Prints the time blocks for each date in the given time block data structure.
|
205
74
|
#
|
75
|
+
# @param time_block [Hash] A hash where keys are date strings and values are time block data.
|
76
|
+
# @param colors [Hash] A hash containing color codes for formatting.
|
77
|
+
# @param start_hour [Integer] The starting hour for the time blocks.
|
78
|
+
# @return [void]
|
206
79
|
def print_blocks(time_block, colors, start_hour)
|
207
80
|
return unless time_block
|
208
81
|
|
@@ -210,32 +83,83 @@ module Timet
|
|
210
83
|
time_block.each_key do |date_string|
|
211
84
|
date = Date.parse(date_string)
|
212
85
|
day = date.strftime('%a')[0..1]
|
213
|
-
weekend = date_string
|
214
|
-
if %w[Sa Su].include?(day)
|
215
|
-
day = day.red
|
216
|
-
weekend = weekend.red
|
217
|
-
end
|
218
86
|
|
219
|
-
weeks
|
220
|
-
|
221
|
-
week = if (weeks[n] == weeks[n - 1]) && n.positive?
|
222
|
-
' '
|
223
|
-
else
|
224
|
-
weeks[n].to_s.underline
|
225
|
-
end
|
226
|
-
puts "┆ ┆#{' ' * (24 - start_hour) * 4} ┼░░░░".gray if week != ' ' && n.positive?
|
227
|
-
print '┆'.gray + "#{week} #{weekend} #{day} " + '┆- '.gray
|
87
|
+
format_and_print_date_info(date_string, day, weeks, start_hour)
|
88
|
+
|
228
89
|
time_block_initial = time_block[date_string]
|
229
90
|
print_time_blocks(start_hour, time_block_initial, colors)
|
230
91
|
|
231
|
-
|
232
|
-
hours_per_day = (total_seconds / 3600.0).round(1)
|
233
|
-
print "-┆#{hours_per_day}h".gray
|
234
|
-
puts
|
92
|
+
calculate_and_print_hours(time_block_initial)
|
235
93
|
end
|
236
94
|
print_footer(start_hour)
|
237
95
|
end
|
238
96
|
|
97
|
+
# Calculates the total hours from the given time block data and prints it.
|
98
|
+
#
|
99
|
+
# @param time_block_initial [Hash] A hash containing time block data for a specific date.
|
100
|
+
# @return [void]
|
101
|
+
def calculate_and_print_hours(time_block_initial)
|
102
|
+
total_seconds = time_block_initial.values.map { |item| item[0] }.sum
|
103
|
+
hours_per_day = (total_seconds / 3600.0).round(1)
|
104
|
+
print "-┆#{hours_per_day}h".gray
|
105
|
+
puts
|
106
|
+
end
|
107
|
+
|
108
|
+
# Formats and prints the date information including the week and day.
|
109
|
+
#
|
110
|
+
# @param date_string [String] The date string in a parsable format.
|
111
|
+
# @param day [String] The abbreviated day of the week (e.g., "Mo" for Monday).
|
112
|
+
# @param weeks [Array<Integer>] An array storing the week numbers.
|
113
|
+
# @param start_hour [Integer] The starting hour for the time blocks.
|
114
|
+
# @return [void]
|
115
|
+
def format_and_print_date_info(date_string, day, weeks, start_hour)
|
116
|
+
weekend = date_string
|
117
|
+
day = day.red if %w[Sa Su].include?(day)
|
118
|
+
weekend = weekend.red if %w[Sa Su].include?(day)
|
119
|
+
|
120
|
+
week = format_and_print_week(date_string, weeks, start_hour)
|
121
|
+
|
122
|
+
print '┆'.gray + "#{week} #{weekend} #{day} " + '┆- '.gray
|
123
|
+
end
|
124
|
+
|
125
|
+
# Formats and prints the week information including the separator if necessary.
|
126
|
+
#
|
127
|
+
# @param date_string [String] The date string in a parsable format.
|
128
|
+
# @param weeks [Array<Integer>] An array storing the week numbers.
|
129
|
+
# @param start_hour [Integer] The starting hour for the time blocks.
|
130
|
+
# @return [String] The formatted week string.
|
131
|
+
def format_and_print_week(date_string, weeks, start_hour)
|
132
|
+
week, current_index = determine_week(date_string, weeks)
|
133
|
+
print_separator(start_hour, week, current_index)
|
134
|
+
week
|
135
|
+
end
|
136
|
+
|
137
|
+
# Determines the week string based on the date and the previous week.
|
138
|
+
#
|
139
|
+
# @param date_string [String] The date string in a parsable format.
|
140
|
+
# @param weeks [Array<Integer>] An array storing the week numbers.
|
141
|
+
# @return [Array<String, Integer>] An array containing the formatted week string and the current index.
|
142
|
+
def determine_week(date_string, weeks)
|
143
|
+
weeks << Date.parse(date_string).cweek
|
144
|
+
current_index = weeks.size - 1
|
145
|
+
current_week = weeks[current_index]
|
146
|
+
week = current_week == weeks[current_index - 1] && current_index.positive? ? ' ' : current_week.to_s.underline
|
147
|
+
[week, current_index]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Prints the separator line if the week string is not empty and the current index is positive.
|
151
|
+
#
|
152
|
+
# @param start_hour [Integer] The starting hour for the time blocks.
|
153
|
+
# @param week [String] The formatted week string.
|
154
|
+
# @param current_index [Integer] The current index in the weeks array.
|
155
|
+
# @return [void]
|
156
|
+
def print_separator(start_hour, week, current_index)
|
157
|
+
return unless week != ' ' && current_index.positive?
|
158
|
+
|
159
|
+
sep = SEPARATOR_CHAR
|
160
|
+
puts "┆#{sep * 17}┼#{sep * (24 - start_hour) * 4}#{sep * 3}┼#{sep * 4}".gray
|
161
|
+
end
|
162
|
+
|
239
163
|
# Prints the footer of the report.
|
240
164
|
#
|
241
165
|
# @param start_hour [Integer] The start time used to calculate the footer length.
|
data/lib/timet/time_report.rb
CHANGED
@@ -2,17 +2,20 @@
|
|
2
2
|
|
3
3
|
require 'date'
|
4
4
|
require 'csv'
|
5
|
-
require_relative 'status_helper'
|
6
|
-
require_relative 'formatter'
|
7
5
|
require_relative 'time_report_helper'
|
6
|
+
require_relative 'table'
|
7
|
+
require_relative 'time_block_chart'
|
8
|
+
require_relative 'tag_distribution'
|
8
9
|
|
9
10
|
module Timet
|
10
11
|
# The TimeReport class is responsible for displaying a report of tracked time
|
11
12
|
# entries. It allows filtering the report by time periods and displays
|
12
13
|
# a formatted table with the relevant information.
|
13
14
|
class TimeReport
|
14
|
-
include Formatter
|
15
15
|
include TimeReportHelper
|
16
|
+
include Table
|
17
|
+
include TimeBlockChart
|
18
|
+
include TagDistribution
|
16
19
|
|
17
20
|
# Provides access to the database instance.
|
18
21
|
attr_reader :db
|
@@ -54,15 +57,12 @@ module Timet
|
|
54
57
|
def display
|
55
58
|
return puts 'No tracked time found for the specified filter.' if items.empty?
|
56
59
|
|
57
|
-
|
58
|
-
time_block, duration_by_tag = process_time_entries
|
59
|
-
puts format_table_separator
|
60
|
-
total
|
60
|
+
time_block, duration_by_tag = table
|
61
61
|
|
62
62
|
colors = duration_by_tag.map { |x| x[0] }.sort.each_with_index.to_h
|
63
63
|
print_time_block_chart(time_block, colors)
|
64
64
|
|
65
|
-
|
65
|
+
tag_distribution(duration_by_tag, colors)
|
66
66
|
end
|
67
67
|
|
68
68
|
# Displays a single row of the report.
|
@@ -76,9 +76,9 @@ module Timet
|
|
76
76
|
#
|
77
77
|
# @note The method formats and prints the table header, row, and total duration.
|
78
78
|
def show_row(item)
|
79
|
-
|
79
|
+
header
|
80
80
|
display_time_entry(item)
|
81
|
-
puts
|
81
|
+
puts separator
|
82
82
|
total
|
83
83
|
end
|
84
84
|
|
@@ -116,42 +116,18 @@ module Timet
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
-
#
|
120
|
-
#
|
121
|
-
# @param item [Array] The item to display.
|
122
|
-
# @param date [String, nil] The date to display. If nil, the date is not displayed.
|
123
|
-
#
|
124
|
-
# @return [void] This method does not return a value; it performs side effects such as printing the row.
|
125
|
-
#
|
126
|
-
# @example Display a time entry
|
127
|
-
# display_time_entry(item, '2021-10-01')
|
128
|
-
#
|
129
|
-
# @note The method formats and prints the row for the time entry.
|
130
|
-
def display_time_entry(item, date = nil)
|
131
|
-
return puts 'Missing time entry data.' unless item
|
132
|
-
|
133
|
-
id, start_time_value, end_time_value, tag_name, notes = item
|
134
|
-
duration = TimeHelper.calculate_duration(start_time_value, end_time_value)
|
135
|
-
start_time = TimeHelper.format_time(start_time_value)
|
136
|
-
end_time = TimeHelper.format_time(end_time_value) || '- -'
|
137
|
-
start_date = date || (' ' * 10)
|
138
|
-
puts format_table_row(id, tag_name[0..5], start_date, start_time, end_time, duration, notes)
|
139
|
-
end
|
140
|
-
|
141
|
-
# Displays the total duration of the tracked time entries.
|
142
|
-
#
|
143
|
-
# @return [void] This method does not return a value; it performs side effects such as printing the total duration.
|
119
|
+
# Writes the CSV rows for the time report.
|
144
120
|
#
|
145
|
-
# @
|
146
|
-
#
|
121
|
+
# @param csv [CSV] The CSV object to which the rows will be written.
|
122
|
+
# @return [void]
|
147
123
|
#
|
148
|
-
# @
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
124
|
+
# @example
|
125
|
+
# csv = CSV.new(file)
|
126
|
+
# write_csv_rows(csv)
|
127
|
+
def write_csv_rows(csv)
|
128
|
+
items.each do |item|
|
129
|
+
csv << format_item(item)
|
130
|
+
end
|
155
131
|
end
|
156
132
|
|
157
133
|
# Filters the items based on the specified filter and tag.
|
@@ -6,58 +6,6 @@ module Timet
|
|
6
6
|
# and validating date formats.
|
7
7
|
# This module is designed to be included in classes that require time report processing functionalities.
|
8
8
|
module TimeReportHelper
|
9
|
-
# Processes each time entry in the items array and updates the time block and duration by tag.
|
10
|
-
#
|
11
|
-
# @return [Array<(Hash, Hash)>] An array containing the updated time block and duration by tag.
|
12
|
-
#
|
13
|
-
# @example
|
14
|
-
# items = [
|
15
|
-
# [start_time1, end_time1, tag1],
|
16
|
-
# [start_time2, end_time2, tag2]
|
17
|
-
# ]
|
18
|
-
# process_time_entries
|
19
|
-
# #=> [{ '2024-10-21' => { 8 => [duration1, tag1], 9 => [duration2, tag2] } }, { tag1 => total_duration1,
|
20
|
-
# tag2 => total_duration2 }]
|
21
|
-
def process_time_entries
|
22
|
-
duration_by_tag = Hash.new(0)
|
23
|
-
time_block = Hash.new { |hash, key| hash[key] = {} }
|
24
|
-
|
25
|
-
items.each_with_index do |item, idx|
|
26
|
-
display_time_entry(item, TimeHelper.extract_date(items, idx))
|
27
|
-
start_time = item[1]
|
28
|
-
end_time = item[2]
|
29
|
-
tag = item[3]
|
30
|
-
time_block = process_time_block_item(start_time, end_time, tag, time_block)
|
31
|
-
|
32
|
-
duration_by_tag[tag] += TimeHelper.calculate_duration(start_time, end_time)
|
33
|
-
end
|
34
|
-
[time_block, duration_by_tag]
|
35
|
-
end
|
36
|
-
|
37
|
-
# Processes a time block item and updates the time block hash.
|
38
|
-
#
|
39
|
-
# @param start_time [Time] The start time of the time block.
|
40
|
-
# @param end_time [Time] The end time of the time block.
|
41
|
-
# @param tag [String] The tag associated with the time block.
|
42
|
-
# @param time_block [Hash] A hash containing time block data, where keys are dates and values are hashes of time
|
43
|
-
# slots and their corresponding values.
|
44
|
-
# @return [Hash] The updated time block hash.
|
45
|
-
#
|
46
|
-
# @example
|
47
|
-
# start_time = Time.new(2024, 10, 21, 8, 0, 0)
|
48
|
-
# end_time = Time.new(2024, 10, 21, 9, 0, 0)
|
49
|
-
# tag = 'work'
|
50
|
-
# time_block = {}
|
51
|
-
# process_time_block_item(start_time, end_time, tag, time_block)
|
52
|
-
# #=> { '2024-10-21' => { 8 => [duration, 'work'] } }
|
53
|
-
def process_time_block_item(*args)
|
54
|
-
start_time, end_time, tag, time_block = args
|
55
|
-
block_hour = TimeHelper.count_seconds_per_hour_block(start_time, end_time, tag)
|
56
|
-
date_line = TimeHelper.timestamp_to_date(start_time)
|
57
|
-
time_block[date_line] = add_hashes(time_block[date_line], block_hour)
|
58
|
-
time_block
|
59
|
-
end
|
60
|
-
|
61
9
|
# Provides predefined date ranges for filtering.
|
62
10
|
#
|
63
11
|
# @return [Hash] A hash containing predefined date ranges.
|
@@ -132,19 +80,5 @@ module Timet
|
|
132
80
|
[summed_number, old_value[1]]
|
133
81
|
end
|
134
82
|
end
|
135
|
-
|
136
|
-
# Writes the CSV rows for the time report.
|
137
|
-
#
|
138
|
-
# @param csv [CSV] The CSV object to which the rows will be written.
|
139
|
-
# @return [void]
|
140
|
-
#
|
141
|
-
# @example
|
142
|
-
# csv = CSV.new(file)
|
143
|
-
# write_csv_rows(csv)
|
144
|
-
def write_csv_rows(csv)
|
145
|
-
items.each do |item|
|
146
|
-
csv << format_item(item)
|
147
|
-
end
|
148
|
-
end
|
149
83
|
end
|
150
84
|
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: 1.
|
4
|
+
version: 1.4.0
|
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-10-
|
11
|
+
date: 2024-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -82,8 +82,10 @@ files:
|
|
82
82
|
- lib/timet/application_helper.rb
|
83
83
|
- lib/timet/color_codes.rb
|
84
84
|
- lib/timet/database.rb
|
85
|
-
- lib/timet/formatter.rb
|
86
85
|
- lib/timet/status_helper.rb
|
86
|
+
- lib/timet/table.rb
|
87
|
+
- lib/timet/tag_distribution.rb
|
88
|
+
- lib/timet/time_block_chart.rb
|
87
89
|
- lib/timet/time_helper.rb
|
88
90
|
- lib/timet/time_report.rb
|
89
91
|
- lib/timet/time_report_helper.rb
|