fewald-worklog 0.3.27 → 0.3.28
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/.version +1 -1
- data/lib/activity_graph.rb +154 -0
- data/lib/worklog.rb +3 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98413c2f56d95220ed96323e1bd719273a010fed346334650078e45adb7ca6e4
|
|
4
|
+
data.tar.gz: 42eadd693d07e91ac3e13a814c986671b62427d8c98488b809f5fb420b8a9aef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f04df40355750a5c4efc7519faac0e5041f73e5ecd55b1ae9fbacdf694cb7f036b49b9d6d47c7edfc3316d29589b814969cbdfd5932fe3c77bcfe284667d84a3
|
|
7
|
+
data.tar.gz: 7cb02f4167a272f6f6da1ad58c8571cc3fdf9a9369afeb2f32887a8c679e9b269f1bd47e283e7978734a508d9997ee520559b732cbccbd63ff175bf01d4268ed
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.28
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'rainbow'
|
|
5
|
+
|
|
6
|
+
# Renders a GitHub-style ASCII activity heatmap for worklog entries.
|
|
7
|
+
# Accepts an array of DailyLog objects and produces a 53-column × 7-row grid
|
|
8
|
+
# where each cell represents one day and darker (brighter) color means more activity.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# graph = ActivityGraph.new(storage.all_days)
|
|
12
|
+
# puts graph.render
|
|
13
|
+
class ActivityGraph
|
|
14
|
+
DAYS_OF_WEEK = %w[Mon Tue Wed Thu Fri Sat Sun].freeze
|
|
15
|
+
|
|
16
|
+
# Number of weeks to display (approx. one year).
|
|
17
|
+
NUM_WEEKS = 53
|
|
18
|
+
|
|
19
|
+
# Minimum column distance between consecutive month labels to prevent overlap.
|
|
20
|
+
MIN_MONTH_GAP = 3
|
|
21
|
+
|
|
22
|
+
def initialize(daily_logs)
|
|
23
|
+
@daily_logs = Array(daily_logs)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Render the activity graph as a printable multi-line string.
|
|
27
|
+
# @return [String]
|
|
28
|
+
def render
|
|
29
|
+
counts = count_by_date
|
|
30
|
+
today = Date.today
|
|
31
|
+
start_date = week_start(today - ((NUM_WEEKS * 7) - 1))
|
|
32
|
+
weeks = build_weeks(start_date, today)
|
|
33
|
+
max_count = counts.values.max || 0
|
|
34
|
+
|
|
35
|
+
lines = []
|
|
36
|
+
lines << " #{Rainbow('Activity — past year').bold}"
|
|
37
|
+
lines << ''
|
|
38
|
+
lines << month_header(weeks)
|
|
39
|
+
|
|
40
|
+
DAYS_OF_WEEK.each_with_index do |day_name, day_idx|
|
|
41
|
+
cells = weeks.map do |week|
|
|
42
|
+
date = week[day_idx]
|
|
43
|
+
next ' ' if date > today
|
|
44
|
+
|
|
45
|
+
count = counts[date] || 0
|
|
46
|
+
"#{colored_block(intensity_level(count, max_count))} "
|
|
47
|
+
end
|
|
48
|
+
lines << "#{day_name} #{cells.join}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
lines << ''
|
|
52
|
+
lines << legend
|
|
53
|
+
lines.join("\n")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Build a Hash mapping Date → entry count from the provided DailyLog objects.
|
|
59
|
+
# @return [Hash{Date => Integer}]
|
|
60
|
+
def count_by_date
|
|
61
|
+
@daily_logs.each_with_object({}) do |daily_log, hash|
|
|
62
|
+
date = daily_log.date.is_a?(Date) ? daily_log.date : Date.parse(daily_log.date.to_s)
|
|
63
|
+
hash[date] = daily_log.entries.length
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Return the Monday of the week that contains +date+.
|
|
68
|
+
# @param date [Date]
|
|
69
|
+
# @return [Date]
|
|
70
|
+
def week_start(date)
|
|
71
|
+
date - ((date.wday - 1) % 7)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Build an array of weeks between +start_date+ and +end_date+.
|
|
75
|
+
# Each week is an Array of 7 Date objects (index 0 = Monday … index 6 = Sunday).
|
|
76
|
+
# +start_date+ must be a Monday.
|
|
77
|
+
# @param start_date [Date]
|
|
78
|
+
# @param end_date [Date]
|
|
79
|
+
# @return [Array<Array<Date>>]
|
|
80
|
+
def build_weeks(start_date, end_date)
|
|
81
|
+
weeks = []
|
|
82
|
+
current = start_date
|
|
83
|
+
while current <= end_date
|
|
84
|
+
weeks << (0..6).map { |i| current + i }
|
|
85
|
+
current += 7
|
|
86
|
+
end
|
|
87
|
+
weeks
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Map an entry count to an intensity level 0–4.
|
|
91
|
+
# Uses relative scaling so the full colour range adapts to the user's habits.
|
|
92
|
+
# Any day with at least one entry is guaranteed level ≥ 1.
|
|
93
|
+
# @param count [Integer]
|
|
94
|
+
# @param max_count [Integer]
|
|
95
|
+
# @return [Integer] 0–4
|
|
96
|
+
def intensity_level(count, max_count)
|
|
97
|
+
return 0 if count.zero? || max_count.zero?
|
|
98
|
+
|
|
99
|
+
ratio = count.to_f / max_count
|
|
100
|
+
return 1 if ratio <= 0.25
|
|
101
|
+
return 2 if ratio <= 0.50
|
|
102
|
+
return 3 if ratio <= 0.75
|
|
103
|
+
|
|
104
|
+
4
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Return a Rainbow-coloured Unicode block for the given intensity level.
|
|
108
|
+
# Brighter green = more activity (optimised for dark-background terminals).
|
|
109
|
+
# @param level [Integer] 0–4
|
|
110
|
+
# @return [String]
|
|
111
|
+
def colored_block(level)
|
|
112
|
+
case level
|
|
113
|
+
when 0 then Rainbow('░').color(0x4a, 0x4a, 0x4a) # dark grey – no activity
|
|
114
|
+
when 1 then Rainbow('█').color(0x0e, 0x44, 0x29) # very dark green
|
|
115
|
+
when 2 then Rainbow('█').color(0x30, 0xa1, 0x4e) # medium green
|
|
116
|
+
when 3 then Rainbow('█').color(0x40, 0xc4, 0x63) # bright green
|
|
117
|
+
else Rainbow('█').color(0x9b, 0xe9, 0xa8) # very bright green – peak activity
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Build the month-label header row aligned with the week columns.
|
|
122
|
+
# Month names are placed at the column where the month first appears.
|
|
123
|
+
# A label is skipped when fewer than MIN_MONTH_GAP columns separate it from
|
|
124
|
+
# the previous one, preventing overlapping abbreviations at the graph edges.
|
|
125
|
+
# @param weeks [Array<Array<Date>>]
|
|
126
|
+
# @return [String]
|
|
127
|
+
def month_header(weeks)
|
|
128
|
+
# 4-char prefix to align with "Mon " row labels; 2 chars per week column.
|
|
129
|
+
buffer = ' ' * (4 + (weeks.length * 2))
|
|
130
|
+
prev_month = nil
|
|
131
|
+
last_label_col = -ActivityGraph::MIN_MONTH_GAP # allow first label at col 0
|
|
132
|
+
|
|
133
|
+
weeks.each_with_index do |week, col_idx|
|
|
134
|
+
month = week.first.month
|
|
135
|
+
next if month == prev_month
|
|
136
|
+
next if col_idx - last_label_col < MIN_MONTH_GAP
|
|
137
|
+
|
|
138
|
+
name = week.first.strftime('%b')
|
|
139
|
+
pos = 4 + (col_idx * 2)
|
|
140
|
+
buffer[pos, name.length] = name
|
|
141
|
+
prev_month = month
|
|
142
|
+
last_label_col = col_idx
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
buffer
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Return the legend row.
|
|
149
|
+
# @return [String]
|
|
150
|
+
def legend
|
|
151
|
+
blocks = (0..4).map { |level| colored_block(level) }
|
|
152
|
+
" Less #{blocks.join(' ')} More"
|
|
153
|
+
end
|
|
154
|
+
end
|
data/lib/worklog.rb
CHANGED
|
@@ -18,6 +18,7 @@ require 'printer'
|
|
|
18
18
|
require 'project_storage'
|
|
19
19
|
require 'standup'
|
|
20
20
|
require 'statistics'
|
|
21
|
+
require 'activity_graph'
|
|
21
22
|
require 'storage'
|
|
22
23
|
require 'summary'
|
|
23
24
|
require 'takeout'
|
|
@@ -441,6 +442,8 @@ module Worklog
|
|
|
441
442
|
puts "#{format_left('Entries per day')}: #{format('%.2f', stats.avg_entries)}"
|
|
442
443
|
puts "#{format_left('First entry')}: #{stats.first_entry}"
|
|
443
444
|
puts "#{format_left('Last entry')}: #{stats.last_entry}"
|
|
445
|
+
puts ''
|
|
446
|
+
puts ActivityGraph.new(@storage.all_days).render
|
|
444
447
|
end
|
|
445
448
|
|
|
446
449
|
def summary(options = {})
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fewald-worklog
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.28
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Friedrich Ewald
|
|
@@ -166,6 +166,7 @@ files:
|
|
|
166
166
|
- assets/prompts/standup.system.md.erb
|
|
167
167
|
- assets/prompts/standup.user.md.erb
|
|
168
168
|
- bin/wl
|
|
169
|
+
- lib/activity_graph.rb
|
|
169
170
|
- lib/cli.rb
|
|
170
171
|
- lib/configuration.rb
|
|
171
172
|
- lib/daily_log.rb
|