howzit 2.1.34 → 2.1.38

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.
@@ -267,11 +267,30 @@ module Howzit
267
267
 
268
268
  # Log functions
269
269
  log() {
270
+ local report_mode=false
271
+ # Check for -r or --report flag
272
+ if [ "$1" = "-r" ] || [ "$1" = "--report" ]; then
273
+ report_mode=true
274
+ shift
275
+ fi
270
276
  local level="$1"
271
277
  shift
272
278
  local message="$*"
273
- if [ -n "$HOWZIT_COMM_FILE" ]; then
279
+
280
+ if [ "$report_mode" = true ] && [ -n "$HOWZIT_COMM_FILE" ]; then
281
+ # Report mode: write to communication file
274
282
  echo "LOG:$level:$message" >> "$HOWZIT_COMM_FILE"
283
+ else
284
+ # Immediate mode: output with colors
285
+ local color=""
286
+ local reset="\\033[0m"
287
+ case "$level" in
288
+ info) color="\\033[36m" ;; # cyan
289
+ warn) color="\\033[33m" ;; # yellow
290
+ error) color="\\033[31m" ;; # red
291
+ debug) color="\\033[2m" ;; # dim
292
+ esac
293
+ echo -e "${color}${message}${reset}" >&2
275
294
  fi
276
295
  }
277
296
 
@@ -306,11 +325,34 @@ module Howzit
306
325
  # Howzit helper functions for fish
307
326
 
308
327
  function log -d "Log a message at the specified level"
328
+ set report_mode false
329
+ # Check for -r or --report flag
330
+ if test "$argv[1]" = "-r"; or test "$argv[1]" = "--report"
331
+ set report_mode true
332
+ set -e argv[1]
333
+ end
309
334
  set level $argv[1]
310
335
  set -e argv[1]
311
336
  set message (string join " " $argv)
312
- if test -n "$HOWZIT_COMM_FILE"
337
+
338
+ if test "$report_mode" = true; and test -n "$HOWZIT_COMM_FILE"
339
+ # Report mode: write to communication file
313
340
  echo "LOG:$level:$message" >> "$HOWZIT_COMM_FILE"
341
+ else
342
+ # Immediate mode: output with colors
343
+ set color ""
344
+ set reset "\\033[0m"
345
+ switch "$level"
346
+ case info
347
+ set color "\\033[36m" # cyan
348
+ case warn
349
+ set color "\\033[33m" # yellow
350
+ case error
351
+ set color "\\033[31m" # red
352
+ case debug
353
+ set color "\\033[2m" # dim
354
+ end
355
+ echo -e "$color$message$reset" >&2
314
356
  end
315
357
  end
316
358
 
@@ -362,30 +404,43 @@ module Howzit
362
404
  end
363
405
 
364
406
  class Logger
365
- def info(message)
366
- log(:info, message)
407
+ def info(message, report: false)
408
+ log(:info, message, report: report)
367
409
  end
368
410
 
369
- def warn(message)
370
- log(:warn, message)
411
+ def warn(message, report: false)
412
+ log(:warn, message, report: report)
371
413
  end
372
414
 
373
- def error(message)
374
- log(:error, message)
415
+ def error(message, report: false)
416
+ log(:error, message, report: report)
375
417
  end
376
418
 
377
- def debug(message)
378
- log(:debug, message)
419
+ def debug(message, report: false)
420
+ log(:debug, message, report: report)
379
421
  end
380
422
 
381
423
  private
382
424
 
383
- def log(level, message)
384
- comm_file = ENV['HOWZIT_COMM_FILE']
385
- return unless comm_file
425
+ def log(level, message, report: false)
426
+ if report
427
+ comm_file = ENV['HOWZIT_COMM_FILE']
428
+ return unless comm_file
386
429
 
387
- File.open(comm_file, 'a') do |f|
388
- f.puts "LOG:#{level}:#{message}"
430
+ File.open(comm_file, 'a') do |f|
431
+ f.puts "LOG:#{level}:#{message}"
432
+ end
433
+ else
434
+ # Immediate mode: output with colors
435
+ color = case level
436
+ when :info then "\033[36m" # cyan
437
+ when :warn then "\033[33m" # yellow
438
+ when :error then "\033[31m" # red
439
+ when :debug then "\033[2m" # dim
440
+ else ""
441
+ end
442
+ reset = "\033[0m"
443
+ $stderr.puts "#{color}#{message}#{reset}"
389
444
  end
390
445
  end
391
446
  end
@@ -419,25 +474,39 @@ module Howzit
419
474
  # Howzit helper module for Python
420
475
 
421
476
  import os
477
+ import sys
422
478
 
423
479
  class _Logger:
424
- def _log(self, level, message):
425
- comm_file = os.environ.get('HOWZIT_COMM_FILE')
426
- if comm_file:
427
- with open(comm_file, 'a') as f:
428
- f.write(f"LOG:{level}:{message}\\n")
429
-
430
- def info(self, message):
431
- self._log('info', message)
432
-
433
- def warn(self, message):
434
- self._log('warn', message)
435
-
436
- def error(self, message):
437
- self._log('error', message)
438
-
439
- def debug(self, message):
440
- self._log('debug', message)
480
+ def _log(self, level, message, report=False):
481
+ if report:
482
+ comm_file = os.environ.get('HOWZIT_COMM_FILE')
483
+ if comm_file:
484
+ with open(comm_file, 'a') as f:
485
+ f.write(f"LOG:{level}:{message}\\n")
486
+ else:
487
+ # Immediate mode: output with colors
488
+ colors = {
489
+ 'info': '\\033[36m', # cyan
490
+ 'warn': '\\033[33m', # yellow
491
+ 'error': '\\033[31m', # red
492
+ 'debug': '\\033[2m' # dim
493
+ }
494
+ reset = '\\033[0m'
495
+ color = colors.get(level, '')
496
+ sys.stderr.write(f"{color}{message}{reset}\\n")
497
+ sys.stderr.flush()
498
+
499
+ def info(self, message, report=False):
500
+ self._log('info', message, report=report)
501
+
502
+ def warn(self, message, report=False):
503
+ self._log('warn', message, report=report)
504
+
505
+ def error(self, message, report=False):
506
+ self._log('error', message, report=report)
507
+
508
+ def debug(self, message, report=False):
509
+ self._log('debug', message, report=report)
441
510
 
442
511
  class Howzit:
443
512
  logger = _Logger()
@@ -471,13 +540,33 @@ module Howzit
471
540
  use warnings;
472
541
 
473
542
  sub log {
543
+ my $report = 0;
544
+ # Check for -r or --report flag
545
+ if (@_ > 0 && ($_[0] eq '-r' || $_[0] eq '--report')) {
546
+ $report = 1;
547
+ shift;
548
+ }
474
549
  my ($level, $message) = @_;
475
- my $comm_file = $ENV{'HOWZIT_COMM_FILE'};
476
- return unless $comm_file;
477
550
 
478
- open(my $fh, '>>', $comm_file) or return;
479
- print $fh "LOG:$level:$message\\n";
480
- close($fh);
551
+ if ($report) {
552
+ my $comm_file = $ENV{'HOWZIT_COMM_FILE'};
553
+ return unless $comm_file;
554
+
555
+ open(my $fh, '>>', $comm_file) or return;
556
+ print $fh "LOG:$level:$message\\n";
557
+ close($fh);
558
+ } else {
559
+ # Immediate mode: output with colors
560
+ my %colors = (
561
+ 'info' => "\\033[36m", # cyan
562
+ 'warn' => "\\033[33m", # yellow
563
+ 'error' => "\\033[31m", # red
564
+ 'debug' => "\\033[2m" # dim
565
+ );
566
+ my $reset = "\\033[0m";
567
+ my $color = $colors{$level} || '';
568
+ print STDERR "$color$message$reset\\n";
569
+ }
481
570
  }
482
571
 
483
572
  sub log_info { log('info', $_[0]); }
@@ -516,27 +605,40 @@ module Howzit
516
605
  const path = require('path');
517
606
 
518
607
  class Logger {
519
- _log(level, message) {
520
- const commFile = process.env.HOWZIT_COMM_FILE;
521
- if (commFile) {
522
- fs.appendFileSync(commFile, `LOG:${level}:${message}\\n`);
608
+ _log(level, message, report = false) {
609
+ if (report) {
610
+ const commFile = process.env.HOWZIT_COMM_FILE;
611
+ if (commFile) {
612
+ fs.appendFileSync(commFile, `LOG:${level}:${message}\\n`);
613
+ }
614
+ } else {
615
+ // Immediate mode: output with colors
616
+ const colors = {
617
+ 'info': '\\033[36m', // cyan
618
+ 'warn': '\\033[33m', // yellow
619
+ 'error': '\\033[31m', // red
620
+ 'debug': '\\033[2m' // dim
621
+ };
622
+ const reset = '\\033[0m';
623
+ const color = colors[level] || '';
624
+ process.stderr.write(`${color}${message}${reset}\\n`);
523
625
  }
524
626
  }
525
627
 
526
- info(message) {
527
- this._log('info', message);
628
+ info(message, report = false) {
629
+ this._log('info', message, report);
528
630
  }
529
631
 
530
- warn(message) {
531
- this._log('warn', message);
632
+ warn(message, report = false) {
633
+ this._log('warn', message, report);
532
634
  }
533
635
 
534
- error(message) {
535
- this._log('error', message);
636
+ error(message, report = false) {
637
+ this._log('error', message, report);
536
638
  }
537
639
 
538
- debug(message) {
539
- this._log('debug', message);
640
+ debug(message, report = false) {
641
+ this._log('debug', message, report);
540
642
  }
541
643
  }
542
644
 
@@ -91,7 +91,26 @@ module Howzit
91
91
  def build_note?
92
92
  return false if downcase !~ /^(howzit[^.]*|build[^.]+)/
93
93
 
94
- return false if Howzit.config.should_ignore(self)
94
+ # Avoid recursion: only check ignore patterns if config is fully initialized
95
+ # and we're not in the middle of loading ignore patterns or initializing
96
+ begin
97
+ # Check if config exists without triggering initialization
98
+ return true unless Howzit.instance_variable_defined?(:@config)
99
+
100
+ config = Howzit.instance_variable_get(:@config)
101
+ return true unless config
102
+
103
+ # Check if config is initializing or loading ignore patterns to prevent recursion
104
+ return true if config.instance_variable_defined?(:@initializing) && config.instance_variable_get(:@initializing)
105
+ if config.instance_variable_defined?(:@loading_ignore_patterns) && config.instance_variable_get(:@loading_ignore_patterns)
106
+ return true
107
+ end
108
+
109
+ return false if config.respond_to?(:should_ignore) && config.should_ignore(self)
110
+ rescue StandardError
111
+ # If config access fails for any reason, skip the ignore check
112
+ # This prevents recursion and handles initialization edge cases
113
+ end
95
114
 
96
115
  true
97
116
  end
data/lib/howzit/task.rb CHANGED
@@ -5,7 +5,7 @@ require 'English'
5
5
  module Howzit
6
6
  # Task object
7
7
  class Task
8
- attr_reader :type, :title, :action, :arguments, :parent, :optional, :default, :last_status, :log_level
8
+ attr_reader :type, :title, :action, :arguments, :parent, :optional, :default, :last_status, :log_level, :source_file
9
9
 
10
10
  ##
11
11
  ## Initialize a Task object
@@ -21,6 +21,7 @@ module Howzit
21
21
  ## @option attributes :action [String] task action
22
22
  ## @option attributes :parent [String] title of nested (included) topic origin
23
23
  ## @option attributes :log_level [String] log level for this task (debug, info, warn, error)
24
+ ## @option attributes :source_file [String] path to the build note file this task came from
24
25
  def initialize(attributes, optional: false, default: true)
25
26
  @prefix = "{bw}\u{25B7}\u{25B7} {x}"
26
27
  # arrow = "{bw}\u{279F}{x}"
@@ -32,6 +33,9 @@ module Howzit
32
33
 
33
34
  @action = attributes[:action].render_arguments || nil
34
35
  @log_level = attributes[:log_level]
36
+ # Get source_file from parent topic if available, or from attributes
37
+ parent_obj = attributes[:parent]
38
+ @source_file = attributes[:source_file] || parent_obj&.source_file
35
39
 
36
40
  @optional = optional
37
41
  @default = default
@@ -68,7 +72,35 @@ module Howzit
68
72
  script = Tempfile.new('howzit_script')
69
73
  comm_file = ScriptComm.setup
70
74
  old_log_level = apply_log_level
75
+
76
+ # Get execution directory from source_file
77
+ # Only change directory in stack mode, and only if source_file is from a parent directory (not a template)
78
+ exec_dir = nil
79
+ if @source_file && Howzit.options[:stack]
80
+ expanded_source = File.expand_path(@source_file)
81
+ source_dir = File.dirname(expanded_source)
82
+
83
+ # Check if this is a template file - don't change directory for templates
84
+ is_template = false
85
+ if Howzit.config.respond_to?(:template_folder) && Howzit.config.template_folder
86
+ template_folder = File.expand_path(Howzit.config.template_folder)
87
+ is_template = expanded_source.start_with?(template_folder)
88
+ end
89
+
90
+ # Only change directory if not a template
91
+ exec_dir = source_dir unless is_template
92
+ end
93
+ original_dir = Dir.pwd
94
+
71
95
  begin
96
+ # Change to source file directory if available and different from current
97
+ # Only change if the directory exists and is actually different
98
+ if exec_dir && Dir.exist?(exec_dir)
99
+ expanded_exec_dir = File.expand_path(exec_dir)
100
+ expanded_original = File.expand_path(original_dir)
101
+ Dir.chdir(expanded_exec_dir) if expanded_exec_dir != expanded_original
102
+ end
103
+
72
104
  # Ensure support directory exists and install helpers
73
105
  ScriptSupport.ensure_support_dir
74
106
  ENV['HOWZIT_SUPPORT_DIR'] = ScriptSupport.support_dir
@@ -89,6 +121,12 @@ module Howzit
89
121
  system(cmd)
90
122
  end
91
123
  ensure
124
+ # Restore original directory
125
+ if exec_dir && Dir.exist?(exec_dir)
126
+ expanded_exec_dir = File.expand_path(exec_dir)
127
+ expanded_original = File.expand_path(original_dir)
128
+ Dir.chdir(expanded_original) if expanded_exec_dir != expanded_original
129
+ end
92
130
  restore_log_level(old_log_level) if old_log_level
93
131
  script.close
94
132
  script.unlink
@@ -171,9 +209,43 @@ module Howzit
171
209
  ENV['HOWZIT_SCRIPTS'] = File.expand_path('~/.config/howzit/scripts')
172
210
  comm_file = ScriptComm.setup
173
211
  old_log_level = apply_log_level
212
+
213
+ # Get execution directory from source_file
214
+ # Only change directory in stack mode, and only if source_file is from a parent directory (not a template)
215
+ exec_dir = nil
216
+ if @source_file && Howzit.options[:stack]
217
+ expanded_source = File.expand_path(@source_file)
218
+ source_dir = File.dirname(expanded_source)
219
+
220
+ # Check if this is a template file - don't change directory for templates
221
+ is_template = false
222
+ if Howzit.config.respond_to?(:template_folder) && Howzit.config.template_folder
223
+ template_folder = File.expand_path(Howzit.config.template_folder)
224
+ is_template = expanded_source.start_with?(template_folder)
225
+ end
226
+
227
+ # Only change directory if not a template
228
+ exec_dir = source_dir unless is_template
229
+ end
230
+ original_dir = Dir.pwd
231
+
174
232
  begin
233
+ # Change to source file directory if available and different from current
234
+ # Only change if the directory exists and is actually different
235
+ if exec_dir && Dir.exist?(exec_dir)
236
+ expanded_exec_dir = File.expand_path(exec_dir)
237
+ expanded_original = File.expand_path(original_dir)
238
+ Dir.chdir(expanded_exec_dir) if expanded_exec_dir != expanded_original
239
+ end
240
+
175
241
  res = system(@action)
176
242
  ensure
243
+ # Restore original directory
244
+ if exec_dir && Dir.exist?(exec_dir)
245
+ expanded_exec_dir = File.expand_path(exec_dir)
246
+ expanded_original = File.expand_path(original_dir)
247
+ Dir.chdir(expanded_original) if expanded_exec_dir != expanded_original
248
+ end
177
249
  restore_log_level(old_log_level) if old_log_level
178
250
  # Process script communication
179
251
  ScriptComm.apply(comm_file) if comm_file
@@ -245,7 +317,13 @@ module Howzit
245
317
  ## @return [String] List representation of the object.
246
318
  ##
247
319
  def to_list
248
- " * #{@type}: #{@title.preserve_escapes}"
320
+ # Highlight variables in title if parent topic has the method
321
+ display_title = if @parent.respond_to?(:highlight_variables)
322
+ @parent.highlight_variables(@title.preserve_escapes)
323
+ else
324
+ @title.preserve_escapes
325
+ end
326
+ " * #{@type}: #{display_title}"
249
327
  end
250
328
  end
251
329
  end
data/lib/howzit/topic.rb CHANGED
@@ -7,22 +7,24 @@ module Howzit
7
7
 
8
8
  attr_accessor :content
9
9
 
10
- attr_reader :title, :tasks, :prereqs, :postreqs, :results, :named_args, :directives
10
+ attr_reader :title, :tasks, :prereqs, :postreqs, :results, :named_args, :directives, :arg_definitions, :source_file
11
11
 
12
12
  ##
13
13
  ## Initialize a topic object
14
14
  ##
15
- ## @param title [String] The topic title
16
- ## @param content [String] The raw topic content
17
- ## @param metadata [Hash] Optional metadata hash
15
+ ## @param title [String] The topic title
16
+ ## @param content [String] The raw topic content
17
+ ## @param metadata [Hash] Optional metadata hash
18
+ ## @param source_file [String] Optional path to the build note file this topic came from
18
19
  ##
19
- def initialize(title, content, metadata = nil)
20
+ def initialize(title, content, metadata = nil, source_file: nil)
20
21
  @title = title
21
22
  @content = content
22
23
  @parent = nil
23
24
  @nest_level = 0
24
25
  @named_args = {}
25
26
  @metadata = metadata
27
+ @source_file = source_file
26
28
  arguments
27
29
 
28
30
  @directives = parse_directives_with_conditionals
@@ -32,6 +34,7 @@ module Howzit
32
34
 
33
35
  # Get named arguments from title
34
36
  def arguments
37
+ @arg_definitions = []
35
38
  return unless @title =~ /\(.*?\) *$/
36
39
 
37
40
  a = @title.match(/\((?<args>.*?)\) *$/)
@@ -39,6 +42,8 @@ module Howzit
39
42
 
40
43
  args.each_with_index do |arg, idx|
41
44
  arg_name, default = arg.split(/:/).map(&:strip)
45
+ # Store original definition for display purposes
46
+ @arg_definitions << (default ? "#{arg_name}:#{default}" : arg_name)
42
47
 
43
48
  @named_args[arg_name] = if Howzit.arguments && Howzit.arguments.count >= idx + 1
44
49
  Howzit.arguments[idx]
@@ -277,7 +282,13 @@ module Howzit
277
282
 
278
283
  output = []
279
284
  if opt[:header]
280
- output.push(@title.format_header)
285
+ # Include argument definitions in header if present
286
+ header_title = @title.dup
287
+ unless @arg_definitions.nil? || @arg_definitions.empty?
288
+ formatted_args = @arg_definitions.map { |arg| format_arg_definition(arg) }.join('{l}, '.c)
289
+ header_title += " {l}({x}#{formatted_args}{l}){x}".c
290
+ end
291
+ output.push(header_title.format_header)
281
292
  output.push('')
282
293
  end
283
294
  # Process conditional blocks first
@@ -302,11 +313,50 @@ module Howzit
302
313
  output.push("{bmK}\u{25B6} {bwK}#{title_code_block(Regexp.last_match.named_captures.symbolize_keys)}{x}".c)
303
314
  else
304
315
  l.wrap!(Howzit.options[:wrap]) if Howzit.options[:wrap].positive?
305
- output.push(l)
316
+ # Highlight variable placeholders in content
317
+ output.push(highlight_variables(l))
306
318
  end
307
319
  end
308
320
  Howzit.named_arguments = @named_args
309
- output.push('').map(&:render_arguments)
321
+ output.push('')
322
+ end
323
+
324
+ ##
325
+ ## Format an argument definition with syntax highlighting
326
+ ## Parentheses in blue, variable name in bright white, default in yellow
327
+ ##
328
+ ## @param arg [String] The argument definition (e.g., "var" or "var:default")
329
+ ##
330
+ ## @return [String] Colorized argument definition
331
+ ##
332
+ def format_arg_definition(arg)
333
+ if arg.include?(':')
334
+ name, default = arg.split(':', 2)
335
+ "{bw}#{name}{l}:{y}#{default}{x}".c
336
+ else
337
+ "{bw}#{arg}{x}".c
338
+ end
339
+ end
340
+
341
+ ##
342
+ ## Highlight variable placeholders in content
343
+ ## Format: ${variable} or ${variable:default}
344
+ ## Dollar sign and braces in blue, variable name in bright white, default in yellow
345
+ ##
346
+ ## @param text [String] The text to process
347
+ ##
348
+ ## @return [String] Text with highlighted variables
349
+ ##
350
+ def highlight_variables(text)
351
+ text.gsub(/\$\{([A-Za-z0-9_]+)(?::([^}]*))?\}/) do
352
+ var_name = Regexp.last_match(1)
353
+ default = Regexp.last_match(2)
354
+ if default
355
+ "{l}\\$\\{{bw}#{var_name}{l}:{y}#{default}{l}\\}{x}".c
356
+ else
357
+ "{l}\\$\\{{bw}#{var_name}{l}\\}{x}".c
358
+ end
359
+ end
310
360
  end
311
361
 
312
362
  include Comparable
@@ -3,5 +3,5 @@
3
3
  # Primary module for this gem.
4
4
  module Howzit
5
5
  # Current Howzit version.
6
- VERSION = '2.1.34'
6
+ VERSION = '2.1.38'
7
7
  end