howzit 2.1.28 → 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.
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module Howzit
6
+ # Directive class
7
+ # Represents a parsed directive from topic content (tasks, conditionals, etc.)
8
+ class Directive
9
+ attr_reader :type, :content, :condition, :directive_type, :optional, :default, :line_number, :conditional_path,
10
+ :log_level_value, :var_name, :var_value
11
+
12
+ ##
13
+ ## Initialize a Directive
14
+ ##
15
+ ## @param type [Symbol] :task, :if, :unless, :elsif, :else, :end, :log_level
16
+ ## @param content [String] The directive content (action, block, etc.)
17
+ ## @param condition [String, nil] Condition string for conditionals
18
+ ## @param directive_type [String, nil] 'if', 'unless', 'elsif', 'else' for conditionals
19
+ ## @param optional [Boolean] Whether task requires confirmation
20
+ ## @param default [Boolean] Default response for confirmation
21
+ ## @param line_number [Integer] Line number in original content
22
+ ## @param conditional_path [Array] Array of conditional indices this directive is nested in
23
+ ## @param log_level_value [String, nil] Log level value for @log_level directives
24
+ ## @param var_name [String, nil] Variable name for @set_var directives
25
+ ## @param var_value [String, nil] Variable value for @set_var directives
26
+ ##
27
+ def initialize(type:, content: nil, condition: nil, directive_type: nil, optional: false, default: true,
28
+ line_number: nil, conditional_path: [], log_level_value: nil, var_name: nil, var_value: nil)
29
+ @type = type
30
+ @content = content
31
+ @condition = condition
32
+ @directive_type = directive_type
33
+ @optional = optional
34
+ @default = default
35
+ @line_number = line_number
36
+ @conditional_path = conditional_path || []
37
+ @log_level_value = log_level_value
38
+ @var_name = var_name
39
+ @var_value = var_value
40
+ end
41
+
42
+ ##
43
+ ## Is this a conditional directive?
44
+ ##
45
+ def conditional?
46
+ %i[if unless elsif else end].include?(@type)
47
+ end
48
+
49
+ ##
50
+ ## Is this a task directive?
51
+ ##
52
+ def task?
53
+ @type == :task
54
+ end
55
+
56
+ ##
57
+ ## Is this a log_level directive?
58
+ ##
59
+ def log_level?
60
+ @type == :log_level
61
+ end
62
+
63
+ ##
64
+ ## Is this a set_var directive?
65
+ ##
66
+ def set_var?
67
+ @type == :set_var
68
+ end
69
+
70
+ ##
71
+ ## Convert directive to a Task object (only works for task directives)
72
+ ##
73
+ ## @param parent [Topic] The parent topic
74
+ ## @param current_log_level [String, nil] Current log level to apply to task
75
+ ##
76
+ ## @return [Task] Task object
77
+ ##
78
+ def to_task(parent, current_log_level: nil)
79
+ return nil unless task?
80
+
81
+ task_data = @content.dup
82
+ task_type = task_data[:type]
83
+
84
+ # Apply current log level if set and task doesn't have its own
85
+ task_data[:log_level] = current_log_level if current_log_level && !task_data[:log_level]
86
+
87
+ # Set named_arguments before processing titles for variable substitution
88
+ Howzit.named_arguments = parent.named_args
89
+
90
+ case task_type
91
+ when :block
92
+ # Block tasks are already properly formatted
93
+ task_data[:parent] = parent
94
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
95
+ when :run
96
+ # Run tasks need title rendering (similar to define_task_args)
97
+ title = task_data[:title]
98
+ title = title.render_arguments if title && !title.empty?
99
+ task_data[:title] = title
100
+ task_data[:parent] = parent
101
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
102
+ when :copy
103
+ # Copy tasks need title rendering and action escaping
104
+ title = task_data[:title]
105
+ title = title.render_arguments if title && !title.empty?
106
+ task_data[:title] = title
107
+ task_data[:action] = Shellwords.escape(task_data[:action])
108
+ task_data[:parent] = parent
109
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
110
+ when :open
111
+ # Open tasks need title rendering
112
+ title = task_data[:title]
113
+ title = title.render_arguments if title && !title.empty?
114
+ task_data[:title] = title
115
+ task_data[:parent] = parent
116
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
117
+ when :include
118
+ # Include tasks need special handling (title processing, arguments, etc.)
119
+ title = task_data[:title]
120
+ if title =~ /\[(.*?)\] *$/
121
+ args = Regexp.last_match(1).split(/ *, */).map(&:render_arguments)
122
+ Howzit.arguments = args
123
+ parent.arguments
124
+ title.sub!(/ *\[.*?\] *$/, '')
125
+ end
126
+ title = title.render_arguments if title && !title.empty?
127
+ task_data[:title] = title
128
+ task_data[:parent] = parent
129
+ task_data[:arguments] = Howzit.named_arguments
130
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
131
+ else
132
+ task_data[:parent] = parent
133
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
134
+ end
135
+ end
136
+ end
137
+ end
data/lib/howzit/prompt.rb CHANGED
@@ -100,9 +100,7 @@ module Howzit
100
100
  return fzf_result(res)
101
101
  end
102
102
 
103
- if Util.command_exist?('gum')
104
- return gum_choose(matches, query: query, multi: true)
105
- end
103
+ return gum_choose(matches, query: query, multi: true) if Util.command_exist?('gum')
106
104
 
107
105
  tty_menu(matches, query: query)
108
106
  end
@@ -150,7 +148,11 @@ module Howzit
150
148
  end
151
149
 
152
150
  if query
153
- puts "\nSelect a topic for `#{query}`:"
151
+ begin
152
+ puts "\nSelect a topic for `#{query}`:"
153
+ rescue Errno::EPIPE
154
+ # Pipe closed, ignore
155
+ end
154
156
  end
155
157
  options_list(matches)
156
158
  read_selection(matches)
@@ -168,7 +170,11 @@ module Howzit
168
170
 
169
171
  return [matches[line - 1]] if line.positive? && line <= matches.length
170
172
 
171
- puts 'Out of range'
173
+ begin
174
+ puts 'Out of range'
175
+ rescue Errno::EPIPE
176
+ # Pipe closed, ignore
177
+ end
172
178
  read_selection(matches)
173
179
  end
174
180
  ensure
@@ -229,9 +235,7 @@ module Howzit
229
235
  return res.empty? ? [] : res.split(/\n/)
230
236
  end
231
237
 
232
- if Util.command_exist?('gum')
233
- return gum_choose(matches, prompt: prompt_text, multi: true, required: false)
234
- end
238
+ return gum_choose(matches, prompt: prompt_text, multi: true, required: false) if Util.command_exist?('gum')
235
239
 
236
240
  text_template_input(matches)
237
241
  end
@@ -265,7 +269,11 @@ module Howzit
265
269
  exit
266
270
  end
267
271
 
268
- puts "\n{bw}Available templates:{x} #{available.join(', ')}".c
272
+ begin
273
+ puts "\n{bw}Available templates:{x} #{available.join(', ')}".c
274
+ rescue Errno::EPIPE
275
+ # Pipe closed, ignore
276
+ end
269
277
  printf '{bw}Enter templates to include, comma-separated (return to skip):{x} '.c
270
278
  input = Readline.readline('', true).strip
271
279
 
@@ -321,7 +329,7 @@ module Howzit
321
329
  ## @return [String] the entered value
322
330
  ##
323
331
  def get_line(prompt_text, default: nil)
324
- return (default || '') unless $stdout.isatty
332
+ return default || '' unless $stdout.isatty
325
333
 
326
334
  if Util.command_exist?('gum')
327
335
  result = gum_input(prompt_text, placeholder: default || '')
@@ -346,7 +354,7 @@ module Howzit
346
354
  ##
347
355
  def gum_choose(matches, prompt: nil, multi: false, required: true, query: nil)
348
356
  prompt_text = prompt || (query ? "Select for '#{query}'" : 'Select')
349
- args = ['gum', 'choose']
357
+ args = %w[gum choose]
350
358
  args << '--no-limit' if multi
351
359
  args << "--header=#{Shellwords.escape(prompt_text)}"
352
360
  args << '--cursor.foreground=6'
@@ -376,7 +384,7 @@ module Howzit
376
384
  ## @return [String] The entered value
377
385
  ##
378
386
  def gum_input(prompt_text, placeholder: '')
379
- args = ['gum', 'input']
387
+ args = %w[gum input]
380
388
  args << "--header=#{Shellwords.escape(prompt_text)}"
381
389
  args << "--placeholder=#{Shellwords.escape(placeholder)}" unless placeholder.empty?
382
390
  args << '--cursor.foreground=6'
@@ -57,7 +57,7 @@ module Howzit
57
57
 
58
58
  # Build the table with emoji header - center emoji in 6-char column
59
59
  header = "| 🚥 | #{'Task'.ljust(task_width)} |"
60
- separator = "| :--: | #{':' + '-' * (task_width - 1)} |"
60
+ separator = "| :--: | #{":#{'-' * (task_width - 1)}"} |"
61
61
 
62
62
  table_lines = [header, separator]
63
63
  rows.each do |row|
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Howzit
4
+ # Script Communication module
5
+ # Handles communication from scripts to Howzit via a communication file
6
+ module ScriptComm
7
+ class << self
8
+ ##
9
+ ## Create a communication file for scripts to write to
10
+ ##
11
+ ## @return [String] Path to the communication file
12
+ ##
13
+ def create_comm_file
14
+ file = Tempfile.new('howzit_comm')
15
+ file.close
16
+ file.path
17
+ end
18
+
19
+ ##
20
+ ## Set up the communication file and environment variable
21
+ ##
22
+ ## @return [String] Path to the communication file
23
+ ##
24
+ def setup
25
+ comm_file = create_comm_file
26
+ ENV['HOWZIT_COMM_FILE'] = comm_file
27
+ comm_file
28
+ end
29
+
30
+ ##
31
+ ## Read and process the communication file after script execution
32
+ ##
33
+ ## @param comm_file [String] Path to the communication file
34
+ ##
35
+ ## @return [Hash] Hash with :logs and :vars keys
36
+ ##
37
+ def process(comm_file)
38
+ return { logs: [], vars: {} } unless File.exist?(comm_file)
39
+
40
+ logs = []
41
+ vars = {}
42
+
43
+ begin
44
+ content = File.read(comm_file)
45
+ content.each_line do |line|
46
+ line = line.strip
47
+ next if line.empty?
48
+
49
+ case line
50
+ when /^LOG:(info|warn|error|debug):(.+)$/i
51
+ level = Regexp.last_match(1).downcase.to_sym
52
+ message = Regexp.last_match(2)
53
+ logs << { level: level, message: message }
54
+ when /^VAR:([A-Z0-9_]+)=(.*)$/i
55
+ key = Regexp.last_match(1)
56
+ value = Regexp.last_match(2)
57
+ vars[key] = value
58
+ end
59
+ end
60
+ rescue StandardError => e
61
+ Howzit.console&.warn("Error reading communication file: #{e.message}")
62
+ ensure
63
+ # Clean up the file
64
+ File.unlink(comm_file) if File.exist?(comm_file)
65
+ end
66
+
67
+ { logs: logs, vars: vars }
68
+ end
69
+
70
+ ##
71
+ ## Process communication and apply logs/variables
72
+ ##
73
+ ## @param comm_file [String] Path to the communication file
74
+ ##
75
+ def apply(comm_file)
76
+ result = process(comm_file)
77
+ return if result[:logs].empty? && result[:vars].empty?
78
+
79
+ # Apply log messages
80
+ result[:logs].each do |log_entry|
81
+ level = log_entry[:level]
82
+ message = log_entry[:message]
83
+ next unless Howzit.console
84
+
85
+ case level
86
+ when :info
87
+ Howzit.console.info(message)
88
+ when :warn
89
+ Howzit.console.warn(message)
90
+ when :error
91
+ Howzit.console.error(message)
92
+ when :debug
93
+ Howzit.console.debug(message)
94
+ end
95
+ end
96
+
97
+ # Apply variables to named_arguments
98
+ return if result[:vars].empty?
99
+
100
+ Howzit.named_arguments ||= {}
101
+ Howzit.named_arguments.merge!(result[:vars])
102
+ end
103
+ end
104
+ end
105
+ end