1#
2#   xmp.rb - irb version of gotoken xmp
3#   	$Release Version: 0.9$
4#   	$Revision: 38515 $
5#   	by Keiju ISHITSUKA(Nippon Rational Inc.)
6#
7# --
8#
9#
10#
11
12require "irb"
13require "irb/frame"
14
15# An example printer for irb.
16#
17# It's much like the standard library PrettyPrint, that shows the value of each
18# expression as it runs.
19#
20# In order to use this library, you must first require it:
21#
22#     require 'irb/xmp'
23#
24# Now, you can take advantage of the Object#xmp convenience method.
25#
26#     xmp <<END
27#       foo = "bar"
28#       baz = 42
29#     END
30#     #=> foo = "bar"
31#       #==>"bar"
32#     #=> baz = 42
33#       #==>42
34#
35# You can also create an XMP object, with an optional binding to print
36# expressions in the given binding:
37#
38#     ctx = binding
39#     x = XMP.new ctx
40#     x.puts
41#     #=> today = "a good day"
42#       #==>"a good day"
43#     ctx.eval 'today # is what?'
44#     #=> "a good day"
45class XMP
46  @RCS_ID='-$Id: xmp.rb 38515 2012-12-21 05:45:50Z zzak $-'
47
48  # Creates a new XMP object.
49  #
50  # The top-level binding or, optional +bind+ parameter will be used when
51  # creating the workspace. See WorkSpace.new for more information.
52  #
53  # This uses the +:XMP+ prompt mode, see IRB@Customizing+the+IRB+Prompt for
54  # full detail.
55  def initialize(bind = nil)
56    IRB.init_config(nil)
57    #IRB.parse_opts
58    #IRB.load_modules
59
60    IRB.conf[:PROMPT_MODE] = :XMP
61
62    bind = IRB::Frame.top(1) unless bind
63    ws = IRB::WorkSpace.new(bind)
64    @io = StringInputMethod.new
65    @irb = IRB::Irb.new(ws, @io)
66    @irb.context.ignore_sigint = false
67
68#    IRB.conf[:IRB_RC].call(@irb.context) if IRB.conf[:IRB_RC]
69    IRB.conf[:MAIN_CONTEXT] = @irb.context
70  end
71
72  # Evaluates the given +exps+, for example:
73  #
74  #   require 'irb/xmp'
75  #   x = XMP.new
76  #
77  #   x.puts '{:a => 1, :b => 2, :c => 3}'
78  #   #=> {:a => 1, :b => 2, :c => 3}
79  #     # ==>{:a=>1, :b=>2, :c=>3}
80  #   x.puts 'foo = "bar"'
81  #   # => foo = "bar"
82  #     # ==>"bar"
83  def puts(exps)
84    @io.puts exps
85
86    if @irb.context.ignore_sigint
87      begin
88	trap_proc_b = trap("SIGINT"){@irb.signal_handle}
89	catch(:IRB_EXIT) do
90	  @irb.eval_input
91	end
92      ensure
93	trap("SIGINT", trap_proc_b)
94      end
95    else
96      catch(:IRB_EXIT) do
97	@irb.eval_input
98      end
99    end
100  end
101
102  # A custom InputMethod class used by XMP for evaluating string io.
103  class StringInputMethod < IRB::InputMethod
104    # Creates a new StringInputMethod object
105    def initialize
106      super
107      @exps = []
108    end
109
110    # Whether there are any expressions left in this printer.
111    def eof?
112      @exps.empty?
113    end
114
115    # Reads the next expression from this printer.
116    #
117    # See IO#gets for more information.
118    def gets
119      while l = @exps.shift
120	next if /^\s+$/ =~ l
121	l.concat "\n"
122	print @prompt, l
123	break
124      end
125      l
126    end
127
128    # Concatenates all expressions in this printer, separated by newlines.
129    #
130    # An Encoding::CompatibilityError is raised of the given +exps+'s encoding
131    # doesn't match the previous expression evaluated.
132    def puts(exps)
133      if @encoding and exps.encoding != @encoding
134	enc = Encoding.compatible?(@exps.join("\n"), exps)
135	if enc.nil?
136	  raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one"
137	else
138	  @encoding = enc
139	end
140      else
141	@encoding = exps.encoding
142      end
143      @exps.concat exps.split(/\n/)
144    end
145
146    # Returns the encoding of last expression printed by #puts.
147    attr_reader :encoding
148  end
149end
150
151# A convenience method that's only available when the you require the IRB::XMP standard library.
152#
153# Creates a new XMP object, using the given expressions as the +exps+
154# parameter, and optional binding as +bind+ or uses the top-level binding. Then
155# evaluates the given expressions using the +:XMP+ prompt mode.
156#
157# For example:
158#
159#   require 'irb/xmp'
160#   ctx = binding
161#   xmp 'foo = "bar"', ctx
162#   #=> foo = "bar"
163#     #==>"bar"
164#   ctx.eval 'foo'
165#   #=> "bar"
166#
167# See XMP.new for more information.
168def xmp(exps, bind = nil)
169  bind = IRB::Frame.top(1) unless bind
170  xmp = XMP.new(bind)
171  xmp.puts exps
172  xmp
173end
174