Class: MarkdownExec::MDoc
Overview
MDoc represents an imported markdown document.
It provides methods to extract and manipulate specific sections of the document, such as code blocks. It also supports recursion to fetch related or dependent blocks.
Instance Attribute Summary collapse
-
#table ⇒ Object
readonly
Returns the value of attribute table.
Instance Method Summary collapse
- #collect_block_code_cann(fcb) ⇒ Object
-
#collect_block_code_stdout(fcb) ⇒ String
Collects and formats the shell command output to redirect script block code to a file or a variable.
-
#collect_block_dependencies(anyname:) ⇒ Array<Hash>
Retrieves code blocks that are required by a specified code block.
-
#collect_dependencies(block: nil, memo: {}, pubname: nil) ⇒ Hash
Recursively collects dependencies of a given source.
-
#collect_recursively_required_code(anyname:, block_source:, label_body: true, label_format_above: nil, label_format_below: nil) ⇒ Array<String>
Collects recursively required code blocks and returns them as an array of strings.
- #collect_unique_names(hash) ⇒ Object
-
#collect_wrapped_blocks(blocks) ⇒ Array<Hash>
Retrieves code blocks that are wrapped wraps are applied from left to right e.g.
- #error_handler(name = '', opts = {}) ⇒ Object
-
#fcbs_per_options(opts = {}) ⇒ Array<Hash>
Retrieves code blocks based on the provided options.
-
#generate_env_variable_shell_commands(fcb) ⇒ Array<String>
Generates shell code lines to set environment variables named in the body of the given object.
-
#generate_label_body_code(fcb, block_source, label_format_above, label_format_below) ⇒ Array<String>
Generates a formatted code block with labels above and below the main content.
-
#get_block_by_anyname(name, default = {}) ⇒ Hash
Retrieves a code block by its name.
-
#get_blocks_by_anyname(name) ⇒ Hash
Retrieves code blocks by a name.
-
#hide_menu_block_on_name(opts, block) ⇒ Boolean
Checks if a code block should be hidden based on the given options.
-
#initialize(table = []) ⇒ MDoc
constructor
Initializes an instance of MDoc with the given table of markdown sections.
-
#recursively_required(reqs) ⇒ Array<String>
Recursively fetches required code blocks for a given list of requirements.
-
#recursively_required_hash(source, memo = Hash.new([])) ⇒ Hash
Recursively fetches required code blocks for a given list of requirements.
- #select_elements_with_neighbor_conditions(array, last_selected_placeholder = nil, next_selected_placeholder = nil) ⇒ Object
-
#table_not_split ⇒ Object
exclude blocks with duplicate code the first block in each split contains the same data as the rest of the split.
Constructor Details
#initialize(table = []) ⇒ MDoc
Initializes an instance of MDoc with the given table of markdown sections.
66 67 68 |
# File 'lib/mdoc.rb', line 66 def initialize(table = []) @table = table end |
Instance Attribute Details
#table ⇒ Object (readonly)
Returns the value of attribute table.
60 61 62 |
# File 'lib/mdoc.rb', line 60 def table @table end |
Instance Method Details
#collect_block_code_cann(fcb) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/mdoc.rb', line 70 def collect_block_code_cann(fcb) body = fcb.body.join("\n") xcall = fcb[:cann][1..-2] mstdin = xcall.match(/<(?<type>\$)?(?<name>[\-.\w]+)/) mstdout = xcall.match(/>(?<type>\$)?(?<name>[\-.\w]+)/) yqcmd = if mstdin[:type] "echo \"$#{mstdin[:name]}\" | yq '#{body}'" else "yq e '#{body}' '#{mstdin[:name]}'" end if mstdout[:type] "export #{mstdout[:name]}=$(#{yqcmd})" else "#{yqcmd} > '#{mstdout[:name]}'" end end |
#collect_block_code_stdout(fcb) ⇒ String
Collects and formats the shell command output to redirect script block code to a file or a variable.
99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/mdoc.rb', line 99 def collect_block_code_stdout(fcb) stdout = fcb[:stdout] body = fcb.body.join("\n") if stdout[:type] %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n)) else "cat > '#{stdout[:name]}' <<\"EOF\"\n" \ "#{body}\n" \ "EOF\n" end end |
#collect_block_dependencies(anyname:) ⇒ Array<Hash>
Retrieves code blocks that are required by a specified code block.
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 160 161 162 |
# File 'lib/mdoc.rb', line 116 def collect_block_dependencies(anyname:) name_block = get_block_by_anyname(anyname) if name_block.nil? || name_block.keys.empty? raise "Named code block `#{anyname}` not found. (@#{__LINE__})" end nickname = name_block.pub_name ref = name_block.id dependencies = collect_dependencies(pubname: ref) wwt :dependencies, 'dependencies.count:', dependencies.count all_dependency_names = collect_unique_names(dependencies).push(ref).uniq wwt :dependencies, 'all_dependency_names.count:', all_dependency_names.count # select blocks in order of appearance in source documents # blocks = table_not_split.select do |fcb| fcb.is_dependency_of?(all_dependency_names) end wwt :blocks, 'blocks.count:', blocks.count ## add cann key to blocks, calc unmet_dependencies # unmet_dependencies = all_dependency_names.dup blocks = blocks.map do |fcb| fcb.delete_matching_name!(unmet_dependencies) if (call = fcb.call) fcb1 = get_block_by_anyname("[#{call.match(/^%\((\S+) |\)/)[1]}]") fcb1.cann = call [fcb1] else [] end + [fcb] end.flatten(1) wwt :unmet_dependencies, 'unmet_dependencies.count:', unmet_dependencies.count wwt :dependencies, 'dependencies.keys:', dependencies.keys { all_dependency_names: all_dependency_names, blocks: blocks, dependencies: dependencies, unmet_dependencies: unmet_dependencies } rescue StandardError wwe $! end |
#collect_dependencies(block: nil, memo: {}, pubname: nil) ⇒ Hash
Recursively collects dependencies of a given source.
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
# File 'lib/mdoc.rb', line 468 def collect_dependencies(block: nil, memo: {}, pubname: nil) if block.nil? return memo unless pubname blocks = get_blocks_by_anyname(pubname) if blocks.empty? raise "Named code block `#{pubname}` not found. (@#{__LINE__})" end else blocks = [block] end blocks.each do |block| memo[block.id] = [] end return memo unless blocks.count.positive? required_blocks = blocks.map(&:reqs).flatten(1) return memo unless required_blocks.count.positive? blocks.each do |block| memo[block.id] = required_blocks end required_blocks.each do |req| collect_dependencies(pubname: req, memo: memo) end memo end |
#collect_recursively_required_code(anyname:, block_source:, label_body: true, label_format_above: nil, label_format_below: nil) ⇒ Array<String>
Collects recursively required code blocks and returns them as an array of strings.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/mdoc.rb', line 169 def collect_recursively_required_code( anyname:, block_source:, label_body: true, label_format_above: nil, label_format_below: nil ) block_search = collect_block_dependencies(anyname: anyname) if block_search[:blocks] blocks = collect_wrapped_blocks(block_search[:blocks]) # !!t blocks.count block_search.merge( { block_names: blocks.map(&:pub_name), code: blocks.map do |fcb| if fcb[:cann] collect_block_code_cann(fcb) elsif fcb[:stdout] collect_block_code_stdout(fcb) elsif [BlockType::OPTS].include? fcb.type fcb.body # entire body is returned to requesing block elsif [BlockType::LINK, BlockType::LOAD, BlockType::UX, BlockType::VARS].include? fcb.type nil # Vars for all types are collected later elsif fcb[:chrome] # for Link blocks like History nil elsif fcb.type == BlockType::PORT generate_env_variable_shell_commands(fcb) elsif label_body generate_label_body_code( fcb, block_source, label_format_above, label_format_below ) else # raw body fcb.body end end.compact.flatten(1).compact } ) else block_search.merge({ block_names: [], code: [] }) end rescue StandardError error_handler('collect_recursively_required_code') end |
#collect_unique_names(hash) ⇒ Object
214 215 216 |
# File 'lib/mdoc.rb', line 214 def collect_unique_names(hash) hash.values.flatten.uniq end |
#collect_wrapped_blocks(blocks) ⇒ Array<Hash>
Retrieves code blocks that are wrapped wraps are applied from left to right e.g. w1 w2 => w1-before w2-before w1 w2 w2-after w1-after
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/mdoc.rb', line 224 def collect_wrapped_blocks(blocks) blocks.map do |fcb| next if fcb.is_split_rest? (fcb[:wraps] || []).map do |wrap| wrap_before = wrap.sub('}', '-before}') ### hardcoded wrap name table_not_split.select { |fcb| fcb.code_name_included?(wrap_before, wrap) } end.flatten(1) + [fcb] + (fcb[:wraps] || []).reverse.map do |wrap| wrap_after = wrap.sub('}', '-after}') ### hardcoded wrap name table_not_split.select { |fcb| fcb.code_name_included?(wrap_after) } end.flatten(1) end.flatten(1).compact end |
#error_handler(name = '', opts = {}) ⇒ Object
242 243 244 245 246 247 |
# File 'lib/mdoc.rb', line 242 def error_handler(name = '', opts = {}) Exceptions.error_handler( "MDoc.#{name} -- #{$!}", opts ) end |
#fcbs_per_options(opts = {}) ⇒ Array<Hash>
Retrieves code blocks based on the provided options.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 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 |
# File 'lib/mdoc.rb', line 254 def (opts = {}) = opts.merge(block_name_hidden_match: nil) selrows = @table.select do |fcb_title_groups| Filter.fcb_select? , fcb_title_groups end ### hide rows correctly unless opts[:menu_include_imported_blocks] selrows = selrows.reject do |fcb| fcb.fetch(:depth, 0).positive? end end if opts[:hide_blocks_by_name] selrows = selrows.reject do |fcb| (opts, fcb) end end collapser = Collapser.new( options: opts, compress_ids: opts[:compressed_ids] || {}, expand_ids: opts[:expanded_ids] || {} ) selrows = collapser.reject( selrows, initialize: opts[:compressed_ids].nil? ) do |fcb, _hide, _collapsed_level| # update fcb per state next unless fcb.collapsible fcb.s1decorated = fcb.s1decorated + ' ' + (if fcb.collapse opts[:menu_collapsible_symbol_collapsed] else opts[:menu_collapsible_symbol_expanded] end) end opts[:compressed_ids] = collapser.compress_ids opts[:expanded_ids] = collapser. # remove # . empty chrome between code; edges are same as blanks # select_elements_with_neighbor_conditions(selrows) do |prev_element, current, next_element| !(current[:chrome] && !current.oname.present?) || !(!prev_element.nil? && prev_element.shell.present? && !next_element.nil? && next_element.shell.present?) end end |
#generate_env_variable_shell_commands(fcb) ⇒ Array<String>
Generates shell code lines to set environment variables named in the body of the given object. Reads a whitespace-separated list of environment variable names from ‘fcb.body`, retrieves their values from the current environment, and constructs shell commands to set these environment variables.
Example:
If `fcb.body` returns ["PATH", "HOME"], and the current environment has PATH=/usr/bin
and HOME=/home/user, this method will return:
["PATH=/usr/bin", "HOME=/home/user"]
325 326 327 328 329 |
# File 'lib/mdoc.rb', line 325 def generate_env_variable_shell_commands(fcb) fcb.body.join(' ').split.compact.map do |key| "#{key}=#{Shellwords.escape ENV.fetch(key, '')}" end end |
#generate_label_body_code(fcb, block_source, label_format_above, label_format_below) ⇒ Array<String>
Generates a formatted code block with labels above and below the main content. The labels and content are based on the provided format strings and the body of the given object.
Example:
If `fcb.pub_name` returns "Example Block", `fcb.body` returns ["line1", "line2"],
`block_source` is { source: "source_info" }, `label_format_above` is "Start of %{block_name}",
and `label_format_below` is "End of %{block_name}", the method will return:
["Start of Example_Block", "line1", "line2", "End of Example_Block"]
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/mdoc.rb', line 346 def generate_label_body_code(fcb, block_source, label_format_above, label_format_below) block_name_for_bash_comment = fcb.pub_name.gsub(/\s+/, '_') label_above = if label_format_above.present? format(label_format_above, block_source.merge( { block_name: block_name_for_bash_comment } )) else nil end label_below = if label_format_below.present? format(label_format_below, block_source.merge( { block_name: block_name_for_bash_comment } )) else nil end [label_above, *fcb.body, label_below].compact end |
#get_block_by_anyname(name, default = {}) ⇒ Hash
Retrieves a code block by its name.
376 377 378 379 380 |
# File 'lib/mdoc.rb', line 376 def get_block_by_anyname(name, default = {}) table_not_split.select do |fcb| fcb.is_named?(name) end.fetch(0, default) end |
#get_blocks_by_anyname(name) ⇒ Hash
Retrieves code blocks by a name.
388 389 390 391 392 |
# File 'lib/mdoc.rb', line 388 def get_blocks_by_anyname(name) table_not_split.select do |fcb| fcb.is_named?(name) end end |
#hide_menu_block_on_name(opts, block) ⇒ Boolean
Checks if a code block should be hidden based on the given options.
:reek:UtilityFunction
401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
# File 'lib/mdoc.rb', line 401 def (opts, block) if block.fetch(:chrome, false) false else opts[:hide_blocks_by_name] && ((opts[:block_name_hidden_match]&.present? && block.s2title&.match(Regexp.new(opts[:block_name_hidden_match]))) || (opts[:block_name_include_match]&.present? && block.s2title&.match(Regexp.new(opts[:block_name_include_match]))) || (opts[:block_name_wrapper_match]&.present? && block.s2title&.match(Regexp.new(opts[:block_name_wrapper_match])))) && (block.s2title&.present? || block[:label]&.present?) end end |
#recursively_required(reqs) ⇒ Array<String>
Recursively fetches required code blocks for a given list of requirements.
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'lib/mdoc.rb', line 421 def recursively_required(reqs) return [] unless reqs rem = reqs memo = [] while rem && rem.count.positive? rem = rem.map do |req| next if memo.include? req memo += [req] get_block_by_anyname(req).reqs end .compact .flatten(1) end memo end |
#recursively_required_hash(source, memo = Hash.new([])) ⇒ Hash
Recursively fetches required code blocks for a given list of requirements.
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/mdoc.rb', line 444 def recursively_required_hash(source, memo = Hash.new([])) return memo unless source return memo if memo.keys.include? source blocks = get_blocks_by_anyname(source) if blocks.empty? raise "Named code block `#{source}` not found. (@#{__LINE__})" end memo[source] = blocks.map(&:reqs).flatten(1) return memo unless memo[source]&.count&.positive? memo[source].each do |req| next if memo.keys.include? req recursively_required_hash(req, memo) end memo end |
#select_elements_with_neighbor_conditions(array, last_selected_placeholder = nil, next_selected_placeholder = nil) ⇒ Object
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/mdoc.rb', line 498 def select_elements_with_neighbor_conditions( array, last_selected_placeholder = nil, next_selected_placeholder = nil ) selected_elements = [] last_selected = last_selected_placeholder array.each_with_index do |current, index| next_element = if index < array.size - 1 array[index + 1] else next_selected_placeholder end if yield(last_selected, current, next_element) selected_elements << current last_selected = current end end selected_elements end |
#table_not_split ⇒ Object
exclude blocks with duplicate code the first block in each split contains the same data as the rest of the split
524 525 526 |
# File 'lib/mdoc.rb', line 524 def table_not_split @table.reject(&:is_split_rest?) end |