Class: Commander::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/MrMurano/commands/completion.rb,
lib/MrMurano/ReCommander.rb

Overview

rubocop:disable Style/ClassAndModuleChildren

"Use nested module/class definitions instead of compact style."
except that nested style (class [::]Commander\nclass Runner)
does not work.

Constant Summary collapse

SHELL_TYPES =

The shells for which we have completion templates.

i[bash zsh].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#command_exitObject

Returns the value of attribute command_exit.



78
79
80
# File 'lib/MrMurano/ReCommander.rb', line 78

def command_exit
  @command_exit
end

Instance Method Details

#args_without_command_nameObject



137
138
139
140
141
# File 'lib/MrMurano/ReCommander.rb', line 137

def args_without_command_name
  args_sans = old_args_without_command_name
  args_sans += ['--'] + @positional unless @positional.empty?
  args_sans
end

#cleanup_args_simple_command_helpObject



322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/MrMurano/ReCommander.rb', line 322

def cleanup_args_simple_command_help
  # This is a single-word command, e.g., 'link', not 'link list',
  #   as in `murano link --help`, not `murano link list --help`.
  # Positional parameters break Commander. E.g.,
  #   $ murano --help config application.id
  #   invalid command. Use --help for more information
  # so remove any remaining --options and use the first term.
  @args -= help_opts
  @args = @args[0..0]
  # Add back in the --help if the command is not a subcommand help.
  @args.push('--help') unless active_command.subcmdgrouphelp
end

#cmdMaxDepthObject

Get maximum depth of sub-commands.



78
79
80
81
82
83
84
85
# File 'lib/MrMurano/commands/completion.rb', line 78

def cmdMaxDepth
  depth = 0
  @commands.keys.sort.each do |name|
    levels = name.split
    depth = levels.count if levels.count > depth
  end
  depth
end

#cmdTreeObject

Get a tree of all commands and sub commands



62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/MrMurano/commands/completion.rb', line 62

def cmdTree
  tree = {}
  @commands.sort.each do |name, cmd|
    levels = name.split
    pos = tree
    levels.each do |step|
      pos[step] = {} unless pos.key? step
      pos = pos[step]
    end
    pos["\0cmd"] = cmd
  end
  tree
end

#cmdTreeBObject

Alternate tree of sub-commands.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/MrMurano/commands/completion.rb', line 89

def cmdTreeB
  tree = {}
  @commands.sort.each do |name, cmd|
    levels = name.split
    tree[levels.join(' ')] = { cmd: cmd }

    # load parent.
    left = levels[0..-2]
    right = levels[-1]
    key = left.join(' ')
    tree[key] = {} unless tree.key? key
    if tree[key].key?(:subs)
      tree[key][:subs] << right
    else
      tree[key][:subs] = [right]
    end
  end
  tree
end

#end_of_options_hackObject



178
179
180
181
182
183
184
# File 'lib/MrMurano/ReCommander.rb', line 178

def end_of_options_hack
  @positional = []
  i_positional = @args.index('--')
  return if i_positional.nil?
  @positional = @args[i_positional + 1..-1]
  @args = @args[0, i_positional]
end

#find_exact_match_maybe(arg, arg_opts) ⇒ Object



287
288
289
290
291
292
293
294
295
# File 'lib/MrMurano/ReCommander.rb', line 287

def find_exact_match_maybe(arg, arg_opts)
  return arg_opts unless arg =~ /^--/
  exact = arg_opts.select do |opt|
    opt[:switches].include?(arg) || opt[:switches].any? do |sw|
      sw.start_with?(arg + ' ')
    end
  end
  exact.empty? && exact || arg_opts
end

#find_matching_options(arg) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
# File 'lib/MrMurano/ReCommander.rb', line 275

def find_matching_options(arg)
  @options.select do |opt|
    if arg =~ /^-[^-]/
      # Single char abbrev: look for exact match.
      opt[:switches].include?(arg)
    else
      # Long switch: Commander matches abbrevs of longs...
      opt[:switches].any? { |oarg| oarg =~ /^#{arg}/ }
    end
  end
end

#fix_args_for_helpObject



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/MrMurano/ReCommander.rb', line 210

def fix_args_for_help
  # If `murano --help` is specified, let rb-commander handle it.
  # But if `murano command --help` is specified, don't let cmdr
  # handle it, otherwise it just shows the command description,
  # but not any of the subcommands (our SubCmdGroupContext code).
  # Note: not checking help_opts here, which includes 'help', because
  #   'help' might really be a command argument (e.g., a solution name).
  # Note: `murano -v`'s active_command is 'help'.
  do_help = (@args & %w[-h --help]).any? || active_command.name == 'help'
  trim_options_from_args if do_help
  @purargs = @args - help_opts
  return do_help if active_command.name == 'help'
  # Any command other than `murano help` or `murano --help`.
  return do_help if !do_help || active_command.name.include?(' ')
  # Command is not --help, or it's help for a single-word command.
  cleanup_args_simple_command_help
  do_help
end

#flatswitches(option) ⇒ Object

Change the ‘–[no-]foo’ switch into ‘–no-foo’ and ‘–foo’



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/MrMurano/commands/completion.rb', line 31

def flatswitches(option)
  # if there is a --[no-]foo format, break that into two switches.
  switches = option[:switches].map do |switch|
    switch = switch.sub(/\s.*$/, '') # drop argument spec if exists.
    if switch =~ /\[no-\]/
      [switch.sub(/\[no-\]/, ''), switch.gsub(/[\[\]]/, '')]
    else
      switch
    end
  end
  switches.flatten
end

#help_hackObject

2017-08-22: Commander’s help infrastructure is either really weak, or we did something elsewhere that seriously cripples it. In any case, this fixes all its quirks.



189
190
191
192
193
# File 'lib/MrMurano/ReCommander.rb', line 189

def help_hack
  do_help = fix_args_for_help
  show_alias_help_maybe! if do_help
  do_help
end

#help_optsObject



195
196
197
# File 'lib/MrMurano/ReCommander.rb', line 195

def help_opts
  %w[-h --help help].freeze
end

#must_be_sane_option!(arg_opts) ⇒ Object

Returns true if next arg is input to current –option.



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/MrMurano/ReCommander.rb', line 298

def must_be_sane_option!(arg_opts)
  if arg_opts.length > 1
    # MAYBE/2017-08-23: Always do this check, not just for help.
    ambig = MrMurano::Verbose.fancy_ticks(arg)
    match = arg_opts.map do |opt|
      opt[:switches].map { |sw| MrMurano::Verbose.fancy_ticks(sw) }.join('|')
    end
    match = match.flatten
    match[-1] = "and #{match[-1]}" if match.length > 1
    match = match.join(', ')
    MrMurano::Verbose.error("Ambiguous option: #{ambig} matches: #{match}")
    exit 2
  elsif arg_opts.length == 1
    # See if this option (the only option) has input.
    arg_opts.first[:switches].any? do |opt|
      # There may be a space in a --long option, e.g.,
      # '--config KEY=VAL', which means an argument follows.
      opt.start_with?('--') && opt.include?(' ')
    end
  else
    false
  end
end

#must_not_option_passed_first!Object



203
204
205
206
207
208
# File 'lib/MrMurano/ReCommander.rb', line 203

def must_not_option_passed_first!
  return unless @args[0].to_s.start_with? '-'
  return if %w[-h --help -v --version].include? @args[0]
  MrMurano::Verbose.warning('Usage: murano <sub-cmd> [<options>]')
  exit 1
end

#old_args_without_command_nameObject



136
# File 'lib/MrMurano/ReCommander.rb', line 136

alias old_args_without_command_name args_without_command_name

#old_parse_global_optionsObject



151
# File 'lib/MrMurano/ReCommander.rb', line 151

alias old_parse_global_options parse_global_options

#old_remove_global_optionsObject



143
# File 'lib/MrMurano/ReCommander.rb', line 143

alias old_remove_global_options remove_global_options

#old_run_active_commandObject

run_active_command is called by commander-rb’s at_exit hook. We override – monkey patch – it to do other stuff.



82
# File 'lib/MrMurano/ReCommander.rb', line 82

alias old_run_active_command run_active_command

#optionDesc(option) ⇒ Object

truncate the description of an option



56
57
58
# File 'lib/MrMurano/commands/completion.rb', line 56

def optionDesc(option)
  option[:description].sub(/\n.*$/, '')
end

#parse_global_optionsObject



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/MrMurano/ReCommander.rb', line 152

def parse_global_options
  # User can specify, e.g., status.options, to set options for specific commands.
  defopts = ($cfg["#{active_command.name}.options"] || '').split
  @args.push(*defopts)
  end_of_options_hack
  # Check if they are passing an option first
  must_not_option_passed_first!
  do_help = help_hack
  $cfg.validate_cmd_business_and_project(active_command) unless do_help
  parse_global_options_real
end

#parse_global_options_realObject



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/MrMurano/ReCommander.rb', line 164

def parse_global_options_real
  old_parse_global_options
rescue OptionParser::MissingArgument => err
  if err.message.start_with?('missing argument:')
    puts err.message
  else
    err_msg = MrMurano::Verbose.fancy_ticks(err.message)
    MrMurano::Verbose.error(
      "There was a problem interpreting the options: #{err_msg}"
    )
  end
  exit 2
end

#remove_global_options(options, args) ⇒ Object



144
145
146
147
148
149
# File 'lib/MrMurano/ReCommander.rb', line 144

def remove_global_options(options, args)
  # Look for sole '--' argument, which signals start of positionals.
  i_positional = args.index('--')
  args = args[0, i_positional] unless i_positional.nil?
  old_remove_global_options(options, args)
end

#run_active_commandObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/MrMurano/ReCommander.rb', line 83

def run_active_command
  exit @command_exit if defined?(@command_exit) && @command_exit
  $cfg.must_be_valid_values!

  section = active_command.name
  hooked = MrMurano::Hooked.new(section)
  hooked.check_run_pre_hook

  verify_solutions_unmanaged!

  begin
    old_run_active_command
  rescue LocalJumpError => _err
    # This happens when you `return` from a command, since
    # commands are blocks, and returning from a block would
    # really mean returning from the thing running the block,
    # which would be bad. So Ruby barfs instead.
    return
  rescue OptionParser::InvalidArgument => err
    MrMurano::Verbose.whirly_stop
    MrMurano::Verbose.error err.message
    exit 1
  rescue OptionParser::InvalidOption => err
    MrMurano::Verbose.whirly_stop
    MrMurano::Verbose.error err.message
    MrMurano::Verbose.error 'invalid command' if section == 'help'
    exit 1
  rescue OptionParser::MissingArgument => err
    MrMurano::Verbose.whirly_stop
    MrMurano::Verbose.error err.message
    exit 1
  rescue OptionParser::NeedlessArgument => err
    MrMurano::Verbose.whirly_stop
    MrMurano::Verbose.error err.message
    pattern = /^needless argument: --(?<arg>[_a-zA-Z0-9]+)=(?<val>.*)/
    md = pattern.match(err.message)
    unless md.nil?
      puts %(Try the option without the equals sign, e.g.,)
      puts %(  --#{md[:arg]} "#{md[:val]}")
    end
    exit 1
  rescue MrMurano::ConfigError => err
    # Clear whirly if it was running.
    MrMurano::Verbose.whirly_stop
    MrMurano::Verbose.error err.message
    exit 1
  rescue StandardError => _err
    raise
  end

  hooked.check_run_post_hook
end

#show_alias_help_maybe!Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/MrMurano/ReCommander.rb', line 335

def show_alias_help_maybe!
  return unless alias?(command_name_from_args) || (active_command.name == 'help' && @args.length > 1)
  # Why, oh why, Commander, do you flake out on aliases?
  # E.g.,
  #   $ murano product --help
  #   invalid command. Use --help for more information
  # Though it sometimes work, like with:
  #   $ murano --help product device enable
  # but only because Commander shows help for the 'device' command.
  # I.e., this doesn't work: `murano product push --help`
  # So we'll just roll our own help for aliases!
  @args -= help_opts
  cli_cmd = MrMurano::Verbose.fancy_ticks(@purargs.join(' '))
  if active_command.name == 'help'
    arg_cmd = @args.join(' ')
  else
    arg_cmd = command_name_from_args
  end
  mur_msg = ''
  if @aliases[arg_cmd].nil?
    matches = @aliases.keys.find_all { |key| key.start_with?('arg_cmd') }
    matches = @aliases.keys.find_all { |key| key.start_with?(@args[0]) } if matches.empty?
    unless matches.empty?
      matches = matches.map { |match| MrMurano::Verbose.fancy_ticks(match) }
      matches = matches.sort.join(', ')
      mur_msg = %(The #{cli_cmd} command includes: #{matches})
    end
  else
    mur_cmd = []
    mur_cmd += [active_command.name] if active_command.name != 'help'
    mur_cmd += @aliases[arg_cmd] unless @aliases[arg_cmd].empty?
    mur_cmd = mur_cmd.join(' ')
    #mur_cmd = active_command.name if mur_cmd.empty?
    mur_cmd = MrMurano::Verbose.fancy_ticks(mur_cmd)
    mur_msg = %(The #{cli_cmd} command is really: #{mur_cmd})
  end
  return if mur_msg.empty?
  puts mur_msg
  exit 0
end

#takesArg(option, yes = '=', no = '') ⇒ Object

If the switches take an argument, return =



46
47
48
49
50
51
52
# File 'lib/MrMurano/commands/completion.rb', line 46

def takesArg(option, yes='=', no='')
  if option[:switches].select { |switch| switch =~ /\s\S+$/ }.empty?
    no
  else
    yes
  end
end

#trim_options_from_argsObject

If there are options in addition to –help, then Commander runs the command! So remove all options.

E.g., this shows the help for usage:

$ murano --help usage

but if a flag is added, the command gets run, e.g.,

$ murano usage --id 1234 --help

runs the usage command.

(lb): I walked the code and it looks like when Commander tries to validate the args, it doesn’t like the –id flag (maybe because the “help” command is being used to validate, and it does not define any options?). Then it removes the –id switch (after the –help switch was previously removed) and tries the command again (in gems/commander-4.4.3/lib/commander/runner.rb, look for the comment, “Remove the offending args and retry”). So in the example given above, ‘murano usage –id 1234 –help`, both the `–id` flag and `–help` flag are moved from @args, and then `murano usage 1234` is executed (and args are not validated by Commander but merely passed to the command action, so `usage` gets args=). Whadda wreck.



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/MrMurano/ReCommander.rb', line 253

def trim_options_from_args
  reject_next = false
  @args.reject! do |arg|
    if reject_next
      reject_next = false
      true
    elsif (
      arg.start_with?('-') &&
      !help_opts.include?(arg) &&
      !vers_opts.include?(arg)
    )
      # See if the next argument should also be consumed.
      arg_opts = find_matching_options(arg)
      arg_opts = find_exact_match_maybe(arg, arg_opts)
      reject_next = must_be_sane_option!(arg_opts)
      true
    else
      false
    end
  end
end

#verify_solutions_unmanaged!Object

(lb): I’m not a huge fan of mingling business logic with our Commander monkey patch, but it’s a lot more readable that having every command make the call!



379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/MrMurano/ReCommander.rb', line 379

def verify_solutions_unmanaged!
  return if $cfg['tool.skip-managed']
  # (lb): All @exosite.com employees are welcome behind the curtain.
  if $cfg['user.name'] && $cfg['user.name'].end_with?('@exosite.com')
    MrMurano::Verbose.verbose(
      "Welcome behind the curtain, #{$cfg['user.name']}!"
    )
    return
  end
  # FIXME/LATER: (lb): The Element Author should also be allowed in.
  return unless active_command.must_not_be_managed
  MrMurano::Business.must_not_be_managed!
end

#vers_optsObject



199
200
201
# File 'lib/MrMurano/ReCommander.rb', line 199

def vers_opts
  %w[-v --version].freeze
end