dietrb 0.1.2 → 0.2.0

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.
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ Copyright (c) <2010> Eloy Duran, <eloy.de.enige@gmail.com>
2
+
3
+ This software is available under the Ruby license.
data/README.rdoc ADDED
@@ -0,0 +1,99 @@
1
+ = IRB on a diet, for MacRuby / Ruby 1.9
2
+
3
+ The goal is to have a small and cleaned up version of IRB. Trimmed down to only
4
+ do the stuff I, and most people I know, actually use.
5
+
6
+ Trimming down the core code is done mainly by using Ripper, which comes with
7
+ Ruby 1.9, instead of shipping it's own parser etc.
8
+
9
+ There's still lots to be done, but the ‘basic functionality’ as is now, should
10
+ not grow too much more. For now my things to-do are .irbrc support, completion,
11
+ and investigate what else people really really need. After that it's time to
12
+ polish.
13
+
14
+ == Differences
15
+
16
+ * This IRB version specifically targets MacRuby, for now, and allows Cocoa
17
+ development to be done from the command-line. Dietrb will automatically
18
+ override the normal runloop to be ran in a thread and start a NSRunLoop on
19
+ the main thread.
20
+
21
+ * Dietrb will try to warn about syntax errors as soon as a line is entered and
22
+ only reset the buffer to the previous line. This means that you don't need to
23
+ loose any previous work:
24
+
25
+ IRB:
26
+
27
+ irb(main):001:0> class A
28
+ irb(main):002:1> def foo
29
+ irb(main):003:2> } p :ok
30
+ irb(main):004:1> end
31
+ SyntaxError: compile error
32
+ (irb):3: syntax error, unexpected '}'
33
+ } p :ok
34
+ ^
35
+ (irb):4: syntax error, unexpected $end, expecting kEND
36
+ from (irb):4
37
+ from :0
38
+ irb(main):005:0> A.new.foo
39
+ NameError: uninitialized constant A
40
+ from (irb):5
41
+ from :0
42
+
43
+ Dietrb:
44
+
45
+ irb(main):001:0> class A
46
+ irb(main):002:1> def foo
47
+ irb(main):003:2> } p :ok
48
+ SyntaxError: compile error
49
+ (irb):3: syntax error, unexpected '}'
50
+ irb(main):004:2> p :ok
51
+ irb(main):005:2> end
52
+ irb(main):006:1> end
53
+ => nil
54
+ irb(main):007:0> A.new.foo
55
+ :ok
56
+ => :ok
57
+
58
+ == Play
59
+
60
+ Normal usage:
61
+
62
+ irb(main):001:0> class A
63
+ irb(main):002:1> def foo
64
+ irb(main):003:2> :ok
65
+ irb(main):004:2> end
66
+ irb(main):005:1> end
67
+ => nil
68
+ irb(main):006:0> irb A.new
69
+ irb(#<#<Class:…>::A:…>):001:0> foo
70
+ => :ok
71
+ irb(#<#<Class:…>::A:…>):002:0> quit
72
+ => nil
73
+ irb(main):007:0> quit
74
+
75
+ Or on MacRuby, try:
76
+
77
+ irb(main):001:0> win = NSWindow.alloc.initWithContentRect([200, 300, 250, 100],
78
+ irb(main):002:0> styleMask: NSTitledWindowMask|NSResizableWindowMask,
79
+ irb(main):003:0> backing: NSBackingStoreBuffered,
80
+ irb(main):004:0> defer: false)
81
+ => #<NSWindow:0x20023eb00>
82
+ irb(main):005:0> win.orderFrontRegardless
83
+ => #<NSWindow:0x20023eb00>
84
+ irb(main):006:0> win.title = 'Hello World'
85
+ => "Hello World"
86
+ irb(main):007:0> bye = NSButton.alloc.initWithFrame([10, 10, 80, 80])
87
+ => #<NSButton:0x20027f820>
88
+ irb(main):008:0> win.contentView.addSubview(bye)
89
+ => #<NSView:0x200210320>
90
+ irb(main):009:0> bye.bezelStyle = NSThickerSquareBezelStyle
91
+ => 4
92
+ irb(main):010:0> bye.title = 'Goodbye!'
93
+ => "Goodbye!"
94
+ irb(main):011:0> bye.target = NSApp
95
+ => #<NSApplication:0x200257fe0>
96
+ irb(main):012:0> bye.action = 'terminate:'
97
+ => "terminate:"
98
+ irb(main):013:0> bye.sound = NSSound.soundNamed('Basso')
99
+ => #<NSSound:0x200248b20>
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- task :default => :spec
1
+ task :default => :run
2
2
 
3
3
  desc "Run the specs"
4
4
  task :spec do
@@ -10,6 +10,11 @@ task :kick do
10
10
  sh "kicker -e 'rake spec' lib spec"
11
11
  end
12
12
 
13
+ desc "Run dietrb with ruby19"
14
+ task :run do
15
+ sh "ruby19 -Ilib ./bin/dietrb"
16
+ end
17
+
13
18
  begin
14
19
  require 'jeweler'
15
20
  Jeweler::Tasks.new do |gemspec|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
data/bin/dietrb CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'irb'
4
- IRB(self)
4
+ irb(self)
data/dietrb.gemspec CHANGED
@@ -5,29 +5,33 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dietrb}
8
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Eloy Duran"]
12
- s.date = %q{2010-02-07}
12
+ s.date = %q{2010-02-18}
13
13
  s.default_executable = %q{dietrb}
14
14
  s.description = %q{IRB on a diet, for MacRuby / Ruby 1.9}
15
15
  s.email = %q{eloy.de.enige@gmail.com}
16
16
  s.executables = ["dietrb"]
17
17
  s.extra_rdoc_files = [
18
- "README"
18
+ "LICENSE",
19
+ "README.rdoc"
19
20
  ]
20
21
  s.files = [
21
22
  ".gitignore",
22
- "README",
23
+ "LICENSE",
24
+ "README.rdoc",
23
25
  "Rakefile",
24
26
  "VERSION",
25
27
  "bin/dietrb",
26
28
  "dietrb.gemspec",
27
29
  "lib/irb.rb",
28
30
  "lib/irb/context.rb",
31
+ "lib/irb/ext/completion.rb",
29
32
  "lib/irb/ext/macruby.rb",
30
33
  "lib/irb/source.rb",
34
+ "spec/completion_spec.rb",
31
35
  "spec/context_spec.rb",
32
36
  "spec/irb_spec.rb",
33
37
  "spec/source_spec.rb",
@@ -40,7 +44,8 @@ Gem::Specification.new do |s|
40
44
  s.rubygems_version = %q{1.3.5}
41
45
  s.summary = %q{IRB on a diet, for MacRuby / Ruby 1.9}
42
46
  s.test_files = [
43
- "spec/context_spec.rb",
47
+ "spec/completion_spec.rb",
48
+ "spec/context_spec.rb",
44
49
  "spec/irb_spec.rb",
45
50
  "spec/source_spec.rb",
46
51
  "spec/spec_helper.rb"
data/lib/irb/context.rb CHANGED
@@ -2,6 +2,17 @@ require 'readline'
2
2
 
3
3
  module IRB
4
4
  class Context
5
+ class << self
6
+ attr_accessor :current
7
+
8
+ def make_current(context)
9
+ before, @current = @current, context
10
+ yield
11
+ ensure
12
+ @current = before
13
+ end
14
+ end
15
+
5
16
  attr_reader :object, :binding, :line, :source
6
17
 
7
18
  def initialize(object)
@@ -11,8 +22,12 @@ module IRB
11
22
  clear_buffer
12
23
  end
13
24
 
25
+ def __evaluate__(source)
26
+ eval(source, @binding)
27
+ end
28
+
14
29
  def evaluate(source)
15
- result = eval("_ = (#{source})", @binding)
30
+ result = __evaluate__("_ = (#{source})")
16
31
  puts format_result(result)
17
32
  result
18
33
  rescue Exception => e
@@ -24,9 +39,11 @@ module IRB
24
39
  end
25
40
 
26
41
  def run
27
- while line = readline
28
- continue = process_line(line)
29
- break unless continue
42
+ self.class.make_current(self) do
43
+ while line = readline
44
+ continue = process_line(line)
45
+ break unless continue
46
+ end
30
47
  end
31
48
  end
32
49
 
@@ -46,7 +63,10 @@ module IRB
46
63
  @source << line
47
64
  return false if @source.to_s == "quit"
48
65
 
49
- if @source.valid?
66
+ if @source.syntax_error?
67
+ puts format_syntax_error(@source.syntax_error)
68
+ @source.pop
69
+ elsif @source.code_block?
50
70
  evaluate(@source)
51
71
  clear_buffer
52
72
  end
@@ -69,6 +89,10 @@ module IRB
69
89
  "#{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
70
90
  end
71
91
 
92
+ def format_syntax_error(e)
93
+ "SyntaxError: compile error\n(irb):#{@line}: #{e}"
94
+ end
95
+
72
96
  private
73
97
 
74
98
  def clear_buffer
@@ -0,0 +1,140 @@
1
+ require 'ripper'
2
+
3
+ module IRB
4
+ class Completion
5
+ # Convenience constants for sexp access of Ripper::SexpBuilder.
6
+ TYPE = 0
7
+ VALUE = 1
8
+ CALLEE = 3
9
+
10
+ # Returns an array of possible completion results, with the current
11
+ # IRB::Context.
12
+ #
13
+ # This is meant to be used with Readline which takes a completion proc.
14
+ def self.call(source)
15
+ new(IRB::Context.current, source).results
16
+ end
17
+
18
+ attr_reader :context, :source
19
+
20
+ def initialize(context, source)
21
+ @context, @source = context, source
22
+ end
23
+
24
+ def evaluate(s)
25
+ @context.__evaluate__(s)
26
+ end
27
+
28
+ def local_variables
29
+ evaluate('local_variables').map(&:to_s)
30
+ end
31
+
32
+ def instance_methods
33
+ @context.object.methods.map(&:to_s)
34
+ end
35
+
36
+ # TODO: test and or fix the fact that we need to get constants from the
37
+ # singleton class.
38
+ def constants
39
+ evaluate('Object.constants + self.class.constants + (class << self; constants; end)').map(&:to_s)
40
+ end
41
+
42
+ def results
43
+ source = @source
44
+ filter = nil
45
+
46
+ # if ends with period, remove it to remove the syntax error it causes
47
+ call = (source[-1,1] == '.')
48
+ receiver = source = source[0..-2] if call
49
+
50
+ if sexp = Ripper::SexpBuilder.new(source).parse
51
+ # [:program, [:stmts_add, [:stmts_new], [x, …]]]
52
+ # ^
53
+ root = sexp[1][2]
54
+
55
+ # [:call, [:hash, nil], :".", [:@ident, x, …]]
56
+ if root[TYPE] == :call
57
+ call = true
58
+ filter = root[CALLEE][VALUE]
59
+ receiver = source[0..-(filter.length + 2)]
60
+ root = root[VALUE]
61
+ end
62
+
63
+ if call
64
+ format_methods(receiver, methods_of_object(root), filter)
65
+ else
66
+ match_methods_vars_or_consts_in_scope(root)
67
+ end.sort
68
+ end
69
+ end
70
+
71
+ def match_methods_vars_or_consts_in_scope(symbol)
72
+ var = symbol[VALUE]
73
+ filter = var[VALUE]
74
+ case var[TYPE]
75
+ when :@ident
76
+ local_variables + instance_methods
77
+ when :@gvar
78
+ global_variables.map(&:to_s)
79
+ when :@const
80
+ if symbol[TYPE] == :top_const_ref
81
+ filter = "::#{filter}"
82
+ Object.constants.map { |c| "::#{c}" }
83
+ else
84
+ constants
85
+ end
86
+ end.grep(/^#{Regexp.quote(filter)}/)
87
+ end
88
+
89
+ def format_methods(receiver, methods, filter)
90
+ (filter ? methods.grep(/^#{filter}/) : methods).map { |m| "#{receiver}.#{m}" }
91
+ end
92
+
93
+ def methods_of_object(root)
94
+ result = case root[TYPE]
95
+ # [:unary, :-@, [x, …]]
96
+ # ^
97
+ when :unary then return methods_of_object(root[2]) # TODO: do we really need this?
98
+ when :var_ref, :top_const_ref then return methods_of_object_in_variable(root)
99
+ when :array, :words_add, :qwords_add then Array
100
+ when :@int then Fixnum
101
+ when :@float then Float
102
+ when :hash then Hash
103
+ when :lambda then Proc
104
+ when :dot2, :dot3 then Range
105
+ when :regexp_literal then Regexp
106
+ when :string_literal then String
107
+ when :symbol_literal, :dyna_symbol then Symbol
108
+ end.instance_methods
109
+ end
110
+
111
+ def methods_of_object_in_variable(var)
112
+ subtype, name = var[VALUE][0..1]
113
+
114
+ if var[TYPE] == :top_const_ref
115
+ if subtype == :@const && Object.constants.include?(name.to_sym)
116
+ evaluate("::#{name}").methods
117
+ end
118
+ else
119
+ case subtype
120
+ when :@ident
121
+ evaluate(name).methods if local_variables.include?(name)
122
+ when :@gvar
123
+ eval(name).methods if global_variables.include?(name.to_sym)
124
+ when :@const
125
+ evaluate(name).methods if constants.include?(name)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ if defined?(Readline)
133
+ if Readline.respond_to?("basic_word_break_characters=")
134
+ # IRB adds a few breaking chars. that would break literals for us:
135
+ # * String: " and '
136
+ # * Hash: = and >
137
+ Readline.basic_word_break_characters= " \t\n`<;|&("
138
+ end
139
+ Readline.completion_proc = IRB::Completion
140
+ end
data/lib/irb/source.rb CHANGED
@@ -14,6 +14,12 @@ module IRB
14
14
  @buffer << source.chomp
15
15
  end
16
16
 
17
+ # Removes the last line from the buffer and flushes the cached reflection.
18
+ def pop
19
+ @reflection = nil
20
+ @buffer.pop
21
+ end
22
+
17
23
  # Returns the accumulated source as a string, joined by newlines.
18
24
  def source
19
25
  @buffer.join("\n")
@@ -28,8 +34,19 @@ module IRB
28
34
  end
29
35
 
30
36
  # Reflects on the accumulated source to see if it's a valid code block.
31
- def valid?
32
- reflect.valid?
37
+ def code_block?
38
+ reflect.code_block?
39
+ end
40
+
41
+ # Reflects on the accumulated source to see if it contains a syntax error.
42
+ def syntax_error?
43
+ reflect.syntax_error?
44
+ end
45
+
46
+ # Reflects on the accumulated source and returns the actual syntax error
47
+ # message if one occurs.
48
+ def syntax_error
49
+ reflect.syntax_error
33
50
  end
34
51
 
35
52
  # Returns a Reflector for the accumulated source and caches it.
@@ -37,11 +54,11 @@ module IRB
37
54
  @reflection ||= Reflector.new(source)
38
55
  end
39
56
 
40
- class Reflector < Ripper::SexpBuilder
57
+ class Reflector < Ripper
41
58
  def initialize(source)
42
59
  super
43
60
  @level = 0
44
- @valid = !parse.nil?
61
+ parse
45
62
  end
46
63
 
47
64
  # Returns the code block indentation level.
@@ -53,8 +70,12 @@ module IRB
53
70
  # Reflector.new("class Foo; def foo; end; end").level # => 0
54
71
  attr_reader :level
55
72
 
73
+ # Returns the actual syntax error message if one occurs.
74
+ attr_reader :syntax_error
75
+
56
76
  # Returns whether or not the source is a valid code block, but does not
57
- # take syntax errors into account.
77
+ # take syntax errors into account. In short, it's a valid code block if
78
+ # after parsing the level is at zero.
58
79
  #
59
80
  # For example, this is not a valid full code block:
60
81
  #
@@ -63,8 +84,30 @@ module IRB
63
84
  # This however is:
64
85
  #
65
86
  # def foo; p :ok; end
66
- def valid?
67
- @valid
87
+ def code_block?
88
+ @level == 0
89
+ end
90
+
91
+ # Returns whether or not the source contains a syntax error. However, it
92
+ # ignores a syntax error resulting in a code block not ending yet.
93
+ #
94
+ # For example, this contains a syntax error:
95
+ #
96
+ # def; foo
97
+ #
98
+ # This does not:
99
+ #
100
+ # def foo
101
+ def syntax_error?
102
+ !@syntax_error.nil?
103
+ end
104
+
105
+ UNEXPECTED_END = "syntax error, unexpected $end"
106
+
107
+ def on_parse_error(error) #:nodoc:
108
+ if code_block? || !error.start_with?(UNEXPECTED_END)
109
+ @syntax_error = error
110
+ end
68
111
  end
69
112
 
70
113
  INCREASE_LEVEL_KEYWORDS = %w{ class module def begin if unless case while for do }
data/lib/irb.rb CHANGED
@@ -7,9 +7,9 @@ end
7
7
 
8
8
  module Kernel
9
9
  # Creates a new IRB::Context with the given +object+ and runs it.
10
- def IRB(object)
10
+ def irb(object)
11
11
  IRB::Context.new(object).run
12
12
  end
13
13
 
14
- private :IRB
15
- end
14
+ private :irb
15
+ end
@@ -0,0 +1,221 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+ require 'irb/ext/completion'
3
+
4
+ module CompletionHelper
5
+ def complete(str)
6
+ IRB::Completion.new(@context, str).results
7
+ end
8
+
9
+ def imethods(klass, receiver = nil)
10
+ klass.instance_methods.map { |m| [receiver, m.to_s].compact.join('.') }.sort
11
+ end
12
+
13
+ def methods(object, receiver = nil)
14
+ object.methods.map { |m| [receiver, m.to_s].compact.join('.') }.sort
15
+ end
16
+ end
17
+
18
+ Bacon::Context.send(:include, CompletionHelper)
19
+
20
+ class CompletionStub
21
+ def self.a_cmethod
22
+ end
23
+
24
+ def an_imethod
25
+ end
26
+ end
27
+
28
+ class Playground
29
+ CompletionStub = Object.new
30
+ def CompletionStub.a_singleton_method; end
31
+
32
+ def a_local_method; end
33
+ end
34
+
35
+ $a_completion_stub = CompletionStub.new
36
+
37
+ describe "IRB::Completion" do
38
+ before do
39
+ @context = IRB::Context.new(Playground.new)
40
+ end
41
+
42
+ it "quacks like a Proc" do
43
+ IRB::Completion.call('//.').should == imethods(Regexp, '//')
44
+ end
45
+
46
+ describe "when doing a method call on an explicit receiver," do
47
+ describe "and the source ends with a period," do
48
+ describe "returns *all* public methods of the receiver that" do
49
+ it "matches as a local variable" do
50
+ @context.__evaluate__('foo = ::CompletionStub.new')
51
+ complete('foo.').should == imethods(::CompletionStub, 'foo')
52
+
53
+ @context.__evaluate__('def foo.singleton_method; end')
54
+ complete('foo.').should.include('foo.singleton_method')
55
+ end
56
+
57
+ it "matches as a global variable" do
58
+ complete('$a_completion_stub.').should == imethods(::CompletionStub, '$a_completion_stub')
59
+ end
60
+
61
+ # TODO: fix
62
+ # it "matches as a local constant" do
63
+ # complete('CompletionStub.').should == methods(Playground::CompletionStub)
64
+ # end
65
+
66
+ it "matches as a top level constant" do
67
+ complete('::CompletionStub.').should == methods(::CompletionStub, '::CompletionStub')
68
+ end
69
+ end
70
+
71
+ describe "returns *all* public instance methods of the class (the receiver) that" do
72
+ it "matches as a Regexp literal" do
73
+ complete('//.').should == imethods(Regexp, '//')
74
+ complete('/^(:[^:.]+)\.([^.]*)$/.').should == imethods(Regexp, '/^(:[^:.]+)\.([^.]*)$/')
75
+ complete('/^(#{oops})\.([^.]*)$/.').should == imethods(Regexp, '/^(#{oops})\.([^.]*)$/')
76
+ complete('%r{/foo/.*/bar}.').should == imethods(Regexp, '%r{/foo/.*/bar}')
77
+ end
78
+
79
+ it "matches as an Array literal" do
80
+ complete('[].').should == imethods(Array, '[]')
81
+ complete('[:ok, {}, "foo",].').should == imethods(Array, '[:ok, {}, "foo",]')
82
+ complete('[*foo].').should == imethods(Array, '[*foo]')
83
+ complete('%w{foo}.').should == imethods(Array, '%w{foo}')
84
+ complete('%W{#{:foo}}.').should == imethods(Array, '%W{#{:foo}}')
85
+ end
86
+
87
+ # fails on MacRuby
88
+ it "matches as a lambda literal" do
89
+ complete('->{}.').should == imethods(Proc, '->{}')
90
+ complete('->{x=:ok}.').should == imethods(Proc, '->{x=:ok}')
91
+ complete('->(x){x=:ok}.').should == imethods(Proc, '->(x){x=:ok}')
92
+ end
93
+
94
+ it "matches as a Hash literal" do
95
+ complete('{}.').should == imethods(Hash, '{}')
96
+ complete('{:foo=>:bar,}.').should == imethods(Hash, '{:foo=>:bar,}')
97
+ complete('{foo:"bar"}.').should == imethods(Hash, '{foo:"bar"}')
98
+ end
99
+
100
+ it "matches as a Symbol literal" do
101
+ complete(':foo.').should == imethods(Symbol, ':foo')
102
+ complete(':"foo.bar".').should == imethods(Symbol, ':"foo.bar"')
103
+ complete(':"foo.#{"bar"}".').should == imethods(Symbol, ':"foo.#{"bar"}"')
104
+ complete(':\'foo.#{"bar"}\'.').should == imethods(Symbol, ':\'foo.#{"bar"}\'')
105
+ complete('%s{foo.bar}.').should == imethods(Symbol, '%s{foo.bar}')
106
+ end
107
+
108
+ it "matches as a String literal" do
109
+ complete("'foo\\'bar'.").should == imethods(String, "'foo\\'bar'")
110
+ complete('"foo\"bar".').should == imethods(String, '"foo\"bar"')
111
+ complete('"foo#{"bar"}".').should == imethods(String, '"foo#{"bar"}"')
112
+ complete('%{foobar}.').should == imethods(String, '%{foobar}')
113
+ complete('%q{foo#{:bar}}.').should == imethods(String, '%q{foo#{:bar}}')
114
+ complete('%Q{foo#{:bar}}.').should == imethods(String, '%Q{foo#{:bar}}')
115
+ end
116
+
117
+ it "matches as a Range literal" do
118
+ complete('1..10.').should == imethods(Range, '1..10')
119
+ complete('1...10.').should == imethods(Range, '1...10')
120
+ complete('"a".."z".').should == imethods(Range, '"a".."z"')
121
+ complete('"a"..."z".').should == imethods(Range, '"a"..."z"')
122
+ end
123
+
124
+ it "matches as a Fixnum literal" do
125
+ complete('42.').should == imethods(Fixnum, '42')
126
+ complete('+42.').should == imethods(Fixnum, '+42')
127
+ complete('-42.').should == imethods(Fixnum, '-42')
128
+ complete('42_000.').should == imethods(Fixnum, '42_000')
129
+ end
130
+
131
+ it "matches as a Bignum literal as a Fixnum" do
132
+ complete('100_000_000_000_000_000_000.').should == imethods(Fixnum, '100_000_000_000_000_000_000')
133
+ complete('-100_000_000_000_000_000_000.').should == imethods(Fixnum, '-100_000_000_000_000_000_000')
134
+ complete('+100_000_000_000_000_000_000.').should == imethods(Fixnum, '+100_000_000_000_000_000_000')
135
+ end
136
+
137
+ it "matches as a Float with exponential literal" do
138
+ complete('1.2e-3.').should == imethods(Float, '1.2e-3')
139
+ complete('+1.2e-3.').should == imethods(Float, '+1.2e-3')
140
+ complete('-1.2e-3.').should == imethods(Float, '-1.2e-3')
141
+ end
142
+
143
+ it "matches as a hex literal as a Fixnum" do
144
+ complete('0xffff.').should == imethods(Fixnum, '0xffff')
145
+ complete('+0xffff.').should == imethods(Fixnum, '+0xffff')
146
+ complete('-0xffff.').should == imethods(Fixnum, '-0xffff')
147
+ end
148
+
149
+ it "matches as a binary literal as a Fixnum" do
150
+ complete('0b01011.').should == imethods(Fixnum, '0b01011')
151
+ complete('-0b01011.').should == imethods(Fixnum, '-0b01011')
152
+ complete('+0b01011.').should == imethods(Fixnum, '+0b01011')
153
+ end
154
+
155
+ it "matches as an octal literal as a Fixnum" do
156
+ complete('0377.').should == imethods(Fixnum, '0377')
157
+ complete('-0377.').should == imethods(Fixnum, '-0377')
158
+ complete('+0377.').should == imethods(Fixnum, '+0377')
159
+ end
160
+
161
+ it "matches as a Float literal" do
162
+ complete('42.0.').should == imethods(Float, '42.0')
163
+ complete('-42.0.').should == imethods(Float, '-42.0')
164
+ complete('+42.0.').should == imethods(Float, '+42.0')
165
+ complete('42_000.0.').should == imethods(Float, '42_000.0')
166
+ end
167
+
168
+ it "matches as a Bignum float literal as a Float" do
169
+ complete('100_000_000_000_000_000_000.0.').should == imethods(Float, '100_000_000_000_000_000_000.0')
170
+ complete('+100_000_000_000_000_000_000.0.').should == imethods(Float, '+100_000_000_000_000_000_000.0')
171
+ complete('-100_000_000_000_000_000_000.0.').should == imethods(Float, '-100_000_000_000_000_000_000.0')
172
+ end
173
+ end
174
+ end
175
+
176
+ describe "and the source does *not* end with a period," do
177
+ it "filters the methods, of the literal receiver, by the given method name" do
178
+ complete('//.nam').should == %w{ //.named_captures //.names }
179
+ complete('//.named').should == %w{ //.named_captures }
180
+ end
181
+
182
+ it "filters the methods, of the variable receiver, by the given method name" do
183
+ @context.__evaluate__('foo = ::CompletionStub.new')
184
+ complete('foo.an_im').should == %w{ foo.an_imethod }
185
+ complete('$a_completion_stub.an_im').should == %w{ $a_completion_stub.an_imethod }
186
+ # TODO: fix
187
+ # complete('CompletionStub.a_sing').should == %w{ CompletionStub.a_singleton_method }
188
+ end
189
+ end
190
+ end
191
+
192
+ describe "when *not* doing a method call on an explicit receiver" do
193
+ before do
194
+ @context.__evaluate__("a_local_variable = :ok")
195
+ end
196
+
197
+ it "matches local variables" do
198
+ complete("a_local_v").should == %w{ a_local_variable }
199
+ end
200
+
201
+ it "matches instance methods of the context object" do
202
+ complete("a_local_m").should == %w{ a_local_method }
203
+ end
204
+
205
+ it "matches local variables and instance method of the context object" do
206
+ complete("a_loc").should == %w{ a_local_method a_local_variable }
207
+ end
208
+
209
+ it "matches global variables" do
210
+ complete("$a_completion_s").should == %w{ $a_completion_stub }
211
+ end
212
+
213
+ it "matches constants" do
214
+ complete("Playgr").should == %w{ Playground }
215
+ end
216
+
217
+ it "matches top level constants" do
218
+ complete("::CompletionSt").should == %w{ ::CompletionStub }
219
+ end
220
+ end
221
+ end
data/spec/context_spec.rb CHANGED
@@ -1,6 +1,19 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
  require 'tempfile'
3
3
 
4
+ class << Readline
5
+ attr_reader :received
6
+
7
+ def stub_input(*input)
8
+ @input = input
9
+ end
10
+
11
+ def readline(prompt, history)
12
+ @received = [prompt, history]
13
+ @input.shift
14
+ end
15
+ end
16
+
4
17
  main = self
5
18
 
6
19
  describe "IRB::Context" do
@@ -45,6 +58,16 @@ describe "IRB::Context" do
45
58
  @context.format_exception(exception).should ==
46
59
  "NameError: uninitialized constant Bacon::Context::DoesNotExist\n\t#{exception.backtrace.join("\n\t")}"
47
60
  end
61
+
62
+ it "makes itself the current running context during the runloop and resigns once it's done" do
63
+ IRB::Context.current.should == nil
64
+
65
+ Readline.stub_input("current_during_run = IRB::Context.current")
66
+ @context.run
67
+ eval('current_during_run', @context.binding).should == @context
68
+
69
+ IRB::Context.current.should == nil
70
+ end
48
71
  end
49
72
 
50
73
  describe "IRB::Context, when evaluating source" do
@@ -54,7 +77,7 @@ describe "IRB::Context, when evaluating source" do
54
77
  end
55
78
 
56
79
  it "evaluates code with the object's binding" do
57
- @context.evaluate("self").should == main
80
+ @context.__evaluate__("self").should == main
58
81
  end
59
82
 
60
83
  it "prints the result" do
@@ -89,19 +112,6 @@ describe "IRB::Context, when evaluating source" do
89
112
  end
90
113
  end
91
114
 
92
- class << Readline
93
- attr_reader :received
94
-
95
- def stub_input(*input)
96
- @input = input
97
- end
98
-
99
- def readline(prompt, history)
100
- @received = [prompt, history]
101
- @input.shift
102
- end
103
- end
104
-
105
115
  describe "IRB::Context, when receiving input" do
106
116
  before do
107
117
  @context = IRB::Context.new(main)
@@ -145,6 +155,17 @@ describe "IRB::Context, when receiving input" do
145
155
  source.to_s.should == "def foo\n:ok\nend; p foo"
146
156
  end
147
157
 
158
+ it "prints that a syntax error occurred on the last line and reset the buffer to the previous line" do
159
+ def @context.puts(str); @printed = str; end
160
+
161
+ @context.process_line("def foo")
162
+ @context.process_line(" };")
163
+
164
+ @context.source.to_s.should == "def foo"
165
+ printed = @context.instance_variable_get(:@printed)
166
+ printed.should == "SyntaxError: compile error\n(irb):2: syntax error, unexpected '}'"
167
+ end
168
+
148
169
  it "returns whether or not the runloop should continue, but only if the level is 0" do
149
170
  @context.process_line("def foo").should == true
150
171
  @context.process_line("quit").should == true
data/spec/irb_spec.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
- describe "Kernel::IRB()" do
3
+ describe "Kernel::irb" do
4
4
  it "creates a new context for the given object and runs it" do
5
5
  Readline.stub_input("::IRBRan = self")
6
6
  o = Object.new
7
- IRB(o)
7
+ irb(o)
8
8
  IRBRan.should == o
9
9
  end
10
10
  end
data/spec/source_spec.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
+ class Should
4
+ alias have be
5
+ end
6
+
3
7
  describe "IRB::Source" do
4
8
  before do
5
9
  @source = IRB::Source.new
@@ -15,6 +19,13 @@ describe "IRB::Source" do
15
19
  @source.buffer.should == %w{ foo bar }
16
20
  end
17
21
 
22
+ it "removes the last line from the buffer" do
23
+ @source << "foo\n"
24
+ @source << "bar\r\n"
25
+ @source.pop.should == "bar"
26
+ @source.buffer.should == %w{ foo }
27
+ end
28
+
18
29
  it "returns the full buffered source, joined by newlines" do
19
30
  @source.source.should == ""
20
31
  @source << "foo\n"
@@ -34,7 +45,7 @@ describe "IRB::Source" do
34
45
  ["def foo", "p :ok", "end"],
35
46
  ["class A; def", "foo(x); p x", "end; end"]
36
47
  ].each do |buffer|
37
- IRB::Source.new(buffer).should.be.valid
48
+ IRB::Source.new(buffer).should.be.code_block
38
49
  end
39
50
  end
40
51
 
@@ -43,10 +54,18 @@ describe "IRB::Source" do
43
54
  ["def foo", "p :ok"],
44
55
  ["class A; def", "foo(x); p x", "end"]
45
56
  ].each do |buffer|
46
- IRB::Source.new(buffer).should.not.be.valid
57
+ IRB::Source.new(buffer).should.not.be.code_block
47
58
  end
48
59
  end
49
60
 
61
+ it "returns whether or not the accumulated source contains a syntax error" do
62
+ @source.should.not.have.syntax_error
63
+ @source << "def foo"
64
+ @source.should.not.have.syntax_error
65
+ @source << " def;"
66
+ @source.should.have.syntax_error
67
+ end
68
+
50
69
  it "returns the current code block indentation level" do
51
70
  @source.level.should == 0
52
71
  @source << "class A"
@@ -71,14 +90,23 @@ describe "IRB::Source" do
71
90
  @source << "def foo"
72
91
  reflection = @source.reflect
73
92
  @source.level
74
- @source.valid?
93
+ @source.code_block?
75
94
  @source.reflect.should == reflection
76
95
 
77
96
  @source << "end"
78
97
  @source.level
79
98
  new_reflection = @source.reflect
80
99
  new_reflection.should.not == reflection
81
- @source.valid?
100
+ @source.code_block?
101
+ @source.reflect.should == new_reflection
102
+
103
+ reflection = new_reflection
104
+
105
+ @source.pop
106
+ @source.level
107
+ new_reflection = @source.reflect
108
+ new_reflection.should.not == reflection
109
+ @source.syntax_error?
82
110
  @source.reflect.should == new_reflection
83
111
  end
84
112
  end
@@ -89,9 +117,24 @@ describe "IRB::Source::Reflector" do
89
117
  end
90
118
 
91
119
  it "returns whether or not the source is a valid code block" do
92
- reflect("def foo").should.not.be.valid
93
- reflect("def foo; p :ok").should.not.be.valid
94
- reflect("def foo; p :ok; end").should.be.valid
120
+ reflect("def foo").should.not.be.code_block
121
+ reflect("def foo; p :ok").should.not.be.code_block
122
+ reflect("def foo; p :ok; end").should.be.code_block
123
+ end
124
+
125
+ it "returns whether or not the source contains a syntax error, except a code block not ending" do
126
+ reflect("def;").should.have.syntax_error
127
+ reflect("def;").should.have.syntax_error
128
+ reflect("def foo").should.not.have.syntax_error
129
+ reflect("class A; }").should.have.syntax_error
130
+ reflect("class A; {" ).should.not.have.syntax_error
131
+ reflect("class A def foo").should.have.syntax_error
132
+ reflect("class A; def foo" ).should.not.have.syntax_error
133
+ end
134
+
135
+ it "returns the actual syntax error message if one occurs" do
136
+ reflect("def foo").syntax_error.should == nil
137
+ reflect("}").syntax_error.should == "syntax error, unexpected '}'"
95
138
  end
96
139
 
97
140
  it "returns the code block indentation level" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dietrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Duran
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-07 00:00:00 +01:00
12
+ date: 2010-02-18 00:00:00 +01:00
13
13
  default_executable: dietrb
14
14
  dependencies: []
15
15
 
@@ -20,18 +20,22 @@ executables:
20
20
  extensions: []
21
21
 
22
22
  extra_rdoc_files:
23
- - README
23
+ - LICENSE
24
+ - README.rdoc
24
25
  files:
25
26
  - .gitignore
26
- - README
27
+ - LICENSE
28
+ - README.rdoc
27
29
  - Rakefile
28
30
  - VERSION
29
31
  - bin/dietrb
30
32
  - dietrb.gemspec
31
33
  - lib/irb.rb
32
34
  - lib/irb/context.rb
35
+ - lib/irb/ext/completion.rb
33
36
  - lib/irb/ext/macruby.rb
34
37
  - lib/irb/source.rb
38
+ - spec/completion_spec.rb
35
39
  - spec/context_spec.rb
36
40
  - spec/irb_spec.rb
37
41
  - spec/source_spec.rb
@@ -65,6 +69,7 @@ signing_key:
65
69
  specification_version: 3
66
70
  summary: IRB on a diet, for MacRuby / Ruby 1.9
67
71
  test_files:
72
+ - spec/completion_spec.rb
68
73
  - spec/context_spec.rb
69
74
  - spec/irb_spec.rb
70
75
  - spec/source_spec.rb
data/README DELETED
@@ -1 +0,0 @@
1
- IRB on a diet, for MacRuby / Ruby 1.9