howzit 2.1.23 → 2.1.25
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 +26 -0
- data/lib/howzit/buildnote.rb +94 -9
- data/lib/howzit/colors.rb +4 -2
- data/lib/howzit/run_report.rb +44 -12
- data/lib/howzit/task.rb +27 -5
- data/lib/howzit/topic.rb +11 -3
- data/lib/howzit/version.rb +1 -1
- data/spec/run_report_spec.rb +4 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 330829109ccbd73e32ce82690327d2776c1a2f4e60004165fbcde80bdb2b68d6
|
|
4
|
+
data.tar.gz: 402805ec02154f4ed0f219bc8975be8b1f24e5ca8726e2eb6040f905f32854c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f6f3ad81b25dc520f43a4ba1ca063a093dece43129c5ba86ed4364ecc4de5d9222f86ba64dbc91220d73197586846d92ff7f7b98251739881a3d140f453c0ea
|
|
7
|
+
data.tar.gz: 7b5eef5eb1cca9c57e3f7fa7af222911d43790b15bfb30b682c1c6f521cd1c54f8995a357141f1533830c888b91d79b107f3ec295b6238360719e1a6f2bb6032
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
### 2.1.25
|
|
2
|
+
|
|
3
|
+
2025-12-19 07:41
|
|
4
|
+
|
|
5
|
+
#### NEW
|
|
6
|
+
|
|
7
|
+
- Added default metadata support to automatically run topics when executing `howzit --run` with no arguments. Supports multiple comma-separated topics with optional bracketed arguments (e.g., `default: Build Project, Run Tests[verbose]`).
|
|
8
|
+
|
|
9
|
+
#### FIXED
|
|
10
|
+
|
|
11
|
+
- Task titles are now correctly displayed in run reports and "Running" messages instead of showing the command. When a title is provided after @run or @include directives (e.g., `@run(ls) List directory`), the title is used throughout.
|
|
12
|
+
- Fixed color code interpretation issues when task or topic names contain dollar signs or braces. These characters are now properly escaped to prevent them from being interpreted as color template codes.
|
|
13
|
+
- Fixed issue where task titles containing braces would show literal `{x}` in output due to color template parsing.
|
|
14
|
+
|
|
15
|
+
### 2.1.24
|
|
16
|
+
|
|
17
|
+
2025-12-13 07:11
|
|
18
|
+
|
|
19
|
+
#### CHANGED
|
|
20
|
+
|
|
21
|
+
- Run summary now displays as simple list with emoji status indicators
|
|
22
|
+
|
|
23
|
+
#### IMPROVED
|
|
24
|
+
|
|
25
|
+
- Use horizontal rule (***) as separator instead of box borders
|
|
26
|
+
|
|
1
27
|
### 2.1.23
|
|
2
28
|
|
|
3
29
|
2025-12-13 06:38
|
data/lib/howzit/buildnote.rb
CHANGED
|
@@ -955,18 +955,32 @@ module Howzit
|
|
|
955
955
|
titles.each { |title| topic_matches.push(find_topic(title)[0]) }
|
|
956
956
|
process_topic_matches(topic_matches, output)
|
|
957
957
|
elsif !Howzit.cli_args.empty?
|
|
958
|
-
#
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
958
|
+
# Check if first arg is "default"
|
|
959
|
+
if Howzit.options[:run] && Howzit.cli_args[0].downcase == 'default'
|
|
960
|
+
process_default_metadata(output)
|
|
961
|
+
else
|
|
962
|
+
# Collect all topic matches first (showing menus as needed)
|
|
963
|
+
search = topic_search_terms_from_cli
|
|
964
|
+
topic_matches = collect_topic_matches(search, output)
|
|
965
|
+
process_topic_matches(topic_matches, output)
|
|
966
|
+
end
|
|
962
967
|
else
|
|
963
|
-
# No arguments
|
|
968
|
+
# No arguments
|
|
964
969
|
if Howzit.options[:run]
|
|
965
|
-
|
|
966
|
-
|
|
970
|
+
# Check for default metadata when running with no args
|
|
971
|
+
if @metadata.key?('default')
|
|
972
|
+
process_default_metadata(output)
|
|
973
|
+
else
|
|
974
|
+
Howzit.run_log = []
|
|
975
|
+
Howzit.multi_topic_run = topics.length > 1
|
|
976
|
+
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
977
|
+
finalize_output(output)
|
|
978
|
+
end
|
|
979
|
+
else
|
|
980
|
+
# Show all topics
|
|
981
|
+
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
982
|
+
finalize_output(output)
|
|
967
983
|
end
|
|
968
|
-
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
969
|
-
finalize_output(output)
|
|
970
984
|
end
|
|
971
985
|
end
|
|
972
986
|
|
|
@@ -1060,6 +1074,77 @@ module Howzit
|
|
|
1060
1074
|
finalize_output(output)
|
|
1061
1075
|
end
|
|
1062
1076
|
|
|
1077
|
+
##
|
|
1078
|
+
## Process default metadata and run the specified topics
|
|
1079
|
+
##
|
|
1080
|
+
## @param output [Array] Output array
|
|
1081
|
+
##
|
|
1082
|
+
def process_default_metadata(output)
|
|
1083
|
+
default_value = @metadata['default']
|
|
1084
|
+
return if default_value.nil? || default_value.strip.empty?
|
|
1085
|
+
|
|
1086
|
+
Howzit.run_log = []
|
|
1087
|
+
default_topics = parse_default_metadata(default_value)
|
|
1088
|
+
Howzit.multi_topic_run = default_topics.length > 1
|
|
1089
|
+
|
|
1090
|
+
topic_specs = []
|
|
1091
|
+
default_topics.each do |topic_spec|
|
|
1092
|
+
topic_name, args = parse_topic_with_args(topic_spec)
|
|
1093
|
+
matches = find_topic(topic_name)
|
|
1094
|
+
if matches.empty?
|
|
1095
|
+
output.push(%({bR}ERROR:{xr} No topic match found for {bw}#{topic_name}{x}\n).c)
|
|
1096
|
+
else
|
|
1097
|
+
# Store topic and its arguments together (take first match, like @include does)
|
|
1098
|
+
topic_specs << [matches[0], args]
|
|
1099
|
+
end
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
if topic_specs.empty?
|
|
1103
|
+
Util.show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
|
|
1104
|
+
Process.exit 1
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
# Run each topic with its specific arguments
|
|
1108
|
+
topic_specs.each do |topic_match, args|
|
|
1109
|
+
# Set arguments if provided, otherwise clear them
|
|
1110
|
+
if args && !args.empty?
|
|
1111
|
+
Howzit.arguments = args.split(/ *, */).map(&:render_arguments)
|
|
1112
|
+
else
|
|
1113
|
+
Howzit.arguments = []
|
|
1114
|
+
end
|
|
1115
|
+
output.push(process_topic(topic_match, Howzit.options[:run], single: true))
|
|
1116
|
+
end
|
|
1117
|
+
finalize_output(output)
|
|
1118
|
+
end
|
|
1119
|
+
|
|
1120
|
+
##
|
|
1121
|
+
## Parse default metadata value into individual topic specifications
|
|
1122
|
+
##
|
|
1123
|
+
## @param default_value [String] The default metadata value
|
|
1124
|
+
##
|
|
1125
|
+
## @return [Array] Array of topic specification strings
|
|
1126
|
+
##
|
|
1127
|
+
def parse_default_metadata(default_value)
|
|
1128
|
+
default_value.strip.split(/\s*,\s*/).map(&:strip).reject(&:empty?)
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
##
|
|
1132
|
+
## Parse a topic specification that may include bracketed arguments
|
|
1133
|
+
##
|
|
1134
|
+
## @param topic_spec [String] Topic specification like "Run Snippet[document.md]"
|
|
1135
|
+
##
|
|
1136
|
+
## @return [Array] [topic_name, args_string]
|
|
1137
|
+
##
|
|
1138
|
+
def parse_topic_with_args(topic_spec)
|
|
1139
|
+
if topic_spec =~ /\[(.*?)\]$/
|
|
1140
|
+
args = Regexp.last_match(1)
|
|
1141
|
+
topic_name = topic_spec.sub(/\[.*?\]$/, '').strip
|
|
1142
|
+
[topic_name, args]
|
|
1143
|
+
else
|
|
1144
|
+
[topic_spec.strip, nil]
|
|
1145
|
+
end
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1063
1148
|
##
|
|
1064
1149
|
## Finalize and display output with run summary if applicable
|
|
1065
1150
|
##
|
data/lib/howzit/colors.rb
CHANGED
|
@@ -272,7 +272,7 @@ module Howzit
|
|
|
272
272
|
def template(input)
|
|
273
273
|
input = input.join(' ') if input.is_a? Array
|
|
274
274
|
fmt = input.gsub(/%/, '%%')
|
|
275
|
-
fmt = fmt.gsub(/(?<!\\u
|
|
275
|
+
fmt = fmt.gsub(/(?<!\\u|\$|\\\\)\{(\w+)\}/i) do
|
|
276
276
|
m = Regexp.last_match(1)
|
|
277
277
|
if m =~ /^[wkglycmrWKGLYCMRdbuix]+$/
|
|
278
278
|
m.split('').map { |c| "%<#{c}>s" }.join('')
|
|
@@ -287,7 +287,9 @@ module Howzit
|
|
|
287
287
|
Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
|
|
288
288
|
d: dark, b: bold, u: underline, i: italic, x: reset }
|
|
289
289
|
|
|
290
|
-
fmt.empty? ? input : format(fmt, colors)
|
|
290
|
+
result = fmt.empty? ? input : format(fmt, colors)
|
|
291
|
+
# Unescape braces that were escaped to prevent color code interpretation
|
|
292
|
+
result.gsub(/\\\{/, '{').gsub(/\\\}/, '}')
|
|
291
293
|
end
|
|
292
294
|
end
|
|
293
295
|
|
data/lib/howzit/run_report.rb
CHANGED
|
@@ -21,16 +21,43 @@ module Howzit
|
|
|
21
21
|
def format
|
|
22
22
|
return '' if entries.empty?
|
|
23
23
|
|
|
24
|
+
lines = entries.map { |entry| format_line(entry, Howzit.multi_topic_run) }
|
|
25
|
+
output_lines = ["\n\n***\n"] + lines
|
|
26
|
+
output_lines.join("\n")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def format_line(entry, prefix_topic)
|
|
30
|
+
symbol = entry[:success] ? '✅' : '❌'
|
|
31
|
+
parts = ["#{symbol} "]
|
|
32
|
+
if prefix_topic && entry[:topic] && !entry[:topic].empty?
|
|
33
|
+
# Escape braces in topic name to prevent color code interpretation
|
|
34
|
+
topic_escaped = entry[:topic].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
35
|
+
parts << "{bw}#{topic_escaped}{x}: "
|
|
36
|
+
end
|
|
37
|
+
# Escape braces in task name to prevent color code interpretation
|
|
38
|
+
task_escaped = entry[:task].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
39
|
+
parts << "{by}#{task_escaped}{x}"
|
|
40
|
+
unless entry[:success]
|
|
41
|
+
reason = entry[:exit_status] ? "exit code #{entry[:exit_status]}" : 'failed'
|
|
42
|
+
parts << " {br}(#{reason}){x}"
|
|
43
|
+
end
|
|
44
|
+
parts.join.c
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Table formatting methods kept for possible future use
|
|
48
|
+
def format_as_table
|
|
49
|
+
return '' if entries.empty?
|
|
50
|
+
|
|
24
51
|
rows = entries.map { |entry| format_row(entry, Howzit.multi_topic_run) }
|
|
25
52
|
|
|
26
|
-
# Status column:
|
|
27
|
-
#
|
|
28
|
-
status_width =
|
|
53
|
+
# Status column width: " :--: " = 6 chars (4 for :--: plus 1 space each side)
|
|
54
|
+
# Emoji is 2-width in terminal, so we need 2 spaces on each side to center it
|
|
55
|
+
status_width = 6
|
|
29
56
|
task_width = [4, rows.map { |r| r[:task_plain].length }.max].max
|
|
30
57
|
|
|
31
|
-
# Build the table with emoji header
|
|
32
|
-
header = "|
|
|
33
|
-
separator = "|
|
|
58
|
+
# Build the table with emoji header - center emoji in 6-char column
|
|
59
|
+
header = "| 🚥 | #{'Task'.ljust(task_width)} |"
|
|
60
|
+
separator = "| :--: | #{':' + '-' * (task_width - 1)} |"
|
|
34
61
|
|
|
35
62
|
table_lines = [header, separator]
|
|
36
63
|
rows.each do |row|
|
|
@@ -40,25 +67,30 @@ module Howzit
|
|
|
40
67
|
table_lines.join("\n")
|
|
41
68
|
end
|
|
42
69
|
|
|
43
|
-
def table_row_colored(status, task, task_plain,
|
|
70
|
+
def table_row_colored(status, task, task_plain, _status_width, task_width)
|
|
44
71
|
task_padding = task_width - task_plain.length
|
|
45
72
|
|
|
46
|
-
"|
|
|
73
|
+
"| #{status} | #{task}#{' ' * task_padding} |"
|
|
47
74
|
end
|
|
48
75
|
|
|
49
76
|
def format_row(entry, prefix_topic)
|
|
77
|
+
# Use plain emoji without color codes - the emoji itself provides visual meaning
|
|
78
|
+
# and complex ANSI codes interfere with mdless table rendering
|
|
50
79
|
symbol = entry[:success] ? '✅' : '❌'
|
|
51
|
-
symbol_colored = entry[:success] ? '{bg}✅{x}'.c : '{br}❌{x}'.c
|
|
52
80
|
|
|
53
81
|
task_parts = []
|
|
54
82
|
task_parts_plain = []
|
|
55
83
|
|
|
56
84
|
if prefix_topic && entry[:topic] && !entry[:topic].empty?
|
|
57
|
-
|
|
85
|
+
# Escape braces in topic name to prevent color code interpretation
|
|
86
|
+
topic_escaped = entry[:topic].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
87
|
+
task_parts << "{bw}#{topic_escaped}{x}: "
|
|
58
88
|
task_parts_plain << "#{entry[:topic]}: "
|
|
59
89
|
end
|
|
60
90
|
|
|
61
|
-
|
|
91
|
+
# Escape braces in task name to prevent color code interpretation
|
|
92
|
+
task_escaped = entry[:task].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
93
|
+
task_parts << "{by}#{task_escaped}{x}"
|
|
62
94
|
task_parts_plain << entry[:task]
|
|
63
95
|
|
|
64
96
|
unless entry[:success]
|
|
@@ -68,7 +100,7 @@ module Howzit
|
|
|
68
100
|
end
|
|
69
101
|
|
|
70
102
|
{
|
|
71
|
-
status:
|
|
103
|
+
status: symbol,
|
|
72
104
|
status_plain: symbol,
|
|
73
105
|
task: task_parts.join.c,
|
|
74
106
|
task_plain: task_parts_plain.join
|
data/lib/howzit/task.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Howzit
|
|
|
26
26
|
@arguments = attributes[:arguments] || []
|
|
27
27
|
|
|
28
28
|
@type = attributes[:type] || :run
|
|
29
|
-
@title = attributes[:title]
|
|
29
|
+
@title = attributes[:title].nil? ? nil : attributes[:title].to_s
|
|
30
30
|
@parent = attributes[:parent] || nil
|
|
31
31
|
|
|
32
32
|
@action = attributes[:action].render_arguments || nil
|
|
@@ -98,8 +98,19 @@ module Howzit
|
|
|
98
98
|
## Execute a run task
|
|
99
99
|
##
|
|
100
100
|
def run_run
|
|
101
|
-
title
|
|
102
|
-
|
|
101
|
+
# If a title was explicitly provided (different from action), always use it
|
|
102
|
+
# Otherwise, use action (or respect show_all_code if no title)
|
|
103
|
+
display_title = if @title && !@title.empty? && @title != @action
|
|
104
|
+
# Title was explicitly provided, use it
|
|
105
|
+
@title
|
|
106
|
+
elsif Howzit.options[:show_all_code]
|
|
107
|
+
# No explicit title, show code if requested
|
|
108
|
+
@action
|
|
109
|
+
else
|
|
110
|
+
# No explicit title, use title if available (might be same as action), otherwise action
|
|
111
|
+
@title && !@title.empty? ? @title : @action
|
|
112
|
+
end
|
|
113
|
+
Howzit.console.info("#{@prefix}{bg}Running {bw}#{display_title}{x}".c)
|
|
103
114
|
ENV['HOWZIT_SCRIPTS'] = File.expand_path('~/.config/howzit/scripts')
|
|
104
115
|
res = system(@action)
|
|
105
116
|
update_last_status(res ? 0 : 1)
|
|
@@ -110,8 +121,19 @@ module Howzit
|
|
|
110
121
|
## Execute a copy task
|
|
111
122
|
##
|
|
112
123
|
def run_copy
|
|
113
|
-
title
|
|
114
|
-
|
|
124
|
+
# If a title was explicitly provided (different from action), always use it
|
|
125
|
+
# Otherwise, use action (or respect show_all_code if no title)
|
|
126
|
+
display_title = if @title && !@title.empty? && @title != @action
|
|
127
|
+
# Title was explicitly provided, use it
|
|
128
|
+
@title
|
|
129
|
+
elsif Howzit.options[:show_all_code]
|
|
130
|
+
# No explicit title, show code if requested
|
|
131
|
+
@action
|
|
132
|
+
else
|
|
133
|
+
# No explicit title, use title if available (might be same as action), otherwise action
|
|
134
|
+
@title && !@title.empty? ? @title : @action
|
|
135
|
+
end
|
|
136
|
+
Howzit.console.info("#{@prefix}{bg}Copied {bw}#{display_title}{bg} to clipboard{x}".c)
|
|
115
137
|
Util.os_copy(@action)
|
|
116
138
|
@last_status = 0
|
|
117
139
|
true
|
data/lib/howzit/topic.rb
CHANGED
|
@@ -288,11 +288,18 @@ module Howzit
|
|
|
288
288
|
def define_task_args(keys)
|
|
289
289
|
cmd = keys[:cmd]
|
|
290
290
|
obj = keys[:action]
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
# Extract and clean the title
|
|
292
|
+
raw_title = keys[:title]
|
|
293
|
+
# Determine the title: use provided title if available, otherwise use action
|
|
294
|
+
title = if raw_title.nil? || raw_title.to_s.strip.empty?
|
|
295
|
+
obj
|
|
296
|
+
else
|
|
297
|
+
raw_title.to_s.strip
|
|
298
|
+
end
|
|
299
|
+
# Store the actual title (not overridden by show_all_code - that's only for display)
|
|
293
300
|
task_args = { type: :include,
|
|
294
301
|
arguments: nil,
|
|
295
|
-
title: title,
|
|
302
|
+
title: title.dup, # Make a copy to avoid reference issues
|
|
296
303
|
action: obj,
|
|
297
304
|
parent: self }
|
|
298
305
|
case cmd
|
|
@@ -303,6 +310,7 @@ module Howzit
|
|
|
303
310
|
Howzit.arguments = args
|
|
304
311
|
arguments
|
|
305
312
|
title.sub!(/ *\[.*?\] *$/, '')
|
|
313
|
+
task_args[:title] = title
|
|
306
314
|
end
|
|
307
315
|
|
|
308
316
|
task_args[:type] = :include
|
data/lib/howzit/version.rb
CHANGED
data/spec/run_report_spec.rb
CHANGED
|
@@ -13,11 +13,10 @@ describe Howzit::RunReport do
|
|
|
13
13
|
Howzit.multi_topic_run = false
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
it 'renders a
|
|
16
|
+
it 'renders a simple list for single topic runs' do
|
|
17
17
|
Howzit::RunReport.log({ topic: 'Git: Config', task: 'Run Git Origin', success: true, exit_status: 0 })
|
|
18
18
|
plain = Howzit::RunReport.format.uncolor
|
|
19
|
-
expect(plain).to include('
|
|
20
|
-
expect(plain).to include('| Task')
|
|
19
|
+
expect(plain).to include('***')
|
|
21
20
|
expect(plain).to include('✅')
|
|
22
21
|
expect(plain).to include('Run Git Origin')
|
|
23
22
|
expect(plain).not_to include('Git: Config:')
|
|
@@ -35,10 +34,10 @@ describe Howzit::RunReport do
|
|
|
35
34
|
expect(plain).to include('exit code 12')
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
it 'formats as a proper markdown table with aligned columns' do
|
|
37
|
+
it 'formats as a proper markdown table with aligned columns using format_as_table' do
|
|
39
38
|
Howzit::RunReport.log({ topic: 'Test', task: 'Short', success: true, exit_status: 0 })
|
|
40
39
|
Howzit::RunReport.log({ topic: 'Test', task: 'A much longer task name', success: true, exit_status: 0 })
|
|
41
|
-
plain = Howzit::RunReport.
|
|
40
|
+
plain = Howzit::RunReport.format_as_table.uncolor
|
|
42
41
|
lines = plain.split("\n")
|
|
43
42
|
# All lines should start and end with pipe
|
|
44
43
|
lines.each do |line|
|