require 'boson/view' require 'boson/pipe' require 'boson/pipes' require 'boson/more_scientist' module Boson class OptionCommand BASIC_OPTIONS.update( :delete_options=>{:type=>:array, :desc=>'Deletes global options starting with given strings' }, :usage_options=>{:type=>:string, :desc=>"Render options to pass to usage/help"}, :render=> {:type=>:boolean, :desc=>"Toggle a command's default rendering behavior"}) PIPE_OPTIONS = { :sort=>{:type=>:string, :desc=>"Sort by given field"}, :reverse_sort=>{:type=>:boolean, :desc=>"Reverse a given sort"}, :query=>{:type=>:hash, :desc=>"Queries fields given field:search pairs"}, :pipes=>{:alias=>'P', :type=>:array, :desc=>"Pipe to commands sequentially"} } #:nodoc: RENDER_OPTIONS = { :fields=>{:type=>:array, :desc=>"Displays fields in the order given"}, :class=>{:type=>:string, :desc=>"Hirb helper class which renders"}, :max_width=>{:type=>:numeric, :desc=>"Max width of a table"}, :vertical=>{:type=>:boolean, :desc=>"Display a vertical table"}, } #:nodoc: # Adds render and pipe global options # For more about pipe and render options see Pipe and View respectively. # === Toggling Views With the Basic Global Option --render # One of the more important global options is --render. This option toggles the rendering of a command's # output done with View and Hirb[http://github.com/cldwalker/hirb]. # # Here's a simple example of toggling Hirb's table view: # # Defined in a library file: # #@options {} # def list(options={}) # [1,2,3] # end # # Using it in irb: # >> list # => [1,2,3] # >> list '-r' # or list --render # +-------+ # | value | # +-------+ # | 1 | # | 2 | # | 3 | # +-------+ # 3 rows in set # => true # == Additional config keys for the main repo config # [:render_options] Hash of render options available to all option commands to be passed to a Hirb view (see View). Since # this merges with default render options, it's possible to override default render options. # [:no_auto_render] When set, turns off commandline auto-rendering of a command's output. Default is false. module ClassRender def default_options default_pipe_options.merge(default_render_options.merge(BASIC_OPTIONS)) end def default_pipe_options @default_pipe_options ||= PIPE_OPTIONS.merge Pipe.pipe_options end def default_render_options @default_render_options ||= RENDER_OPTIONS.merge Boson.repo.config[:render_options] || {} end def delete_non_render_options(opt) opt.delete_if {|k,v| BASIC_OPTIONS.keys.include?(k) } end end extend ClassRender module Render def option_parser @option_parser ||= @command.render_options ? OptionParser.new(all_global_options) : self.class.default_option_parser end def all_global_options OptionParser.make_mergeable! @command.render_options render_opts = Util.recursive_hash_merge(@command.render_options, Util.deep_copy(self.class.default_render_options)) merged_opts = Util.recursive_hash_merge Util.deep_copy(self.class.default_pipe_options), render_opts opts = Util.recursive_hash_merge merged_opts, Util.deep_copy(BASIC_OPTIONS) set_global_option_defaults opts end def set_global_option_defaults(opts) if !opts[:fields].key?(:values) if opts[:fields][:default] opts[:fields][:values] = opts[:fields][:default] else if opts[:change_fields] && (changed = opts[:change_fields][:default]) opts[:fields][:values] = changed.is_a?(Array) ? changed : changed.values end opts[:fields][:values] ||= opts[:headers][:default].keys if opts[:headers] && opts[:headers][:default] end opts[:fields][:enum] = false if opts[:fields][:values] && !opts[:fields].key?(:enum) end if opts[:fields][:values] opts[:sort][:values] ||= opts[:fields][:values] opts[:query][:keys] ||= opts[:fields][:values] opts[:query][:default_keys] ||= "*" end opts end end include Render end module Scientist # * Before a method returns its value, it pipes its return value through pipe commands if pipe options are specified. See Pipe. # * Methods can have any number of optional views associated with them via global render options (see View). Views can be toggled # on/off with the global option --render (see OptionCommand). module Render attr_accessor :rendered, :render def after_parse (@global_options[:delete_options] || []).map {|e| @global_options.keys.map {|k| k.to_s }.grep(/^#{e}/) }.flatten.each {|e| @global_options.delete(e.to_sym) } end def process_result(result) if (@rendered = can_render?) if @global_options.key?(:class) || @global_options.key?(:method) result = Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options) end View.render(result, OptionCommand.delete_non_render_options(@global_options.dup), false) else Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options) end rescue StandardError raise Scientist::Error, $!.message, $!.backtrace end def can_render? render.nil? ? command_renders? : render end def command_renders? (!!@command.render_options ^ @global_options[:render]) && !Pipe.any_no_render_pipes?(@global_options) end def run_pretend_option(args) super @rendered = true if @global_options[:pretend] end def help_options super.tap do |opts| if @global_options[:usage_options] opts << "--render_options=#{@global_options[:usage_options]}" end opts end end end extend Render end class Command module ScienceClassMethods attr_accessor :all_option_commands def create(name, library) super.tap do |obj| if @all_option_commands && !%w{get method_missing}.include?(name) obj.make_option_command(library) end end end end extend ScienceClassMethods module Science # Option parser for command as defined by @render_options. def render_option_parser option_command? ? Boson::Scientist.option_command(self).option_parser : nil end def make_option_command(lib=library) @option_command = true @args = [['*args']] unless args(lib) || arg_size end def option_command? super || render_options end end include Science end # [*:render_options*] Hash of rendering options to pass to OptionParser. If the key :output_class is passed, # that class's Hirb config will serve as defaults for this rendering hash. class Command attr_accessor :render_options module Science def after_initialize(hash) if hash[:render_options] && (@render_options = hash.delete(:render_options))[:output_class] @render_options = Util.recursive_hash_merge View.class_config(@render_options[:output_class]), @render_options end super end end end if defined? BinRunner class BinRunner < BareRunner GLOBAL_OPTIONS.update( option_commands: { :type=>:boolean, :desc=>"Toggles on all commands to be defined as option commands" }, render: {:type=>:boolean, :desc=>"Renders a Hirb view from result of command without options"} ) # [:render] Toggles the auto-rendering done for commands that don't have views. Doesn't affect commands that already have views. # Default is false. Also see Auto Rendering section below. # # ==== Auto Rendering # Commands that don't have views (defined via render_options) have their return value auto-rendered as a view as follows: # * nil,false and true aren't rendered # * arrays are rendered with Hirb's tables # * non-arrays are printed with inspect() # * Any of these cases can be toggled to render/not render with the global option :render # To turn off auto-rendering by default, add a :no_auto_render: true entry to the main config. module Science def init Command.all_option_commands = true if @options[:option_commands] super end def render_output(output) if (!Scientist.rendered && !View.silent_object?(output)) ^ @options[:render] ^ Boson.repo.config[:no_auto_render] opts = output.is_a?(String) ? {:method=>'puts'} : {:inspect=>!output.is_a?(Array) || (Scientist.global_options || {})[:render] } View.render output, opts end end def allowed_argument_error?(err, cmd, args) err.class == OptionCommand::CommandArgumentError || super end def execute_command(cmd, args) render_output super end end class <