1#
2#   irb/extend-command.rb - irb extend command
3#   	$Release Version: 0.9.6$
4#   	$Revision: 38515 $
5#   	by Keiju ISHITSUKA(keiju@ruby-lang.org)
6#
7# --
8#
9#
10#
11module IRB # :nodoc:
12  # Installs the default irb extensions command bundle.
13  module ExtendCommandBundle
14    EXCB = ExtendCommandBundle # :nodoc:
15
16    # See #install_alias_method.
17    NO_OVERRIDE = 0
18    # See #install_alias_method.
19    OVERRIDE_PRIVATE_ONLY = 0x01
20    # See #install_alias_method.
21    OVERRIDE_ALL = 0x02
22
23    # Quits the current irb context
24    #
25    # +ret+ is the optional signal or message to send to Context#exit
26    #
27    # Same as <code>IRB.CurrentContext.exit</code>.
28    def irb_exit(ret = 0)
29      irb_context.exit(ret)
30    end
31
32    # Displays current configuration.
33    #
34    # Modifing the configuration is achieved by sending a message to IRB.conf.
35    def irb_context
36      IRB.CurrentContext
37    end
38
39    @ALIASES = [
40      [:context, :irb_context, NO_OVERRIDE],
41      [:conf, :irb_context, NO_OVERRIDE],
42      [:irb_quit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
43      [:exit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
44      [:quit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
45    ]
46
47    @EXTEND_COMMANDS = [
48      [:irb_current_working_workspace, :CurrentWorkingWorkspace, "irb/cmd/chws",
49	[:irb_print_working_workspace, OVERRIDE_ALL],
50	[:irb_cwws, OVERRIDE_ALL],
51	[:irb_pwws, OVERRIDE_ALL],
52#	[:irb_cww, OVERRIDE_ALL],
53#	[:irb_pww, OVERRIDE_ALL],
54	[:cwws, NO_OVERRIDE],
55	[:pwws, NO_OVERRIDE],
56#	[:cww, NO_OVERRIDE],
57#	[:pww, NO_OVERRIDE],
58	[:irb_current_working_binding, OVERRIDE_ALL],
59	[:irb_print_working_binding, OVERRIDE_ALL],
60	[:irb_cwb, OVERRIDE_ALL],
61	[:irb_pwb, OVERRIDE_ALL],
62#	[:cwb, NO_OVERRIDE],
63#	[:pwb, NO_OVERRIDE]
64      ],
65      [:irb_change_workspace, :ChangeWorkspace, "irb/cmd/chws",
66	[:irb_chws, OVERRIDE_ALL],
67#	[:irb_chw, OVERRIDE_ALL],
68	[:irb_cws, OVERRIDE_ALL],
69#	[:irb_cw, OVERRIDE_ALL],
70	[:chws, NO_OVERRIDE],
71#	[:chw, NO_OVERRIDE],
72	[:cws, NO_OVERRIDE],
73#	[:cw, NO_OVERRIDE],
74	[:irb_change_binding, OVERRIDE_ALL],
75	[:irb_cb, OVERRIDE_ALL],
76	[:cb, NO_OVERRIDE]],
77
78      [:irb_workspaces, :Workspaces, "irb/cmd/pushws",
79	[:workspaces, NO_OVERRIDE],
80	[:irb_bindings, OVERRIDE_ALL],
81	[:bindings, NO_OVERRIDE]],
82      [:irb_push_workspace, :PushWorkspace, "irb/cmd/pushws",
83	[:irb_pushws, OVERRIDE_ALL],
84#	[:irb_pushw, OVERRIDE_ALL],
85	[:pushws, NO_OVERRIDE],
86#	[:pushw, NO_OVERRIDE],
87	[:irb_push_binding, OVERRIDE_ALL],
88	[:irb_pushb, OVERRIDE_ALL],
89	[:pushb, NO_OVERRIDE]],
90      [:irb_pop_workspace, :PopWorkspace, "irb/cmd/pushws",
91	[:irb_popws, OVERRIDE_ALL],
92#	[:irb_popw, OVERRIDE_ALL],
93	[:popws, NO_OVERRIDE],
94#	[:popw, NO_OVERRIDE],
95	[:irb_pop_binding, OVERRIDE_ALL],
96	[:irb_popb, OVERRIDE_ALL],
97	[:popb, NO_OVERRIDE]],
98
99      [:irb_load, :Load, "irb/cmd/load"],
100      [:irb_require, :Require, "irb/cmd/load"],
101      [:irb_source, :Source, "irb/cmd/load",
102	[:source, NO_OVERRIDE]],
103
104      [:irb, :IrbCommand, "irb/cmd/subirb"],
105      [:irb_jobs, :Jobs, "irb/cmd/subirb",
106	[:jobs, NO_OVERRIDE]],
107      [:irb_fg, :Foreground, "irb/cmd/subirb",
108	[:fg, NO_OVERRIDE]],
109      [:irb_kill, :Kill, "irb/cmd/subirb",
110	[:kill, OVERRIDE_PRIVATE_ONLY]],
111
112      [:irb_help, :Help, "irb/cmd/help",
113        [:help, NO_OVERRIDE]],
114
115    ]
116
117    # Installs the default irb commands:
118    #
119    # +irb_current_working_workspace+::   Context#main
120    # +irb_change_workspace+::            Context#change_workspace
121    # +irb_workspaces+::                  Context#workspaces
122    # +irb_push_workspace+::              Context#push_workspace
123    # +irb_pop_workspace+::               Context#pop_workspace
124    # +irb_load+::                        #irb_load
125    # +irb_require+::                     #irb_require
126    # +irb_source+::                      IrbLoader#source_file
127    # +irb+::                             IRB.irb
128    # +irb_jobs+::                        JobManager
129    # +irb_fg+::                          JobManager#switch
130    # +irb_kill+::                        JobManager#kill
131    # +irb_help+::                        IRB@Command+line+options
132    def self.install_extend_commands
133      for args in @EXTEND_COMMANDS
134	def_extend_command(*args)
135      end
136    end
137
138    # Evaluate the given +cmd_name+ on the given +cmd_class+ Class.
139    #
140    # Will also define any given +aliases+ for the method.
141    #
142    # The optional +load_file+ parameter will be required within the method
143    # definition.
144    def self.def_extend_command(cmd_name, cmd_class, load_file = nil, *aliases)
145      case cmd_class
146      when Symbol
147	cmd_class = cmd_class.id2name
148      when String
149      when Class
150	cmd_class = cmd_class.name
151      end
152
153      if load_file
154	line = __LINE__; eval %[
155	  def #{cmd_name}(*opts, &b)
156	    require "#{load_file}"
157	    arity = ExtendCommand::#{cmd_class}.instance_method(:execute).arity
158	    args = (1..(arity < 0 ? ~arity : arity)).map {|i| "arg" + i.to_s }
159	    args << "*opts" if arity < 0
160	    args << "&block"
161	    args = args.join(", ")
162	    line = __LINE__; eval %[
163	      def #{cmd_name}(\#{args})
164		ExtendCommand::#{cmd_class}.execute(irb_context, \#{args})
165	      end
166	    ], nil, __FILE__, line
167	    send :#{cmd_name}, *opts, &b
168	  end
169	], nil, __FILE__, line
170      else
171	line = __LINE__; eval %[
172	  def #{cmd_name}(*opts, &b)
173	    ExtendCommand::#{cmd_class}.execute(irb_context, *opts, &b)
174	  end
175	], nil, __FILE__, line
176      end
177
178      for ali, flag in aliases
179	@ALIASES.push [ali, cmd_name, flag]
180      end
181    end
182
183    # Installs alias methods for the default irb commands, see
184    # ::install_extend_commands.
185    def install_alias_method(to, from, override = NO_OVERRIDE)
186      to = to.id2name unless to.kind_of?(String)
187      from = from.id2name unless from.kind_of?(String)
188
189      if override == OVERRIDE_ALL or
190	  (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or
191	  (override == NO_OVERRIDE) &&  !respond_to?(to, true)
192	target = self
193	(class << self; self; end).instance_eval{
194	  if target.respond_to?(to, true) &&
195	      !target.respond_to?(EXCB.irb_original_method_name(to), true)
196	    alias_method(EXCB.irb_original_method_name(to), to)
197	  end
198	  alias_method to, from
199	}
200      else
201	print "irb: warn: can't alias #{to} from #{from}.\n"
202      end
203    end
204
205    def self.irb_original_method_name(method_name) # :nodoc:
206      "irb_" + method_name + "_org"
207    end
208
209    # Installs alias methods for the default irb commands on the given object
210    # using #install_alias_method.
211    def self.extend_object(obj)
212      unless (class << obj; ancestors; end).include?(EXCB)
213	super
214	for ali, com, flg in @ALIASES
215	  obj.install_alias_method(ali, com, flg)
216	end
217      end
218    end
219
220    install_extend_commands
221  end
222
223  # Extends methods for the Context module
224  module ContextExtender
225    CE = ContextExtender # :nodoc:
226
227    @EXTEND_COMMANDS = [
228      [:eval_history=, "irb/ext/history.rb"],
229      [:use_tracer=, "irb/ext/tracer.rb"],
230      [:math_mode=, "irb/ext/math-mode.rb"],
231      [:use_loader=, "irb/ext/use-loader.rb"],
232      [:save_history=, "irb/ext/save-history.rb"],
233    ]
234
235    # Installs the default context extensions as irb commands:
236    #
237    # Context#eval_history=::   +irb/ext/history.rb+
238    # Context#use_tracer=::     +irb/ext/tracer.rb+
239    # Context#math_mode=::      +irb/ext/math-mode.rb+
240    # Context#use_loader=::     +irb/ext/use-loader.rb+
241    # Context#save_history=::   +irb/ext/save-history.rb+
242    def self.install_extend_commands
243      for args in @EXTEND_COMMANDS
244	def_extend_command(*args)
245      end
246    end
247
248    # Evaluate the given +command+ from the given +load_file+ on the Context
249    # module.
250    #
251    # Will also define any given +aliases+ for the method.
252    def self.def_extend_command(cmd_name, load_file, *aliases)
253      line = __LINE__; Context.module_eval %[
254        def #{cmd_name}(*opts, &b)
255	  Context.module_eval {remove_method(:#{cmd_name})}
256	  require "#{load_file}"
257	  send :#{cmd_name}, *opts, &b
258	end
259	for ali in aliases
260	  alias_method ali, cmd_name
261	end
262      ], __FILE__, line
263    end
264
265    CE.install_extend_commands
266  end
267
268  # A convenience module for extending Ruby methods.
269  module MethodExtender
270    # Extends the given +base_method+ with a prefix call to the given
271    # +extend_method+.
272    def def_pre_proc(base_method, extend_method)
273      base_method = base_method.to_s
274      extend_method = extend_method.to_s
275
276      alias_name = new_alias_name(base_method)
277      module_eval %[
278        alias_method alias_name, base_method
279        def #{base_method}(*opts)
280	  send :#{extend_method}, *opts
281	  send :#{alias_name}, *opts
282	end
283      ]
284    end
285
286    # Extends the given +base_method+ with a postfix call to the given
287    # +extend_method+.
288    def def_post_proc(base_method, extend_method)
289      base_method = base_method.to_s
290      extend_method = extend_method.to_s
291
292      alias_name = new_alias_name(base_method)
293      module_eval %[
294        alias_method alias_name, base_method
295        def #{base_method}(*opts)
296	  send :#{alias_name}, *opts
297	  send :#{extend_method}, *opts
298	end
299      ]
300    end
301
302    # Returns a unique method name to use as an alias for the given +name+.
303    #
304    # Usually returns <code>#{prefix}#{name}#{postfix}<num></code>, example:
305    #
306    #     new_alias_name('foo') #=> __alias_of__foo__
307    #     def bar; end
308    #     new_alias_name('bar') #=> __alias_of__bar__2
309    def new_alias_name(name, prefix = "__alias_of__", postfix = "__")
310      base_name = "#{prefix}#{name}#{postfix}"
311      all_methods = instance_methods(true) + private_instance_methods(true)
312      same_methods = all_methods.grep(/^#{Regexp.quote(base_name)}[0-9]*$/)
313      return base_name if same_methods.empty?
314      no = same_methods.size
315      while !same_methods.include?(alias_name = base_name + no)
316	no += 1
317      end
318      alias_name
319    end
320  end
321end
322
323