class Luban::CLI::Base
Constants
- DefaultSummaryIndent
- DefaultSummaryWidth
- DefaultTitleIndent
Attributes
config_blk[R]
arguments[R]
commands[R]
default_argv[R]
description[R]
options[R]
parent[R]
prefix[R]
program_name[R]
result[R]
summary[R]
summary_indent[RW]
summary_width[RW]
title_indent[RW]
version[R]
Public Class Methods
configure(&blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 15 def configure(&blk); @config_blk = blk; end
inherited(subclass)
click to toggle source
Calls superclass method
# File lib/luban/cli/base/dsl.rb, line 5 def inherited(subclass) super # Ensure configuration block from base class # got inherited to its subclasses blk = instance_variable_get('@config_blk') subclass.instance_variable_set('@config_blk', blk.clone) unless blk.nil? end
new(parent, action_name, prefix: default_prefix, auto_help: true, &config_blk)
click to toggle source
# File lib/luban/cli/base/core.rb, line 36 def initialize(parent, action_name, prefix: default_prefix, auto_help: true, &config_blk) @parent = parent @action_name = action_name @prefix = prefix @action_defined = false @program_name = default_program_name @options = {} @arguments = {} @summary = '' @description = '' @version = '' @default_argv = ARGV @result = { cmd: nil, argv: @default_argv, args: {}, opts: {} } @commands = {} @title_indent = DefaultTitleIndent @summary_width = DefaultSummaryWidth @summary_indent = DefaultSummaryIndent configure(&config_blk) setup_default_action unless @action_defined self.auto_help if auto_help end
Protected Class Methods
define_class(class_name, base:, namespace:)
click to toggle source
# File lib/luban/cli/base/core.rb, line 89 def self.define_class(class_name, base:, namespace:) mods = class_name.split('::') cmd_class = mods.pop mods.inject(namespace) do |ns, mod| ns.const_set(mod, Module.new) unless ns.const_defined?(mod, false) ns.const_get(mod, false) end.const_set(cmd_class, Class.new(base)) end
Public Instance Methods
action(method_name = nil, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 92 def action(method_name = nil, &blk) create_action(method_name, preserve_argv: true, &blk) end
action!(method_name = nil, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 96 def action!(method_name = nil, &blk) create_action(method_name, preserve_argv: false, &blk) end
action_method()
click to toggle source
# File lib/luban/cli/base/core.rb, line 63 def action_method @action_method ||= "#{@prefix}#{@action_name.to_s.gsub(':', '_')}" end
alter(&blk)
click to toggle source
# File lib/luban/cli/base/core.rb, line 81 def alter(&blk) instance_eval(&blk) on_alter after_alter end
argument(name, desc, **config, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 46 def argument(name, desc, **config, &blk) @arguments[name] = Argument.new(name, desc, **config, &blk) end
command(cmd, base: Command, **opts, &blk)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 6 def command(cmd, base: Command, **opts, &blk) cmd = cmd.to_sym @commands[cmd] = command_class(cmd, base).new(self, cmd, **opts, &blk) end
default_prefix()
click to toggle source
# File lib/luban/cli/base/core.rb, line 61 def default_prefix; ''; end
default_program_name()
click to toggle source
# File lib/luban/cli/base/core.rb, line 71 def default_program_name @default_program_name ||= File.basename($0, '.*') end
desc(string)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 22 def desc(string) @summary = string.to_s unless string.nil? end
has_command?(cmd)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 31 def has_command?(cmd) @commands.has_key?(cmd.to_sym) end
has_commands?()
click to toggle source
# File lib/luban/cli/base/commands.rb, line 35 def has_commands? !@commands.empty? end
help(short: :h, desc: "Show this help message.", &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 50 def help(short: :h, desc: "Show this help message.", &blk) switch :help, desc, short: short, &blk end
Also aliased as: auto_help
help_command(**opts, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 65 def help_command(**opts, &blk) if block_given? command(**opts, &blk) else validator = method(:has_command?) command(:help, **opts) do desc "List all commands or help for one command" argument :command, "Command to help for", type: :symbol, required: false, assure: validator action :show_help_for_command end end end
Also aliased as: auto_help_command
list_commands()
click to toggle source
# File lib/luban/cli/base/commands.rb, line 27 def list_commands @commands.keys end
long_desc(string)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 26 def long_desc(string) @description = string.to_s unless string.nil? end
option(name, desc, nullable: false, **config, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 30 def option(name, desc, nullable: false, **config, &blk) @options[name] = if nullable NullableOption.new(name, desc, **config, &blk) else Option.new(name, desc, **config, &blk) end end
parse(argv=default_argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 4 def parse(argv=default_argv) argv = argv.dup parse!(argv) end
parse!(argv=default_argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 9 def parse!(argv=default_argv) if commands.empty? parse_without_commands(argv) else parse_with_commands(argv) end update_result(argv) end
parser()
click to toggle source
# File lib/luban/cli/base/core.rb, line 67 def parser @parser ||= create_parser end
program(name)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 18 def program(name) @program_name = name.to_s unless name.nil? end
reset()
click to toggle source
# File lib/luban/cli/base/core.rb, line 75 def reset @options.each_value { |o| o.reset } @arguments.each_value { |a| a.reset } @result = { cmd: nil, argv: @default_argv, args: {}, opts: {} } end
show_help()
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 55 def show_help; puts parser.help; end
show_help_for_command(args:, **params)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 57 def show_help_for_command(args:, **params) if args[:command].nil? show_help else commands[args[:command]].show_help end end
show_version()
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 90 def show_version; puts parser.ver; end
switch(name, desc, negatable: false, **config, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 38 def switch(name, desc, negatable: false, **config, &blk) @options[name] = if negatable NegatableSwitch.new(name, desc, **config, &blk) else Switch.new(name, desc, **config, &blk) end end
task(cmd, **opts, &blk)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 39 def task(cmd, **opts, &blk) command(cmd, **opts, &blk).tap do |t| add_common_task_options(t) if !t.summary.nil? and t.description.empty? t.long_desc "#{t.summary} in #{self.class.name}" end end end
undef_command(cmd)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 11 def undef_command(cmd) c = @commands.delete(cmd.to_sym) if c.nil? raise RuntimeError, "Command #{cmd.inspect} is NOT defined." else undef_singleton_method(c.action_method) end end
Also aliased as: undef_task
use_commands(module_name, **opts, &blk)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 20 def use_commands(module_name, **opts, &blk) module_class = Object.const_get(module_name.camelcase, false) module_class.constants(false).map { |c| module_class.const_get(c, false) }.each do |c| command(c.name.snakecase, base: c, **opts, &blk) if c < Command end end
Protected Instance Methods
action_abort()
click to toggle source
# File lib/luban/cli/base/core.rb, line 129 def action_abort name = @action_name action do abort "Aborted! Action is NOT defined for #{name} in #{self.class.name}." end end
action_noops()
click to toggle source
# File lib/luban/cli/base/core.rb, line 125 def action_noops action { } # NOOPS end
add_common_task_options(task)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 73 def add_common_task_options(task) if @parent != self and @parent.respond_to?(__method__, true) @parent.send(__method__, task) end end
add_parser_arguments()
click to toggle source
# File lib/luban/cli/base/core.rb, line 251 def add_parser_arguments add_section("Arguments") do |rows| @arguments.each_value do |arg| rows.concat(summarize(arg.display_name, arg.description)) end end end
add_parser_commands()
click to toggle source
# File lib/luban/cli/base/core.rb, line 271 def add_parser_commands add_section("Commands") do |rows| commands.each_value do |cmd| rows.concat(summarize(cmd.name, cmd.summary, summary_width, summary_width * 1.5)) end end end
add_parser_defaults()
click to toggle source
# File lib/luban/cli/base/core.rb, line 267 def add_parser_defaults add_section("Defaults", options.values.map(&:default_str).reject { |d| d.empty? }) end
add_parser_description()
click to toggle source
# File lib/luban/cli/base/core.rb, line 263 def add_parser_description add_section("Description", wrap(description, summary_width * 2)) end
add_parser_options()
click to toggle source
# File lib/luban/cli/base/core.rb, line 243 def add_parser_options add_section("Options") do @options.each_value do |option| parser.on(*option.specs) { |v| option.value = v } end end end
add_parser_summary()
click to toggle source
# File lib/luban/cli/base/core.rb, line 259 def add_parser_summary add_section("Summary", wrap(summary, summary_width * 2)) end
add_parser_usage()
click to toggle source
# File lib/luban/cli/base/core.rb, line 196 def add_parser_usage parser.banner = compose_banner text end
add_parser_version()
click to toggle source
# File lib/luban/cli/base/core.rb, line 239 def add_parser_version parser.version = @version end
add_section(title, rows = []) { |rows| ... }
click to toggle source
# File lib/luban/cli/base/core.rb, line 279 def add_section(title, rows = [], &blk) text compose_title(title) yield rows if block_given? rows.each { |row| text compose_row(row) } text end
after_alter()
click to toggle source
# File lib/luban/cli/base/core.rb, line 115 def after_alter; end
after_configure()
click to toggle source
# File lib/luban/cli/base/core.rb, line 113 def after_configure; end
command_class(cmd, base)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 52 def command_class(cmd, base) class_name = cmd.camelcase if command_class_defined?(class_name) get_command_class(class_name) else define_command_class(class_name, base) end end
command_class_defined?(class_name)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 61 def command_class_defined?(class_name) self.class.const_defined?(class_name, false) end
compose_row(string, indent = ' ' * summary_indent)
click to toggle source
# File lib/luban/cli/base/core.rb, line 290 def compose_row(string, indent = ' ' * summary_indent) "#{indent}#{string}" end
compose_summary(item, summary, width)
click to toggle source
# File lib/luban/cli/base/core.rb, line 304 def compose_summary(item, summary, width) "%-#{width}s %-#{width}s" % [item, summary] end
compose_synopsis()
click to toggle source
# File lib/luban/cli/base/core.rb, line 205 def compose_synopsis if has_commands? compose_synopsis_with_commands else compose_synopsis_without_commands end end
compose_synopsis_with_arguments()
click to toggle source
# File lib/luban/cli/base/core.rb, line 225 def compose_synopsis_with_arguments synopsis = '' @arguments.each_value do |arg| synopsis += arg.required? ? arg.display_name : "[#{arg.display_name}]" synopsis += "[ #{arg.display_name}]*" if arg.multiple? synopsis += ' ' end synopsis end
compose_synopsis_with_commands()
click to toggle source
# File lib/luban/cli/base/core.rb, line 213 def compose_synopsis_with_commands "[options] command [command options] [arguments ...]" end
compose_synopsis_with_options()
click to toggle source
# File lib/luban/cli/base/core.rb, line 221 def compose_synopsis_with_options options.empty? ? '' : '[options] ' end
compose_synopsis_without_commands()
click to toggle source
# File lib/luban/cli/base/core.rb, line 217 def compose_synopsis_without_commands "#{compose_synopsis_with_options}#{compose_synopsis_with_arguments}" end
compose_title(title, indent = ' ' * title_indent, suffix = ':')
click to toggle source
# File lib/luban/cli/base/core.rb, line 286 def compose_title(title, indent = ' ' * title_indent, suffix = ':') "#{indent}#{title}#{suffix}" end
configure(&blk)
click to toggle source
# File lib/luban/cli/base/core.rb, line 104 def configure(&blk) [self.class.config_blk, blk].each do |callback| instance_eval(&callback) unless callback.nil? end on_configure after_configure end
create_action(method_name = nil, preserve_argv: true, &blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 102 def create_action(method_name = nil, preserve_argv: true, &blk) handler = if method_name lambda { |**opts| invoke_action(method_name, **opts) } elsif block_given? blk end if handler.nil? raise ArgumentError, "Code block to execute command #{@action_name} is MISSING." end _base = self parse_method = preserve_argv ? :parse : :parse! define_action_method do |argv = _base.default_argv| _base.send(:process, parse_method, argv) do |params| instance_exec(**params, &handler) end end @action_defined = true end
create_parser()
click to toggle source
# File lib/luban/cli/base/core.rb, line 183 def create_parser @parser = OptionParser.new add_parser_usage add_parser_version unless @version.empty? add_parser_options unless options.empty? add_parser_arguments unless arguments.empty? add_parser_summary unless summary.empty? add_parser_description unless description.empty? add_parser_defaults unless options.values.all? { |o| o.default_str.empty? } add_parser_commands if has_commands? @parser end
define_action_method(&action_blk)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 121 def define_action_method(&action_blk) @parent.send(:define_singleton_method, action_method, &action_blk) end
define_command_class(class_name, base)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 69 def define_command_class(class_name, base) self.class.send(:define_class, class_name, base: base, namespace: self.class) end
dispatch_command(cmd:, argv:)
click to toggle source
# File lib/luban/cli/base/core.rb, line 136 def dispatch_command(cmd:, argv:) validate_command(cmd) send(commands[cmd].action_method, argv) end
find_action_handler(action_method)
click to toggle source
# File lib/luban/cli/base/core.rb, line 150 def find_action_handler(action_method) if respond_to?(action_method, true) self elsif @parent != self and @parent.respond_to?(:find_action_handler, true) @parent.send(:find_action_handler, action_method) else nil end end
get_command_class(class_name)
click to toggle source
# File lib/luban/cli/base/commands.rb, line 65 def get_command_class(class_name) self.class.const_get(class_name, false) end
invoke_action(action_method, **opts)
click to toggle source
# File lib/luban/cli/base/core.rb, line 141 def invoke_action(action_method, **opts) handler = find_action_handler(action_method) if handler.nil? raise RuntimeError, "Action handler #{action_method} is MISSING." else handler.send(action_method, **opts) end end
on_alter()
click to toggle source
# File lib/luban/cli/base/core.rb, line 114 def on_alter; end
on_configure()
click to toggle source
# File lib/luban/cli/base/core.rb, line 112 def on_configure; end
on_parse_error(error)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 144 def on_parse_error(error) show_error_and_exit(error) end
parse_arguments(argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 38 def parse_arguments(argv) @arguments.each_value do |arg| break if argv.empty? arg.value = arg.multiple? ? argv.slice!(0..-1) : argv.shift end end
parse_command(argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 26 def parse_command(argv) cmd = argv.shift cmd = cmd.to_sym unless cmd.nil? @result[:cmd] = cmd end
parse_with_commands(argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 20 def parse_with_commands(argv) parser.order!(argv) parse_command(argv) end
Also aliased as: parse_posixly_correct
parse_without_commands(argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 32 def parse_without_commands(argv) parser.permute!(argv) parse_arguments(argv) end
Also aliased as: parse_permutationally
process(parse_method, argv) { |args: result, opts: result| ... }
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 125 def process(parse_method, argv) send(parse_method, argv) if result[:opts][:help] show_help elsif !@version.empty? and result[:opts][:version] show_version else validate_required_options validate_required_arguments result[:opts][:__remaining__] = result[:argv] yield args: result[:args], opts: result[:opts] if has_commands? dispatch_command(cmd: result[:cmd], argv: result[:argv]) end end rescue OptionParser::ParseError, Error => e on_parse_error(e) end
setup_default_action()
click to toggle source
# File lib/luban/cli/base/core.rb, line 117 def setup_default_action if has_commands? action_noops else action_abort end end
show_error(error)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 154 def show_error(error) puts "#{error.message} (#{error.class.name})" puts end
show_error_and_exit(error)
click to toggle source
# File lib/luban/cli/base/dsl.rb, line 148 def show_error_and_exit(error) show_error(error) show_help exit 64 # Linux standard for bad command line end
summarize(item, summary, width = summary_width, max_width = width - 1)
click to toggle source
# File lib/luban/cli/base/core.rb, line 294 def summarize(item, summary, width = summary_width, max_width = width - 1) item_rows = wrap(item.to_s, max_width) summary_rows = wrap(summary, max_width) num_of_rows = [item_rows.size, summary_rows.size].max rows = (0...num_of_rows).collect do |i| compose_summary(item_rows[i], summary_rows[i], width) end return rows end
text(string = nil)
click to toggle source
# File lib/luban/cli/base/core.rb, line 235 def text(string = nil) @parser.separator(string) end
undef_singleton_method(method_name)
click to toggle source
# File lib/luban/cli/base/core.rb, line 98 def undef_singleton_method(method_name) # Undefine methods that are defined in the eigenclass singleton_class.send(:undef_method, method_name) #(class << self; self; end).send(:undef_method, method_name) end
update_result(argv)
click to toggle source
# File lib/luban/cli/base/parse.rb, line 45 def update_result(argv) @result[:argv] = argv @result[:opts] = options.values.inject({}) { |r, o| r[o.name] = o.value; r } @result[:args] = arguments.values.inject({}) { |r, a| r[a.name] = a.value; r } @result end
validate_command(cmd)
click to toggle source
# File lib/luban/cli/base/core.rb, line 160 def validate_command(cmd) if cmd.nil? raise MissingCommand, "Please specify a command to execute." end unless has_command?(cmd) raise InvalidCommand, "Invalid command: #{cmd}" end end
validate_required_arguments()
click to toggle source
# File lib/luban/cli/base/core.rb, line 176 def validate_required_arguments missing_args = @arguments.each_value.select(&:missing?).collect(&:display_name) unless missing_args.empty? raise MissingRequiredArguments, "Missing required argument(s): #{missing_args.join(', ')}" end end
validate_required_options()
click to toggle source
# File lib/luban/cli/base/core.rb, line 169 def validate_required_options missing_opts = @options.each_value.select(&:missing?).collect(&:display_name) unless missing_opts.empty? raise MissingRequiredOptions, "Missing required option(s): #{missing_opts.join(', ')}" end end
wrap(string, width)
click to toggle source
# File lib/luban/cli/base/core.rb, line 308 def wrap(string, width) rows = [] row = '' string.split(/\s+/).each do |word| if row.size + word.size >= width rows << row row = word elsif row.empty? row = word else row << ' ' << word end end rows << row if row rows end