Module: Boson::Scientist

Extended by:
Scientist
Included in:
Scientist
Defined in:
lib/boson/scientist.rb

Overview

Scientist wraps around and redefines an object’s method to give it the following features:

  • Methods can take shell command input with options or receive its normal arguments. See the Commandification section.

  • Methods have a slew of global options available. See OptionCommand for an explanation of basic global options.

  • 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).

The main methods Scientist provides are redefine_command() for redefining an object’s method with a Command object and commandify() for redefining with a hash of method attributes. Note that for an object’s method to be redefined correctly, its last argument must expect a hash.

Commandification

Take for example this basic method/command with an options definition:

options :level=>:numeric, :verbose=>:boolean
def foo(*args)
  args
end

When Scientist wraps around foo(), it can take arguments normally or as a shell command:

foo 'one', 'two', :verbose=>true   # normal call
foo 'one two -v'                 # commandline call

Both calls return: ['one', 'two', {:verbose=>true}]

Non-string arguments can be passed as well:

foo Object, 'two', :level=>1
foo Object, 'two -l1'

Both calls return: [Object, 'two', {:level=>1}]

Defined Under Namespace

Classes: Error

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#global_optionsObject

Returns the value of attribute global_options.



37
38
39
# File 'lib/boson/scientist.rb', line 37

def global_options
  @global_options
end

#renderObject

Returns the value of attribute render.



37
38
39
# File 'lib/boson/scientist.rb', line 37

def render
  @render
end

#renderedObject

Returns the value of attribute rendered.



37
38
39
# File 'lib/boson/scientist.rb', line 37

def rendered
  @rendered
end

Instance Method Details

#call_original_command(args, &block) ⇒ Object



99
100
101
# File 'lib/boson/scientist.rb', line 99

def call_original_command(args, &block)
  block.call(args)
end

#can_render?Boolean

Returns:

  • (Boolean)


173
174
175
# File 'lib/boson/scientist.rb', line 173

def can_render?
  render.nil? ? command_renders? : render
end

#command_renders?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/boson/scientist.rb', line 177

def command_renders?
  (!!@command.render_options ^ @global_options[:render]) && !Pipe.any_no_render_pipes?(@global_options)
end

#commandify(obj, hash) ⇒ Object

A wrapper around redefine_command that doesn’t depend on a Command object. Rather you simply pass a hash of command attributes (see Command.new) or command methods and let OpenStruct mock a command. The only required attribute is :name, though to get any real use you should define :options and :arg_size (default is ‘*’). Example:

>> def checkit(*args); args; end
=> nil
>> Boson::Scientist.commandify(self, :name=>'checkit', :options=>{:verbose=>:boolean, :num=>:numeric})
=> ['checkit']
# regular ruby method
>> checkit 'one', 'two', :num=>13, :verbose=>true
=> ["one", "two", {:num=>13, :verbose=>true}]
# commandline ruby method
>> checkit 'one two -v -n=13'
=> ["one", "two", {:num=>13, :verbose=>true}]

Raises:

  • (ArgumentError)


67
68
69
70
71
72
73
74
# File 'lib/boson/scientist.rb', line 67

def commandify(obj, hash)
  raise ArgumentError, ":name required" unless hash[:name]
  hash[:arg_size] ||= '*'
  hash[:has_splat_args?] = true if hash[:arg_size] == '*'
  fake_cmd = OpenStruct.new(hash)
  fake_cmd.option_parser ||= OptionParser.new(fake_cmd.options || {})
  redefine_command(obj, fake_cmd)
end

#object_methods(obj) ⇒ Object

:stopdoc:



91
92
93
# File 'lib/boson/scientist.rb', line 91

def object_methods(obj)
  @object_methods[obj] ||= {}
end

#option_command(cmd = @command) ⇒ Object



95
96
97
# File 'lib/boson/scientist.rb', line 95

def option_command(cmd=@command)
  @option_commands[cmd] ||= OptionCommand.new(cmd)
end

#redefine_command(obj, command) ⇒ Object

Redefines an object’s method with a Command of the same name.



43
44
45
46
47
48
49
50
51
# File 'lib/boson/scientist.rb', line 43

def redefine_command(obj, command)
  cmd_block = redefine_command_block(obj, command)
  @no_option_commands << command if command.options.nil?
  [command.name, command.alias].compact.each {|e|
    obj.instance_eval("class<<self;self;end").send(:define_method, e, cmd_block)
  }
rescue Error
  $stderr.puts "Error: #{$!.message}"
end

#redefine_command_block(obj, command) ⇒ Object

The actual method which redefines a command’s original method



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/boson/scientist.rb', line 77

def redefine_command_block(obj, command)
  object_methods(obj)[command.name] ||= begin
    obj.method(command.name)
  rescue NameError
    raise Error, "No method exists to redefine command '#{command.name}'."
  end
  lambda {|*args|
    Scientist.translate_and_render(obj, command, args) {|args|
      Scientist.object_methods(obj)[command.name].call(*args)
    }
  }
end

#render_or_raw(result) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/boson/scientist.rb', line 160

def render_or_raw(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 Error, $!.message, $!.backtrace
end

#run_help_optionObject



147
148
149
150
151
# File 'lib/boson/scientist.rb', line 147

def run_help_option
  opts = @global_options[:verbose] ? ['--verbose'] : []
  opts << "--render_options=#{@global_options[:usage_options]}" if @global_options[:usage_options]
  Boson.invoke :usage, @command.full_name + " " + opts.join(' ')
end

#run_pretend_option(args) ⇒ Object



153
154
155
156
157
158
# File 'lib/boson/scientist.rb', line 153

def run_pretend_option(args)
  if @global_options[:verbose] || @global_options[:pretend]
    puts "Arguments: #{args.inspect}", "Global options: #{@global_options.inspect}"
  end
  @rendered = true if @global_options[:pretend]
end

#run_verbose_help(option_command, original_args) ⇒ Object



137
138
139
140
141
142
143
144
145
# File 'lib/boson/scientist.rb', line 137

def run_verbose_help(option_command, original_args)
  global_opts = option_command.parse_global_options(original_args)
  if global_opts[:help] && global_opts[:verbose]
    @global_options = global_opts
    run_help_option
    return true
  end
  false
end

#translate_and_render(obj, command, args, &block) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/boson/scientist.rb', line 103

def translate_and_render(obj, command, args, &block)
  @global_options, @command, original_args = {}, command, args.dup
  @args = translate_args(obj, args)
  return run_help_option if @global_options[:help]
  run_pretend_option(@args)
  render_or_raw call_original_command(@args, &block) unless @global_options[:pretend]
rescue OptionCommand::CommandArgumentError
  run_pretend_option(@args ||= [])
  return if !@global_options[:pretend] && run_verbose_help(option_command, original_args)
  raise unless @global_options[:pretend]
rescue OptionParser::Error, Error
  raise if Runner.in_shell?
  message = @global_options[:verbose] ? "#{$!}\n#{$!.backtrace.inspect}" : $!.message
  $stderr.puts "Error: " + message
end

#translate_args(obj, args) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/boson/scientist.rb', line 119

def translate_args(obj, args)
  option_command.modify_args(args)
  @global_options, @current_options, args = option_command.parse(args)
  return if @global_options[:help]

  (@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) }

  if @current_options
    option_command.add_default_args(args, obj)
    return args if @no_option_commands.include?(@command)
    args << @current_options
    option_command.check_argument_size(args)
  end
  args
end