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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fc52d90b2a71d349a14e77f6e313baed51003a43ce2ca615a31f0b070f0681e
4
- data.tar.gz: fa0fa63742822ef396b45abc56b6ae7dfbc7acb72a4c896a9e6570cc9e13b1c3
3
+ metadata.gz: 330829109ccbd73e32ce82690327d2776c1a2f4e60004165fbcde80bdb2b68d6
4
+ data.tar.gz: 402805ec02154f4ed0f219bc8975be8b1f24e5ca8726e2eb6040f905f32854c7
5
5
  SHA512:
6
- metadata.gz: d7e01306310519e4454960b5246740572a7731aa4946fa3d4d455ee3f9a3c30b1415eabdf91a58dca48d0123a0b78332754d7e4d86bd112692574b517a328072
7
- data.tar.gz: 99ab41b0612da8c5a0e456c5a5006e9dc6cf5e679726b461cba170622d3aa615fe6a259c50dec67b2b4c7151afff200af8d8cf2c3dfe1d775b0dea83e3975fba
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
@@ -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
- # Collect all topic matches first (showing menus as needed)
959
- search = topic_search_terms_from_cli
960
- topic_matches = collect_topic_matches(search, output)
961
- process_topic_matches(topic_matches, output)
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 - show all topics
968
+ # No arguments
964
969
  if Howzit.options[:run]
965
- Howzit.run_log = []
966
- Howzit.multi_topic_run = topics.length > 1
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|\$)\{(\w+)\}/i) do
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
 
@@ -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: emoji + 1 space on each side = 3 chars wide visually
27
- # But emojis are 2-width in terminal, so we need width of 4 for " "
28
- status_width = 4
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 = "| 🚥 | #{'Task'.ljust(task_width)} |"
33
- separator = "| #{':' + '-' * 2 + ':'} | #{':' + '-' * (task_width - 2)} |"
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, status_width, task_width)
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
- "| #{status} | #{task}#{' ' * task_padding} |"
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
- task_parts << "{bw}#{entry[:topic]}{x}: "
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
- task_parts << "{by}#{entry[:task]}{x}"
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: symbol_colored,
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] || nil
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 = Howzit.options[:show_all_code] ? @action : @title
102
- Howzit.console.info("#{@prefix}{bg}Running {bw}#{title}{x}".c)
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 = Howzit.options[:show_all_code] ? @action : @title
114
- Howzit.console.info("#{@prefix}{bg}Copied {bw}#{title}{bg} to clipboard{x}".c)
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
- title = keys[:title].nil? ? obj : keys[:title].strip
292
- title = Howzit.options[:show_all_code] ? obj : title
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
@@ -3,5 +3,5 @@
3
3
  # Primary module for this gem.
4
4
  module Howzit
5
5
  # Current Howzit version.
6
- VERSION = '2.1.23'
6
+ VERSION = '2.1.25'
7
7
  end
@@ -13,11 +13,10 @@ describe Howzit::RunReport do
13
13
  Howzit.multi_topic_run = false
14
14
  end
15
15
 
16
- it 'renders a markdown table for single topic runs' do
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.format.uncolor
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|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: howzit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.23
4
+ version: 2.1.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra