2011-12-31 00:35:08 +00:00
require 'pry/command_context'
2011-04-24 14:25:07 +00:00
class Pry
class NoCommandError < StandardError
def initialize ( name , owner )
super " Command ' #{ name } ' not found in command set #{ owner } "
end
end
2011-05-30 04:46:44 +00:00
# This class is used to create sets of commands. Commands can be imported from
2011-04-24 14:25:07 +00:00
# different sets, aliased, removed, etc.
class CommandSet
2011-12-31 00:35:08 +00:00
class Command < Pry :: CommandContext
2011-05-04 02:43:35 +00:00
2011-12-31 00:35:08 +00:00
class << self
attr_accessor :name
attr_accessor :description
attr_accessor :options
attr_accessor :block
end
2011-09-25 06:52:07 +00:00
2011-12-31 00:35:08 +00:00
%w( name description options block ) . each do | attribute |
define_method ( attribute ) { self . class . send ( attribute ) }
end
class << self
def inspect
" # <class(Pry::Command #{ name . inspect } > "
end
def subclass ( name , description , options , & block )
klass = Class . new ( self )
klass . name = name
klass . description = description
klass . options = options
klass . block = block
klass
end
end
end
class StubCommand < Command
def call ( * args )
gems_needed = Array ( options [ :requires_gem ] )
gems_not_installed = gems_needed . select { | g | ! gem_installed? ( g ) }
output . puts " \n The command ' #{ name } ' is #{ Helpers :: Text . bold ( " unavailable " ) } because it requires the following gems to be installed: #{ ( gems_not_installed . join ( " , " ) ) } "
output . puts " - "
output . puts " Type `install-command #{ name } ` to install the required gems and activate this command. "
end
end
class BlockCommand < Command
def call ( * args )
if options [ :argument_required ] && args . empty?
raise CommandError , " The command ' #{ command . name } ' requires an argument. "
2011-04-25 19:31:38 +00:00
end
2011-12-31 00:35:08 +00:00
instance_exec ( * correct_arg_arity ( block . arity , args ) , & block )
2011-04-24 14:25:07 +00:00
end
2011-05-04 02:43:35 +00:00
private
def correct_arg_arity ( arity , args )
2011-12-29 07:23:26 +00:00
case
when arity < 0
2011-05-04 02:43:35 +00:00
args
2011-12-29 07:23:26 +00:00
when arity == 0
2011-08-22 19:52:03 +00:00
[ ]
2011-12-29 07:23:26 +00:00
when arity > 0
2011-12-29 12:16:15 +00:00
args . values_at * ( 0 .. ( arity - 1 ) ) . to_a
2011-05-04 02:43:35 +00:00
end
end
2011-04-24 14:25:07 +00:00
end
2011-05-30 10:56:50 +00:00
include Enumerable
2011-04-25 20:58:06 +00:00
include Pry :: Helpers :: BaseHelpers
2011-04-25 19:31:38 +00:00
2011-04-24 14:25:07 +00:00
attr_reader :commands
2011-04-30 10:28:58 +00:00
attr_reader :helper_module
2011-04-24 14:25:07 +00:00
# @param [Array<CommandSet>] imported_sets Sets which will be imported
# automatically
# @yield Optional block run to define commands
2011-05-07 05:32:05 +00:00
def initialize ( * imported_sets , & block )
2011-04-30 10:28:58 +00:00
@commands = { }
@helper_module = Module . new
2011-04-24 14:25:07 +00:00
2011-04-25 11:26:25 +00:00
define_default_commands
2011-04-24 14:25:07 +00:00
import ( * imported_sets )
instance_eval ( & block ) if block
end
# Defines a new Pry command.
2011-05-28 14:23:56 +00:00
# @param [String, Regexp] name The name of the command. Can be
# Regexp as well as String.
2011-04-24 14:25:07 +00:00
# @param [String] description A description of the command.
# @param [Hash] options The optional configuration parameters.
# @option options [Boolean] :keep_retval Whether or not to use return value
# of the block for return of `command` or just to return `nil`
# (the default).
2011-05-28 14:23:56 +00:00
# @option options [Array<String>] :requires_gem Whether the command has
# any gem dependencies, if it does and dependencies not met then
# command is disabled and a stub proc giving instructions to
# install command is provided.
# @option options [Boolean] :interpolate Whether string #{} based
# interpolation is applied to the command arguments before
# executing the command. Defaults to true.
# @option options [String] :listing The listing name of the
# command. That is the name by which the command is looked up by
# help and by show-command. Necessary for regex based commands.
2011-07-26 09:42:44 +00:00
# @option options [Boolean] :use_prefix Whether the command uses
# `Pry.config.command_prefix` prefix (if one is defined). Defaults
# to true.
2011-10-02 07:53:23 +00:00
# @option options [Boolean] :shellwords Whether the command's arguments
# should be split using Shellwords instead of just split on spaces.
# Defaults to true.
2011-04-24 14:25:07 +00:00
# @yield The action to perform. The parameters in the block
# determines the parameters the command will receive. All
# parameters passed into the block will be strings. Successive
# command parameters are separated by whitespace at the Pry prompt.
# @example
2011-05-07 05:32:05 +00:00
# MyCommands = Pry::CommandSet.new do
2011-04-24 14:25:07 +00:00
# command "greet", "Greet somebody" do |name|
# puts "Good afternoon #{name.capitalize}!"
# end
# end
#
# # From pry:
# # pry(main)> _pry_.commands = MyCommands
# # pry(main)> greet john
# # Good afternoon John!
# # pry(main)> help greet
# # Greet somebody
2011-05-28 14:23:56 +00:00
# @example Regexp command
# MyCommands = Pry::CommandSet.new do
# command /number-(\d+)/, "number-N regex command", :listing => "number" do |num, name|
# puts "hello #{name}, nice number: #{num}"
# end
# end
#
# # From pry:
# # pry(main)> _pry_.commands = MyCommands
# # pry(main)> number-10 john
# # hello john, nice number: 10
# # pry(main)> help number
# # number-N regex command
2011-12-29 09:13:51 +00:00
def command ( name , description = " No description. " , options = { } , & block )
2011-05-22 23:57:50 +00:00
options = {
:requires_gem = > [ ] ,
:keep_retval = > false ,
:argument_required = > false ,
2011-05-27 16:15:10 +00:00
:interpolate = > true ,
2011-10-02 07:53:23 +00:00
:shellwords = > true ,
2011-07-26 07:34:54 +00:00
:listing = > name ,
2011-07-26 09:42:44 +00:00
:use_prefix = > true
2011-05-22 23:57:50 +00:00
} . merge! ( options )
2011-04-25 19:31:38 +00:00
2011-12-31 00:35:08 +00:00
if command_dependencies_met? options
commands [ name ] = BlockCommand . subclass ( name , description , options , & block )
else
commands [ name ] = StubCommand . subclass ( name , description , options )
2011-04-25 19:31:38 +00:00
end
2011-04-24 14:25:07 +00:00
end
2011-11-14 14:12:19 +00:00
# Execute a block of code before a command is invoked. The block also
# gets access to parameters that will be passed to the command and
# is evaluated in the same context.
# @param [String, Regexp] name The name of the command.
# @yield The block to be run before the command.
# @example Display parameter before invoking command
# Pry.commands.before_command("whereami") do |n|
# output.puts "parameter passed was #{n}"
# end
def before_command ( name , & block )
2011-11-15 13:51:17 +00:00
cmd = find_command_by_name_or_listing ( name )
2011-12-29 11:07:49 +00:00
prev_callable = cmd . callable
2011-11-15 13:51:17 +00:00
2011-11-14 14:12:19 +00:00
wrapper_block = proc do | * args |
instance_exec ( * args , & block )
2011-12-29 11:07:49 +00:00
if prev_callable . is_a? ( Proc )
instance_exec ( * args , & prev_callable )
else
prev_callable . call ( * args )
end
2011-11-14 14:12:19 +00:00
end
2011-12-29 11:07:49 +00:00
cmd . callable = wrapper_block
2011-11-14 14:12:19 +00:00
end
# Execute a block of code after a command is invoked. The block also
# gets access to parameters that will be passed to the command and
# is evaluated in the same context.
# @param [String, Regexp] name The name of the command.
# @yield The block to be run after the command.
# @example Display text 'command complete' after invoking command
# Pry.commands.after_command("whereami") do |n|
# output.puts "command complete!"
# end
def after_command ( name , & block )
2011-11-15 13:51:17 +00:00
cmd = find_command_by_name_or_listing ( name )
2011-12-29 11:07:49 +00:00
prev_callable = cmd . callable
2011-11-15 13:51:17 +00:00
2011-11-14 14:12:19 +00:00
wrapper_block = proc do | * args |
2011-12-29 11:07:49 +00:00
if prev_callable . is_a? ( Proc )
instance_exec ( * args , & prev_callable )
else
prev_callable . call ( * args )
end
2011-11-14 14:12:19 +00:00
instance_exec ( * args , & block )
end
2011-12-29 11:07:49 +00:00
cmd . callable = wrapper_block
2011-11-14 14:12:19 +00:00
end
2011-05-30 10:56:50 +00:00
def each & block
2011-07-26 09:42:44 +00:00
@commands . each ( & block )
2011-05-30 10:56:50 +00:00
end
2011-04-24 14:25:07 +00:00
# Removes some commands from the set
2011-05-03 15:49:09 +00:00
# @param [Array<String>] names name of the commands to remove
2011-04-24 14:25:07 +00:00
def delete ( * names )
2011-11-15 13:51:17 +00:00
names . each do | name |
cmd = find_command_by_name_or_listing ( name )
commands . delete cmd . name
end
2011-04-24 14:25:07 +00:00
end
# Imports all the commands from one or more sets.
# @param [Array<CommandSet>] sets Command sets, all of the commands of which
# will be imported.
def import ( * sets )
2011-04-30 10:28:58 +00:00
sets . each do | set |
commands . merge! set . commands
helper_module . send :include , set . helper_module
end
2011-04-24 14:25:07 +00:00
end
# Imports some commands from a set
# @param [CommandSet] set Set to import commands from
# @param [Array<String>] names Commands to import
def import_from ( set , * names )
2011-05-01 06:19:50 +00:00
helper_module . send :include , set . helper_module
2011-11-15 13:51:17 +00:00
names . each do | name |
cmd = set . find_command_by_name_or_listing ( name )
commands [ cmd . name ] = cmd
end
2011-04-24 14:25:07 +00:00
end
2011-11-24 05:27:30 +00:00
# @param [String, Regexp] name_or_listing The name or listing name
# of the command to retrieve.
# @return [Command] The command object matched.
2011-11-15 13:51:17 +00:00
def find_command_by_name_or_listing ( name_or_listing )
if commands [ name_or_listing ]
cmd = commands [ name_or_listing ]
else
_ , cmd = commands . find { | name , command | command . options [ :listing ] == name_or_listing }
end
raise ArgumentError , " Cannot find a command with name: ' #{ name_or_listing } '! " if ! cmd
cmd
end
protected :find_command_by_name_or_listing
2011-04-24 14:25:07 +00:00
# Aliases a command
2011-04-25 11:26:25 +00:00
# @param [String] new_name New name of the command.
# @param [String] old_name Old name of the command.
2011-05-06 14:18:23 +00:00
# @param [String, nil] desc New description of the command.
2011-09-18 06:36:39 +00:00
def alias_command ( new_name , old_name , desc = " " )
2011-11-15 13:51:17 +00:00
orig_command = find_command_by_name_or_listing ( old_name )
commands [ new_name ] = orig_command . dup
2011-04-24 14:25:07 +00:00
commands [ new_name ] . name = new_name
2011-09-18 06:36:39 +00:00
commands [ new_name ] . description = desc
2011-04-24 14:25:07 +00:00
end
2011-11-24 09:33:37 +00:00
# Rename a command. Accepts either actual name or listing name for
# the `old_name`.
# `new_name` must be the actual name of the new command.
2011-11-24 05:27:30 +00:00
# @param [String, Regexp] new_name The new name for the command.
# @param [String, Regexp] old_name The command's current name.
# @param [Hash] options The optional configuration parameters,
# accepts the same as the `command` method, but also allows the
# command description to be passed this way too.
# @example Renaming the `ls` command and changing its description.
# Pry.config.commands.rename "dir", "ls", :description => "DOS friendly ls"
def rename_command ( new_name , old_name , options = { } )
2011-11-24 09:33:37 +00:00
cmd = find_command_by_name_or_listing ( old_name )
2011-11-24 05:27:30 +00:00
options = {
:listing = > new_name ,
2011-11-24 09:33:37 +00:00
:description = > cmd . description
2011-11-24 05:27:30 +00:00
} . merge! ( options )
2011-11-24 09:33:37 +00:00
commands [ new_name ] = cmd . dup
2011-11-24 05:27:30 +00:00
commands [ new_name ] . name = new_name
commands [ new_name ] . description = options . delete ( :description )
commands [ new_name ] . options . merge! ( options )
2011-11-24 09:33:37 +00:00
commands . delete ( cmd . name )
2011-11-24 05:27:30 +00:00
end
2011-11-15 13:51:17 +00:00
# Sets or gets the description for a command (replacing the old
# description). Returns current description if no description
# parameter provided.
# @param [String, Regexp] name The command name.
2011-04-25 06:04:55 +00:00
# @param [String] description The command description.
2011-11-15 13:51:17 +00:00
# @example Setting
2011-05-07 05:32:05 +00:00
# MyCommands = Pry::CommandSet.new do
2011-04-25 06:04:55 +00:00
# desc "help", "help description"
# end
2011-11-15 13:51:17 +00:00
# @example Getting
# Pry.config.commands.desc "amend-line"
def desc ( name , description = nil )
cmd = find_command_by_name_or_listing ( name )
return cmd . description if ! description
cmd . description = description
2011-04-25 06:04:55 +00:00
end
2011-04-25 11:26:25 +00:00
2011-04-30 10:28:58 +00:00
# Defines helpers methods for this command sets.
# Those helpers are only defined in this command set.
#
# @yield A block defining helper methods
# @example
# helpers do
# def hello
# puts "Hello!"
# end
#
# include OtherModule
# end
def helpers ( & block )
helper_module . class_eval ( & block )
end
2011-05-27 16:15:10 +00:00
2011-05-30 12:06:55 +00:00
# @return [Array] The list of commands provided by the command set.
def list_commands
commands . keys
end
2011-04-25 11:26:25 +00:00
private
def define_default_commands
2011-05-27 16:15:10 +00:00
2011-04-25 11:26:25 +00:00
command " help " , " This menu. " do | cmd |
if ! cmd
output . puts
help_text = heading ( " Command List: " ) + " \n "
2011-07-27 18:42:36 +00:00
help_text << commands . map do | key , command |
2011-04-27 15:24:52 +00:00
if command . description && ! command . description . empty?
2011-07-27 18:42:36 +00:00
" #{ command . options [ :listing ] } " . ljust ( 18 ) + command . description
2011-04-25 11:26:25 +00:00
end
2011-07-27 18:42:36 +00:00
end . compact . sort . join ( " \n " )
2011-04-25 11:26:25 +00:00
stagger_output ( help_text )
else
2011-05-27 16:15:10 +00:00
if command = find_command ( cmd )
2011-04-25 11:26:25 +00:00
output . puts command . description
else
output . puts " No info for command: #{ cmd } "
end
end
end
2011-04-25 19:31:38 +00:00
2011-07-26 15:14:24 +00:00
command " install-command " , " Install a disabled command. " do | name |
2011-10-09 19:18:08 +00:00
require 'rubygems/dependency_installer' unless defined? Gem :: DependencyInstaller
2011-05-27 16:15:10 +00:00
command = find_command ( name )
stub_info = command . options [ :stub_info ]
2011-04-25 19:31:38 +00:00
if ! stub_info
output . puts " Not a command stub. Nothing to do. "
next
end
output . puts " Attempting to install ` #{ name } ` command... "
2011-05-27 16:15:10 +00:00
gems_to_install = Array ( command . options [ :requires_gem ] )
2011-04-25 19:31:38 +00:00
gem_install_failed = false
gems_to_install . each do | g |
next if gem_installed? ( g )
output . puts " Installing ` #{ g } ` gem... "
begin
Gem :: DependencyInstaller . new . install ( g )
rescue Gem :: GemNotFoundException
output . puts " Required Gem: ` #{ g } ` not found. Aborting command installation. "
gem_install_failed = true
next
end
end
next if gem_install_failed
Gem . refresh
2011-05-29 15:40:18 +00:00
gems_to_install . each do | g |
begin
require g
rescue LoadError
output . puts " Required Gem: ` #{ g } ` installed but not found?!. Aborting command installation. "
gem_install_failed = true
end
end
next if gem_install_failed
2011-05-27 16:15:10 +00:00
command . options . delete :stub_info
2011-04-25 19:31:38 +00:00
output . puts " Installation of ` #{ name } ` successful! Type `help #{ name } ` for information "
end
2011-04-25 11:26:25 +00:00
end
2011-04-24 14:25:07 +00:00
end
end