timet 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cf486dcc89da9e06f1a77b36d3183a1f8c55a11ce51e79a761cbb84280af9ba
4
- data.tar.gz: fe4cbc8baf03c019cfbb8b31514a9e5fd99ba1320c8cf27945dacf6fd1033784
3
+ metadata.gz: a3f1c37d739d4c6712cf21af39357a45ded45b8a63d8d7e9932ac859fe72b000
4
+ data.tar.gz: 20bf38e7554d3eb1326d1d7e2ae1095792c9b20a473d383f9f123e2ac0bbd2b2
5
5
  SHA512:
6
- metadata.gz: 0d2ddea11803e4d0225858ed1edb2b3143ad7579959f057a9025ff9db82acccaf3de1aed4018f99c856ed97c896d3d775a2d14224251e9d325b9aceef223db11
7
- data.tar.gz: d6b6b852de2aea0966aa0e108ee73a64c30bbdb88217ad8806ab66ce732de32641f71692c3eaf4fd1fc9421c4722d6300ae86ce8051f5c3ec9d34f6d4631ad96
6
+ metadata.gz: 92892c05063d444a713eaafec9eb63ff970a4926364b594dd31b61f229e9b415d3a171da61ffaa96660512708076423348ece7f120c0e21e075aa777e8e5b1b4
7
+ data.tar.gz: 3e1799d361eb28b500ce81740eb9ee1f16d55048e9425203aa6482265a9bc1d891e6d6897c626b454b2a18854523b99ed4acb0edf3a63e0dc043f16d1ebf4b04
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.0] - 2024-10-11
4
+
5
+ **Improvements:**
6
+
7
+ - Enhanced the README to provide more detailed and user-friendly information about the tool.
8
+ - Added a visually appealing title and logo to make the README more engaging.
9
+ - Organized key features into bullet points for better readability.
10
+ - Provided clear and concise installation instructions.
11
+ - Made the command reference more user-friendly by adding a table of contents.
12
+ - Added more details about data storage and development guidelines.
13
+ - Encouraged contributions and provided guidelines for contributing.
14
+ - Made the support section more prominent.
15
+ - Ensured the license and code of conduct are clearly stated.
16
+ - Updated the `timet` gem version to 1.2.0 in `Gemfile.lock`.
17
+ - Refactored the `play_sound_and_notify` condition to use the `positive?` method for better readability.
18
+ - Enhanced table header formatting with a blinking effect for better user interaction.
19
+ - Added new methods for tag distribution and time block chart visualization:
20
+ - `format_tag_distribution`: Displays tag distribution with progress bars.
21
+ - `print_time_block_chart`: Prints the entire time block chart.
22
+ - `print_header`: Prints the header of the time block chart.
23
+ - `print_blocks`: Prints the block characters for each hour.
24
+ - `get_block_char`: Determines the block character based on value.
25
+ - Added `count_seconds_per_hour_block` method to count seconds per hour block.
26
+ - Added `aggregate_hash_values` method to aggregate hash values.
27
+ - Integrated new methods into `time_report` to enhance time tracking visualization.
28
+ - Updated the version number in `lib/timet/version.rb` to 1.2.0.
29
+
30
+ ### Additional Considerations:
31
+
32
+ - The enhancements made in this pull request aim to improve the user experience and provide more powerful visualization and reporting capabilities for time tracking.
33
+ - Reviewers are encouraged to test the new visualization methods and provide feedback on their effectiveness and usability.
34
+ - The README updates should make it easier for new users to understand and use the `timet` tool.
35
+
3
36
  ## [1.0.0] - 2024-10-07
4
37
 
5
38
  **Improvements:**
data/README.md CHANGED
@@ -5,18 +5,42 @@
5
5
 
6
6
  # Timet
7
7
 
8
+ ![Timet](timet.webp)
9
+
8
10
  Timet refers to a command-line tool designed to track your activities by recording the time spent on each task, allowing you to monitor your work hours and productivity directly from your terminal without needing a graphical interface; essentially, it's a way to log your time spent on different projects or tasks using simple text commands
9
11
 
10
- Timet utilizes SQLite to store your time tracking data. This means your data is stored locally and securely, with no need for external databases or cloud storage. This makes Timet lightweight, fast, and perfect for users who value privacy and control over their data.
12
+ **Key Features:**
11
13
 
12
- While a YAML file might seem like a simple option for storing time tracking data, Timet leverages SQLite for several key advantages:
14
+ - **Local Data Storage:** Timet utilizes SQLite to store your time tracking data locally, ensuring privacy and security.
15
+ - **Lightweight and Fast:** Its efficient design and local data storage make Timet a speedy and responsive tool.
16
+ - **Structured Data:** SQLite ensures your data is organized and easily accessible.
17
+ - **Scalability:** Timet can handle growing time tracking needs.
18
+ - **Data Integrity:** SQLite maintains the accuracy and consistency of your data.
19
+ - **Querying and Reporting:** Generate detailed reports for specific periods.
20
+ - **CSV Export:** Easily export your time tracking data to CSV format for further analysis or sharing.
21
+ - **Pomodoro Integration:** The pomodoro option in the start command enhances time tracking by integrating the Pomodoro Technique.
22
+ - **Block Time Plot:** Visualizes the distribution of tracked time across a 24-hour period, with bars in each column representing the amount of time tracked during that specific hour.
23
+ - **Tag Distribution Plot:** Illustrates the proportion of total tracked time allocated to each tag, showing the relative contribution of each tag to the overall time tracked.
13
24
 
14
- - Structured Data
15
- - Scalability
16
- - Data Integrity
17
- - Querying and Reporting
25
+ Example:
18
26
 
19
- In addition, if possible, export your time tracking data to CSV for analysis and sharing.
27
+ ```bash
28
+ Tracked time report [today]:
29
+ +-------+------------+--------+----------+----------+----------+--------------------------+
30
+ | Id | Date | Tag | Start | End | Duration | Notes |
31
+ +-------+------------+--------+----------+----------+----------+--------------------------+
32
+ | 20 | 2024-10-10 | Tag8 | 19:26:58 | 20:26:58 | 01:00:00 | Notes 2 |
33
+ | 19 | | Tag3 | 07:52:26 | 08:52:26 | 01:00:00 | Notes 7 |
34
+ +-------+------------+--------+----------+----------+----------+--------------------------+
35
+ | Total: | 02:00:00 | |
36
+ +-------+------------+--------+----------+----------+----------+--------------------------+
37
+
38
+ ⏳ ↦ ┏ 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
39
+ ┗ ▂▂ ▇▇ ▅▅ ▄▄
40
+
41
+ Tag8: 50.0% ▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅
42
+ Tag3: 50.0% ▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅
43
+ ```
20
44
 
21
45
  ## Requirements
22
46
 
@@ -207,9 +231,14 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/frankv
207
231
 
208
232
  Many people have contacted me asking how to contribute. Any contribution, from a virtual coffee to a kind word, is greatly appreciated and helps me continue my work. Please only donate if you're able, as there are no refunds. Your support is entirely voluntary, and I thank you for your consideration.
209
233
 
234
+ **Bitcoin Address:**
235
+ ```sh
236
+ bc1qkg9me2jsuhpzu2hp9kkpxagwtf9ewnyfl4kszl
237
+ ```
238
+
210
239
  ![Buy me a coffee!](btc.png)
211
240
 
212
- bc1qkg9me2jsuhpzu2hp9kkpxagwtf9ewnyfl4kszl
241
+ ---
213
242
 
214
243
  ## License
215
244
 
@@ -66,7 +66,7 @@ module Timet
66
66
 
67
67
  if VALID_STATUSES_FOR_INSERTION.include?(@db.last_item_status)
68
68
  @db.insert_item(start_time, tag, notes)
69
- play_sound_and_notify(pomodoro * 60, tag) if pomodoro > 0
69
+ play_sound_and_notify(pomodoro * 60, tag) if pomodoro.positive?
70
70
  end
71
71
  summary
72
72
  end
@@ -14,7 +14,7 @@ module Timet
14
14
  # @note The method constructs a string representing the table header and prints it.
15
15
  def format_table_header
16
16
  header = <<~TABLE
17
- Tracked time report \u001b[31m[#{@filter}]\033[0m:
17
+ Tracked time report \e[5m\u001b[31m[#{@filter}]\033[0m:
18
18
  #{format_table_separator}
19
19
  \033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
20
20
  #{format_table_separator}
@@ -64,5 +64,120 @@ module Timet
64
64
  notes = "#{notes.slice(0, 20)}..." if notes.length > 20
65
65
  notes.ljust(23)
66
66
  end
67
+
68
+ # @!method format_tag_distribution(duration_by_tag)
69
+ # Formats and displays the tag distribution.
70
+ #
71
+ # @example
72
+ # duration_by_tag = { "timet" => 3600, "nextjs" => 1800 }
73
+ # Formatter.format_tag_distribution(duration_by_tag)
74
+ # # Output:
75
+ # # timet: 66.67% \u001b[38;5;42m====================\u001b[0m
76
+ # # nextjs: 33.33% \u001b[38;5;42m==========\u001b[0m
77
+ #
78
+ # @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
79
+ # @return [void] This method outputs the formatted tag distribution to the console.
80
+ def format_tag_distribution(duration_by_tag)
81
+ block = '▅'
82
+ total = duration_by_tag.values.sum
83
+ return unless total.positive?
84
+
85
+ factor = duration_by_tag.size < 3 ? 2 : 1
86
+ sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
87
+
88
+ sorted_duration_by_tag.each do |tag, duration|
89
+ value = (duration.to_f / total * 100).round(2)
90
+ puts "#{tag.rjust(8)}: #{value.to_s.rjust(7)}% \u001b[38;5;#{rand(256)}m#{block * (value / factor).to_i}\u001b[0m"
91
+ end
92
+ end
93
+
94
+ # Prints the entire time block chart.
95
+ #
96
+ # This method orchestrates the printing of the entire time block chart by calling
97
+ # the `print_header` and `print_blocks` methods. It also prints the separator line
98
+ # between the header and the blocks, and adds a double newline at the end for
99
+ # separation.
100
+ #
101
+ # @param time_block [Hash] A hash where the keys are formatted hour strings
102
+ # (e.g., "00", "01") and the values are the corresponding
103
+ # values to determine the block character.
104
+ # @example
105
+ # time_block = { "00" => 100, "01" => 200, ..., "23" => 300 }
106
+ # print_time_block_chart(time_block)
107
+ # # Output:
108
+ # # ⏳ ↦ ┏ 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
109
+ # # ┗ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █
110
+ # #
111
+ # # (followed by two newlines)
112
+ #
113
+ def print_time_block_chart(time_block)
114
+ print_header
115
+ print ' ┗ '
116
+ print_blocks(time_block)
117
+ end
118
+
119
+ # Prints the header of the time block chart.
120
+ #
121
+ # This method outputs the header line of the chart, which includes the hours
122
+ # from 00 to 23, formatted and aligned for readability.
123
+ #
124
+ # @example
125
+ # print_header
126
+ # # Output:
127
+ # # ⏳ ↦ ┏ 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
128
+ #
129
+ def print_header
130
+ puts
131
+ print '⏳ ↦ ┏ '
132
+ (0..23).each { |hour| print format('%02d', hour).ljust(4) }
133
+ puts
134
+ end
135
+
136
+ # Prints the block characters for each hour in the time block chart.
137
+ #
138
+ # This method iterates over each hour from 0 to 23, retrieves the corresponding
139
+ # block character using the `get_block_char` method, and prints it aligned for
140
+ # readability. It also adds a double newline at the end for separation.
141
+ #
142
+ # @param time_block [Hash] A hash where the keys are formatted hour strings
143
+ # (e.g., "00", "01") and the values are the corresponding
144
+ # values to determine the block character.
145
+ # @example
146
+ # time_block = { "00" => 100, "01" => 200, ..., "23" => 300 }
147
+ # print_blocks(time_block)
148
+ # # Output:
149
+ # # ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █
150
+ # #
151
+ # # (followed by two newlines)
152
+ #
153
+ def print_blocks(time_block)
154
+ return unless time_block
155
+
156
+ (0..23).each do |hour|
157
+ block_char = get_block_char(time_block[format('%02d', hour)])
158
+ print (block_char * 2).ljust(4)
159
+ end
160
+ puts "\n\n"
161
+ end
162
+
163
+ # Determines the block character based on the value.
164
+ #
165
+ # @param value [Integer] The value to determine the block character for.
166
+ # @return [String] The block character corresponding to the value.
167
+ def get_block_char(value)
168
+ range_to_char = {
169
+ 0..120 => ' ',
170
+ 121..450 => '▁',
171
+ 451..900 => '▂',
172
+ 901..1350 => '▃',
173
+ 1351..1800 => '▄',
174
+ 1801..2250 => '▅',
175
+ 2251..2700 => '▆',
176
+ 2701..3150 => '▇',
177
+ 3151..3600 => '█'
178
+ }
179
+
180
+ range_to_char.find { |range, _| range.include?(value) }&.last || ' '
181
+ end
67
182
  end
68
183
  end
@@ -168,5 +168,71 @@ module Timet
168
168
  def self.current_timestamp
169
169
  Time.now.utc.to_i
170
170
  end
171
+
172
+ # Counts the number of seconds for each hour block between the given start and end times.
173
+ #
174
+ # This method calculates the number of seconds each event spans within each hour block
175
+ # and aggregates the results in a hash where the keys are the hour blocks (in 'HH' format)
176
+ # and the values are the total number of seconds for each hour block.
177
+ #
178
+ # @param start_time [Integer] The start time in seconds since the Unix epoch.
179
+ # @param end_time [Integer] The end time in seconds since the Unix epoch. If not provided,
180
+ # the current time will be used.
181
+ # @return [Hash] A hash where the keys are the hour blocks (in 'HH' format) and the values
182
+ # are the total number of seconds for each hour block.
183
+ # @example
184
+ # start_time = 1728577349 # 8:30 AM
185
+ # end_time = 1728579200 # 11:20 AM
186
+ # result = count_seconds_per_hour_block(start_time, end_time)
187
+ # # Output: {"08"=>1800, "09"=>1800, "10"=>3600, "11"=>1200}
188
+ #
189
+ def self.count_seconds_per_hour_block(start_time, end_time)
190
+ hour_blocks = Hash.new(0)
191
+
192
+ current_time = Time.at(start_time)
193
+ end_time = Time.at(end_time || current_timestamp)
194
+
195
+ while current_time < end_time
196
+ current_hour = current_time.hour
197
+ next_hour_boundary = Time.new(current_time.year, current_time.month, current_time.day, current_hour + 1)
198
+
199
+ block_end_time = [next_hour_boundary, end_time].min
200
+ seconds_in_block = (block_end_time - current_time).to_i
201
+
202
+ hour_block = current_time.strftime('%H')
203
+ hour_blocks[hour_block] += seconds_in_block
204
+
205
+ current_time = block_end_time
206
+ end
207
+
208
+ hour_blocks
209
+ end
210
+
211
+ # Aggregates the values of the same keys from an array of hashes.
212
+ #
213
+ # This method takes an array of hashes, reverses it, and then aggregates the values
214
+ # for the same keys into a single hash. If a key appears in multiple hashes, its
215
+ # values are summed.
216
+ #
217
+ # @param time_block [Array<Hash>] An array of hashes where each hash contains key-value pairs.
218
+ # @return [Hash] A hash where the keys are the aggregated keys from the input hashes
219
+ # and the values are the summed values for each key.
220
+ # @example
221
+ # time_block = [
222
+ # {"01": 10},
223
+ # {"01": 30},
224
+ # {"02": 50}
225
+ # ]
226
+ # result = aggregate_hash_values(time_block)
227
+ # # Output: {"01"=>40, "02"=>50}
228
+ #
229
+ def self.aggregate_hash_values(time_block)
230
+ time_block.reverse.each_with_object({}) do |hash, acc|
231
+ hash.each do |key, value|
232
+ acc[key] ||= 0
233
+ acc[key] += value
234
+ end
235
+ end
236
+ end
171
237
  end
172
238
  end
@@ -51,12 +51,23 @@ module Timet
51
51
  return puts 'No tracked time found for the specified filter.' if items.empty?
52
52
 
53
53
  format_table_header
54
+ duration_by_tag = Hash.new(0)
55
+ time_block = []
54
56
  items.each_with_index do |item, idx|
55
57
  date = TimeHelper.extract_date(items, idx)
56
58
  display_time_entry(item, date)
59
+ time_block << TimeHelper.count_seconds_per_hour_block(item[1], item[2])
60
+ duration_by_tag[item[3]] += TimeHelper.calculate_duration(item[1], item[2])
57
61
  end
58
62
  puts format_table_separator
59
63
  total
64
+
65
+ if Time.now.to_i - items.map { |x| x[1] }.min < 86_400
66
+ time_block_reverse = TimeHelper.aggregate_hash_values(time_block)
67
+ print_time_block_chart(time_block_reverse)
68
+ end
69
+
70
+ format_tag_distribution(duration_by_tag)
60
71
  end
61
72
 
62
73
  # Displays a single row of the report.
data/lib/timet/version.rb CHANGED
@@ -6,6 +6,6 @@ module Timet
6
6
  # @return [String] The version number in the format 'major.minor.patch'.
7
7
  #
8
8
  # @example Get the version of the Timet application
9
- # Timet::VERSION # => '1.1.0'
10
- VERSION = '1.1.0'
9
+ # Timet::VERSION # => '1.2.0'
10
+ VERSION = '1.2.0'
11
11
  end
data/timet.webp ADDED
Binary file
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.1.0
4
+ version: 1.2.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-09 00:00:00.000000000 Z
11
+ date: 2024-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -89,6 +89,7 @@ files:
89
89
  - lib/timet/validation_edit_helper.rb
90
90
  - lib/timet/version.rb
91
91
  - sig/timet.rbs
92
+ - timet.webp
92
93
  homepage: https://frankvielma.github.io/posts/timet-a-powerful-command-line-tool-for-tracking-your-time/
93
94
  licenses:
94
95
  - MIT