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(source, memo = {}) ⇒ 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.
-
#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
Constructor Details
#initialize(table = []) ⇒ MDoc
Initializes an instance of MDoc with the given table of markdown sections.
26 27 28 29 |
# File 'lib/mdoc.rb', line 26 def initialize(table = []) @table = table # !!t @table.count end |
Instance Attribute Details
#table ⇒ Object (readonly)
Returns the value of attribute table.
20 21 22 |
# File 'lib/mdoc.rb', line 20 def table @table end |
Instance Method Details
#collect_block_code_cann(fcb) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/mdoc.rb', line 31 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.
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/mdoc.rb', line 60 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.
77 78 79 80 81 82 83 84 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 |
# File 'lib/mdoc.rb', line 77 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 dependencies = collect_dependencies(nickname) # !!t dependencies.count all_dependency_names = collect_unique_names(dependencies).push(nickname).uniq # !!t all_dependency_names.count # select blocks in order of appearance in source documents # blocks = @table.select do |fcb| # 2024-08-04 match nickname all_dependency_names.include?(fcb.pub_name) || all_dependency_names.include?(fcb.nickname) || all_dependency_names.include?(fcb.oname) end # !!t blocks.count ## add cann key to blocks, calc unmet_dependencies # unmet_dependencies = all_dependency_names.dup blocks = blocks.map do |fcb| # 2024-08-04 match oname for long block names # 2024-08-04 match nickname unmet_dependencies.delete(fcb.pub_name) || unmet_dependencies.delete(fcb.nickname) || unmet_dependencies.delete(fcb.oname) # may not exist if block name is duplicated if (call = fcb.call) fcb1 = get_block_by_anyname("[#{call.match(/^%\((\S+) |\)/)[1]}]") fcb1.cann = call [fcb1] else [] end + [fcb] end.flatten(1) # !!t unmet_dependencies.count { all_dependency_names: all_dependency_names, blocks: blocks, dependencies: dependencies, unmet_dependencies: unmet_dependencies } end |
#collect_dependencies(source, memo = {}) ⇒ Hash
Recursively collects dependencies of a given source.
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/mdoc.rb', line 381 def collect_dependencies(source, memo = {}) return memo unless source block = get_block_by_anyname(source) if block.nil? || block.instance_of?(Hash) raise "Named code block `#{source}` not found. (@#{__LINE__})" end return memo unless block.reqs memo[source] = block.reqs block.reqs.each do |req| collect_dependencies(req, memo) unless memo.key?(req) 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.
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 163 |
# File 'lib/mdoc.rb', line 126 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::VARS].include? fcb.type nil 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
165 166 167 |
# File 'lib/mdoc.rb', line 165 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
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/mdoc.rb', line 175 def collect_wrapped_blocks(blocks) blocks.map do |block| (block[:wraps] || []).map do |wrap| wrap_before = wrap.sub('}', '-before}') ### hardcoded wrap name @table.select { |fcb| [wrap_before, wrap].include? fcb.oname } end.flatten(1) + [block] + (block[:wraps] || []).reverse.map do |wrap| wrap_after = wrap.sub('}', '-after}') ### hardcoded wrap name @table.select { |fcb| fcb.oname == wrap_after } end.flatten(1) end.flatten(1).compact end |
#error_handler(name = '', opts = {}) ⇒ Object
189 190 191 192 193 194 |
# File 'lib/mdoc.rb', line 189 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.
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 231 |
# File 'lib/mdoc.rb', line 201 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 [:menu_include_imported_blocks] selrows = selrows.reject do |block| block.fetch(:depth, 0).positive? end end if opts[:hide_blocks_by_name] selrows = selrows.reject do |block| opts, block end end # 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"]
248 249 250 251 252 |
# File 'lib/mdoc.rb', line 248 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"]
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/mdoc.rb', line 269 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.
295 296 297 298 299 300 301 302 303 |
# File 'lib/mdoc.rb', line 295 def get_block_by_anyname(name, default = {}) # !!t name @table.select do |fcb| fcb.nickname == name || fcb.dname == name || fcb.oname == name || fcb.pub_name == name end.fetch(0, default) end |
#hide_menu_block_on_name(opts, block) ⇒ Boolean
Checks if a code block should be hidden based on the given options.
:reek:UtilityFunction
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/mdoc.rb', line 312 def (opts, block) if block.fetch(:chrome, false) false else opts[:hide_blocks_by_name] && ((opts[:block_name_hidden_match]&.present? && block.oname&.match(Regexp.new(opts[:block_name_hidden_match]))) || (opts[:block_name_include_match]&.present? && block.oname&.match(Regexp.new(opts[:block_name_include_match]))) || (opts[:block_name_wrapper_match]&.present? && block.oname&.match(Regexp.new(opts[:block_name_wrapper_match])))) && (block.oname&.present? || block[:label]&.present?) end end |
#recursively_required(reqs) ⇒ Array<String>
Recursively fetches required code blocks for a given list of requirements.
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/mdoc.rb', line 333 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.
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/mdoc.rb', line 356 def recursively_required_hash(source, memo = Hash.new([])) return memo unless source return memo if memo.keys.include? source block = get_block_by_anyname(source) # if block.nil? || block.keys.nil? || block.keys.empty? if block.nil? raise "Named code block `#{source}` not found. (@#{__LINE__})" end memo[source] = block.reqs 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
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/mdoc.rb', line 399 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 |