emonti-wxirb 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. data/README.rdoc +173 -0
  2. data/bin/wxirb +14 -0
  3. data/lib/wxirb.rb +393 -0
  4. metadata +64 -0
data/README.rdoc ADDED
@@ -0,0 +1,173 @@
1
+
2
+ = WxIRB
3
+
4
+ This is a GUI "irb-alike" console based on WxRuby. I wrote this because I
5
+ needed a better way to prototype and debug my wxruby applications as I was
6
+ developing them. WxIRB puts you "inside" a Wx::App.run event loop, which
7
+ lets you easily play with window objects during run-time.
8
+
9
+ This is mostly just a port of why_the_lucky_stiff's Shoes GUI irb example to
10
+ wxruby with the addition of a command history and a few other convenience
11
+ methods added in the window classes.
12
+
13
+ Credit to Why.
14
+ See: http://github.com/why/shoes/blob/master/samples/expert-irb.rb
15
+
16
+ == Installation
17
+
18
+ WxIRB is available as a gem from github.
19
+
20
+ gem sources -a http://gems.github.com #(you only have to do this once)
21
+ sudo gem install wxirb
22
+
23
+ Or you can install it manually:
24
+
25
+ git clone git://github.com/emonti/wxirb.git
26
+ sudo cp wxirb/lib/wxirb.rb /usr/lib/ruby/1.8/site_ruby/1.8 # or wherever
27
+ sudo cp wxirb/bin/wxirb /usr/local/bin # or wherever
28
+
29
+ == Keyboard Interaction
30
+
31
+ WxIRB is designed to allow you to edit multi-line ruby statements in the
32
+ input window. The keyboard commands in the input text area are what you'
33
+ d expect in a multi-line text-box with some additional special keyboard
34
+ modifiers:
35
+
36
+ * ENTER : runs a statement through the mock IRB object. (what you'd expect)
37
+ * META+ENTER : sends a newline inside the window instead of running a command
38
+ * META+UP-ARROW : scroll up in history
39
+ * META+DOWN-ARROW : scroll down in history
40
+
41
+ WxIRB does not have a "run" button. Thank goodness!
42
+
43
+ The output text area is read-only from the UI. Tabbing focus from the output
44
+ text area should land you back in the input text area.
45
+
46
+
47
+ == Interacting with the WxIRB Window Objects
48
+
49
+ When running wx_irb.rb directly a global variable named '$wxirb' is created
50
+ for you which holds a reference to the WxIRB::BaseFrame window you are using.
51
+
52
+ This is done so you can easily access the UI frame object and children from
53
+ within the actual interface. A few convenience methods and accessors are
54
+ exposed this way:
55
+
56
+ * '$wxirb.clear' lets you clear the output window
57
+
58
+ * '$wxirb.history' returns the command history object (basically an array)
59
+
60
+ * '$wxirb.histdump(s=0, e=-1)' prints the history to the output text area.
61
+ to the output text area. Takes optional start and indexes for viewing just
62
+ a slice of the history array.
63
+
64
+ * '$wxirb.set_binding(bind)' sets the object binding to 'bind' which is used
65
+ when running eval() on user input. See also, 'Object Binding'.
66
+
67
+
68
+ == WxIRB CommandHistory
69
+
70
+ WxIRB maintains a persistent history log. The WxIRB history uses a separate
71
+ file from IRB which is defined by WxIRB::CommandHistory::HISTFILE. It is
72
+ actually just a YAMLized array.
73
+
74
+ By default, this is $HOME/.wxirb_history (no way of 'convenient' way of
75
+ configuring this right now)
76
+
77
+ History is implemented in the WxIRB::CommandHistory class. This is basically
78
+ an array with a few convenience methods and accessors:
79
+
80
+ * 'hpos' is an accessor to the history array position.
81
+
82
+ * 'prev' moves hpos back one and returns the history value at that position
83
+
84
+ * 'next' does the same thing, but forward
85
+
86
+ * '<<' is an override for the Array superclass that just updates the history
87
+ position variable.
88
+
89
+ * 'save!' saves current history to the persistent history file
90
+
91
+ * 'save' saves to the persistent history file only if the history has changed.
92
+ (this is used for an evt_idle event handler by the input text window)
93
+
94
+ * 'clear' empties the history array and persistent history file
95
+
96
+
97
+ == Output to WxIRB
98
+
99
+ The WxIRB::BaseFrame object also has an 'output' accessor which returns a
100
+ reference to the OutputTextCtrl text window half of the display. This object
101
+ has a few methods to make it usable (in duck-typing cases) as an IO object.
102
+ Note: This doesn't actually inherit from or implement all of IO class, however.
103
+
104
+ * '<<' outputs to the output window
105
+
106
+ * 'puts' prints directly to the output text area like 'IO.puts' and returns
107
+ nil.
108
+
109
+ * 'print' prints directly to the output text area like 'IO.print'
110
+ and returns nil.
111
+
112
+ * 'write' prints directly to the output text area like 'IO.write' and returns
113
+ the number of bytes written.
114
+
115
+ * Note: write, print, and puts all use brown text to differentiate from other
116
+ output.
117
+
118
+ * 'close', 'closed?' and 'flush' are also all defined but just emulate an
119
+ IO object with their return values. Other than that, they do nothing.
120
+
121
+
122
+ == Object Binding
123
+
124
+ Originally, WxIRB just ran 'eval' using the TOPLEVEL_BINDING (aka main)
125
+ This is how you usually run IRB. However, it may be desirable to instantiate
126
+ a wxirb console bound to a different namespace for debugging specific objects
127
+ and whatnot. So a few ways to manage bindings were added.
128
+
129
+ * WxIRB::BaseFrame.new now accepts an optional binding parameter. For example:
130
+
131
+ >> WxIRB::BaseFrame.new(nil, :binding => TOPLEVEL_BINDING)
132
+
133
+ By default, new WxIRB::BaseFrame objects will bind to themselves. Meaning,
134
+ commands will run in the scope of the WxIRB::BaseFrame scope itself.
135
+ TOPLEVEL_BINDING makes the scope 'main'.
136
+
137
+ When running 'wxirb.rb' directly, you will start with TOPLEVEL_BINDING, but
138
+ you can use the 'set_binding' method of $wxirb to change the binding on the
139
+ fly.
140
+
141
+ * As mentioned above, WxIRB::BaseFrame also has an instance method called
142
+ set_binding. This method lets you change WxIRB's binding on the fly.
143
+ Here's a short example from inside WxIRB running 'wxirb.rb' directly:
144
+
145
+ >> self
146
+ => main
147
+ >> $wxirb
148
+ => #<WxIRB::BaseFrame:0x51e8c0>
149
+ >> $wxirb.set_binding $wxirb.instance_eval { binding }
150
+ => #<Binding:0x17235fe8>
151
+ >> self
152
+ => #<WxIRB::BaseFrame:0x51e8c0>
153
+ >> @output
154
+ => #<WxIRB::InputTextCtrl:0x51d5b0>
155
+ >> @output.puts "hello self"
156
+ hello self
157
+ => nil
158
+ >> set_binding TOPLEVEL_BINDING
159
+ => #<Binding:0x296d0>
160
+ >> self
161
+ => main
162
+
163
+
164
+ == BUGS
165
+
166
+ * Running statements gets slow when the Output window gets very full. Not
167
+ really sure why this is, but running 'wxirb.clear' periodically helps.
168
+
169
+ * An effort is made to rescue most exceptions, but sometimes wxirb will
170
+ close due to an un-handled exception. Regular 'irb' does this too
171
+ sometimes... so I don't feel too bad.
172
+
173
+
data/bin/wxirb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'rubygems'
4
+ rescue LoadError
5
+ end
6
+
7
+ require 'wx'
8
+ require 'wxirb'
9
+
10
+ Wx::App.run do
11
+ $wxirb = WxIRB::BaseFrame.new(nil, :binding => TOPLEVEL_BINDING)
12
+ $wxirb.show
13
+ end
14
+
data/lib/wxirb.rb ADDED
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'rubygems'
4
+ rescue LoadError
5
+ end
6
+ require 'wx'
7
+ require 'yaml'
8
+ require 'irb/ruby-lex'
9
+ require 'stringio'
10
+
11
+ # This class is stolen almost verbatim from why_the_lucky_stiff's Shoes
12
+ # example. (http://github.com/why/shoes/blob/master/samples/expert-irb.rb)
13
+ # He gets all the credit.
14
+ class MimickIRB < RubyLex
15
+ attr_accessor :started
16
+
17
+ class Continue < StandardError; end
18
+ class Empty < StandardError; end
19
+
20
+ def initialize(bind=TOPLEVEL_BINDING)
21
+ set_binding(bind)
22
+ super()
23
+ set_input(StringIO.new)
24
+ end
25
+
26
+ def set_binding(bind)
27
+ if bind.is_a? Binding
28
+ @bind = bind
29
+ else
30
+ raise "Invalid binding #{bind.inspect}"
31
+ end
32
+ end
33
+
34
+ def run(str)
35
+ obj = nil
36
+ @io << str
37
+ @io.rewind
38
+ unless l = lex
39
+ raise Empty if @line == ''
40
+ else
41
+ case l.strip
42
+ when "reset"
43
+ @line = ""
44
+ else
45
+ @line << l << "\n"
46
+ if @ltype or @continue or @indent > 0
47
+ raise Continue
48
+ end
49
+ end
50
+ end
51
+ unless @line.empty?
52
+ obj = eval @line, @bind, "(irb)", @line_no
53
+ end
54
+ @line_no += @line.scan(/\n/).length
55
+ @line = ''
56
+ @exp_line_no = @line_no
57
+
58
+ @indent = 0
59
+ @indent_stack = []
60
+
61
+ $stdout.rewind
62
+ output = $stdout.read
63
+ $stdout.truncate(0)
64
+ $stdout.rewind
65
+ [output, obj]
66
+ rescue Object => e
67
+ case e when Empty, Continue
68
+ else @line = ""
69
+ end
70
+ raise e
71
+ ensure
72
+ set_input(StringIO.new)
73
+ end
74
+ end
75
+
76
+
77
+ # Docs say there's a KeyEvent.cmd_down which is platform independent.
78
+ # But they LIE so we create our own...
79
+ class Wx::KeyEvent
80
+ case Wx::PLATFORM
81
+ when "WXMAC"
82
+ def inputmod_down; meta_down; end
83
+ when "WXMSW"
84
+ def inputmod_down; alt_down; end
85
+ end
86
+ end
87
+
88
+
89
+ module WxIRB
90
+ # WxIRB maintains a persistent history log. The WxIRB history uses a separate
91
+ # file from IRB which is defined by WxIRB::CommandHistory::HISTFILE. It is
92
+ # actually just a YAMLized array.
93
+ #
94
+ # By default, the file is $HOME/.wxirb_history (no 'convenient' way of
95
+ # configuring this right now aside from changing the constant)
96
+ #
97
+ # History is implemented in the WxIRB::CommandHistory class. This is
98
+ # basically just an Array with a few convenience methods and accessors.
99
+ class CmdHistory < Array
100
+ # an accessor to the history array position.
101
+ attr_accessor :hpos
102
+
103
+ # location of the persistent history file
104
+ HISTFILE = "#{ENV["HOME"]}/.wxirb_history"
105
+
106
+ # Initializes a CommandHistory object and loads persistent history
107
+ # from the file specified in HISTFILE
108
+ def initialize(*opts)
109
+ super(*opts)
110
+ @hpos = nil
111
+ begin
112
+ self.replace YAML.load_file(HISTFILE)
113
+ @hpos = self.size-1
114
+ rescue
115
+ STDERR.puts "Note - error restoring history: #{$!.inspect}"
116
+ end
117
+ end
118
+
119
+ # moves hpos back one and returns the history value at that position
120
+ def prev
121
+ return nil if self.empty?
122
+ val = self[@hpos].to_s
123
+ @hpos -= 1 unless @hpos == 0
124
+ return val
125
+ end
126
+
127
+ # moves hpos forward one and returns the history value at that position
128
+ def next
129
+ if not self.empty? and @hpos != self.size-1
130
+ @hpos += 1
131
+ self[@hpos].to_s
132
+ end
133
+ end
134
+
135
+ # An override for the Array superclass that just updates the history
136
+ # position variable. This also ensures that history elements are appended
137
+ # as strings.
138
+ def << (val)
139
+ @hpos = self.size
140
+ @changed=true
141
+ super(val.to_s)
142
+ end
143
+
144
+ # empties the history array and persistent history file
145
+ def clear
146
+ self.replace([])
147
+ self.save!
148
+ end
149
+
150
+ # saves current history to the persistent history file
151
+ def save!
152
+ ret=nil
153
+ begin
154
+ ret=File.open(HISTFILE, "w") {|f| f.write(self.to_yaml) }
155
+ rescue Errno::ENOENT
156
+ STDERR.puts "Error: couldn't save history - #{$!}"
157
+ end
158
+ @changed=nil
159
+ ret
160
+ end
161
+
162
+ # saves to the persistent history file only if the history has changed.
163
+ # (this is used for an evt_idle event handler by the input text window)
164
+ def save(force=false)
165
+ return nil unless @changed or force
166
+ self.save!
167
+ end
168
+ end
169
+
170
+
171
+ # Keyboard commands in the input text area are what you'd expect in a
172
+ # multi-line textbox with some additional special keyboard modifiers.
173
+ # See InputTextCtrl#on_char
174
+ class InputTextCtrl < Wx::TextCtrl
175
+ attr_accessor :history
176
+ include Wx
177
+ STYLE = TE_PROCESS_TAB|TE_PROCESS_ENTER|WANTS_CHARS|TE_MULTILINE
178
+
179
+ def initialize(parent, output, mirb)
180
+ super(parent, :style => STYLE)
181
+ @history = CmdHistory.new
182
+ @output = output
183
+
184
+ @stdout_save = $stdout
185
+ $stdout = StringIO.new
186
+ @mirb = mirb
187
+ evt_idle :on_idle
188
+ evt_char :on_char
189
+
190
+ @font = Wx::Font.new(10, Wx::MODERN, Wx::NORMAL, Wx::NORMAL)
191
+ set_default_style Wx::TextAttr.new(Wx::BLACK, Wx::WHITE, @font)
192
+
193
+ paint do |dc|
194
+ b_height = dc.get_text_extent("@", @font)[1] * 4
195
+ cache_best_size Wx::Size.new(self.size.width, b_height)
196
+ end
197
+ end
198
+
199
+ # Fires on Wx::IdleEvent - saves persistent history if history has changed
200
+ def on_idle(evt)
201
+ history.save
202
+ end
203
+
204
+ # Fires on text input events.
205
+ # Implements a few special keyboard handlers
206
+ # * META+ENTER : sends a newline inside the input window instead of
207
+ # actually running a command
208
+ # * META+UP-ARROW : scroll up in history
209
+ # * META+DOWN-ARROW : scroll down in history
210
+ def on_char(evt)
211
+ k = evt.key_code
212
+ mflag = evt.modifiers
213
+
214
+ if [MOD_NONE, MOD_SHIFT].include?(mflag) and (0x20..0x7f).include?(k)
215
+ evt.skip()
216
+ return
217
+ end
218
+
219
+ case k
220
+ when K_RETURN
221
+ if evt.inputmod_down
222
+ # multi-line command uses meta-down-arrow for newline
223
+ self.write_text("\n")
224
+ else
225
+ @history << self.value
226
+ run self.value
227
+ self.clear
228
+ end
229
+ return
230
+ when (evt.inputmod_down and K_UP)
231
+ if hist=history.prev
232
+ self.value = hist
233
+ self.set_insertion_point_end
234
+ return
235
+ end
236
+ when (evt.inputmod_down and K_DOWN)
237
+ if hist=history.next
238
+ self.value = hist
239
+ self.set_insertion_point_end
240
+ return
241
+ else
242
+ self.clear
243
+ end
244
+ end
245
+ evt.skip()
246
+ end
247
+
248
+ # Runs a command through the mock irb class, handles display details.
249
+ def run(cmd)
250
+ (lines = cmd.split(/\r?\n/)).each_index do |idx|
251
+ begin
252
+ line = lines[idx] + "\n"
253
+ @output.default_style = Wx::TextAttr.new(Wx::BLUE)
254
+ @output << ">> #{line}"
255
+
256
+ out, obj = @mirb.run(line)
257
+ @output.default_style = Wx::TextAttr.new(Wx::BLACK)
258
+ @output << out
259
+ @output.default_style = Wx::TextAttr.new(Wx::Colour.new(100,100,100))
260
+ @output << "=> #{obj.inspect}\n"
261
+ rescue MimickIRB::Empty
262
+ rescue MimickIRB::Continue
263
+ if idx == lines.size-1
264
+ @output.default_style = Wx::TextAttr.new(Wx::LIGHT_GREY)
265
+ @output << "...\n"
266
+ end
267
+ rescue Exception => se
268
+ @output.default_style = Wx::TextAttr.new(Wx::RED)
269
+ @output << (se.inspect + "\n" + se.backtrace.join("\n") + "\n")
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+
276
+ # The output textbox.
277
+ # This object has a few methods to make it usable (in duck-typing cases) as
278
+ # an IO object. This doesn't actually inherit or implement from the IO
279
+ # class, however.
280
+ class OutputTextCtrl < Wx::TextCtrl
281
+ include Wx
282
+ STYLE = TE_READONLY|TE_MULTILINE|TE_RICH|TE_RICH2|TE_CHARWRAP
283
+
284
+ def initialize(parent)
285
+ super(parent, :style => STYLE)
286
+
287
+ font = Wx::Font.new(10, Wx::MODERN, Wx::NORMAL, Wx::NORMAL)
288
+ set_default_style Wx::TextAttr.new(Wx::BLACK, Wx::WHITE, font)
289
+
290
+ @io_style=Wx::TextAttr.new(Wx::Colour.new(180, 104, 52))
291
+ end
292
+
293
+ #----------------------------------------------------------------------
294
+ # some methods (as i think of/need them) to make it possible to use
295
+ # the output text area as a fake IO object
296
+ #----------------------------------------------------------------------
297
+
298
+ # '<<' outputs to the output window
299
+ alias :<< :append_text
300
+
301
+ # 'close', 'closed?' and 'flush' are also all defined but just emulate
302
+ # an IO object with their return values. Other than that, they do nothing.
303
+ def close ; nil; end
304
+ def closed? ; false ; end
305
+ def flush; self; end
306
+
307
+ # prints directly to the output text area like 'IO.print'
308
+ # and returns nil.
309
+ def print(*args)
310
+ set_default_style @io_style
311
+ self << args.flatten.join
312
+ nil
313
+ end
314
+
315
+ # Displays directly to the output text area like 'IO.puts' and returns nil.
316
+ def puts(*args)
317
+ set_default_style @io_style
318
+ self << args.flatten.map do |o|
319
+ s=o.to_s
320
+ (s[-1,1]=="\n") ? s : s + "\n"
321
+ end.join
322
+ nil
323
+ end
324
+
325
+ # Displays directly to the output text area like 'IO.write' and returns
326
+ # the number of bytes written.
327
+ def write(dat)
328
+ set_default_style @io_style
329
+ out = dat.to_s
330
+ self << out
331
+ out.size
332
+ end
333
+ end
334
+
335
+ # This window object is parent to the Input and Output text areas. It
336
+ # provides a sliding splitter control between the top and bottom text boxes.
337
+ class TerminalSplitter < Wx::SplitterWindow
338
+ def initialize(parent)
339
+ super(parent, :style => Wx::SP_LIVE_UPDATE)
340
+ self.set_sash_gravity(1.0)
341
+ evt_splitter_dclick self, :on_dclick
342
+ end
343
+
344
+ def on_dclick(evt)
345
+ set_sash_position(- @bottom.best_size.height)
346
+ end
347
+
348
+ def split_horizontally(top, bottom, pos=nil)
349
+ @top ||= top
350
+ @bottom ||= bottom
351
+ minsz = @bottom.best_size.height
352
+ set_minimum_pane_size(minsz) # this also prevents unsplitting.
353
+ pos ||= - minsz
354
+ super(@top, @bottom, pos)
355
+ end
356
+ end
357
+
358
+
359
+ # Parent and top-level window for all the wxirb controls.
360
+ class BaseFrame < Wx::Frame
361
+ attr_reader :output, :input
362
+
363
+ def initialize(parent, opts={})
364
+ bind = (opts.delete(:binding) || binding)
365
+ @mirb=MimickIRB.new(bind)
366
+
367
+ opts[:title] ||= "WxIRB"
368
+ super(parent, opts)
369
+
370
+ @splitter = TerminalSplitter.new(self)
371
+ @output = OutputTextCtrl.new(@splitter)
372
+ @input = InputTextCtrl.new(@splitter, @output, @mirb)
373
+ @splitter.split_horizontally(@output, @input)
374
+ @input.set_focus()
375
+ end
376
+
377
+ # Allow our binding to be changed on the fly
378
+ def set_binding(bind); @mirb.set_binding(bind); end
379
+
380
+ # Clears the output window
381
+ def clear; @output.clear ; end
382
+
383
+ # Returns the window's command history object (see WxIRB::CommandHistory)
384
+ def history; @input.history ; end
385
+
386
+ # Prints the history to the output text area. Takes optional start and
387
+ # end position indexes for viewing just a slice of the history array.
388
+ def histdump(s=0, e=-1)
389
+ puts self.history[s..e]
390
+ end
391
+ end
392
+ end
393
+
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emonti-wxirb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Eric Monti
8
+ autorequire: wxirb
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-13 00:00:00 -08:00
13
+ default_executable: wxirb
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: wxruby
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.9.9
24
+ version:
25
+ description:
26
+ email: emonti@matasano.com
27
+ executables:
28
+ - wxirb
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ files:
34
+ - README.rdoc
35
+ - bin/wxirb
36
+ - lib/wxirb.rb
37
+ has_rdoc: true
38
+ homepage: http://www.matasano.com
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.2.0
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: A GUI IRB-like console based on WxRuby
63
+ test_files: []
64
+