howzit 2.1.29 → 2.1.30
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 +74 -0
- data/README.md +5 -1
- data/Rakefile +7 -1
- data/lib/howzit/config.rb +13 -0
- data/lib/howzit/console_logger.rb +62 -2
- data/lib/howzit/directive.rb +137 -0
- data/lib/howzit/script_support.rb +479 -0
- data/lib/howzit/task.rb +57 -4
- data/lib/howzit/topic.rb +548 -5
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +2 -0
- data/spec/log_level_spec.rb +247 -0
- data/spec/sequential_conditional_spec.rb +319 -0
- data/spec/set_var_spec.rb +603 -0
- data/spec/topic_spec.rb +8 -6
- data/src/_README.md +5 -1
- metadata +10 -2
data/lib/howzit/topic.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Howzit
|
|
|
7
7
|
|
|
8
8
|
attr_accessor :content
|
|
9
9
|
|
|
10
|
-
attr_reader :title, :tasks, :prereqs, :postreqs, :results, :named_args
|
|
10
|
+
attr_reader :title, :tasks, :prereqs, :postreqs, :results, :named_args, :directives
|
|
11
11
|
|
|
12
12
|
##
|
|
13
13
|
## Initialize a topic object
|
|
@@ -25,6 +25,7 @@ module Howzit
|
|
|
25
25
|
@metadata = metadata
|
|
26
26
|
arguments
|
|
27
27
|
|
|
28
|
+
@directives = parse_directives_with_conditionals
|
|
28
29
|
@tasks = gather_tasks
|
|
29
30
|
@results = { total: 0, success: 0, errors: 0, message: ''.c }
|
|
30
31
|
end
|
|
@@ -81,6 +82,15 @@ module Howzit
|
|
|
81
82
|
|
|
82
83
|
cols = check_cols
|
|
83
84
|
|
|
85
|
+
# Use sequential processing if we have directives with conditionals
|
|
86
|
+
if @directives && @directives.any?(&:conditional?)
|
|
87
|
+
return run_sequential(nested: nested, output: output, cols: cols)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Fall back to old behavior for backward compatibility
|
|
91
|
+
# Note: @set_var directives are already processed in gather_tasks for non-sequential path
|
|
92
|
+
# This section is kept for backward compatibility but shouldn't be needed
|
|
93
|
+
|
|
84
94
|
if @tasks.count.positive?
|
|
85
95
|
unless @prereqs.empty?
|
|
86
96
|
begin
|
|
@@ -178,7 +188,8 @@ module Howzit
|
|
|
178
188
|
output = []
|
|
179
189
|
|
|
180
190
|
if keys[:action] =~ / *\[(.*?)\] *$/
|
|
181
|
-
Howzit.named_arguments
|
|
191
|
+
Howzit.named_arguments ||= {}
|
|
192
|
+
Howzit.named_arguments.merge!(@named_args) if @named_args
|
|
182
193
|
Howzit.arguments = Regexp.last_match(1).split(/ *, */).map!(&:render_arguments)
|
|
183
194
|
end
|
|
184
195
|
|
|
@@ -318,7 +329,9 @@ module Howzit
|
|
|
318
329
|
action: obj,
|
|
319
330
|
parent: self }
|
|
320
331
|
# Set named_arguments before processing titles for variable substitution
|
|
321
|
-
|
|
332
|
+
# Merge with existing named_arguments to preserve @set_var variables
|
|
333
|
+
Howzit.named_arguments ||= {}
|
|
334
|
+
Howzit.named_arguments.merge!(@named_args) if @named_args
|
|
322
335
|
case cmd
|
|
323
336
|
when /include/i
|
|
324
337
|
if title =~ /\[(.*?)\] *$/
|
|
@@ -335,6 +348,14 @@ module Howzit
|
|
|
335
348
|
when /run/i
|
|
336
349
|
task_args[:type] = :run
|
|
337
350
|
task_args[:title] = title.render_arguments
|
|
351
|
+
# Parse log_level from action if present (format: script, log_level=level)
|
|
352
|
+
if obj =~ /,\s*log_level\s*=\s*(\w+)/i
|
|
353
|
+
log_level = Regexp.last_match(1).downcase
|
|
354
|
+
task_args[:log_level] = log_level
|
|
355
|
+
# Remove log_level parameter from action
|
|
356
|
+
obj = obj.sub(/,\s*log_level\s*=\s*\w+/i, '').strip
|
|
357
|
+
end
|
|
358
|
+
task_args[:action] = obj
|
|
338
359
|
when /copy/i
|
|
339
360
|
task_args[:type] = :copy
|
|
340
361
|
task_args[:action] = Shellwords.escape(obj)
|
|
@@ -379,7 +400,39 @@ module Howzit
|
|
|
379
400
|
runnable = []
|
|
380
401
|
# Process conditional blocks first
|
|
381
402
|
# Set named_arguments before processing so conditions can access them
|
|
382
|
-
Howzit.named_arguments
|
|
403
|
+
Howzit.named_arguments ||= {}
|
|
404
|
+
Howzit.named_arguments.merge!(@named_args) if @named_args
|
|
405
|
+
|
|
406
|
+
# Process @set_var directives before gathering tasks (for non-sequential path)
|
|
407
|
+
# This ensures variables are available when task actions are rendered
|
|
408
|
+
if @directives && !@directives.any?(&:conditional?)
|
|
409
|
+
@directives.each do |dir|
|
|
410
|
+
next unless dir.set_var?
|
|
411
|
+
|
|
412
|
+
value = dir.var_value
|
|
413
|
+
if value
|
|
414
|
+
# Check for command substitution: backticks or $()
|
|
415
|
+
if value =~ /^`(.+)`$/ || value =~ /^\$\((.+)\)$/
|
|
416
|
+
command = Regexp.last_match(1).strip
|
|
417
|
+
# Apply variable substitution to command before execution
|
|
418
|
+
command = command.render_arguments
|
|
419
|
+
# Execute command and capture output
|
|
420
|
+
begin
|
|
421
|
+
value = `#{command}`.strip
|
|
422
|
+
rescue StandardError => e
|
|
423
|
+
Howzit.console.warn("Error executing command in @set_var: #{e.message}")
|
|
424
|
+
value = ''
|
|
425
|
+
end
|
|
426
|
+
else
|
|
427
|
+
# Apply variable substitution to the value (in case it references other variables)
|
|
428
|
+
value = value.render_arguments
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
Howzit.named_arguments[dir.var_name] = value
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
383
436
|
metadata = @metadata || Howzit.buildnote&.metadata
|
|
384
437
|
processed_content = ConditionalContent.process(@content, { metadata: metadata })
|
|
385
438
|
|
|
@@ -394,7 +447,8 @@ module Howzit
|
|
|
394
447
|
processed_content.scan(rx) { matches << Regexp.last_match }
|
|
395
448
|
matches.each do |m|
|
|
396
449
|
c = m.named_captures.symbolize_keys
|
|
397
|
-
Howzit.named_arguments
|
|
450
|
+
Howzit.named_arguments ||= {}
|
|
451
|
+
Howzit.named_arguments.merge!(@named_args) if @named_args
|
|
398
452
|
|
|
399
453
|
if c[:cmd].nil?
|
|
400
454
|
optional, default = define_optional(c[:optional2])
|
|
@@ -418,5 +472,494 @@ module Howzit
|
|
|
418
472
|
|
|
419
473
|
runnable
|
|
420
474
|
end
|
|
475
|
+
|
|
476
|
+
##
|
|
477
|
+
## Parse directives with conditional context for sequential evaluation
|
|
478
|
+
##
|
|
479
|
+
## @return [Array] Array of Directive objects
|
|
480
|
+
##
|
|
481
|
+
def parse_directives_with_conditionals
|
|
482
|
+
directives = []
|
|
483
|
+
lines = @content.split(/\n/)
|
|
484
|
+
conditional_stack = [] # Array of directive indices for @if/@unless directives
|
|
485
|
+
current_branch_index = nil # Track current @if/@elsif/@else branch index
|
|
486
|
+
line_num = 0
|
|
487
|
+
in_code_block = false
|
|
488
|
+
code_block_lines = []
|
|
489
|
+
code_block_fence = nil
|
|
490
|
+
code_block_title = nil
|
|
491
|
+
code_block_optional = nil
|
|
492
|
+
|
|
493
|
+
# Extract prereqs and postreqs from raw content
|
|
494
|
+
@prereqs = @content.scan(/(?<=@before\n).*?(?=\n@end)/im).map(&:strip)
|
|
495
|
+
@postreqs = @content.scan(/(?<=@after\n).*?(?=\n@end)/im).map(&:strip)
|
|
496
|
+
|
|
497
|
+
lines.each do |line|
|
|
498
|
+
line_num += 1
|
|
499
|
+
|
|
500
|
+
# Handle code blocks (fenced code)
|
|
501
|
+
if line =~ /^(`{3,})run([?!]*)\s*(.*?)$/i && !in_code_block
|
|
502
|
+
in_code_block = true
|
|
503
|
+
code_block_fence = Regexp.last_match(1)
|
|
504
|
+
code_block_optional = Regexp.last_match(2)
|
|
505
|
+
code_block_title = Regexp.last_match(3).strip
|
|
506
|
+
code_block_lines = []
|
|
507
|
+
next
|
|
508
|
+
elsif in_code_block
|
|
509
|
+
if line =~ /^#{Regexp.escape(code_block_fence)}\s*$/
|
|
510
|
+
# End of code block
|
|
511
|
+
block_content = code_block_lines.join("\n")
|
|
512
|
+
optional, default = define_optional(code_block_optional)
|
|
513
|
+
conditional_path = conditional_stack.dup
|
|
514
|
+
# If we're inside an @elsif/@else branch, include it in the path
|
|
515
|
+
conditional_path << current_branch_index if current_branch_index
|
|
516
|
+
directives << Howzit::Directive.new(
|
|
517
|
+
type: :task,
|
|
518
|
+
content: {
|
|
519
|
+
type: :block,
|
|
520
|
+
title: code_block_title,
|
|
521
|
+
action: block_content,
|
|
522
|
+
arguments: nil
|
|
523
|
+
},
|
|
524
|
+
optional: optional,
|
|
525
|
+
default: default,
|
|
526
|
+
line_number: line_num,
|
|
527
|
+
conditional_path: conditional_path
|
|
528
|
+
)
|
|
529
|
+
in_code_block = false
|
|
530
|
+
code_block_lines = []
|
|
531
|
+
code_block_fence = nil
|
|
532
|
+
else
|
|
533
|
+
code_block_lines << line
|
|
534
|
+
end
|
|
535
|
+
next
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Handle conditional directives
|
|
539
|
+
if line =~ /^@(if|unless)\s+(.+)$/i
|
|
540
|
+
directive_type = Regexp.last_match(1).downcase
|
|
541
|
+
condition = Regexp.last_match(2).strip
|
|
542
|
+
directive_index = directives.length
|
|
543
|
+
conditional_stack << directive_index
|
|
544
|
+
current_branch_index = directive_index
|
|
545
|
+
directives << Howzit::Directive.new(
|
|
546
|
+
type: directive_type.to_sym,
|
|
547
|
+
condition: condition,
|
|
548
|
+
directive_type: directive_type,
|
|
549
|
+
line_number: line_num,
|
|
550
|
+
conditional_path: conditional_stack[0..-2].dup
|
|
551
|
+
)
|
|
552
|
+
next
|
|
553
|
+
elsif line =~ /^@elsif\s+(.+)$/i
|
|
554
|
+
condition = Regexp.last_match(1).strip
|
|
555
|
+
directive_index = directives.length
|
|
556
|
+
current_branch_index = directive_index
|
|
557
|
+
directives << Howzit::Directive.new(
|
|
558
|
+
type: :elsif,
|
|
559
|
+
condition: condition,
|
|
560
|
+
directive_type: 'elsif',
|
|
561
|
+
line_number: line_num,
|
|
562
|
+
conditional_path: conditional_stack[0..-2].dup
|
|
563
|
+
)
|
|
564
|
+
next
|
|
565
|
+
elsif line =~ /^@else\s*$/i
|
|
566
|
+
directive_index = directives.length
|
|
567
|
+
current_branch_index = directive_index
|
|
568
|
+
directives << Howzit::Directive.new(
|
|
569
|
+
type: :else,
|
|
570
|
+
directive_type: 'else',
|
|
571
|
+
line_number: line_num,
|
|
572
|
+
conditional_path: conditional_stack[0..-2].dup
|
|
573
|
+
)
|
|
574
|
+
next
|
|
575
|
+
elsif line =~ /^@end\s*$/i && !conditional_stack.empty?
|
|
576
|
+
# Closing a conditional block
|
|
577
|
+
conditional_stack.pop
|
|
578
|
+
current_branch_index = nil
|
|
579
|
+
directives << Howzit::Directive.new(
|
|
580
|
+
type: :end,
|
|
581
|
+
directive_type: 'end',
|
|
582
|
+
line_number: line_num,
|
|
583
|
+
conditional_path: conditional_stack.dup
|
|
584
|
+
)
|
|
585
|
+
next
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Handle @log_level directive
|
|
589
|
+
if line =~ /^@log_level\s*\(([^)]+)\)\s*$/i
|
|
590
|
+
log_level = Regexp.last_match(1).strip
|
|
591
|
+
conditional_path = conditional_stack.dup
|
|
592
|
+
directives << Howzit::Directive.new(
|
|
593
|
+
type: :log_level,
|
|
594
|
+
log_level_value: log_level,
|
|
595
|
+
line_number: line_num,
|
|
596
|
+
conditional_path: conditional_path
|
|
597
|
+
)
|
|
598
|
+
next
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# Handle @set_var directive
|
|
602
|
+
if line =~ /^@set_var\s*\(/i
|
|
603
|
+
# Extract content between parentheses, handling nested parentheses
|
|
604
|
+
paren_content = line.sub(/^@set_var\s*\(/i, '').sub(/\)\s*$/, '')
|
|
605
|
+
# Split by first comma only - everything after first comma is the value
|
|
606
|
+
if paren_content =~ /^([^,]+),\s*(.+)$/
|
|
607
|
+
var_name = Regexp.last_match(1).strip
|
|
608
|
+
var_value = Regexp.last_match(2).strip
|
|
609
|
+
# Validate variable name: alphanumeric, dashes, underscores only
|
|
610
|
+
if var_name =~ /^[A-Za-z0-9_-]+$/
|
|
611
|
+
# Remove quotes from value if present (handles both single and double quotes)
|
|
612
|
+
var_value = Regexp.last_match(1) if var_value =~ /^["'](.+)["']$/
|
|
613
|
+
conditional_path = conditional_stack.dup
|
|
614
|
+
directives << Howzit::Directive.new(
|
|
615
|
+
type: :set_var,
|
|
616
|
+
var_name: var_name,
|
|
617
|
+
var_value: var_value,
|
|
618
|
+
line_number: line_num,
|
|
619
|
+
conditional_path: conditional_path
|
|
620
|
+
)
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
next
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Handle task directives (@run, @include, etc.)
|
|
627
|
+
unless line =~ /^@(?<cmd>include|run|copy|open|url)(?<optional>[!?]{1,2})?\((?<action>[^)]*?)\)(?<title>.*?)$/
|
|
628
|
+
next
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
cmd = Regexp.last_match(:cmd)
|
|
632
|
+
optional_str = Regexp.last_match(:optional) || ''
|
|
633
|
+
action = Regexp.last_match(:action)
|
|
634
|
+
title = Regexp.last_match(:title).strip
|
|
635
|
+
|
|
636
|
+
optional, default = define_optional(optional_str)
|
|
637
|
+
conditional_path = conditional_stack.dup
|
|
638
|
+
# If we're inside an @elsif/@else branch, include it in the path
|
|
639
|
+
conditional_path << current_branch_index if current_branch_index
|
|
640
|
+
directives << Howzit::Directive.new(
|
|
641
|
+
type: :task,
|
|
642
|
+
content: {
|
|
643
|
+
type: cmd.downcase.to_sym,
|
|
644
|
+
action: action,
|
|
645
|
+
title: title,
|
|
646
|
+
arguments: nil
|
|
647
|
+
},
|
|
648
|
+
optional: optional,
|
|
649
|
+
default: default,
|
|
650
|
+
line_number: line_num,
|
|
651
|
+
conditional_path: conditional_path
|
|
652
|
+
)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
directives
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
##
|
|
659
|
+
## Run directives sequentially with conditional re-evaluation
|
|
660
|
+
##
|
|
661
|
+
def run_sequential(nested: false, output: [], cols: 80)
|
|
662
|
+
# Initialize conditional state
|
|
663
|
+
conditional_state = {} # { index => { evaluated: bool, result: bool, matched_chain: bool } }
|
|
664
|
+
directive_index = 0
|
|
665
|
+
current_log_level = nil # Track current log level set by @log_level directives
|
|
666
|
+
|
|
667
|
+
# Initialize named_arguments with topic's named args (don't overwrite on each iteration)
|
|
668
|
+
Howzit.named_arguments ||= {}
|
|
669
|
+
Howzit.named_arguments.merge!(@named_args) if @named_args
|
|
670
|
+
|
|
671
|
+
unless @prereqs.empty?
|
|
672
|
+
begin
|
|
673
|
+
puts TTY::Box.frame("{by}#{@prereqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols)
|
|
674
|
+
rescue Errno::EPIPE
|
|
675
|
+
# Pipe closed, ignore
|
|
676
|
+
end
|
|
677
|
+
res = Prompt.yn('Have the above prerequisites been met?', default: true)
|
|
678
|
+
Process.exit 1 unless res
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Process directives sequentially
|
|
682
|
+
while directive_index < @directives.length
|
|
683
|
+
directive = @directives[directive_index]
|
|
684
|
+
directive_index += 1
|
|
685
|
+
|
|
686
|
+
# Update context for condition evaluation
|
|
687
|
+
metadata = @metadata || Howzit.buildnote&.metadata
|
|
688
|
+
context = { metadata: metadata }
|
|
689
|
+
|
|
690
|
+
# Handle conditional directives
|
|
691
|
+
if directive.conditional?
|
|
692
|
+
case directive.type
|
|
693
|
+
when :if, :unless
|
|
694
|
+
# Evaluate condition
|
|
695
|
+
result = ConditionEvaluator.evaluate(directive.condition, context)
|
|
696
|
+
result = !result if directive.directive_type == 'unless'
|
|
697
|
+
|
|
698
|
+
conditional_state[directive_index - 1] = {
|
|
699
|
+
evaluated: true,
|
|
700
|
+
result: result,
|
|
701
|
+
matched_chain: result,
|
|
702
|
+
condition: directive.condition,
|
|
703
|
+
directive_type: directive.directive_type
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
when :elsif
|
|
707
|
+
# Find the matching @if/@unless
|
|
708
|
+
matching_if_index = find_matching_if_index(directive_index - 1)
|
|
709
|
+
if matching_if_index && conditional_state[matching_if_index]
|
|
710
|
+
# If previous branch matched, this is false
|
|
711
|
+
if conditional_state[matching_if_index][:matched_chain]
|
|
712
|
+
conditional_state[directive_index - 1] = {
|
|
713
|
+
evaluated: true,
|
|
714
|
+
result: false,
|
|
715
|
+
matched_chain: false,
|
|
716
|
+
condition: directive.condition,
|
|
717
|
+
directive_type: 'elsif',
|
|
718
|
+
parent_index: matching_if_index
|
|
719
|
+
}
|
|
720
|
+
else
|
|
721
|
+
# Evaluate condition
|
|
722
|
+
result = ConditionEvaluator.evaluate(directive.condition, context)
|
|
723
|
+
conditional_state[directive_index - 1] = {
|
|
724
|
+
evaluated: true,
|
|
725
|
+
result: result,
|
|
726
|
+
matched_chain: result,
|
|
727
|
+
condition: directive.condition,
|
|
728
|
+
directive_type: 'elsif',
|
|
729
|
+
parent_index: matching_if_index
|
|
730
|
+
}
|
|
731
|
+
conditional_state[matching_if_index][:matched_chain] = true if result
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
when :else
|
|
736
|
+
# Find the matching @if/@unless
|
|
737
|
+
matching_if_index = find_matching_if_index(directive_index - 1)
|
|
738
|
+
if matching_if_index && conditional_state[matching_if_index]
|
|
739
|
+
# If any previous branch matched, else is false
|
|
740
|
+
if conditional_state[matching_if_index][:matched_chain]
|
|
741
|
+
conditional_state[directive_index - 1] = {
|
|
742
|
+
evaluated: true,
|
|
743
|
+
result: false,
|
|
744
|
+
matched_chain: false,
|
|
745
|
+
directive_type: 'else',
|
|
746
|
+
parent_index: matching_if_index
|
|
747
|
+
}
|
|
748
|
+
else
|
|
749
|
+
conditional_state[directive_index - 1] = {
|
|
750
|
+
evaluated: true,
|
|
751
|
+
result: true,
|
|
752
|
+
matched_chain: true,
|
|
753
|
+
directive_type: 'else',
|
|
754
|
+
parent_index: matching_if_index
|
|
755
|
+
}
|
|
756
|
+
conditional_state[matching_if_index][:matched_chain] = true
|
|
757
|
+
end
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
when :end
|
|
761
|
+
# End of conditional block - no action needed, state is managed by stack
|
|
762
|
+
end
|
|
763
|
+
next
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Handle @log_level directive (before task check)
|
|
767
|
+
if directive.log_level?
|
|
768
|
+
current_log_level = directive.log_level_value
|
|
769
|
+
next
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# Handle @set_var directive (before task check)
|
|
773
|
+
if directive.set_var?
|
|
774
|
+
# Set the variable in named_arguments
|
|
775
|
+
Howzit.named_arguments ||= {}
|
|
776
|
+
value = directive.var_value
|
|
777
|
+
|
|
778
|
+
if value
|
|
779
|
+
# Check for command substitution: backticks or $()
|
|
780
|
+
if value =~ /^`(.+)`$/ || value =~ /^\$\((.+)\)$/
|
|
781
|
+
command = Regexp.last_match(1).strip
|
|
782
|
+
# Apply variable substitution to command before execution
|
|
783
|
+
command = command.render_arguments
|
|
784
|
+
# Execute command and capture output
|
|
785
|
+
begin
|
|
786
|
+
value = `#{command}`.strip
|
|
787
|
+
rescue StandardError => e
|
|
788
|
+
Howzit.console.warn("Error executing command in @set_var: #{e.message}")
|
|
789
|
+
value = ''
|
|
790
|
+
end
|
|
791
|
+
else
|
|
792
|
+
# Apply variable substitution to the value (in case it references other variables)
|
|
793
|
+
value = value.render_arguments
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
Howzit.named_arguments[directive.var_name] = value
|
|
798
|
+
# Re-evaluate conditionals after setting variable
|
|
799
|
+
re_evaluate_conditionals(conditional_state, directive_index - 1, context)
|
|
800
|
+
next
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# Handle task directives
|
|
804
|
+
next unless directive.task?
|
|
805
|
+
|
|
806
|
+
# Check if all parent conditionals are true
|
|
807
|
+
should_execute = true
|
|
808
|
+
|
|
809
|
+
# If path ends with an @elsif/@else, skip the parent @if index
|
|
810
|
+
# (the index right before the elsif/else in the path)
|
|
811
|
+
path_to_check = directive.conditional_path.dup
|
|
812
|
+
if path_to_check.length >= 2
|
|
813
|
+
last_idx = path_to_check.last
|
|
814
|
+
last_state = conditional_state[last_idx]
|
|
815
|
+
if last_state && %w[elsif else].include?(last_state[:directive_type])
|
|
816
|
+
# Skip the parent @if index (the one before the elsif/else)
|
|
817
|
+
parent_if_idx = path_to_check[path_to_check.length - 2]
|
|
818
|
+
parent_if_state = conditional_state[parent_if_idx]
|
|
819
|
+
if parent_if_state && %w[if unless].include?(parent_if_state[:directive_type])
|
|
820
|
+
path_to_check.delete(parent_if_idx)
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
path_to_check.each do |cond_idx|
|
|
826
|
+
cond_state = conditional_state[cond_idx]
|
|
827
|
+
if cond_state.nil? || !cond_state[:evaluated] || !cond_state[:result]
|
|
828
|
+
should_execute = false
|
|
829
|
+
break
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
next unless should_execute
|
|
834
|
+
|
|
835
|
+
# Convert directive to task
|
|
836
|
+
task = directive.to_task(self, current_log_level: current_log_level)
|
|
837
|
+
next unless task
|
|
838
|
+
|
|
839
|
+
next if (task.optional || Howzit.options[:ask]) && !ask_task(task)
|
|
840
|
+
|
|
841
|
+
run_output, total, success = task.run
|
|
842
|
+
|
|
843
|
+
output.concat(run_output)
|
|
844
|
+
@results[:total] += total
|
|
845
|
+
|
|
846
|
+
if success
|
|
847
|
+
@results[:success] += total
|
|
848
|
+
else
|
|
849
|
+
Howzit.console.warn %({bw}\u{2297} {br}Error running task {bw}"#{task.title}"{x}).c
|
|
850
|
+
|
|
851
|
+
@results[:errors] += total
|
|
852
|
+
|
|
853
|
+
break unless Howzit.options[:force]
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
log_task_result(task, success)
|
|
857
|
+
|
|
858
|
+
# Re-evaluate all open conditionals after task execution
|
|
859
|
+
re_evaluate_conditionals(conditional_state, directive_index - 1, context)
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
total = "{bw}#{@results[:total]}{by} #{@results[:total] == 1 ? 'task' : 'tasks'}".c
|
|
863
|
+
errors = "{bw}#{@results[:errors]}{by} #{@results[:errors] == 1 ? 'error' : 'errors'}".c
|
|
864
|
+
@results[:message] += if @results[:errors].zero?
|
|
865
|
+
"{bg}\u{2713} {by}Ran #{total}{x}".c
|
|
866
|
+
elsif Howzit.options[:force]
|
|
867
|
+
"{br}\u{2715} {by}Completed #{total} with #{errors}{x}".c
|
|
868
|
+
else
|
|
869
|
+
"{br}\u{2715} {by}Ran #{total}, terminated due to error{x}".c
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
output.push(@results[:message]) if Howzit.options[:log_level] < 2 && !nested && !Howzit.options[:run]
|
|
873
|
+
|
|
874
|
+
unless @postreqs.empty?
|
|
875
|
+
begin
|
|
876
|
+
puts TTY::Box.frame("{bw}#{@postreqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols)
|
|
877
|
+
rescue Errno::EPIPE
|
|
878
|
+
# Pipe closed, ignore
|
|
879
|
+
end
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
output
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
##
|
|
886
|
+
## Find the index of the matching @if/@unless for an @elsif/@else/@end
|
|
887
|
+
##
|
|
888
|
+
def find_matching_if_index(current_index)
|
|
889
|
+
stack_depth = 0
|
|
890
|
+
(current_index - 1).downto(0) do |i|
|
|
891
|
+
dir = @directives[i]
|
|
892
|
+
next unless dir.conditional?
|
|
893
|
+
|
|
894
|
+
case dir.type
|
|
895
|
+
when :end
|
|
896
|
+
stack_depth += 1
|
|
897
|
+
when :if, :unless
|
|
898
|
+
return i if stack_depth.zero?
|
|
899
|
+
|
|
900
|
+
stack_depth -= 1
|
|
901
|
+
|
|
902
|
+
when :elsif, :else
|
|
903
|
+
stack_depth -= 1 if stack_depth.positive?
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
nil
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
##
|
|
910
|
+
## Re-evaluate conditionals after a task runs (variables may have changed)
|
|
911
|
+
##
|
|
912
|
+
def re_evaluate_conditionals(conditional_state, current_index, context)
|
|
913
|
+
# Re-evaluate all conditionals that come after the current task
|
|
914
|
+
# and before the next task
|
|
915
|
+
(current_index + 1).upto(@directives.length - 1) do |i|
|
|
916
|
+
dir = @directives[i]
|
|
917
|
+
break if dir.task? # Stop at next task
|
|
918
|
+
|
|
919
|
+
next unless dir.conditional?
|
|
920
|
+
|
|
921
|
+
case dir.type
|
|
922
|
+
when :if, :unless
|
|
923
|
+
if conditional_state[i]
|
|
924
|
+
# Re-evaluate
|
|
925
|
+
result = ConditionEvaluator.evaluate(dir.condition, context)
|
|
926
|
+
result = !result if dir.directive_type == 'unless'
|
|
927
|
+
conditional_state[i][:result] = result
|
|
928
|
+
conditional_state[i][:matched_chain] = result
|
|
929
|
+
end
|
|
930
|
+
when :elsif
|
|
931
|
+
matching_if_index = find_matching_if_index(i)
|
|
932
|
+
if matching_if_index && conditional_state[matching_if_index]
|
|
933
|
+
parent_state = conditional_state[matching_if_index]
|
|
934
|
+
if conditional_state[i]
|
|
935
|
+
if parent_state[:matched_chain] && !conditional_state[i][:matched_chain]
|
|
936
|
+
conditional_state[i][:result] = false
|
|
937
|
+
else
|
|
938
|
+
result = ConditionEvaluator.evaluate(dir.condition, context)
|
|
939
|
+
conditional_state[i][:result] = result
|
|
940
|
+
conditional_state[i][:matched_chain] = result
|
|
941
|
+
parent_state[:matched_chain] = true if result
|
|
942
|
+
end
|
|
943
|
+
end
|
|
944
|
+
end
|
|
945
|
+
when :else
|
|
946
|
+
matching_if_index = find_matching_if_index(i)
|
|
947
|
+
if matching_if_index && conditional_state[matching_if_index]
|
|
948
|
+
parent_state = conditional_state[matching_if_index]
|
|
949
|
+
if conditional_state[i]
|
|
950
|
+
if parent_state[:matched_chain]
|
|
951
|
+
conditional_state[i][:result] = false
|
|
952
|
+
else
|
|
953
|
+
conditional_state[i][:result] = true
|
|
954
|
+
conditional_state[i][:matched_chain] = true
|
|
955
|
+
parent_state[:matched_chain] = true
|
|
956
|
+
end
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
when :end
|
|
960
|
+
# No re-evaluation needed
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
end
|
|
421
964
|
end
|
|
422
965
|
end
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
|
@@ -39,8 +39,10 @@ require_relative 'howzit/stringutils'
|
|
|
39
39
|
require_relative 'howzit/console_logger'
|
|
40
40
|
require_relative 'howzit/config'
|
|
41
41
|
require_relative 'howzit/script_comm'
|
|
42
|
+
require_relative 'howzit/script_support'
|
|
42
43
|
require_relative 'howzit/condition_evaluator'
|
|
43
44
|
require_relative 'howzit/conditional_content'
|
|
45
|
+
require_relative 'howzit/directive'
|
|
44
46
|
require_relative 'howzit/task'
|
|
45
47
|
require_relative 'howzit/topic'
|
|
46
48
|
require_relative 'howzit/buildnote'
|