dietrb 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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