Class: Howzit::Topic
Overview
Topic Class
Instance Attribute Summary collapse
-
#arg_definitions ⇒ Object
readonly
Returns the value of attribute arg_definitions.
-
#content ⇒ Object
Returns the value of attribute content.
-
#directives ⇒ Object
readonly
Returns the value of attribute directives.
-
#named_args ⇒ Object
readonly
Returns the value of attribute named_args.
-
#parent ⇒ Object
writeonly
Sets the attribute parent.
-
#postreqs ⇒ Object
readonly
Returns the value of attribute postreqs.
-
#prereqs ⇒ Object
readonly
Returns the value of attribute prereqs.
-
#results ⇒ Object
readonly
Returns the value of attribute results.
-
#source_file ⇒ Object
readonly
Returns the value of attribute source_file.
-
#tasks ⇒ Object
readonly
Returns the value of attribute tasks.
-
#title ⇒ Object
readonly
Returns the value of attribute title.
Instance Method Summary collapse
- #<=>(other) ⇒ Object
-
#arguments ⇒ Object
Get named arguments from title.
- #ask_task(task) ⇒ Object
- #check_cols ⇒ Object
- #color_directive_yn(keys) ⇒ Object
- #colored_option(color, topic, keys) ⇒ Object
- #colored_yn(color, default) ⇒ Object
- #define_optional(optional) ⇒ Object
- #define_task_args(keys) ⇒ Object
-
#format_arg_definition(arg) ⇒ String
Format an argument definition with syntax highlighting Parentheses in blue, variable name in bright white, default in yellow.
-
#grep(term) ⇒ Object
Search title and contents for a pattern.
-
#highlight_variables(text) ⇒ String
Highlight variable placeholders in content Format: $variable or $variable:default Dollar sign and braces in blue, variable name in bright white, default in yellow.
-
#initialize(title, content, metadata = nil, source_file: nil) ⇒ Topic
constructor
Initialize a topic object.
-
#print_out(options = {}) ⇒ Array
Output a topic with fancy title and bright white text.
- #process_directive(keys) ⇒ Object
-
#process_include(keys, opt) ⇒ Object
Handle an include statement.
-
#run(nested: false) ⇒ Object
Handle run command, execute directives in topic.
- #title_code_block(keys) ⇒ Object
- #title_option(color, topic, keys, opt) ⇒ Object
Constructor Details
#initialize(title, content, metadata = nil, source_file: nil) ⇒ Topic
Initialize a topic object
20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/howzit/topic.rb', line 20 def initialize(title, content, = nil, source_file: nil) @title = title @content = content @parent = nil @nest_level = 0 @named_args = {} @metadata = @source_file = source_file arguments @directives = parse_directives_with_conditionals @tasks = gather_tasks @results = { total: 0, success: 0, errors: 0, message: ''.c } end |
Instance Attribute Details
#arg_definitions ⇒ Object (readonly)
Returns the value of attribute arg_definitions.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def arg_definitions @arg_definitions end |
#content ⇒ Object
Returns the value of attribute content.
8 9 10 |
# File 'lib/howzit/topic.rb', line 8 def content @content end |
#directives ⇒ Object (readonly)
Returns the value of attribute directives.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def directives @directives end |
#named_args ⇒ Object (readonly)
Returns the value of attribute named_args.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def named_args @named_args end |
#parent=(value) ⇒ Object (writeonly)
Sets the attribute parent
6 7 8 |
# File 'lib/howzit/topic.rb', line 6 def parent=(value) @parent = value end |
#postreqs ⇒ Object (readonly)
Returns the value of attribute postreqs.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def postreqs @postreqs end |
#prereqs ⇒ Object (readonly)
Returns the value of attribute prereqs.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def prereqs @prereqs end |
#results ⇒ Object (readonly)
Returns the value of attribute results.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def results @results end |
#source_file ⇒ Object (readonly)
Returns the value of attribute source_file.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def source_file @source_file end |
#tasks ⇒ Object (readonly)
Returns the value of attribute tasks.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def tasks @tasks end |
#title ⇒ Object (readonly)
Returns the value of attribute title.
10 11 12 |
# File 'lib/howzit/topic.rb', line 10 def title @title end |
Instance Method Details
#<=>(other) ⇒ Object
363 364 365 |
# File 'lib/howzit/topic.rb', line 363 def <=>(other) @title <=> other.title end |
#arguments ⇒ Object
Get named arguments from title
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/howzit/topic.rb', line 36 def arguments @arg_definitions = [] return unless @title =~ /\(.*?\) *$/ a = @title.match(/\((?<args>.*?)\) *$/) args = a['args'].split(/ *, */).each(&:strip) args.each_with_index do |arg, idx| arg_name, default = arg.split(/:/).map(&:strip) # Store original definition for display purposes @arg_definitions << (default ? "#{arg_name}:#{default}" : arg_name) @named_args[arg_name] = if Howzit.arguments && Howzit.arguments.count >= idx + 1 Howzit.arguments[idx] else default end end @title = @title.sub(/\(.*?\) *$/, '').strip end |
#ask_task(task) ⇒ Object
67 68 69 70 71 72 73 74 75 76 |
# File 'lib/howzit/topic.rb', line 67 def ask_task(task) note = if task.type == :include task_count = Howzit.buildnote.find_topic(task.action)[0].tasks.count " (#{task_count} tasks)" else '' end q = %({bg}#{task.type.to_s.capitalize} {xw}"{bw}#{task.title}{xw}"#{note}{x}).c Prompt.yn(q, default: task.default) end |
#check_cols ⇒ Object
78 79 80 81 82 |
# File 'lib/howzit/topic.rb', line 78 def check_cols TTY::Screen.columns > 60 ? 60 : TTY::Screen.columns rescue StandardError 60 end |
#color_directive_yn(keys) ⇒ Object
232 233 234 235 236 237 238 239 |
# File 'lib/howzit/topic.rb', line 232 def color_directive_yn(keys) optional, default = define_optional(keys[:optional]) if optional default ? ' {xk}[{g}Y{xk}/{dbw}n{xk}]{x}'.c : ' {xk}[{dbw}y{xk}/{g}N{xk}]{x}'.c else '' end end |
#colored_option(color, topic, keys) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/howzit/topic.rb', line 166 def colored_option(color, topic, keys) if topic.tasks.empty? '' else optional = keys[:optional] =~ /[?!]+/ ? true : false default = keys[:optional] =~ /!/ ? false : true if optional colored_yn(color, default) else '' end end end |
#colored_yn(color, default) ⇒ Object
180 181 182 183 184 185 186 |
# File 'lib/howzit/topic.rb', line 180 def colored_yn(color, default) if default " {xKk}[{gbK}Y{xKk}/{dbwK}n{xKk}]{x}#{color}".c else " {xKk}[{dbwK}y{xKk}/{bgK}N{xKk}]{x}#{color}".c end end |
#define_optional(optional) ⇒ Object
259 260 261 262 263 |
# File 'lib/howzit/topic.rb', line 259 def define_optional(optional) is_optional = optional =~ /[?!]+/ ? true : false default = optional =~ /!/ ? false : true [is_optional, default] end |
#define_task_args(keys) ⇒ Object
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/howzit/topic.rb', line 367 def define_task_args(keys) cmd = keys[:cmd] obj = keys[:action] # Extract and clean the title raw_title = keys[:title] # Determine the title: use provided title if available, otherwise use action title = if raw_title.nil? || raw_title.to_s.strip.empty? obj else raw_title.to_s.strip end # Store the actual title (not overridden by show_all_code - that's only for display) task_args = { type: :include, arguments: nil, title: title.dup, # Make a copy to avoid reference issues action: obj, parent: self } # Set named_arguments before processing titles for variable substitution # Merge with existing named_arguments to preserve @set_var variables Howzit.named_arguments ||= {} Howzit.named_arguments.merge!(@named_args) if @named_args case cmd when /include/i if title =~ /\[(.*?)\] *$/ args = Regexp.last_match(1).split(/ *, */).map(&:render_arguments) Howzit.arguments = args arguments title.sub!(/ *\[.*?\] *$/, '') end # Apply variable substitution to title after bracket processing task_args[:title] = title.render_arguments task_args[:type] = :include task_args[:arguments] = Howzit.named_arguments when /run/i task_args[:type] = :run task_args[:title] = title.render_arguments # Parse log_level from action if present (format: script, log_level=level) if obj =~ /,\s*log_level\s*=\s*(\w+)/i log_level = Regexp.last_match(1).downcase task_args[:log_level] = log_level # Remove log_level parameter from action obj = obj.sub(/,\s*log_level\s*=\s*\w+/i, '').strip end task_args[:action] = obj when /copy/i task_args[:type] = :copy task_args[:action] = Shellwords.escape(obj) task_args[:title] = title.render_arguments when /open|url/i task_args[:type] = :open task_args[:title] = title.render_arguments end task_args end |
#format_arg_definition(arg) ⇒ String
Format an argument definition with syntax highlighting Parentheses in blue, variable name in bright white, default in yellow
332 333 334 335 336 337 338 339 |
# File 'lib/howzit/topic.rb', line 332 def format_arg_definition(arg) if arg.include?(':') name, default = arg.split(':', 2) "{bw}#{name}{l}:{y}#{default}{x}".c else "{bw}#{arg}{x}".c end end |
#grep(term) ⇒ Object
Search title and contents for a pattern
63 64 65 |
# File 'lib/howzit/topic.rb', line 63 def grep(term) @title =~ /#{term}/i || @content =~ /#{term}/i end |
#highlight_variables(text) ⇒ String
Highlight variable placeholders in content Format: $variable or $variable:default Dollar sign and braces in blue, variable name in bright white, default in yellow
350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/howzit/topic.rb', line 350 def highlight_variables(text) text.gsub(/\$\{([A-Za-z0-9_]+)(?::([^}]*))?\}/) do var_name = Regexp.last_match(1) default = Regexp.last_match(2) if default "{l}\\$\\{{bw}#{var_name}{l}:{y}#{default}{l}\\}{x}".c else "{l}\\$\\{{bw}#{var_name}{l}\\}{x}".c end end end |
#print_out(options = {}) ⇒ Array
Output a topic with fancy title and bright white text.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/howzit/topic.rb', line 279 def print_out( = {}) defaults = { single: false, header: true } opt = defaults.merge() output = [] if opt[:header] # Include argument definitions in header if present header_title = @title.dup unless @arg_definitions.nil? || @arg_definitions.empty? formatted_args = @arg_definitions.map { |arg| format_arg_definition(arg) }.join('{l}, '.c) header_title += " {l}({x}#{formatted_args}{l}){x}".c end output.push(header_title.format_header) output.push('') end # Process conditional blocks first = @metadata || Howzit.buildnote&. topic = ConditionalContent.process(@content.dup, { metadata: }) unless Howzit.[:show_all_code] topic.gsub!(/(?mix)^(`{3,})run([?!]*)\s* ([^\n]*)[\s\S]*?\n\1\s*$/, '@@@run\2 \3') end topic.split(/\n/).each do |l| case l when /@(before|after|prereq|end|if|unless)/ next when /@include(?<optional>[!?]{1,2})?\((?<action>[^)]+)\)/ output.concat(process_include(Regexp.last_match.named_captures.symbolize_keys, opt)) when /@(?<cmd>run|copy|open|url)(?<optional>[?!]{1,2})?\((?<action>.*?)\) *(?<title>.*?)$/ output.push(process_directive(Regexp.last_match.named_captures.symbolize_keys)) when /(?<fence>`{3,})run(?<optional>[!?]{1,2})? *(?<title>.*?)$/i desc = title_code_block(Regexp.last_match.named_captures.symbolize_keys) output.push("{bmK}\u{25B6} {bwK}#{desc}{x}\n```".c) when /@@@run(?<optional>[!?]{1,2})? *(?<title>.*?)$/i output.push("{bmK}\u{25B6} {bwK}#{title_code_block(Regexp.last_match.named_captures.symbolize_keys)}{x}".c) else l.wrap!(Howzit.[:wrap]) if Howzit.[:wrap].positive? # Highlight variable placeholders in content output.push(highlight_variables(l)) end end Howzit.named_arguments = @named_args output.push('') end |
#process_directive(keys) ⇒ Object
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/howzit/topic.rb', line 241 def process_directive(keys) cmd = keys[:cmd] obj = keys[:action] title = keys[:title].empty? ? obj : keys[:title].strip title = Howzit.[:show_all_code] ? obj : title option = color_directive_yn(keys) icon = case cmd when 'run' "\u{25B6}" when 'copy' "\u{271A}" when /open|url/ "\u{279A}" end "{bmK}#{icon} {bwK}#{title.preserve_escapes}{x}#{option}".c end |
#process_include(keys, opt) ⇒ Object
Handle an include statement
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/howzit/topic.rb', line 195 def process_include(keys, opt) output = [] if keys[:action] =~ / *\[(.*?)\] *$/ Howzit.named_arguments ||= {} Howzit.named_arguments.merge!(@named_args) if @named_args Howzit.arguments = Regexp.last_match(1).split(/ *, */).map!(&:render_arguments) end matches = Howzit.buildnote.find_topic(keys[:action].sub(/ *\[.*?\] *$/, '')) return [] if matches.empty? topic = matches[0] return [] if topic.nil? rule = '{kKd}' color = '{Kyd}' title = title_option(color, topic, keys, opt) = { color: color, hr: '.', border: rule } output.push("#{'> ' * @nest_level}#{title}".format_header()) unless Howzit.inclusions.include?(topic) if opt[:single] && Howzit.inclusions.include?(topic) output.push("#{'> ' * @nest_level}#{title} included above".format_header()) elsif opt[:single] @nest_level += 1 output.concat(topic.print_out({ single: true, header: false })) output.push("#{'> ' * @nest_level}...".format_header()) @nest_level -= 1 end Howzit.inclusions.push(topic) output end |
#run(nested: false) ⇒ Object
Handle run command, execute directives in topic
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/howzit/topic.rb', line 85 def run(nested: false) output = [] cols = check_cols # Use sequential processing if we have directives with conditionals if @directives && @directives.any?(&:conditional?) return run_sequential(nested: nested, output: output, cols: cols) end # Fall back to old behavior for backward compatibility # Note: @set_var directives are already processed in gather_tasks for non-sequential path # This section is kept for backward compatibility but shouldn't be needed if @tasks.count.positive? unless @prereqs.empty? begin puts TTY::Box.frame("{by}#{@prereqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols) rescue Errno::EPIPE # Pipe closed, ignore end res = Prompt.yn('Have the above prerequisites been met?', default: true) Process.exit 1 unless res end @tasks.each do |task| next if (task.optional || Howzit.[:ask]) && !ask_task(task) run_output, total, success = task.run output.concat(run_output) @results[:total] += total if success @results[:success] += total else Howzit.console.warn %({bw}\u{2297} {br}Error running task {bw}"#{task.title}"{x}).c @results[:errors] += total break unless Howzit.[:force] end log_task_result(task, success) end total = "{bw}#{@results[:total]}{by} #{@results[:total] == 1 ? 'task' : 'tasks'}".c errors = "{bw}#{@results[:errors]}{by} #{@results[:errors] == 1 ? 'error' : 'errors'}".c @results[:message] += if @results[:errors].zero? "{bg}\u{2713} {by}Ran #{total}{x}".c elsif Howzit.[:force] "{br}\u{2715} {by}Completed #{total} with #{errors}{x}".c else "{br}\u{2715} {by}Ran #{total}, terminated due to error{x}".c end else Howzit.console.warn "{r}--run: No {br}@directive{xr} found in {bw}#{@title}{x}".c end output.push(@results[:message]) if Howzit.[:log_level] < 2 && !nested && !Howzit.[:run] unless @postreqs.empty? begin # Apply variable substitution to postreqs content, then wrap each line individually to preserve structure postreqs_content = @postreqs.join("\n\n").render_arguments wrapped_content = postreqs_content.split(/\n/).map { |line| line.wrap(cols - 4) }.join("\n") puts TTY::Box.frame("{bw}#{wrapped_content}{x}".c, width: cols) rescue Errno::EPIPE # Pipe closed, ignore end end output end |
#title_code_block(keys) ⇒ Object
265 266 267 268 269 270 271 |
# File 'lib/howzit/topic.rb', line 265 def title_code_block(keys) if keys[:title].length.positive? "Block: #{keys[:title]}#{color_directive_yn(keys)}" else "Code Block#{color_directive_yn(keys)}" end end |
#title_option(color, topic, keys, opt) ⇒ Object
161 162 163 164 |
# File 'lib/howzit/topic.rb', line 161 def title_option(color, topic, keys, opt) option = colored_option(color, topic, keys) "#{opt[:single] ? 'From' : 'Include'} #{topic.title}#{option}:" end |