class NeverBounce::CLI::Script::Meaningful
A meaningful base script class. Features:
-
Handle command-line options.
-
Handle envars.
-
Handle boilerplate actions like printing usage on `–help`.
@abstract @see CLI::Feature::Envars
@see CLI::Feature::Igetset
Attributes
Public Class Methods
Exception classes which are rescued fromc in {#main} and printed to user. @note Should be a few very high-level excaptions which you fully control. @return [Array]
# File lib/never_bounce/cli/script/meaningful.rb, line 49 def self.error_klasses [Error] end
Format an envar examples string.
format_examples_string(envar) # => ""a", *"B""
@return [String] Insertion-ready string or nil
if envar doesn't have examples.
# File lib/never_bounce/cli/script/meaningful.rb, line 153 def self.format_envar_examples(envar) return nil if envar.examples.empty? envar.examples.map do |v| if envar.default and v == envar.default "[" + v.inspect + "]" else v.inspect end end.join(", ") end
Public Instance Methods
@return [String]
# File lib/never_bounce/cli/script/meaningful.rb, line 32 def envar_text @envar_text ||= begin max_width = (envars = self.class.envars).map(&:name).map(&:size).max envars.sort_by { |_| [_.mandatory?? 0 : 1, _.name] }.map do |r| "%s %-#{max_width}s - %s%s" % [ (r.mandatory?? "*" : "-"), r.name, r.comment, (s = self.class.format_envar_examples(r)) ? " (#{s})" : "", ] end.join("\n") end end
Handle help request and invalid options. @note See method source for important details. @return [Integer] Program exit code (0, 1) if the program should exit now. @return [nil] If all okay.
# File lib/never_bounce/cli/script/meaningful.rb, line 169 def handle_help_and_options # NOTES: # # * Due to `OptionParser` specifics we don't use on-the-fly handling of options. # We do it procedurally instead, to avoid running into a circular dependency. # Thus, it's okay to call this method directly in specs as long as `argv` is concerned. # * The method has internal protection to ensure it's called just ones to make speccing a bit easier. igetset(:handle_help_and_options) do if help? # We ignore errors if there's a help request. stdout.puts help_text 0 elsif (ar = options[:errors]) stderr.puts ar 1 end end end
true
if help has been requested via command-line options.
# File lib/never_bounce/cli/script/meaningful.rb, line 54 def help? !!options[:help] end
# File lib/never_bounce/cli/script/meaningful.rb, line 58 def help_text @help_text ||= begin [ banner_text, "", "USAGE: #{manifest.name} #{manifest.cmdline}", "", options_text, "", "Environment variables:", envar_text, ].join("\n") end end
Main routine which handles most boilerplate. @return [Integer] @see slim_main
# File lib/never_bounce/cli/script/meaningful.rb, line 198 def main # Handle top-level errors like `UsageError`. begin if (res = handle_help_and_options) return res end # Invoke dump'n'exit, if `def dx` defined. # NOTE: This code is production-compatible. It's active only if final script responds to `dx`, which is strictly debug-time. if respond_to? :dx and env_truthy? "DX" dx return 1 end # Do it. result = call_slim_main # Help us stay sane. raise "Unknown `slim_main` result: #{result.inspect}" if not result.is_a? Integer result rescue *self.class.error_klasses => e stderr.puts "#{e.class.to_s.split('::').last}: #{e.message}" # Like "UsageError: this and that". 1 end end
Program manifest object. Successors should return it. @abstract @return [Manicest]
# File lib/never_bounce/cli/script/meaningful.rb, line 191 def manifest raise NotImplementedError, "Redefine `manifest` in your class: #{self.class}" end
Our OptionParser
object.
# File lib/never_bounce/cli/script/meaningful.rb, line 118 def option_parser options if not @options @option_parser end
Parse command-line options with OptionParser
.
options # => {:help => true}
@note This isn't an attribute and there's no writer for it. For simulation and testing
we use {#argv=}.
@return [Hash]
# File lib/never_bounce/cli/script/meaningful.rb, line 80 def options @options ||= begin h = {} @option_parser = OptionParser.new do |opts| opts.banner = "" # A hack to remove Ruby's "-e". opts.on("-h", "--help", "Show help information") do h[:help] = true end end rmn_options = begin @option_parser.parse!(argv) rescue OptionParser::ParseError => e (h[:errors] ||= []) << "Error: #{e.message}" retry end # Parse and add "KEY=value" options to environment. rmn_options.reject! do |s| if s =~ /^(\w+)=(.*)$/m # NOTE: `/m` allows for multiline options. env[$1] = $2 true end end # Treat remaining options as errors. # If this behaviour changes, we should provide access to `rmn_options` via method. rmn_options.each do |s| (h[:errors] ||= []) << "Error: unexpected option: #{s}" end h end end
# File lib/never_bounce/cli/script/meaningful.rb, line 123 def options_text @options_text ||= option_parser.help.strip end
Slim or “real” main routine of the successor class. Called by {#main} considering all boilerplate has been taken care of. @abstract @return [Integer]
# File lib/never_bounce/cli/script/meaningful.rb, line 229 def slim_main raise NotImplementedError, "Redefine `slim_main` in your class: #{self.class}" end
Private Instance Methods
Invoke one of the slim_main
methods available in self.
call_slim_main(3) # Try <tt>slim_main3</tt>, then <tt>slim_main2</tt> down to <tt>slim_main</tt>.
@return [Integer] Result of slim_main[N]
.
# File lib/never_bounce/cli/script/meaningful.rb, line 134 def call_slim_main(from_level = 3) from_level.downto(0) do |i| if respond_to?(m = "slim_main#{i > 0 ? i : ''}") return send(m) end end # This is in theory possible, stay sane. raise "No `slim_main` responded, check your class hierarchy" end