markdown_exec 1.3.2 → 1.3.3.1
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/.rubocop.yml +13 -11
- data/CHANGELOG.md +40 -3
- data/Gemfile +6 -3
- data/Gemfile.lock +112 -34
- data/README.md +5 -84
- data/Rakefile +105 -28
- data/bin/tab_completion.sh +2 -2
- data/lib/cached_nested_file_reader.rb +115 -0
- data/lib/colorize.rb +4 -0
- data/lib/env.rb +1 -0
- data/lib/env_opts.rb +54 -56
- data/lib/environment_opt_parse.rb +24 -19
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +589 -260
- data/lib/menu.yml +70 -4
- data/lib/object_present.rb +44 -0
- data/lib/rspec_helpers.rb +10 -0
- data/lib/tap.rb +51 -7
- metadata +4 -4
- data/lib/globfiles.rb +0 -40
- data/lib/yaml_env_opts.rb +0 -60
    
        data/lib/markdown_exec.rb
    CHANGED
    
    | @@ -11,11 +11,10 @@ require 'shellwords' | |
| 11 11 | 
             
            require 'tty-prompt'
         | 
| 12 12 | 
             
            require 'yaml'
         | 
| 13 13 |  | 
| 14 | 
            +
            require_relative 'cached_nested_file_reader'
         | 
| 14 15 | 
             
            require_relative 'cli'
         | 
| 15 16 | 
             
            require_relative 'colorize'
         | 
| 16 17 | 
             
            require_relative 'env'
         | 
| 17 | 
            -
            # require_relative 'environment_opt_parse'
         | 
| 18 | 
            -
            require_relative 'object_present'
         | 
| 19 18 | 
             
            require_relative 'shared'
         | 
| 20 19 | 
             
            require_relative 'tap'
         | 
| 21 20 | 
             
            require_relative 'markdown_exec/version'
         | 
| @@ -30,13 +29,24 @@ $stdout.sync = true | |
| 30 29 |  | 
| 31 30 | 
             
            BLOCK_SIZE = 1024
         | 
| 32 31 |  | 
| 32 | 
            +
            # custom error: file specified is missing
         | 
| 33 | 
            +
            #
         | 
| 33 34 | 
             
            class FileMissingError < StandardError; end
         | 
| 34 35 |  | 
| 35 36 | 
             
            # hash with keys sorted by name
         | 
| 37 | 
            +
            # add Hash.sym_keys
         | 
| 36 38 | 
             
            #
         | 
| 37 39 | 
             
            class Hash
         | 
| 38 | 
            -
               | 
| 39 | 
            -
                 | 
| 40 | 
            +
              unless defined?(sort_by_key)
         | 
| 41 | 
            +
                def sort_by_key
         | 
| 42 | 
            +
                  keys.sort.to_h { |key| [key, self[key]] }
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              unless defined?(sym_keys)
         | 
| 47 | 
            +
                def sym_keys
         | 
| 48 | 
            +
                  transform_keys(&:to_sym)
         | 
| 49 | 
            +
                end
         | 
| 40 50 | 
             
              end
         | 
| 41 51 | 
             
            end
         | 
| 42 52 |  | 
| @@ -71,69 +81,226 @@ module FOUT | |
| 71 81 | 
             
              end
         | 
| 72 82 | 
             
            end
         | 
| 73 83 |  | 
| 84 | 
            +
            def dp(str)
         | 
| 85 | 
            +
              lout " => #{str}", level: DISPLAY_LEVEL_DEBUG
         | 
| 86 | 
            +
            end
         | 
| 87 | 
            +
             | 
| 74 88 | 
             
            public
         | 
| 75 89 |  | 
| 90 | 
            +
            # :reek:UtilityFunction
         | 
| 91 | 
            +
            def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
         | 
| 92 | 
            +
              Sfiles.new(saved_stdout_folder,
         | 
| 93 | 
            +
                         saved_stdout_glob).most_recent_list(list_count)
         | 
| 94 | 
            +
            end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            # :reek:UtilityFunction
         | 
| 97 | 
            +
            def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
         | 
| 98 | 
            +
              Sfiles.new(saved_script_folder,
         | 
| 99 | 
            +
                         saved_script_glob).most_recent_list(list_count)
         | 
| 100 | 
            +
            end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            # convert regex match groups to a hash with symbol keys
         | 
| 103 | 
            +
            #
         | 
| 104 | 
            +
            # :reek:UtilityFunction
         | 
| 105 | 
            +
            def option_match_groups(str, option)
         | 
| 106 | 
            +
              str.match(Regexp.new(option))&.named_captures&.sym_keys
         | 
| 107 | 
            +
            end
         | 
| 108 | 
            +
             | 
| 76 109 | 
             
            # execute markdown documents
         | 
| 77 110 | 
             
            #
         | 
| 78 111 | 
             
            module MarkdownExec
         | 
| 79 112 | 
             
              # :reek:IrresponsibleModule
         | 
| 80 113 | 
             
              class Error < StandardError; end
         | 
| 81 114 |  | 
| 82 | 
            -
              #  | 
| 115 | 
            +
              # fenced code block
         | 
| 83 116 | 
             
              #
         | 
| 84 | 
            -
              class  | 
| 85 | 
            -
                def initialize
         | 
| 86 | 
            -
                  @ | 
| 117 | 
            +
              class FCB
         | 
| 118 | 
            +
                def initialize(options = {})
         | 
| 119 | 
            +
                  @attrs = {
         | 
| 120 | 
            +
                    body: nil,
         | 
| 121 | 
            +
                    call: nil,
         | 
| 122 | 
            +
                    headings: [],
         | 
| 123 | 
            +
                    name: nil,
         | 
| 124 | 
            +
                    reqs: [],
         | 
| 125 | 
            +
                    shell: '',
         | 
| 126 | 
            +
                    title: '',
         | 
| 127 | 
            +
                    random: Random.new.rand,
         | 
| 128 | 
            +
                    text: nil # displayable in menu
         | 
| 129 | 
            +
                  }.merge options
         | 
| 87 130 | 
             
                end
         | 
| 88 131 |  | 
| 89 | 
            -
                def  | 
| 90 | 
            -
                   | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 132 | 
            +
                def to_h
         | 
| 133 | 
            +
                  @attrs
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def to_yaml
         | 
| 137 | 
            +
                  @attrs.to_yaml
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                private
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                # :reek:ManualDispatch
         | 
| 143 | 
            +
                def method_missing(method, *args, &block)
         | 
| 144 | 
            +
                  method_name = method.to_s
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  if @attrs.respond_to?(method_name)
         | 
| 147 | 
            +
                    @attrs.send(method_name, *args, &block)
         | 
| 148 | 
            +
                  elsif method_name[-1] == '='
         | 
| 149 | 
            +
                    @attrs[method_name.chop.to_sym] = args[0]
         | 
| 94 150 | 
             
                  else
         | 
| 95 | 
            -
                     | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 151 | 
            +
                    @attrs[method_name.to_sym]
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
                rescue StandardError => err
         | 
| 154 | 
            +
                  warn(error = "ERROR ** FCB.method_missing(method: #{method_name}," \
         | 
| 155 | 
            +
                               " *args: #{args.inspect}, &block)")
         | 
| 156 | 
            +
                  warn err.inspect
         | 
| 157 | 
            +
                  warn(caller[0..4])
         | 
| 158 | 
            +
                  raise StandardError, error
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                # option names are available as methods
         | 
| 162 | 
            +
                #
         | 
| 163 | 
            +
                def respond_to_missing?(_method_name, _include_private = false)
         | 
| 164 | 
            +
                  true # recognize all hash methods, rest are treated as hash keys
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
              # select fcb per options
         | 
| 169 | 
            +
              #
         | 
| 170 | 
            +
              # :reek:UtilityFunction
         | 
| 171 | 
            +
              class Filter
         | 
| 172 | 
            +
                #     def self.fcb_title_parse(opts, fcb_title)
         | 
| 173 | 
            +
                #       fcb_title.match(Regexp.new(opts[:fenced_start_ex_match])).named_captures.sym_keys
         | 
| 174 | 
            +
                #     end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                def self.fcb_select?(options, fcb)
         | 
| 177 | 
            +
                  # options.tap_yaml 'options'
         | 
| 178 | 
            +
                  # fcb.tap_inspect 'fcb'
         | 
| 179 | 
            +
                  name = fcb.fetch(:name, '').tap_inspect 'name'
         | 
| 180 | 
            +
                  shell = fcb.fetch(:shell, '').tap_inspect 'shell'
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                  ## include hidden blocks for later use
         | 
| 183 | 
            +
                  #
         | 
| 184 | 
            +
                  name_default = true
         | 
| 185 | 
            +
                  name_exclude = nil
         | 
| 186 | 
            +
                  name_select = nil
         | 
| 187 | 
            +
                  shell_default = true
         | 
| 188 | 
            +
                  shell_exclude = nil
         | 
| 189 | 
            +
                  shell_select = nil
         | 
| 190 | 
            +
                  hidden_name = nil
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  if name.present? && options[:block_name]
         | 
| 193 | 
            +
                    if name =~ /#{options[:block_name]}/
         | 
| 194 | 
            +
                      '=~ block_name'.tap_puts
         | 
| 195 | 
            +
                      name_select = true
         | 
| 196 | 
            +
                      name_exclude = false
         | 
| 197 | 
            +
                    else
         | 
| 198 | 
            +
                      '!~ block_name'.tap_puts
         | 
| 199 | 
            +
                      name_exclude = true
         | 
| 200 | 
            +
                      name_select = false
         | 
| 99 201 | 
             
                    end
         | 
| 100 | 
            -
                    @cache[filename] = lines
         | 
| 101 202 | 
             
                  end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                  if name.present? && name_select.nil? && options[:select_by_name_regex].present?
         | 
| 205 | 
            +
                    '+select_by_name_regex'.tap_puts
         | 
| 206 | 
            +
                    name_select = (!!(name =~ /#{options[:select_by_name_regex]}/)).tap_inspect 'name_select'
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  if shell.present? && options[:select_by_shell_regex].present?
         | 
| 210 | 
            +
                    '+select_by_shell_regex'.tap_puts
         | 
| 211 | 
            +
                    shell_select = (!!(shell =~ /#{options[:select_by_shell_regex]}/)).tap_inspect 'shell_select'
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  if name.present? && name_exclude.nil? && options[:exclude_by_name_regex].present?
         | 
| 215 | 
            +
                    '-exclude_by_name_regex'.tap_puts
         | 
| 216 | 
            +
                    name_exclude = (!!(name =~ /#{options[:exclude_by_name_regex]}/)).tap_inspect 'name_exclude'
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  if shell.present? && options[:exclude_by_shell_regex].present?
         | 
| 220 | 
            +
                    '-exclude_by_shell_regex'.tap_puts
         | 
| 221 | 
            +
                    shell_exclude = (!!(shell =~ /#{options[:exclude_by_shell_regex]}/)).tap_inspect 'shell_exclude'
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  if name.present? && options[:hide_blocks_by_name] &&
         | 
| 225 | 
            +
                     options[:block_name_hidden_match].present?
         | 
| 226 | 
            +
                    '+block_name_hidden_match'.tap_puts
         | 
| 227 | 
            +
                    hidden_name = (!!(name =~ /#{options[:block_name_hidden_match]}/)).tap_inspect 'hidden_name'
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                  if shell.present? && options[:hide_blocks_by_shell] &&
         | 
| 231 | 
            +
                     options[:block_shell_hidden_match].present?
         | 
| 232 | 
            +
                    '-hide_blocks_by_shell'.tap_puts
         | 
| 233 | 
            +
                    (!!(shell =~ /#{options[:block_shell_hidden_match]}/)).tap_inspect 'hidden_shell'
         | 
| 234 | 
            +
                  end
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                  if options[:bash_only]
         | 
| 237 | 
            +
                    '-bash_only'.tap_puts
         | 
| 238 | 
            +
                    shell_default = (shell == 'bash').tap_inspect 'shell_default'
         | 
| 239 | 
            +
                  end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                  ## name matching does not filter hidden blocks
         | 
| 242 | 
            +
                  #
         | 
| 243 | 
            +
                  case
         | 
| 244 | 
            +
                  when options[:no_chrome] && fcb.fetch(:chrome, false)
         | 
| 245 | 
            +
                    '-no_chrome'.tap_puts
         | 
| 246 | 
            +
                    false
         | 
| 247 | 
            +
                  when options[:exclude_expect_blocks] && shell == 'expect'
         | 
| 248 | 
            +
                    '-exclude_expect_blocks'.tap_puts
         | 
| 249 | 
            +
                    false
         | 
| 250 | 
            +
                  when hidden_name == true
         | 
| 251 | 
            +
                    true
         | 
| 252 | 
            +
                  when name_exclude == true, shell_exclude == true,
         | 
| 253 | 
            +
                       name_select == false, shell_select == false
         | 
| 254 | 
            +
                    false
         | 
| 255 | 
            +
                  when name_select == true, shell_select == true
         | 
| 256 | 
            +
                    true
         | 
| 257 | 
            +
                  when name_default == false, shell_default == false
         | 
| 258 | 
            +
                    false
         | 
| 259 | 
            +
                  else
         | 
| 260 | 
            +
                    true
         | 
| 261 | 
            +
                  end.tap_inspect
         | 
| 262 | 
            +
                # binding.pry
         | 
| 263 | 
            +
                rescue StandardError => err
         | 
| 264 | 
            +
                  warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
         | 
| 265 | 
            +
                  raise err
         | 
| 102 266 | 
             
                end
         | 
| 103 | 
            -
              end # class  | 
| 267 | 
            +
              end # class Filter
         | 
| 104 268 |  | 
| 105 269 | 
             
              ## an imported markdown document
         | 
| 106 270 | 
             
              #
         | 
| 107 271 | 
             
              class MDoc
         | 
| 272 | 
            +
                attr_reader :table
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                # convert block name to fcb_parse
         | 
| 275 | 
            +
                #
         | 
| 108 276 | 
             
                def initialize(table)
         | 
| 109 277 | 
             
                  @table = table
         | 
| 110 278 | 
             
                end
         | 
| 111 279 |  | 
| 112 280 | 
             
                def collect_recursively_required_code(name)
         | 
| 113 281 | 
             
                  get_required_blocks(name)
         | 
| 114 | 
            -
                    .map do | | 
| 115 | 
            -
                     | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
                       | 
| 120 | 
            -
                       | 
| 121 | 
            -
             | 
| 282 | 
            +
                    .map do |fcb|
         | 
| 283 | 
            +
                    body = fcb[:body].join("\n")
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                    if fcb[:cann]
         | 
| 286 | 
            +
                      xcall = fcb[:cann][1..-2]
         | 
| 287 | 
            +
                      mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
         | 
| 288 | 
            +
                      mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
         | 
| 289 | 
            +
             | 
| 122 290 | 
             
                      yqcmd = if mstdin[:type]
         | 
| 123 291 | 
             
                                "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
         | 
| 124 292 | 
             
                              else
         | 
| 125 293 | 
             
                                "yq e '#{body}' '#{mstdin[:name]}'"
         | 
| 126 | 
            -
                              end | 
| 294 | 
            +
                              end
         | 
| 127 295 | 
             
                      if mstdout[:type]
         | 
| 128 296 | 
             
                        "export #{mstdout[:name]}=$(#{yqcmd})"
         | 
| 129 297 | 
             
                      else
         | 
| 130 298 | 
             
                        "#{yqcmd} > '#{mstdout[:name]}'"
         | 
| 131 299 | 
             
                      end
         | 
| 132 | 
            -
                    elsif  | 
| 133 | 
            -
                      stdout =  | 
| 134 | 
            -
                      body =  | 
| 300 | 
            +
                    elsif fcb[:stdout]
         | 
| 301 | 
            +
                      stdout = fcb[:stdout]
         | 
| 302 | 
            +
                      body = fcb[:body].join("\n")
         | 
| 135 303 | 
             
                      if stdout[:type]
         | 
| 136 | 
            -
                        # "export #{stdout[:name]}=#{Shellwords.escape body}"
         | 
| 137 304 | 
             
                        %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
         | 
| 138 305 | 
             
                      else
         | 
| 139 306 | 
             
                        "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
         | 
| @@ -141,65 +308,84 @@ module MarkdownExec | |
| 141 308 | 
             
                          "EOF\n"
         | 
| 142 309 | 
             
                      end
         | 
| 143 310 | 
             
                    else
         | 
| 144 | 
            -
                       | 
| 311 | 
            +
                      fcb[:body]
         | 
| 145 312 | 
             
                    end
         | 
| 146 313 | 
             
                  end.flatten(1)
         | 
| 147 | 
            -
                    .tap_yaml
         | 
| 148 314 | 
             
                end
         | 
| 149 315 |  | 
| 150 316 | 
             
                def get_block_by_name(name, default = {})
         | 
| 151 | 
            -
                   | 
| 152 | 
            -
                  @table.select { |block| block[:name] == name }.fetch(0, default).tap_yaml
         | 
| 317 | 
            +
                  @table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
         | 
| 153 318 | 
             
                end
         | 
| 154 319 |  | 
| 155 320 | 
             
                def get_required_blocks(name)
         | 
| 156 321 | 
             
                  name_block = get_block_by_name(name)
         | 
| 157 322 | 
             
                  raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
         | 
| 158 323 |  | 
| 159 | 
            -
                  all = [name_block | 
| 324 | 
            +
                  all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
         | 
| 160 325 |  | 
| 161 326 | 
             
                  # in order of appearance in document
         | 
| 162 | 
            -
                  sel = @table.select { |block| all.include? block[:name] }
         | 
| 163 | 
            -
             | 
| 164 327 | 
             
                  # insert function blocks
         | 
| 165 | 
            -
                   | 
| 166 | 
            -
             | 
| 167 | 
            -
                    if (call =  | 
| 168 | 
            -
                      [get_block_by_name("[#{call.match( | 
| 328 | 
            +
                  @table.select { |fcb| all.include? fcb.fetch(:name, '') }
         | 
| 329 | 
            +
                        .map do |fcb|
         | 
| 330 | 
            +
                    if (call = fcb[:call])
         | 
| 331 | 
            +
                      [get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
         | 
| 332 | 
            +
                        .merge({ cann: call })]
         | 
| 169 333 | 
             
                    else
         | 
| 170 334 | 
             
                      []
         | 
| 171 | 
            -
                    end + [ | 
| 172 | 
            -
                  end.flatten(1) | 
| 335 | 
            +
                    end + [fcb]
         | 
| 336 | 
            +
                  end.flatten(1)
         | 
| 173 337 | 
             
                end
         | 
| 174 338 |  | 
| 175 339 | 
             
                # :reek:UtilityFunction
         | 
| 176 340 | 
             
                def hide_menu_block_per_options(opts, block)
         | 
| 177 341 | 
             
                  (opts[:hide_blocks_by_name] &&
         | 
| 178 | 
            -
                          block[:name] | 
| 342 | 
            +
                          block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
         | 
| 343 | 
            +
                          (block[:name]&.present? || block[:label]&.present?)
         | 
| 344 | 
            +
                  ).tap_inspect
         | 
| 179 345 | 
             
                end
         | 
| 180 346 |  | 
| 181 | 
            -
                def blocks_for_menu(opts)
         | 
| 347 | 
            +
                # def blocks_for_menu(opts)
         | 
| 348 | 
            +
                #   if opts[:hide_blocks_by_name]
         | 
| 349 | 
            +
                #     @table.reject { |block| hide_menu_block_per_options opts, block }
         | 
| 350 | 
            +
                #   else
         | 
| 351 | 
            +
                #     @table
         | 
| 352 | 
            +
                #   end
         | 
| 353 | 
            +
                # end
         | 
| 354 | 
            +
             | 
| 355 | 
            +
                def fcbs_per_options(opts = {})
         | 
| 356 | 
            +
                  options = opts.merge(block_name_hidden_match: nil)
         | 
| 357 | 
            +
                  selrows = @table.select do |fcb_title_groups|
         | 
| 358 | 
            +
                    Filter.fcb_select? options, fcb_title_groups
         | 
| 359 | 
            +
                  end
         | 
| 360 | 
            +
                  # binding.pry
         | 
| 361 | 
            +
                  ### hide rows correctly
         | 
| 362 | 
            +
             | 
| 182 363 | 
             
                  if opts[:hide_blocks_by_name]
         | 
| 183 | 
            -
                     | 
| 364 | 
            +
                    selrows.reject { |block| hide_menu_block_per_options opts, block }
         | 
| 184 365 | 
             
                  else
         | 
| 185 | 
            -
                     | 
| 366 | 
            +
                    selrows
         | 
| 367 | 
            +
                  end.map do |block|
         | 
| 368 | 
            +
                    # block[:name] = block[:text] if block[:name].nil?
         | 
| 369 | 
            +
                    block
         | 
| 186 370 | 
             
                  end
         | 
| 187 371 | 
             
                end
         | 
| 188 372 |  | 
| 189 373 | 
             
                def recursively_required(reqs)
         | 
| 190 | 
            -
                   | 
| 374 | 
            +
                  return [] unless reqs
         | 
| 375 | 
            +
             | 
| 191 376 | 
             
                  rem = reqs
         | 
| 377 | 
            +
                  memo = []
         | 
| 192 378 | 
             
                  while rem.count.positive?
         | 
| 193 379 | 
             
                    rem = rem.map do |req|
         | 
| 194 | 
            -
                      next if  | 
| 380 | 
            +
                      next if memo.include? req
         | 
| 195 381 |  | 
| 196 | 
            -
                       | 
| 382 | 
            +
                      memo += [req]
         | 
| 197 383 | 
             
                      get_block_by_name(req).fetch(:reqs, [])
         | 
| 198 384 | 
             
                    end
         | 
| 199 385 | 
             
                             .compact
         | 
| 200 386 | 
             
                             .flatten(1)
         | 
| 201 387 | 
             
                  end
         | 
| 202 | 
            -
                   | 
| 388 | 
            +
                  memo
         | 
| 203 389 | 
             
                end
         | 
| 204 390 | 
             
              end # class MDoc
         | 
| 205 391 |  | 
| @@ -207,16 +393,30 @@ module MarkdownExec | |
| 207 393 | 
             
              #
         | 
| 208 394 | 
             
              # :reek:TooManyInstanceVariables
         | 
| 209 395 | 
             
              class BlockLabel
         | 
| 210 | 
            -
                def initialize(filename:, headings:, menu_blocks_with_docname:, | 
| 396 | 
            +
                def initialize(filename:, headings:, menu_blocks_with_docname:,
         | 
| 397 | 
            +
                               menu_blocks_with_headings:, title:, body:, text:)
         | 
| 211 398 | 
             
                  @filename = filename
         | 
| 212 399 | 
             
                  @headings = headings
         | 
| 213 400 | 
             
                  @menu_blocks_with_docname = menu_blocks_with_docname
         | 
| 214 401 | 
             
                  @menu_blocks_with_headings = menu_blocks_with_headings
         | 
| 402 | 
            +
                  # @title = title.present? ? title : body
         | 
| 215 403 | 
             
                  @title = title
         | 
| 404 | 
            +
                  @body = body
         | 
| 405 | 
            +
                  @text = text
         | 
| 406 | 
            +
                rescue StandardError => err
         | 
| 407 | 
            +
                  warn(error = "ERROR ** BlockLabel.initialize(); #{err.inspect}")
         | 
| 408 | 
            +
                  binding.pry if $tap_enable
         | 
| 409 | 
            +
                  raise ArgumentError, error
         | 
| 216 410 | 
             
                end
         | 
| 217 411 |  | 
| 412 | 
            +
                # join title, headings, filename
         | 
| 413 | 
            +
                #
         | 
| 218 414 | 
             
                def make
         | 
| 219 | 
            -
                   | 
| 415 | 
            +
                  label = @title
         | 
| 416 | 
            +
                  label = @body unless label.present?
         | 
| 417 | 
            +
                  label = @text unless label.present?
         | 
| 418 | 
            +
                  label.tap_inspect
         | 
| 419 | 
            +
                  ([label] +
         | 
| 220 420 | 
             
                    (if @menu_blocks_with_headings
         | 
| 221 421 | 
             
                       [@headings.compact.join(' # ')]
         | 
| 222 422 | 
             
                     else
         | 
| @@ -229,6 +429,10 @@ module MarkdownExec | |
| 229 429 | 
             
                        []
         | 
| 230 430 | 
             
                      end
         | 
| 231 431 | 
             
                    )).join('  ')
         | 
| 432 | 
            +
                rescue StandardError => err
         | 
| 433 | 
            +
                  warn(error = "ERROR ** BlockLabel.make(); #{err.inspect}")
         | 
| 434 | 
            +
                  binding.pry if $tap_enable
         | 
| 435 | 
            +
                  raise ArgumentError, error
         | 
| 232 436 | 
             
                end
         | 
| 233 437 | 
             
              end # class BlockLabel
         | 
| 234 438 |  | 
| @@ -247,11 +451,13 @@ module MarkdownExec | |
| 247 451 |  | 
| 248 452 | 
             
                def script_name
         | 
| 249 453 | 
             
                  fne = @filename.gsub(FNR11, FNR12)
         | 
| 250 | 
            -
                  "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',', | 
| 454 | 
            +
                  "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',',
         | 
| 455 | 
            +
                      @blockname].join('_')}.sh"
         | 
| 251 456 | 
             
                end
         | 
| 252 457 |  | 
| 253 458 | 
             
                def stdout_name
         | 
| 254 | 
            -
                  "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, | 
| 459 | 
            +
                  "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename,
         | 
| 460 | 
            +
                      @blockname].join('_')}.out.txt"
         | 
| 255 461 | 
             
                end
         | 
| 256 462 | 
             
              end # class SavedAsset
         | 
| 257 463 |  | 
| @@ -308,27 +514,29 @@ module MarkdownExec | |
| 308 514 | 
             
                end
         | 
| 309 515 |  | 
| 310 516 | 
             
                def list_all
         | 
| 311 | 
            -
                  Dir.glob(File.join(@folder, @glob)) | 
| 517 | 
            +
                  Dir.glob(File.join(@folder, @glob))
         | 
| 312 518 | 
             
                end
         | 
| 313 519 |  | 
| 314 520 | 
             
                def most_recent(arr = nil)
         | 
| 315 521 | 
             
                  arr = list_all if arr.nil?
         | 
| 316 522 | 
             
                  return if arr.count < 1
         | 
| 317 523 |  | 
| 318 | 
            -
                  arr.max | 
| 524 | 
            +
                  arr.max
         | 
| 319 525 | 
             
                end
         | 
| 320 526 |  | 
| 321 527 | 
             
                def most_recent_list(list_count, arr = nil)
         | 
| 322 528 | 
             
                  arr = list_all if arr.nil?
         | 
| 323 529 | 
             
                  return if (ac = arr.count) < 1
         | 
| 324 530 |  | 
| 325 | 
            -
                  arr.sort[-[ac, list_count].min..].reverse | 
| 531 | 
            +
                  arr.sort[-[ac, list_count].min..].reverse
         | 
| 326 532 | 
             
                end
         | 
| 327 533 | 
             
              end # class Sfiles
         | 
| 328 534 |  | 
| 329 535 | 
             
              ##
         | 
| 330 536 | 
             
              #
         | 
| 537 | 
            +
              # rubocop:disable Layout/LineLength
         | 
| 331 538 | 
             
              # :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
         | 
| 539 | 
            +
              # rubocop:enable Layout/LineLength
         | 
| 332 540 | 
             
              # :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
         | 
| 333 541 | 
             
              # :reek:TooManyInstanceVariables ### temp
         | 
| 334 542 | 
             
              # :reek:TooManyMethods ### temp
         | 
| @@ -339,8 +547,8 @@ module MarkdownExec | |
| 339 547 |  | 
| 340 548 | 
             
                def initialize(options = {})
         | 
| 341 549 | 
             
                  @options = options
         | 
| 550 | 
            +
                  # hide disabled symbol
         | 
| 342 551 | 
             
                  @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: ' ' })
         | 
| 343 | 
            -
                  # @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: options[:menu_divider_symbol] })
         | 
| 344 552 | 
             
                  @execute_aborted_at = nil
         | 
| 345 553 | 
             
                  @execute_completed_at = nil
         | 
| 346 554 | 
             
                  @execute_error = nil
         | 
| @@ -350,12 +558,35 @@ module MarkdownExec | |
| 350 558 | 
             
                  @execute_script_filespec = nil
         | 
| 351 559 | 
             
                  @execute_started_at = nil
         | 
| 352 560 | 
             
                  @option_parser = nil
         | 
| 353 | 
            -
             | 
| 561 | 
            +
                end
         | 
| 562 | 
            +
             | 
| 563 | 
            +
                # return arguments before `--`
         | 
| 564 | 
            +
                #
         | 
| 565 | 
            +
                def arguments_for_mde(argv = ARGV)
         | 
| 566 | 
            +
                  case ind = argv.find_index('--')
         | 
| 567 | 
            +
                  when nil
         | 
| 568 | 
            +
                    argv
         | 
| 569 | 
            +
                  when 0
         | 
| 570 | 
            +
                    []
         | 
| 571 | 
            +
                  else
         | 
| 572 | 
            +
                    argv[0..ind - 1]
         | 
| 573 | 
            +
                  end #.tap_inspect
         | 
| 574 | 
            +
                end
         | 
| 575 | 
            +
             | 
| 576 | 
            +
                # return arguments after `--`
         | 
| 577 | 
            +
                #
         | 
| 578 | 
            +
                def arguments_for_child(argv = ARGV)
         | 
| 579 | 
            +
                  case ind = argv.find_index('--')
         | 
| 580 | 
            +
                  when nil, argv.count - 1
         | 
| 581 | 
            +
                    []
         | 
| 582 | 
            +
                  else
         | 
| 583 | 
            +
                    argv[ind + 1..-1]
         | 
| 584 | 
            +
                  end #.tap_inspect
         | 
| 354 585 | 
             
                end
         | 
| 355 586 |  | 
| 356 587 | 
             
                ##
         | 
| 357 588 | 
             
                # options necessary to start, parse input, defaults for cli options
         | 
| 358 | 
            -
             | 
| 589 | 
            +
                #
         | 
| 359 590 | 
             
                def base_options
         | 
| 360 591 | 
             
                  menu_iter do |item|
         | 
| 361 592 | 
             
                    # noisy item.tap_yaml name: :item
         | 
| @@ -366,45 +597,39 @@ module MarkdownExec | |
| 366 597 | 
             
                    value = if item_default.nil?
         | 
| 367 598 | 
             
                              item_default
         | 
| 368 599 | 
             
                            else
         | 
| 369 | 
            -
                              env_str(item[:env_var], | 
| 600 | 
            +
                              env_str(item[:env_var],
         | 
| 601 | 
            +
                                      default: OptionValue.new(item_default).for_hash)
         | 
| 370 602 | 
             
                            end
         | 
| 371 603 | 
             
                    [item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
         | 
| 372 | 
            -
                  end.compact.to_h | 
| 373 | 
            -
                    {
         | 
| 374 | 
            -
                      menu_exit_at_top: true,
         | 
| 375 | 
            -
                      menu_with_exit: true
         | 
| 376 | 
            -
                    }
         | 
| 377 | 
            -
                  ).tap_yaml
         | 
| 604 | 
            +
                  end.compact.to_h
         | 
| 378 605 | 
             
                end
         | 
| 379 606 |  | 
| 380 | 
            -
                def  | 
| 607 | 
            +
                def calculated_options
         | 
| 381 608 | 
             
                  {
         | 
| 382 609 | 
             
                    bash: true, # bash block parsing in get_block_summary()
         | 
| 383 | 
            -
                    exclude_expect_blocks: true,
         | 
| 384 | 
            -
                    hide_blocks_by_name: true,
         | 
| 385 | 
            -
                    output_saved_script_filename: false,
         | 
| 386 610 | 
             
                    saved_script_filename: nil, # calculated
         | 
| 387 611 | 
             
                    struct: true # allow get_block_summary()
         | 
| 388 612 | 
             
                  }
         | 
| 389 613 | 
             
                end
         | 
| 390 614 |  | 
| 391 | 
            -
                def  | 
| 615 | 
            +
                def approve_and_execute_block(opts, mdoc)
         | 
| 392 616 | 
             
                  required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
         | 
| 393 | 
            -
                   | 
| 617 | 
            +
                  if opts[:output_script] || opts[:user_must_approve]
         | 
| 618 | 
            +
                    display_required_code(opts,
         | 
| 619 | 
            +
                                          required_blocks)
         | 
| 620 | 
            +
                  end
         | 
| 394 621 |  | 
| 395 622 | 
             
                  allow = true
         | 
| 396 623 | 
             
                  if opts[:user_must_approve]
         | 
| 397 624 | 
             
                    loop do
         | 
| 398 | 
            -
                      (sel = @prompt.select(opts[:prompt_approve_block], | 
| 625 | 
            +
                      (sel = @prompt.select(opts[:prompt_approve_block],
         | 
| 626 | 
            +
                                            filter: true) do |menu|
         | 
| 399 627 | 
             
                         menu.default 1
         | 
| 400 | 
            -
                         # menu.enum '.'
         | 
| 401 | 
            -
                         # menu.filter true
         | 
| 402 | 
            -
             | 
| 403 628 | 
             
                         menu.choice opts[:prompt_yes], 1
         | 
| 404 629 | 
             
                         menu.choice opts[:prompt_no], 2
         | 
| 405 630 | 
             
                         menu.choice opts[:prompt_script_to_clipboard], 3
         | 
| 406 631 | 
             
                         menu.choice opts[:prompt_save_script], 4
         | 
| 407 | 
            -
                       end) | 
| 632 | 
            +
                       end)
         | 
| 408 633 | 
             
                      allow = (sel == 1)
         | 
| 409 634 | 
             
                      if sel == 3
         | 
| 410 635 | 
             
                        text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
         | 
| @@ -414,13 +639,14 @@ module MarkdownExec | |
| 414 639 | 
             
                             " #{text.length} characters"
         | 
| 415 640 | 
             
                      end
         | 
| 416 641 | 
             
                      if sel == 4
         | 
| 417 | 
            -
                        write_command_file(opts.merge(save_executed_script: true), | 
| 642 | 
            +
                        write_command_file(opts.merge(save_executed_script: true),
         | 
| 643 | 
            +
                                           required_blocks)
         | 
| 418 644 | 
             
                        fout "File saved: #{@options[:saved_filespec]}"
         | 
| 419 645 | 
             
                      end
         | 
| 420 646 | 
             
                      break if [1, 2].include? sel
         | 
| 421 647 | 
             
                    end
         | 
| 422 648 | 
             
                  end
         | 
| 423 | 
            -
                  (opts[:ir_approve] = allow) | 
| 649 | 
            +
                  (opts[:ir_approve] = allow)
         | 
| 424 650 |  | 
| 425 651 | 
             
                  selected = mdoc.get_block_by_name opts[:block_name]
         | 
| 426 652 |  | 
| @@ -435,21 +661,25 @@ module MarkdownExec | |
| 435 661 | 
             
                  selected[:name]
         | 
| 436 662 | 
             
                end
         | 
| 437 663 |  | 
| 438 | 
            -
                 | 
| 439 | 
            -
             | 
| 440 | 
            -
             | 
| 664 | 
            +
                def cfile
         | 
| 665 | 
            +
            # puts @options.inspect
         | 
| 666 | 
            +
            # binding.pry
         | 
| 667 | 
            +
                  @cfile ||= CachedNestedFileReader.new(import_pattern: @options.fetch(:import_pattern))
         | 
| 668 | 
            +
                  # @cfile ||= CachedNestedFileReader.new(import_pattern: /^ *#insert (.+)$/)
         | 
| 669 | 
            +
                end
         | 
| 441 670 |  | 
| 442 671 | 
             
                # :reek:DuplicateMethodCall
         | 
| 443 672 | 
             
                # :reek:UncommunicativeVariableName { exclude: [ e ] }
         | 
| 444 673 | 
             
                # :reek:LongYieldList
         | 
| 445 674 | 
             
                def command_execute(opts, command)
         | 
| 446 | 
            -
                  # dd = lambda { |s| puts 'command_execute() ' + s }
         | 
| 447 675 | 
             
                  #d 'execute command and yield outputs'
         | 
| 448 676 | 
             
                  @execute_files = Hash.new([])
         | 
| 449 677 | 
             
                  @execute_options = opts
         | 
| 450 678 | 
             
                  @execute_started_at = Time.now.utc
         | 
| 451 679 |  | 
| 452 | 
            -
                   | 
| 680 | 
            +
                  args = []
         | 
| 681 | 
            +
                  Open3.popen3(@options[:shell], '-c',
         | 
| 682 | 
            +
                               command, ARGV[0], *args) do |stdin, stdout, stderr, exec_thr|
         | 
| 453 683 | 
             
                    #d 'command started'
         | 
| 454 684 | 
             
                    Thread.new do
         | 
| 455 685 | 
             
                      until (line = stdout.gets).nil?
         | 
| @@ -495,26 +725,26 @@ module MarkdownExec | |
| 495 725 | 
             
                  end
         | 
| 496 726 | 
             
                  #d 'command completed'
         | 
| 497 727 | 
             
                  @execute_completed_at = Time.now.utc
         | 
| 498 | 
            -
                rescue Errno::ENOENT =>  | 
| 728 | 
            +
                rescue Errno::ENOENT => err
         | 
| 499 729 | 
             
                  #d 'command error ENOENT triggered by missing command in script'
         | 
| 500 730 | 
             
                  @execute_aborted_at = Time.now.utc
         | 
| 501 | 
            -
                  @execute_error_message =  | 
| 502 | 
            -
                  @execute_error =  | 
| 731 | 
            +
                  @execute_error_message = err.message
         | 
| 732 | 
            +
                  @execute_error = err
         | 
| 503 733 | 
             
                  @execute_files[EF_STDERR] += [@execute_error_message]
         | 
| 504 | 
            -
                  fout "Error ENOENT: #{ | 
| 505 | 
            -
                rescue SignalException =>  | 
| 734 | 
            +
                  fout "Error ENOENT: #{err.inspect}"
         | 
| 735 | 
            +
                rescue SignalException => err
         | 
| 506 736 | 
             
                  #d 'command SIGTERM triggered by user or system'
         | 
| 507 737 | 
             
                  @execute_aborted_at = Time.now.utc
         | 
| 508 738 | 
             
                  @execute_error_message = 'SIGTERM'
         | 
| 509 | 
            -
                  @execute_error =  | 
| 739 | 
            +
                  @execute_error = err
         | 
| 510 740 | 
             
                  @execute_files[EF_STDERR] += [@execute_error_message]
         | 
| 511 | 
            -
                  fout "Error ENOENT: #{ | 
| 741 | 
            +
                  fout "Error ENOENT: #{err.inspect}"
         | 
| 512 742 | 
             
                end
         | 
| 513 743 |  | 
| 514 744 | 
             
                def count_blocks_in_filename
         | 
| 515 745 | 
             
                  fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match]
         | 
| 516 746 | 
             
                  cnt = 0
         | 
| 517 | 
            -
                   | 
| 747 | 
            +
                  cfile.readlines(@options[:filename]).each do |line|
         | 
| 518 748 | 
             
                    cnt += 1 if line.match(fenced_start_and_end_match)
         | 
| 519 749 | 
             
                  end
         | 
| 520 750 | 
             
                  cnt / 2
         | 
| @@ -530,7 +760,7 @@ module MarkdownExec | |
| 530 760 |  | 
| 531 761 | 
             
                # :reek:DuplicateMethodCall
         | 
| 532 762 | 
             
                def exec_block(options, _block_name = '')
         | 
| 533 | 
            -
                  options =  | 
| 763 | 
            +
                  options = calculated_options.merge(options).tap_yaml 'options'
         | 
| 534 764 | 
             
                  update_options options, over: false
         | 
| 535 765 |  | 
| 536 766 | 
             
                  # document and block reports
         | 
| @@ -548,12 +778,16 @@ module MarkdownExec | |
| 548 778 | 
             
                    list_docs: -> { fout_list files },
         | 
| 549 779 | 
             
                    list_default_env: -> { fout_list list_default_env },
         | 
| 550 780 | 
             
                    list_recent_output: lambda {
         | 
| 551 | 
            -
                                          fout_list list_recent_output( | 
| 552 | 
            -
             | 
| 781 | 
            +
                                          fout_list list_recent_output(
         | 
| 782 | 
            +
                                            @options[:saved_stdout_folder],
         | 
| 783 | 
            +
                                            @options[:saved_stdout_glob], @options[:list_count]
         | 
| 784 | 
            +
                                          )
         | 
| 553 785 | 
             
                                        },
         | 
| 554 786 | 
             
                    list_recent_scripts: lambda {
         | 
| 555 | 
            -
                                           fout_list list_recent_scripts( | 
| 556 | 
            -
             | 
| 787 | 
            +
                                           fout_list list_recent_scripts(
         | 
| 788 | 
            +
                                             options[:saved_script_folder],
         | 
| 789 | 
            +
                                             options[:saved_script_glob], options[:list_count]
         | 
| 790 | 
            +
                                           )
         | 
| 557 791 | 
             
                                         },
         | 
| 558 792 | 
             
                    pwd: -> { fout File.expand_path('..', __dir__) },
         | 
| 559 793 | 
             
                    run_last_script: -> { run_last_script },
         | 
| @@ -572,41 +806,43 @@ module MarkdownExec | |
| 572 806 | 
             
                  # process
         | 
| 573 807 | 
             
                  #
         | 
| 574 808 | 
             
                  @options[:filename] = select_md_file(files)
         | 
| 575 | 
            -
                   | 
| 576 | 
            -
             | 
| 577 | 
            -
             | 
| 578 | 
            -
             | 
| 579 | 
            -
                   | 
| 809 | 
            +
                  select_approve_and_execute_block({
         | 
| 810 | 
            +
                                                     bash: true,
         | 
| 811 | 
            +
                                                     struct: true
         | 
| 812 | 
            +
                                                   })
         | 
| 813 | 
            +
                  return unless @options[:output_saved_script_filename]
         | 
| 814 | 
            +
             | 
| 815 | 
            +
                  fout "saved_filespec: #{@execute_script_filespec}"
         | 
| 816 | 
            +
                rescue StandardError => err
         | 
| 817 | 
            +
                  warn(error = "ERROR ** MarkParse.exec_block(); #{err.inspect}")
         | 
| 818 | 
            +
                  binding.pry if $tap_enable
         | 
| 819 | 
            +
                  raise ArgumentError, error
         | 
| 580 820 | 
             
                end
         | 
| 581 821 |  | 
| 582 | 
            -
                 | 
| 583 | 
            -
                 | 
| 822 | 
            +
                ## summarize blocks
         | 
| 823 | 
            +
                #
         | 
| 824 | 
            +
                def get_block_summary(call_options, fcb)
         | 
| 584 825 | 
             
                  opts = optsmerge call_options
         | 
| 585 | 
            -
                  return  | 
| 586 | 
            -
                  return [summarize_block(headings, block_title).merge({ body: block_body })] unless opts[:bash]
         | 
| 587 | 
            -
             | 
| 588 | 
            -
                  block_title.tap_inspect name: :block_title
         | 
| 589 | 
            -
                  call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
         | 
| 590 | 
            -
                                    .map { |scanned| scanned[1..] }
         | 
| 591 | 
            -
                                      &.first.tap_inspect name: :call
         | 
| 592 | 
            -
                  (titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
         | 
| 826 | 
            +
                  # return fcb.body unless opts[:struct]
         | 
| 593 827 |  | 
| 594 | 
            -
                   | 
| 595 | 
            -
                  reqs = titlexcall.scan(Regexp.new(opts[:block_required_scan]))
         | 
| 596 | 
            -
                                   .map { |scanned| scanned[1..] }
         | 
| 597 | 
            -
                  stdin = titlexcall.match(Regexp.new(opts[:block_stdin_scan])).tap_inspect name: :stdin
         | 
| 598 | 
            -
                  stdout = titlexcall.match(Regexp.new(opts[:block_stdout_scan])).tap_inspect name: :stdout
         | 
| 828 | 
            +
                  return fcb unless opts[:bash]
         | 
| 599 829 |  | 
| 600 | 
            -
                   | 
| 601 | 
            -
                   | 
| 602 | 
            -
             | 
| 603 | 
            -
             | 
| 604 | 
            -
             | 
| 605 | 
            -
             | 
| 830 | 
            +
                  fcb.call = fcb.title.match(Regexp.new(opts[:block_calls_scan]))&.fetch(1, nil)
         | 
| 831 | 
            +
                  titlexcall = if fcb.call
         | 
| 832 | 
            +
                                 fcb.title.sub("%#{fcb.call}", '')
         | 
| 833 | 
            +
                               else
         | 
| 834 | 
            +
                                 fcb.title
         | 
| 835 | 
            +
                               end
         | 
| 836 | 
            +
                  bm = option_match_groups(titlexcall, opts[:block_name_match])
         | 
| 837 | 
            +
                  fcb.stdin = option_match_groups(titlexcall, opts[:block_stdin_scan])
         | 
| 838 | 
            +
                  fcb.stdout = option_match_groups(titlexcall, opts[:block_stdout_scan])
         | 
| 839 | 
            +
                  fcb.title = fcb.name = (bm && bm[1] ? bm[:title] : titlexcall)
         | 
| 840 | 
            +
                  fcb
         | 
| 606 841 | 
             
                end
         | 
| 607 842 |  | 
| 608 843 | 
             
                # :reek:DuplicateMethodCall
         | 
| 609 844 | 
             
                # :reek:LongYieldList
         | 
| 845 | 
            +
                # :reek:NestedIterators
         | 
| 610 846 | 
             
                def iter_blocks_in_file(opts = {})
         | 
| 611 847 | 
             
                  # opts = optsmerge call_options, options_block
         | 
| 612 848 |  | 
| @@ -622,14 +858,15 @@ module MarkdownExec | |
| 622 858 |  | 
| 623 859 | 
             
                  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
         | 
| 624 860 | 
             
                  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
         | 
| 625 | 
            -
                   | 
| 626 | 
            -
                  block_body = nil
         | 
| 627 | 
            -
                  headings = []
         | 
| 861 | 
            +
                  fcb = FCB.new
         | 
| 628 862 | 
             
                  in_block = false
         | 
| 863 | 
            +
                  headings = []
         | 
| 629 864 |  | 
| 865 | 
            +
                  ## get type of messages to select
         | 
| 866 | 
            +
                  #
         | 
| 630 867 | 
             
                  selected_messages = yield :filter
         | 
| 631 868 |  | 
| 632 | 
            -
                   | 
| 869 | 
            +
                  cfile.readlines(opts[:filename]).each.with_index do |line, _line_num|
         | 
| 633 870 | 
             
                    continue unless line
         | 
| 634 871 |  | 
| 635 872 | 
             
                    if opts[:menu_blocks_with_headings]
         | 
| @@ -644,73 +881,130 @@ module MarkdownExec | |
| 644 881 |  | 
| 645 882 | 
             
                    if line.match(fenced_start_and_end_match)
         | 
| 646 883 | 
             
                      if in_block
         | 
| 647 | 
            -
                         | 
| 648 | 
            -
             | 
| 649 | 
            -
             | 
| 650 | 
            -
             | 
| 651 | 
            -
                           | 
| 652 | 
            -
             | 
| 884 | 
            +
                        # end fcb
         | 
| 885 | 
            +
                        #
         | 
| 886 | 
            +
                        fcb.name = fcb.title || ''
         | 
| 887 | 
            +
                        if fcb.body
         | 
| 888 | 
            +
                          if fcb.title.nil? || fcb.title.empty?
         | 
| 889 | 
            +
                            fcb.title = fcb.body.join(' ').gsub(/  +/, ' ')[0..64]
         | 
| 890 | 
            +
                          end
         | 
| 891 | 
            +
             | 
| 892 | 
            +
                          if block_given? && selected_messages.include?(:blocks) &&
         | 
| 893 | 
            +
                             Filter.fcb_select?(opts, fcb)
         | 
| 894 | 
            +
                            yield :blocks, fcb
         | 
| 895 | 
            +
                          end
         | 
| 653 896 | 
             
                        end
         | 
| 654 897 | 
             
                        in_block = false
         | 
| 655 | 
            -
                        block_title = ''
         | 
| 656 898 | 
             
                      else
         | 
| 657 | 
            -
                        # start  | 
| 899 | 
            +
                        # start fcb
         | 
| 658 900 | 
             
                        #
         | 
| 659 | 
            -
                        lm = line.match(fenced_start_ex)
         | 
| 660 | 
            -
                        block_allow = false
         | 
| 661 | 
            -
                        if opts[:bash_only]
         | 
| 662 | 
            -
                          block_allow = true if lm && (lm[:shell] == 'bash')
         | 
| 663 | 
            -
                        else
         | 
| 664 | 
            -
                          block_allow = true
         | 
| 665 | 
            -
                          block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
         | 
| 666 | 
            -
                        end
         | 
| 667 | 
            -
             | 
| 668 901 | 
             
                        in_block = true
         | 
| 669 | 
            -
             | 
| 670 | 
            -
             | 
| 671 | 
            -
             | 
| 672 | 
            -
                         | 
| 902 | 
            +
             | 
| 903 | 
            +
                        fcb_title_groups = line.match(fenced_start_ex).named_captures.sym_keys
         | 
| 904 | 
            +
                        fcb = FCB.new
         | 
| 905 | 
            +
                        fcb.headings = headings
         | 
| 906 | 
            +
                        fcb.name = fcb_title_groups.fetch(:name, '')
         | 
| 907 | 
            +
                        fcb.shell = fcb_title_groups.fetch(:shell, '')
         | 
| 908 | 
            +
                        fcb.title = fcb_title_groups.fetch(:name, '')
         | 
| 909 | 
            +
             | 
| 910 | 
            +
                        # selected fcb
         | 
| 911 | 
            +
                        #
         | 
| 912 | 
            +
                        fcb.body = []
         | 
| 913 | 
            +
             | 
| 914 | 
            +
                        rest = fcb_title_groups.fetch(:rest, '')
         | 
| 915 | 
            +
                        fcb.reqs = rest.scan(/\+[^\s]+/).map { |req| req[1..-1] }
         | 
| 916 | 
            +
             | 
| 917 | 
            +
                        fcb.call = rest.match(Regexp.new(opts[:block_calls_scan]))&.to_a&.first
         | 
| 918 | 
            +
                        fcb.stdin = if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
         | 
| 919 | 
            +
                                      tn.named_captures.sym_keys
         | 
| 920 | 
            +
                                    end
         | 
| 921 | 
            +
                        fcb.stdout = if (tn = rest.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/))
         | 
| 922 | 
            +
                                       tn.named_captures.sym_keys
         | 
| 923 | 
            +
                                     end
         | 
| 673 924 | 
             
                      end
         | 
| 674 | 
            -
                    elsif  | 
| 675 | 
            -
                       | 
| 925 | 
            +
                    elsif in_block && fcb.body
         | 
| 926 | 
            +
                      dp 'append line to fcb body'
         | 
| 927 | 
            +
                      fcb.body += [line.chomp]
         | 
| 676 928 | 
             
                    elsif block_given? && selected_messages.include?(:line)
         | 
| 677 | 
            -
                       | 
| 678 | 
            -
                       | 
| 679 | 
            -
                       | 
| 929 | 
            +
                      dp 'text outside of fcb'
         | 
| 930 | 
            +
                      fcb = FCB.new
         | 
| 931 | 
            +
                      fcb.body = [line]
         | 
| 932 | 
            +
                      yield :line, fcb
         | 
| 680 933 | 
             
                    end
         | 
| 681 934 | 
             
                  end
         | 
| 682 935 | 
             
                end
         | 
| 683 936 |  | 
| 937 | 
            +
                # return body, title if option.struct
         | 
| 938 | 
            +
                # return body if not struct
         | 
| 939 | 
            +
                #
         | 
| 684 940 | 
             
                def list_blocks_in_file(call_options = {}, &options_block)
         | 
| 685 | 
            -
                  opts = optsmerge | 
| 686 | 
            -
             | 
| 941 | 
            +
                  opts = optsmerge(call_options, options_block) #.tap_yaml 'opts'
         | 
| 687 942 | 
             
                  blocks = []
         | 
| 688 943 | 
             
                  if opts[:menu_initial_divider].present?
         | 
| 689 | 
            -
                    blocks  | 
| 690 | 
            -
             | 
| 691 | 
            -
             | 
| 692 | 
            -
             | 
| 944 | 
            +
                    blocks.push FCB.new({
         | 
| 945 | 
            +
                                          # name: '',
         | 
| 946 | 
            +
                                          chrome: true,
         | 
| 947 | 
            +
                                          text: format(
         | 
| 948 | 
            +
                                            opts[:menu_divider_format],
         | 
| 949 | 
            +
                                            opts[:menu_initial_divider]
         | 
| 950 | 
            +
                                          ).send(opts[:menu_divider_color].to_sym),
         | 
| 951 | 
            +
                                          disabled: '' # __LINE__.to_s
         | 
| 952 | 
            +
                                        })
         | 
| 693 953 | 
             
                  end
         | 
| 694 | 
            -
             | 
| 954 | 
            +
             | 
| 955 | 
            +
                  iter_blocks_in_file(opts) do |btype, fcb|
         | 
| 956 | 
            +
                    # binding.pry
         | 
| 695 957 | 
             
                    case btype
         | 
| 696 958 | 
             
                    when :filter
         | 
| 959 | 
            +
                      ## return type of blocks to select
         | 
| 960 | 
            +
                      #
         | 
| 697 961 | 
             
                      %i[blocks line]
         | 
| 962 | 
            +
             | 
| 698 963 | 
             
                    when :line
         | 
| 699 | 
            -
                       | 
| 700 | 
            -
             | 
| 701 | 
            -
             | 
| 964 | 
            +
                      ## convert line to block
         | 
| 965 | 
            +
                      #
         | 
| 966 | 
            +
                      # binding.pry
         | 
| 967 | 
            +
                      if opts[:menu_divider_match].present? &&
         | 
| 968 | 
            +
                         (mbody = fcb.body[0].match opts[:menu_divider_match])
         | 
| 969 | 
            +
                        # binding.pry
         | 
| 970 | 
            +
                        blocks.push FCB.new(
         | 
| 971 | 
            +
                          { chrome: true,
         | 
| 972 | 
            +
                            disabled: '',
         | 
| 973 | 
            +
                            text: format(opts[:menu_divider_format],
         | 
| 974 | 
            +
                                         mbody[:name]).send(opts[:menu_divider_color].to_sym) }
         | 
| 975 | 
            +
                        )
         | 
| 976 | 
            +
                      elsif opts[:menu_task_match].present? &&
         | 
| 977 | 
            +
                            (mbody = fcb.body[0].match opts[:menu_task_match])
         | 
| 978 | 
            +
                        blocks.push FCB.new(
         | 
| 979 | 
            +
                          { chrome: true,
         | 
| 980 | 
            +
                            disabled: '',
         | 
| 981 | 
            +
                            text: format(opts[:menu_task_format],
         | 
| 982 | 
            +
                                         mbody[:name]).send(opts[:menu_task_color].to_sym) }
         | 
| 983 | 
            +
                        )
         | 
| 984 | 
            +
                      else
         | 
| 985 | 
            +
                        # line not added
         | 
| 702 986 | 
             
                      end
         | 
| 703 987 | 
             
                    when :blocks
         | 
| 704 | 
            -
                       | 
| 988 | 
            +
                      ## enhance fcb with block summary
         | 
| 989 | 
            +
                      #
         | 
| 990 | 
            +
                      blocks.push get_block_summary(opts, fcb) ### if Filter.fcb_select? opts, fcb
         | 
| 705 991 | 
             
                    end
         | 
| 706 992 | 
             
                  end
         | 
| 993 | 
            +
             | 
| 707 994 | 
             
                  if opts[:menu_divider_format].present? && opts[:menu_final_divider].present?
         | 
| 708 | 
            -
                    blocks  | 
| 709 | 
            -
                       | 
| 710 | 
            -
             | 
| 711 | 
            -
             | 
| 995 | 
            +
                    blocks.push FCB.new(
         | 
| 996 | 
            +
                      { chrome: true,
         | 
| 997 | 
            +
                        disabled: '',
         | 
| 998 | 
            +
                        text: format(opts[:menu_divider_format],
         | 
| 999 | 
            +
                                     opts[:menu_final_divider])
         | 
| 1000 | 
            +
                                             .send(opts[:menu_divider_color].to_sym) }
         | 
| 1001 | 
            +
                    )
         | 
| 712 1002 | 
             
                  end
         | 
| 713 | 
            -
                  blocks. | 
| 1003 | 
            +
                  blocks.tap_inspect
         | 
| 1004 | 
            +
                rescue StandardError => err
         | 
| 1005 | 
            +
                  warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
         | 
| 1006 | 
            +
                  warn(caller[0..4])
         | 
| 1007 | 
            +
                  raise StandardError, error
         | 
| 714 1008 | 
             
                end
         | 
| 715 1009 |  | 
| 716 1010 | 
             
                def list_default_env
         | 
| @@ -741,75 +1035,77 @@ module MarkdownExec | |
| 741 1035 | 
             
                    specified_folder: options[:path],
         | 
| 742 1036 | 
             
                    default_filename: 'README.md',
         | 
| 743 1037 | 
             
                    default_folder: '.'
         | 
| 744 | 
            -
                  ) | 
| 1038 | 
            +
                  )
         | 
| 745 1039 | 
             
                end
         | 
| 746 1040 |  | 
| 747 1041 | 
             
                # :reek:LongParameterList
         | 
| 748 1042 | 
             
                def list_files_specified(specified_filename: nil, specified_folder: nil,
         | 
| 749 1043 | 
             
                                         default_filename: nil, default_folder: nil, filetree: nil)
         | 
| 750 1044 | 
             
                  fn = File.join(if specified_filename&.present?
         | 
| 751 | 
            -
                                   # puts ' LFS 01'
         | 
| 752 1045 | 
             
                                   if specified_filename.start_with? '/'
         | 
| 753 | 
            -
                                     # puts ' LFS 02'
         | 
| 754 1046 | 
             
                                     [specified_filename]
         | 
| 755 1047 | 
             
                                   elsif specified_folder&.present?
         | 
| 756 | 
            -
                                     # puts ' LFS 03'
         | 
| 757 1048 | 
             
                                     [specified_folder, specified_filename]
         | 
| 758 1049 | 
             
                                   else
         | 
| 759 | 
            -
                                     # puts ' LFS 04'
         | 
| 760 1050 | 
             
                                     [default_folder, specified_filename]
         | 
| 761 1051 | 
             
                                   end
         | 
| 762 1052 | 
             
                                 elsif specified_folder&.present?
         | 
| 763 | 
            -
                                   # puts ' LFS 05'
         | 
| 764 1053 | 
             
                                   if filetree
         | 
| 765 | 
            -
                                     # puts ' LFS 06'
         | 
| 766 1054 | 
             
                                     [specified_folder, @options[:md_filename_match]]
         | 
| 767 1055 | 
             
                                   else
         | 
| 768 | 
            -
                                     # puts ' LFS 07'
         | 
| 769 1056 | 
             
                                     [specified_folder, @options[:md_filename_glob]]
         | 
| 770 1057 | 
             
                                   end
         | 
| 771 1058 | 
             
                                 else
         | 
| 772 | 
            -
                                   # puts ' LFS 08'
         | 
| 773 1059 | 
             
                                   [default_folder, default_filename]
         | 
| 774 | 
            -
                                 end) | 
| 1060 | 
            +
                                 end)
         | 
| 775 1061 | 
             
                  if filetree
         | 
| 776 | 
            -
                    filetree.select  | 
| 1062 | 
            +
                    filetree.select do |filename|
         | 
| 1063 | 
            +
                      filename == fn || filename.match(/^#{fn}$/) || filename.match(%r{^#{fn}/.+$})
         | 
| 1064 | 
            +
                    end
         | 
| 777 1065 | 
             
                  else
         | 
| 778 1066 | 
             
                    Dir.glob(fn)
         | 
| 779 | 
            -
                  end | 
| 1067 | 
            +
                  end
         | 
| 780 1068 | 
             
                end
         | 
| 781 1069 |  | 
| 782 1070 | 
             
                def list_markdown_files_in_path
         | 
| 783 | 
            -
                  Dir.glob(File.join(@options[:path], | 
| 1071 | 
            +
                  Dir.glob(File.join(@options[:path],
         | 
| 1072 | 
            +
                                     @options[:md_filename_glob]))
         | 
| 784 1073 | 
             
                end
         | 
| 785 1074 |  | 
| 786 | 
            -
                def  | 
| 787 | 
            -
                  opts | 
| 788 | 
            -
             | 
| 789 | 
            -
                   | 
| 790 | 
            -
             | 
| 791 | 
            -
             | 
| 792 | 
            -
             | 
| 793 | 
            -
             | 
| 1075 | 
            +
                def blocks_per_opts(blocks, opts)
         | 
| 1076 | 
            +
                  if opts[:struct]
         | 
| 1077 | 
            +
                    blocks
         | 
| 1078 | 
            +
                  else
         | 
| 1079 | 
            +
                    # blocks.map(&:name)
         | 
| 1080 | 
            +
                    blocks.map do |block|
         | 
| 1081 | 
            +
                      block.fetch(:text, nil) || block.fetch(:name, nil)
         | 
| 1082 | 
            +
                    end
         | 
| 1083 | 
            +
                  end.compact.reject(&:empty?).tap_inspect
         | 
| 794 1084 | 
             
                end
         | 
| 795 1085 |  | 
| 796 | 
            -
                 | 
| 797 | 
            -
             | 
| 798 | 
            -
                 | 
| 1086 | 
            +
                ## output type (body string or full object) per option struct and bash
         | 
| 1087 | 
            +
                #
         | 
| 1088 | 
            +
                def list_named_blocks_in_file(call_options = {}, &options_block)
         | 
| 1089 | 
            +
                  opts = optsmerge call_options, options_block
         | 
| 799 1090 |  | 
| 800 | 
            -
             | 
| 801 | 
            -
             | 
| 1091 | 
            +
                  blocks = list_blocks_in_file(opts.merge(struct: true)).select do |fcb|
         | 
| 1092 | 
            +
                    # fcb.fetch(:name, '') != '' && Filter.fcb_select?(opts, fcb)
         | 
| 1093 | 
            +
                    Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
         | 
| 1094 | 
            +
                  end
         | 
| 1095 | 
            +
                  blocks_per_opts(blocks, opts).tap_inspect
         | 
| 802 1096 | 
             
                end
         | 
| 803 1097 |  | 
| 804 1098 | 
             
                def make_block_labels(call_options = {})
         | 
| 805 1099 | 
             
                  opts = options.merge(call_options)
         | 
| 806 | 
            -
                  list_blocks_in_file(opts).map do | | 
| 1100 | 
            +
                  list_blocks_in_file(opts).map do |fcb|
         | 
| 807 1101 | 
             
                    BlockLabel.new(filename: opts[:filename],
         | 
| 808 | 
            -
                                   headings:  | 
| 1102 | 
            +
                                   headings: fcb.fetch(:headings, []),
         | 
| 809 1103 | 
             
                                   menu_blocks_with_docname: opts[:menu_blocks_with_docname],
         | 
| 810 1104 | 
             
                                   menu_blocks_with_headings: opts[:menu_blocks_with_headings],
         | 
| 811 | 
            -
                                   title:  | 
| 812 | 
            -
             | 
| 1105 | 
            +
                                   title: fcb[:title],
         | 
| 1106 | 
            +
                                   text: fcb[:text],
         | 
| 1107 | 
            +
                                   body: fcb[:body]).make
         | 
| 1108 | 
            +
                  end.compact
         | 
| 813 1109 | 
             
                end
         | 
| 814 1110 |  | 
| 815 1111 | 
             
                # :reek:DuplicateMethodCall
         | 
| @@ -843,7 +1139,9 @@ module MarkdownExec | |
| 843 1139 | 
             
                                      fout options.sort_by_key.to_yaml
         | 
| 844 1140 | 
             
                                    }
         | 
| 845 1141 | 
             
                                  when 'val_as_bool'
         | 
| 846 | 
            -
                                     | 
| 1142 | 
            +
                                    lambda { |value|
         | 
| 1143 | 
            +
                                      value.instance_of?(::String) ? (value.chomp != '0') : value
         | 
| 1144 | 
            +
                                    }
         | 
| 847 1145 | 
             
                                  when 'val_as_int'
         | 
| 848 1146 | 
             
                                    ->(value) { value.to_i }
         | 
| 849 1147 | 
             
                                  when 'val_as_str'
         | 
| @@ -862,22 +1160,22 @@ module MarkdownExec | |
| 862 1160 | 
             
                end
         | 
| 863 1161 |  | 
| 864 1162 | 
             
                def menu_for_blocks(menu_options)
         | 
| 865 | 
            -
                  options =  | 
| 1163 | 
            +
                  options = calculated_options.merge menu_options
         | 
| 866 1164 | 
             
                  menu = []
         | 
| 867 | 
            -
                  iter_blocks_in_file(options) do |btype,  | 
| 1165 | 
            +
                  iter_blocks_in_file(options) do |btype, fcb|
         | 
| 868 1166 | 
             
                    case btype
         | 
| 869 1167 | 
             
                    when :filter
         | 
| 870 1168 | 
             
                      %i[blocks line]
         | 
| 871 1169 | 
             
                    when :line
         | 
| 872 | 
            -
                      if options[:menu_divider_match] && | 
| 873 | 
            -
             | 
| 1170 | 
            +
                      if options[:menu_divider_match] &&
         | 
| 1171 | 
            +
                         (mbody = fcb.body[0].match(options[:menu_divider_match]))
         | 
| 1172 | 
            +
                        menu.push FCB.new({ name: mbody[:name], disabled: '' })
         | 
| 874 1173 | 
             
                      end
         | 
| 875 1174 | 
             
                    when :blocks
         | 
| 876 | 
            -
                       | 
| 877 | 
            -
                      menu += [summ[0][:name]]
         | 
| 1175 | 
            +
                      menu += [fcb.name]
         | 
| 878 1176 | 
             
                    end
         | 
| 879 1177 | 
             
                  end
         | 
| 880 | 
            -
                  menu | 
| 1178 | 
            +
                  menu
         | 
| 881 1179 | 
             
                end
         | 
| 882 1180 |  | 
| 883 1181 | 
             
                def menu_iter(data = menu_for_optparse, &block)
         | 
| @@ -892,17 +1190,17 @@ module MarkdownExec | |
| 892 1190 | 
             
                  return unless item[:long_name].present? || item[:short_name].present?
         | 
| 893 1191 |  | 
| 894 1192 | 
             
                  opts.on(*[
         | 
| 895 | 
            -
                    # long name
         | 
| 1193 | 
            +
                    # - long name
         | 
| 896 1194 | 
             
                    if item[:long_name].present?
         | 
| 897 1195 | 
             
                      "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
         | 
| 898 1196 | 
             
                    end,
         | 
| 899 1197 |  | 
| 900 | 
            -
                    # short name
         | 
| 1198 | 
            +
                    # - short name
         | 
| 901 1199 | 
             
                    item[:short_name].present? ? "-#{item[:short_name]}" : nil,
         | 
| 902 1200 |  | 
| 903 | 
            -
                    # description and default
         | 
| 1201 | 
            +
                    # - description and default
         | 
| 904 1202 | 
             
                    [item[:description],
         | 
| 905 | 
            -
                      | 
| 1203 | 
            +
                     ("[#{value_for_cli item[:default]}]" if item[:default].present?)].compact.join('  '),
         | 
| 906 1204 |  | 
| 907 1205 | 
             
                    # apply proccode, if present, to value
         | 
| 908 1206 | 
             
                    # save value to options hash if option is named
         | 
| @@ -979,6 +1277,10 @@ module MarkdownExec | |
| 979 1277 | 
             
                  }
         | 
| 980 1278 | 
             
                end
         | 
| 981 1279 |  | 
| 1280 | 
            +
                ## tty prompt to select
         | 
| 1281 | 
            +
                # insert exit option at head or tail
         | 
| 1282 | 
            +
                # return selected option or nil
         | 
| 1283 | 
            +
                #
         | 
| 982 1284 | 
             
                def prompt_with_quit(prompt_text, items, opts = {})
         | 
| 983 1285 | 
             
                  exit_option = '* Exit'
         | 
| 984 1286 | 
             
                  all_items = if @options[:menu_exit_at_top]
         | 
| @@ -1006,7 +1308,8 @@ module MarkdownExec | |
| 1006 1308 |  | 
| 1007 1309 | 
             
                  ## read local configuration file
         | 
| 1008 1310 | 
             
                  #
         | 
| 1009 | 
            -
                  read_configuration_file! @options, | 
| 1311 | 
            +
                  read_configuration_file! @options,
         | 
| 1312 | 
            +
                                           ".#{MarkdownExec::APP_NAME.downcase}.yml"
         | 
| 1010 1313 |  | 
| 1011 1314 | 
             
                  @option_parser = option_parser = OptionParser.new do |opts|
         | 
| 1012 1315 | 
             
                    executable_name = File.basename($PROGRAM_NAME)
         | 
| @@ -1017,28 +1320,35 @@ module MarkdownExec | |
| 1017 1320 | 
             
                    ].join("\n")
         | 
| 1018 1321 |  | 
| 1019 1322 | 
             
                    menu_iter do |item|
         | 
| 1020 | 
            -
                      item.tap_yaml 'item'
         | 
| 1021 1323 | 
             
                      menu_option_append opts, options, item
         | 
| 1022 1324 | 
             
                    end
         | 
| 1023 1325 | 
             
                  end
         | 
| 1024 | 
            -
                  option_parser.load # filename defaults to basename of the program | 
| 1025 | 
            -
                   | 
| 1026 | 
            -
                   | 
| 1326 | 
            +
                  option_parser.load # filename defaults to basename of the program
         | 
| 1327 | 
            +
                  # without suffix in a directory ~/.options
         | 
| 1328 | 
            +
                  option_parser.environment # env defaults to the basename of the program
         | 
| 1329 | 
            +
                  # child_argv = arguments_for_child
         | 
| 1330 | 
            +
                  rest = option_parser.parse!(arguments_for_mde) # (into: options)
         | 
| 1027 1331 |  | 
| 1028 1332 | 
             
                  begin
         | 
| 1029 1333 | 
             
                    options_finalize rest
         | 
| 1030 1334 | 
             
                    exec_block options, options[:block_name]
         | 
| 1031 | 
            -
                  rescue FileMissingError =>  | 
| 1032 | 
            -
                    puts "File missing: #{ | 
| 1335 | 
            +
                  rescue FileMissingError => err
         | 
| 1336 | 
            +
                    puts "File missing: #{err}"
         | 
| 1033 1337 | 
             
                  end
         | 
| 1338 | 
            +
                rescue StandardError => err
         | 
| 1339 | 
            +
                  warn(error = "ERROR ** MarkParse.run(); #{err.inspect}")
         | 
| 1340 | 
            +
                  binding.pry if $tap_enable
         | 
| 1341 | 
            +
                  raise ArgumentError, error
         | 
| 1034 1342 | 
             
                end
         | 
| 1035 1343 |  | 
| 1036 1344 | 
             
                def saved_name_split(name)
         | 
| 1037 | 
            -
                   | 
| 1345 | 
            +
                  # rubocop:disable Layout/LineLength
         | 
| 1346 | 
            +
                  mf = /#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/.match name
         | 
| 1347 | 
            +
                  # rubocop:enable Layout/LineLength
         | 
| 1038 1348 | 
             
                  return unless mf
         | 
| 1039 1349 |  | 
| 1040 | 
            -
                  @options[:block_name] = mf[:block] | 
| 1041 | 
            -
                  @options[:filename] = mf[:file].gsub(FNR12, FNR11) | 
| 1350 | 
            +
                  @options[:block_name] = mf[:block]
         | 
| 1351 | 
            +
                  @options[:filename] = mf[:file].gsub(FNR12, FNR11)
         | 
| 1042 1352 | 
             
                end
         | 
| 1043 1353 |  | 
| 1044 1354 | 
             
                def run_last_script
         | 
| @@ -1048,11 +1358,10 @@ module MarkdownExec | |
| 1048 1358 |  | 
| 1049 1359 | 
             
                  saved_name_split filename
         | 
| 1050 1360 | 
             
                  @options[:save_executed_script] = false
         | 
| 1051 | 
            -
                   | 
| 1361 | 
            +
                  select_approve_and_execute_block({})
         | 
| 1052 1362 | 
             
                end
         | 
| 1053 1363 |  | 
| 1054 1364 | 
             
                def save_execution_output
         | 
| 1055 | 
            -
                  @options.tap_inspect name: :options
         | 
| 1056 1365 | 
             
                  return unless @options[:save_execution_output]
         | 
| 1057 1366 |  | 
| 1058 1367 | 
             
                  @options[:logged_stdout_filename] =
         | 
| @@ -1061,9 +1370,11 @@ module MarkdownExec | |
| 1061 1370 | 
             
                                   prefix: @options[:logged_stdout_filename_prefix],
         | 
| 1062 1371 | 
             
                                   time: Time.now.utc).stdout_name
         | 
| 1063 1372 |  | 
| 1064 | 
            -
                  @options[:logged_stdout_filespec] = | 
| 1373 | 
            +
                  @options[:logged_stdout_filespec] =
         | 
| 1374 | 
            +
                    File.join @options[:saved_stdout_folder],
         | 
| 1375 | 
            +
                              @options[:logged_stdout_filename]
         | 
| 1065 1376 | 
             
                  @logged_stdout_filespec = @options[:logged_stdout_filespec]
         | 
| 1066 | 
            -
                  (dirname = File.dirname(@options[:logged_stdout_filespec])) | 
| 1377 | 
            +
                  (dirname = File.dirname(@options[:logged_stdout_filespec]))
         | 
| 1067 1378 | 
             
                  Dir.mkdir_p dirname
         | 
| 1068 1379 |  | 
| 1069 1380 | 
             
                  ol = ["-STDOUT-\n"]
         | 
| @@ -1076,40 +1387,61 @@ module MarkdownExec | |
| 1076 1387 | 
             
                  File.write(@options[:logged_stdout_filespec], ol.join)
         | 
| 1077 1388 | 
             
                end
         | 
| 1078 1389 |  | 
| 1079 | 
            -
                def  | 
| 1390 | 
            +
                def select_approve_and_execute_block(call_options, &options_block)
         | 
| 1080 1391 | 
             
                  opts = optsmerge call_options, options_block
         | 
| 1081 | 
            -
                  blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect | 
| 1082 | 
            -
                  mdoc = MDoc.new(blocks_in_file)  | 
| 1083 | 
            -
             | 
| 1392 | 
            +
                  blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
         | 
| 1393 | 
            +
                  mdoc = MDoc.new(blocks_in_file) do |nopts|
         | 
| 1394 | 
            +
                    opts.merge!(nopts)
         | 
| 1395 | 
            +
                  end
         | 
| 1396 | 
            +
                  blocks_menu = mdoc.fcbs_per_options(opts.merge(struct: true))
         | 
| 1084 1397 |  | 
| 1085 1398 | 
             
                  repeat_menu = true && !opts[:block_name].present?
         | 
| 1086 1399 | 
             
                  loop do
         | 
| 1087 1400 | 
             
                    unless opts[:block_name].present?
         | 
| 1088 1401 | 
             
                      pt = (opts[:prompt_select_block]).to_s
         | 
| 1089 1402 |  | 
| 1090 | 
            -
                      blocks_menu. | 
| 1091 | 
            -
                        next if  | 
| 1092 | 
            -
             | 
| 1093 | 
            -
             | 
| 1094 | 
            -
                         | 
| 1095 | 
            -
             | 
| 1096 | 
            -
             | 
| 1097 | 
            -
             | 
| 1098 | 
            -
             | 
| 1099 | 
            -
             | 
| 1100 | 
            -
             | 
| 1101 | 
            -
             | 
| 1102 | 
            -
             | 
| 1403 | 
            +
                      bm = blocks_menu.map do |fcb|
         | 
| 1404 | 
            +
                        # next if fcb.fetch(:disabled, false)
         | 
| 1405 | 
            +
                        # next unless fcb.fetch(:name, '').present?
         | 
| 1406 | 
            +
             | 
| 1407 | 
            +
                        fcb.merge!(
         | 
| 1408 | 
            +
                          label: BlockLabel.new(
         | 
| 1409 | 
            +
                            body: fcb[:body],
         | 
| 1410 | 
            +
                            filename: opts[:filename],
         | 
| 1411 | 
            +
                            headings: fcb.fetch(:headings, []),
         | 
| 1412 | 
            +
                            menu_blocks_with_docname: opts[:menu_blocks_with_docname],
         | 
| 1413 | 
            +
                            menu_blocks_with_headings: opts[:menu_blocks_with_headings],
         | 
| 1414 | 
            +
                            text: fcb[:text],
         | 
| 1415 | 
            +
                            title: fcb[:title]
         | 
| 1416 | 
            +
                          ).make
         | 
| 1417 | 
            +
                        )
         | 
| 1418 | 
            +
             | 
| 1419 | 
            +
                        fcb.to_h
         | 
| 1420 | 
            +
                      end.compact
         | 
| 1421 | 
            +
                      return nil if bm.count.zero?
         | 
| 1422 | 
            +
             | 
| 1423 | 
            +
                      # binding.pry
         | 
| 1424 | 
            +
             | 
| 1425 | 
            +
                      sel = prompt_with_quit pt, bm,
         | 
| 1426 | 
            +
                                             per_page: opts[:select_page_height]
         | 
| 1103 1427 | 
             
                      return nil if sel.nil?
         | 
| 1104 1428 |  | 
| 1105 | 
            -
                       | 
| 1106 | 
            -
                       | 
| 1429 | 
            +
                      ## store selected option
         | 
| 1430 | 
            +
                      #
         | 
| 1431 | 
            +
                      label_block = blocks_in_file.select do |fcb|
         | 
| 1432 | 
            +
                        fcb[:label] == sel
         | 
| 1433 | 
            +
                      end.fetch(0, nil)
         | 
| 1434 | 
            +
                      opts[:block_name] = @options[:block_name] = label_block.fetch(:name, '')
         | 
| 1107 1435 | 
             
                    end
         | 
| 1108 | 
            -
                     | 
| 1436 | 
            +
                    approve_and_execute_block opts, mdoc
         | 
| 1109 1437 | 
             
                    break unless repeat_menu
         | 
| 1110 1438 |  | 
| 1111 1439 | 
             
                    opts[:block_name] = ''
         | 
| 1112 1440 | 
             
                  end
         | 
| 1441 | 
            +
                rescue StandardError => err
         | 
| 1442 | 
            +
                  warn(error = "ERROR ** MarkParse.select_approve_and_execute_block(); #{err.inspect}")
         | 
| 1443 | 
            +
                  binding.pry if $tap_enable
         | 
| 1444 | 
            +
                  raise ArgumentError, error
         | 
| 1113 1445 | 
             
                end
         | 
| 1114 1446 |  | 
| 1115 1447 | 
             
                def select_md_file(files = list_markdown_files_in_path)
         | 
| @@ -1117,7 +1449,8 @@ module MarkdownExec | |
| 1117 1449 | 
             
                  if (count = files.count) == 1
         | 
| 1118 1450 | 
             
                    files[0]
         | 
| 1119 1451 | 
             
                  elsif count >= 2
         | 
| 1120 | 
            -
                    prompt_with_quit opts[:prompt_select_md].to_s, files, | 
| 1452 | 
            +
                    prompt_with_quit opts[:prompt_select_md].to_s, files,
         | 
| 1453 | 
            +
                                     per_page: opts[:select_page_height]
         | 
| 1121 1454 | 
             
                  end
         | 
| 1122 1455 | 
             
                end
         | 
| 1123 1456 |  | 
| @@ -1150,15 +1483,11 @@ module MarkdownExec | |
| 1150 1483 |  | 
| 1151 1484 | 
             
                  saved_name_split(filename)
         | 
| 1152 1485 |  | 
| 1153 | 
            -
                   | 
| 1154 | 
            -
             | 
| 1155 | 
            -
             | 
| 1156 | 
            -
             | 
| 1157 | 
            -
             | 
| 1158 | 
            -
                end
         | 
| 1159 | 
            -
             | 
| 1160 | 
            -
                def summarize_block(headings, title)
         | 
| 1161 | 
            -
                  { headings: headings, name: title, title: title }
         | 
| 1486 | 
            +
                  select_approve_and_execute_block({
         | 
| 1487 | 
            +
                                                     bash: true,
         | 
| 1488 | 
            +
                                                     save_executed_script: false,
         | 
| 1489 | 
            +
                                                     struct: true
         | 
| 1490 | 
            +
                                                   })
         | 
| 1162 1491 | 
             
                end
         | 
| 1163 1492 |  | 
| 1164 1493 | 
             
                def menu_export(data = menu_for_optparse)
         | 
| @@ -1182,7 +1511,7 @@ module MarkdownExec | |
| 1182 1511 | 
             
                  else
         | 
| 1183 1512 | 
             
                    @options.merge! opts
         | 
| 1184 1513 | 
             
                  end
         | 
| 1185 | 
            -
                  @options | 
| 1514 | 
            +
                  @options
         | 
| 1186 1515 | 
             
                end
         | 
| 1187 1516 |  | 
| 1188 1517 | 
             
                def write_command_file(call_options, required_blocks)
         |