ripl-multi_line 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemspec +5 -1
- data/CHANGELOG.rdoc +11 -1
- data/README.rdoc +41 -7
- data/Rakefile +2 -2
- data/lib/ripl/multi_line.rb +70 -32
- data/lib/ripl/multi_line/error_check.rb +31 -0
- data/lib/ripl/multi_line/irb.rb +59 -0
- data/lib/ripl/multi_line/live_error.rb +73 -0
- data/lib/ripl/multi_line/ripper.rb +50 -0
- data/lib/ripl/multi_line/ruby_parser.rb +39 -0
- data/test/shell_test.rb +32 -0
- data/test/test_helper.rb +83 -0
- metadata +55 -6
data/.gemspec
CHANGED
@@ -11,7 +11,11 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.summary = "A ripl plugin for multi-line eval."
|
12
12
|
s.description = "This ripl plugin allows you to evaluate multiple lines of Ruby code."
|
13
13
|
s.required_rubygems_version = ">= 1.3.6"
|
14
|
-
s.add_dependency 'ripl', '>= 0.3.
|
14
|
+
s.add_dependency 'ripl', '>= 0.3.6'
|
15
|
+
s.add_development_dependency 'bacon', '>= 1.1.0'
|
16
|
+
s.add_development_dependency 'bacon-bits'
|
17
|
+
s.add_development_dependency 'bacon-rr'
|
18
|
+
s.add_development_dependency 'rr'
|
15
19
|
s.files = Dir.glob(%w[{lib,test}/**/*.rb bin/* [A-Z]*.{txt,rdoc} ext/**/*.{rb,c} **/deps.rip]) + %w{Rakefile .gemspec}
|
16
20
|
s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
|
17
21
|
s.license = 'MIT'
|
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
== 0.3.0
|
2
|
+
* comes with five different multi-line implementations: live_error, error_check, irb, ripper and ruby_parser
|
3
|
+
* can be changed with config[:multi_line_engine], default is :live_error
|
4
|
+
* add :multi_line_history option (default: compact), based on https://gist.github.com/939846
|
5
|
+
* support =begin...=end comments
|
6
|
+
* add Shell#multiline? method to check if input should be handled as multi-line input
|
7
|
+
* separate Ruby-specific multi-line stuff from general implementation
|
8
|
+
* add some basic tests
|
9
|
+
* force multi-line with ; after input
|
10
|
+
|
1
11
|
== 0.2.4
|
2
12
|
* fix 1.8 warning
|
3
13
|
|
@@ -13,7 +23,7 @@
|
|
13
23
|
|
14
24
|
== 0.2.0
|
15
25
|
* store buffer in an array instead of a string
|
16
|
-
* added ctrl+c to remove last line in multi-line
|
26
|
+
* added ctrl+c to remove last line in multi-line situations
|
17
27
|
|
18
28
|
== 0.1.4
|
19
29
|
* be compatible with 0.2.4 (undo monkey patch)
|
data/README.rdoc
CHANGED
@@ -4,25 +4,59 @@ This {ripl}[https://github.com/cldwalker/ripl] plugin allows you to evaluate mul
|
|
4
4
|
== Install
|
5
5
|
Install the gem with
|
6
6
|
|
7
|
-
|
7
|
+
gem install ripl-multi_line
|
8
8
|
|
9
9
|
== Usage
|
10
|
-
|
11
10
|
Add the following line to your <tt>~/.riplrc</tt>
|
12
11
|
|
13
12
|
require 'ripl/multi_line'
|
14
13
|
|
14
|
+
<b>Hint:</b> In multi-line situations, you can press <tt>ctrl+c</tt> and the last line will be removed.
|
15
|
+
|
16
|
+
== Options
|
15
17
|
You can customize your multi-line prompt with the <tt>:multi_line_prompt</tt> option. For example, put this into your <tt>~/.riplrc</tt>:
|
16
18
|
|
17
19
|
Ripl.config[:multi_line_prompt] = ' > '
|
18
20
|
|
19
|
-
|
21
|
+
It also takes a proc as value.
|
22
|
+
|
23
|
+
Then there is the <tt>:multi_line_history</tt> option. The default value is <tt>:compact</tt> which tries to squeeze your last multi-line block into one line (works best in mri, in about 98% of all cases).
|
24
|
+
|
25
|
+
>> def some_multi_line_block
|
26
|
+
| 42
|
27
|
+
| end #=> nil
|
28
|
+
# Press <UP>
|
29
|
+
>> def some_multi_line_block; 42; end
|
30
|
+
|
31
|
+
Another value is <tt>:block</tt>, which joins the multi-line block with new-lines.
|
32
|
+
|
33
|
+
To leave the history untouched, set it to <tt>:plain</tt> or a false value.
|
20
34
|
|
21
35
|
== AutoIndent
|
22
|
-
|
36
|
+
Check out the {ripl-auto_indent}[https://github.com/janlelis/ripl-auto_indent] plugin to get ruby syntax indention.
|
37
|
+
|
38
|
+
== MultiLine detection
|
39
|
+
To use your own multi-line detection engine, implement your version at <tt>lib/ripl/multi_line/your_correct_detection.rb</tt> in which you overwrite <tt>Shell#multiline?</tt> in a <tt>Ripl::MultiLine</tt> sub-module named <tt>YourCorrectDetection</tt> and set <tt>Ripl.config[:multi_line_engine]</tt> to <tt>:your_correct_detection</tt>. See <tt>lib/ripl/multi_line/*.rb</tt> for examples.
|
40
|
+
|
41
|
+
Currently available engines:
|
42
|
+
* <tt>:live_error</tt> (<b>default</b>) - Simply evaluate expression, if it throws a multi-line syntax error, fall back to multi-line input mode. Sounds scary, but works pretty well in practice.
|
43
|
+
* <tt>:error_check</tt> - Same principle as :live_error, but don't do it live
|
44
|
+
* <tt>:ripper</tt> - Analyze input with Ripper (comes with 1.9, but there is also a 1.8 gem).
|
45
|
+
* <tt>:ruby_parser</tt> - Use ruby_parser gem (1.8 only)
|
46
|
+
* <tt>:irb</tt> - You left irb, but you are still used to its multi-line behaviour? Use irb's RubyLex!
|
47
|
+
|
48
|
+
None of the above solutions is perfect, so... maybe it's time to roll your own ;)
|
49
|
+
|
50
|
+
== Troubleshooting
|
51
|
+
<i>Problem:</i> "I've required <tt>ripl-multi_line</tt>, but it doesn't work"
|
52
|
+
|
53
|
+
<i>Answer:</i> This is probably caused by a plugin, which overwrites <tt>:before_loop</tt>, but does not call <tt>super</tt> (or you are using ripl-profiles older than 0.2.0). You can manually work around this issue with this snippet (but you should fix the general problem):
|
54
|
+
|
55
|
+
require 'ripl/multi_line/live_error.rb'
|
56
|
+
Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
|
57
|
+
Ripl::Shell.include Ripl::MultiLine.engine
|
23
58
|
|
24
|
-
==
|
25
|
-
|
26
|
-
* Replaceable check if syntax is valid (then, the plugin can be used for other languages than Ruby)
|
59
|
+
== Credits
|
60
|
+
Contributions & influences by cldwalker and godfat.
|
27
61
|
|
28
62
|
J-_-L
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ def gemspec
|
|
6
6
|
end
|
7
7
|
|
8
8
|
desc "Build the gem"
|
9
|
-
task :gem
|
9
|
+
task :gem => :gemspec do
|
10
10
|
sh "gem build .gemspec"
|
11
11
|
FileUtils.mkdir_p 'pkg'
|
12
12
|
FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
|
@@ -14,7 +14,7 @@ end
|
|
14
14
|
|
15
15
|
desc "Install the gem locally"
|
16
16
|
task :install => :gem do
|
17
|
-
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
|
17
|
+
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version} --no-rdoc --no-ri}
|
18
18
|
end
|
19
19
|
|
20
20
|
desc "Generate the gemspec"
|
data/lib/ripl/multi_line.rb
CHANGED
@@ -2,27 +2,27 @@ require 'ripl'
|
|
2
2
|
|
3
3
|
module Ripl
|
4
4
|
module MultiLine
|
5
|
-
VERSION = '0.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
%q%expecting '\\n' or ';'%,
|
11
|
-
%q%missing 'end'%,
|
12
|
-
%q%expecting '\}'%,
|
13
|
-
# jruby
|
14
|
-
%q%syntax error, unexpected end-of-file%,
|
15
|
-
]*'|' }/
|
5
|
+
VERSION = '0.3.0'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :engine
|
9
|
+
end
|
16
10
|
|
17
11
|
def before_loop
|
12
|
+
@buffer = @buffer_info = nil
|
13
|
+
# include CamelCased implementation
|
14
|
+
require File.join( 'ripl', 'multi_line', config[:multi_line_engine].to_s )
|
15
|
+
Ripl::MultiLine.engine = Ripl::MultiLine.const_get(
|
16
|
+
config[:multi_line_engine].to_s.gsub(/(^|_)(\w)/){ $2.capitalize }
|
17
|
+
)
|
18
|
+
Ripl::Shell.include Ripl::MultiLine.engine
|
18
19
|
super
|
19
|
-
@buffer = nil
|
20
20
|
end
|
21
21
|
|
22
22
|
def prompt
|
23
23
|
if @buffer
|
24
24
|
config[:multi_line_prompt].respond_to?(:call) ?
|
25
|
-
config[:multi_line_prompt].call :
|
25
|
+
config[:multi_line_prompt].call( *@buffer_info[-1] ) :
|
26
26
|
config[:multi_line_prompt]
|
27
27
|
else
|
28
28
|
super
|
@@ -35,49 +35,87 @@ module Ripl
|
|
35
35
|
def loop_once
|
36
36
|
catch(:multiline) do
|
37
37
|
super
|
38
|
-
@buffer
|
38
|
+
if config[:multi_line_history] && @buffer && @input
|
39
|
+
(@buffer.size + 1).times{ history.pop }
|
40
|
+
|
41
|
+
if config[:multi_line_history] == :compact
|
42
|
+
history_entry = ''
|
43
|
+
@buffer.zip(@buffer_info){ |str, type|
|
44
|
+
history_entry << str
|
45
|
+
history_entry << case
|
46
|
+
when !type.is_a?(Array)
|
47
|
+
"\n" # fallback to :block for unsure
|
48
|
+
when type[0] == :statement
|
49
|
+
'; '
|
50
|
+
when type[0] == :literal && ( type[1] == :string || type[1] == :regexp )
|
51
|
+
'\n'
|
52
|
+
else
|
53
|
+
''
|
54
|
+
end
|
55
|
+
}
|
56
|
+
history_entry << @input
|
57
|
+
history << history_entry
|
58
|
+
else # true or :block
|
59
|
+
history << (@buffer << @input).join("\n")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@buffer = @buffer_info = nil
|
39
64
|
end
|
40
65
|
end
|
41
66
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
67
|
+
# This method is overwritten by a multi-line implementation in lib/multi_line/*.rb
|
68
|
+
# It should return a true value for a string that is unfinished and a false one
|
69
|
+
# for complete expressions, which should get evaluated.
|
70
|
+
# It's also possible (and encouraged) to return an array of symbols describing
|
71
|
+
# what's the reason for continuing the expression (this is used in the :compact
|
72
|
+
# history and gets passed to the prompt proc.
|
73
|
+
def multiline?(eval_string)
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_multiline(type = [:statement]) # MAYBE: add second arg for specific information
|
78
|
+
@buffer ||= []
|
79
|
+
@buffer_info ||= []
|
80
|
+
@buffer << @input
|
81
|
+
@buffer_info << type
|
82
|
+
throw :multiline
|
50
83
|
end
|
51
84
|
|
52
85
|
def loop_eval(input)
|
53
|
-
if @buffer
|
54
|
-
|
55
|
-
|
56
|
-
super input
|
86
|
+
eval_string = if @buffer then @buffer*"\n" + "\n" + input else input end
|
87
|
+
if type = multiline?(eval_string)
|
88
|
+
handle_multiline(type)
|
57
89
|
end
|
90
|
+
super eval_string
|
58
91
|
end
|
59
92
|
|
60
93
|
# remove last line from buffer
|
61
|
-
#
|
94
|
+
# MAYBE: terminal rewriting
|
62
95
|
def handle_interrupt
|
63
96
|
if @buffer
|
64
|
-
@buffer.pop
|
97
|
+
@buffer.pop; @buffer_info.pop; history.pop
|
65
98
|
if @buffer.empty?
|
66
|
-
@buffer = nil
|
99
|
+
@buffer = @buffer_info = nil
|
100
|
+
print '[buffer empty]'
|
67
101
|
return super
|
68
102
|
else
|
69
|
-
puts "[previous line removed]"
|
103
|
+
puts "[previous line removed|#{@buffer.size}]"
|
70
104
|
throw :multiline
|
71
105
|
end
|
72
106
|
else
|
73
107
|
super
|
74
108
|
end
|
75
109
|
end
|
110
|
+
|
76
111
|
end
|
77
112
|
end
|
78
113
|
|
79
|
-
Ripl::Shell.include Ripl::MultiLine
|
80
|
-
Ripl.config[:
|
114
|
+
Ripl::Shell.include Ripl::MultiLine # implementation gets included in before_loop
|
115
|
+
Ripl.config[:multi_line_engine] ||= :live_error # not satisfied? try :ripper, :irb or implement your own
|
116
|
+
Ripl.config[:multi_line_history] ||= :compact # possible other values: :block, :blank
|
117
|
+
|
118
|
+
Ripl.config[:multi_line_prompt] ||= proc do # you can also use a plain string here
|
81
119
|
'|' + ' '*(Ripl.shell.instance_variable_get(:@prompt).size-1) # '| '
|
82
120
|
end
|
83
121
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ripl'
|
2
|
+
|
3
|
+
Ripl.config[:multi_line_engine] ||= :error_check
|
4
|
+
require 'ripl/multi_line'
|
5
|
+
require 'ripl/multi_line/live_error'
|
6
|
+
|
7
|
+
# # # #
|
8
|
+
# This multi-line implementation uses IRB's RubyLex parser
|
9
|
+
# works on: 1.9 1.8 jruby rbx
|
10
|
+
# analyze features: see live_error.rb
|
11
|
+
# known issues: see live_error.rb
|
12
|
+
module Ripl::MultiLine::ErrorCheck
|
13
|
+
VERSION = '0.1.0'
|
14
|
+
|
15
|
+
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE.downcase.to_sym
|
16
|
+
ruby_engine = :ruby if !Ripl::MultiLine::LiveError::ERROR_MESSAGES.keys.include?(ruby_engine)
|
17
|
+
|
18
|
+
define_method(:multiline?) do |string|
|
19
|
+
break [:forced] if string =~ /;\s*\Z/ # force multi line with ;
|
20
|
+
|
21
|
+
begin
|
22
|
+
eval "if nil; #{string}; end"
|
23
|
+
rescue SyntaxError => e
|
24
|
+
Ripl::MultiLine::LiveError::ERROR_MESSAGES[ruby_engine].each{ |type, message|
|
25
|
+
break type if message === e.message
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# J-_-L
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'ripl'
|
2
|
+
require 'irb/ruby-lex'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
Ripl.config[:multi_line_engine] ||= :irb
|
6
|
+
require 'ripl/multi_line'
|
7
|
+
|
8
|
+
# # # #
|
9
|
+
# This multi-line implementation uses IRB's RubyLex parser
|
10
|
+
# works on: 1.9 1.8 jruby rbx
|
11
|
+
# analyze features: none
|
12
|
+
module Ripl::MultiLine::Irb
|
13
|
+
VERSION = '0.1.0'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_reader :scanner
|
17
|
+
end
|
18
|
+
|
19
|
+
# create scanner and patch it to our needs
|
20
|
+
@scanner = RubyLex.new
|
21
|
+
|
22
|
+
def @scanner.multiline?
|
23
|
+
initialize_input
|
24
|
+
@continue = false
|
25
|
+
@found_eof = false
|
26
|
+
|
27
|
+
while line = lex
|
28
|
+
@line << line
|
29
|
+
@continue = false
|
30
|
+
end
|
31
|
+
|
32
|
+
!!( !@found_eof or @ltype or @continue or @indent > 0 )
|
33
|
+
end
|
34
|
+
|
35
|
+
def @scanner.lex
|
36
|
+
until (((tk = token).kind_of?(RubyLex::TkNL) || tk.kind_of?(RubyLex::TkEND_OF_SCRIPT)) &&
|
37
|
+
!@continue or
|
38
|
+
tk.nil?)
|
39
|
+
#p tk
|
40
|
+
#p @lex_state
|
41
|
+
#p self
|
42
|
+
end
|
43
|
+
@found_eof = true if tk.kind_of?(RubyLex::TkEND_OF_SCRIPT)
|
44
|
+
line = get_readed
|
45
|
+
# print self.inspect
|
46
|
+
if line == "" and tk.kind_of?(RubyLex::TkEND_OF_SCRIPT) || tk.nil?
|
47
|
+
nil
|
48
|
+
else
|
49
|
+
line
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def multiline?(string)
|
54
|
+
Ripl::MultiLine::Irb.scanner.set_input StringIO.new(string + "\0")
|
55
|
+
Ripl::MultiLine::Irb.scanner.multiline?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# J-_-L
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'ripl'
|
2
|
+
|
3
|
+
Ripl.config[:multi_line_engine] ||= :live_error
|
4
|
+
require 'ripl/multi_line'
|
5
|
+
|
6
|
+
# # # #
|
7
|
+
# This multi-line implementation uses IRB's RubyLex parser
|
8
|
+
# works on: 1.9 1.8 jruby rbx
|
9
|
+
# analyze features: [:literal, :string]
|
10
|
+
# [:literal, :regexp]
|
11
|
+
# [:literal, :array] (mri only)
|
12
|
+
# [:literal, :hash] (mri only)
|
13
|
+
# [:statement]
|
14
|
+
# [:forced]
|
15
|
+
# notes: rbx support buggy (depends on rubinius error messages)
|
16
|
+
module Ripl::MultiLine::LiveError
|
17
|
+
VERSION = "0.1.0"
|
18
|
+
|
19
|
+
ERROR_MESSAGES = {
|
20
|
+
:ruby => [
|
21
|
+
[[:literal, :string], /unterminated string meets end of file/],
|
22
|
+
[[:literal, :regexp], /unterminated regexp meets end of file/],
|
23
|
+
[[:literal, :array], /syntax error, unexpected \$end, expecting '\]'/],
|
24
|
+
[[:literal, :hash], /syntax error, unexpected \$end, expecting '\}'/], # does not work for ranges or {5=>
|
25
|
+
[[:statement], /syntax error, unexpected \$end/],
|
26
|
+
],
|
27
|
+
:jruby => [
|
28
|
+
[[:literal, :string], /unterminated string meets end of file/],
|
29
|
+
[[:literal, :regexp], /unterminated regexp meets end of file/], # array or hash cannot be detected
|
30
|
+
[[:statement], /syntax error, unexpected end-of-file/],
|
31
|
+
],
|
32
|
+
:rbx => [
|
33
|
+
[[:literal, :string], /unterminated [a-z]+ meets end of file/], # no extra message for regexes
|
34
|
+
[[:literal], /expecting '\\n' or ';'/], # TODO: better rbx regexes or rbx bug report
|
35
|
+
[[:literal, :hash], /expecting '\}'/],
|
36
|
+
# [:literal, /expecting '\]'/],
|
37
|
+
# [:literal, /expecting '\)'/],
|
38
|
+
[[:statement], /syntax error, unexpected \$end/],
|
39
|
+
[[:statement], /missing 'end'/], # for i in [2,3,4] do;
|
40
|
+
],
|
41
|
+
}
|
42
|
+
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE.downcase.to_sym
|
43
|
+
ruby_engine = :ruby if !ERROR_MESSAGES.keys.include?(ruby_engine)
|
44
|
+
|
45
|
+
define_method(:print_eval_error) do |e|
|
46
|
+
if e.is_a? SyntaxError
|
47
|
+
ERROR_MESSAGES[ruby_engine].each{ |type, message|
|
48
|
+
handle_multiline(type) if message === e.message
|
49
|
+
}
|
50
|
+
end
|
51
|
+
super(e)
|
52
|
+
end
|
53
|
+
|
54
|
+
def eval_input(input)
|
55
|
+
if input =~ /;\s*\Z/ # force multi line with ;
|
56
|
+
handle_multiline(:forced)
|
57
|
+
elsif input =~ /^=begin(\s.*)?$/ && !@buffer
|
58
|
+
@ignore_mode = true # MAYBE: change prompt
|
59
|
+
elsif !@ignore_mode
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def print_result(result)
|
65
|
+
if @ignore_mode && @input == '=end'
|
66
|
+
@ignore_mode = false
|
67
|
+
elsif !@ignore_mode
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# J-_-L
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# see https://github.com/cldwalker/ripl-ripper/blob/master/lib/ripl/ripper.rb for a different (standalone) plugin
|
2
|
+
require 'ripl'
|
3
|
+
require 'ripper'
|
4
|
+
|
5
|
+
Ripl.config[:multi_line_engine] ||= :ripper
|
6
|
+
require 'ripl/multi_line'
|
7
|
+
|
8
|
+
# # # #
|
9
|
+
# This multi-line implementation uses IRB's RubyLex parser
|
10
|
+
# works on: 1.9 1.8(gem)
|
11
|
+
# analyze features: [:literal, :string]
|
12
|
+
# [:literal, :regexp]
|
13
|
+
# [:literal, :array] (mri only)
|
14
|
+
# [:literal, :hash] (mri only)
|
15
|
+
# [:statement]
|
16
|
+
# [:forced]
|
17
|
+
# notes: statement could also be [
|
18
|
+
module Ripl::MultiLine::Ripper
|
19
|
+
VERSION = '0.1.0'
|
20
|
+
|
21
|
+
def multiline?(string)
|
22
|
+
return [:forced] if string =~ /;\s*\Z/
|
23
|
+
|
24
|
+
expr = ::Ripper::SexpBuilder.new(string).parse
|
25
|
+
|
26
|
+
return [:statement] if !expr # not always statements...
|
27
|
+
|
28
|
+
# literals are problematic..
|
29
|
+
last_expr = expr[-1][-1]
|
30
|
+
|
31
|
+
return [:literal, :regexp] if last_expr == [:regexp_literal, [:regexp_new], nil]
|
32
|
+
|
33
|
+
delimiters = %q_(?:[\[<({][\]>)}]|(.)\1)_
|
34
|
+
|
35
|
+
return [:literal, :string] if last_expr == [:string_literal, [:string_content]] &&
|
36
|
+
string !~ /(?:""|''|%q?#{delimiters})\s*\Z/i # empty literal at $
|
37
|
+
|
38
|
+
return [:literal, :string] if last_expr == [:xstring_literal, [:xstring_new]] &&
|
39
|
+
string !~ /(?:``|%x#{delimiters})\s*\Z/i
|
40
|
+
|
41
|
+
return [:literal, :string] if last_expr == [:words_new] &&
|
42
|
+
string !~ /%W#{delimiters}\s*\Z/
|
43
|
+
|
44
|
+
return [:literal, :string] if last_expr == [:qwords_new] &&
|
45
|
+
string !~ /%w#{delimiters}\s*\Z/
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# J-_-L
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ripl'
|
2
|
+
require 'ruby_parser'
|
3
|
+
|
4
|
+
Ripl.config[:multi_line_engine] ||= :ruby_parser
|
5
|
+
require 'ripl/multi_line'
|
6
|
+
|
7
|
+
# # # #
|
8
|
+
# This multi-line implementation uses IRB's RubyLex parser
|
9
|
+
# works on: 1.8
|
10
|
+
# analyze features: [:literal, :string]
|
11
|
+
# [:literal, :hash]
|
12
|
+
# [:statement]
|
13
|
+
# [:forced]
|
14
|
+
# notes: statement could also be [
|
15
|
+
module Ripl::MultiLine::RubyParser
|
16
|
+
VERSION = '0.1.0'
|
17
|
+
|
18
|
+
ERROR_MESSAGES = [
|
19
|
+
[[:literal, :string], /unterminated string meets end of file/],
|
20
|
+
# [[:literal, :regexp], /unterminated regexp meets end of file/], #-> string
|
21
|
+
# [[:literal, :array], /syntax error, unexpected \$end, expecting '\]'/], #-> ParseError
|
22
|
+
[[:literal, :hash], /syntax error, unexpected \$end, expecting '\}'/], # {45
|
23
|
+
]
|
24
|
+
|
25
|
+
def multiline?(string)
|
26
|
+
return [:forced] if string =~ /;\s*\Z/ # force multi line with ;
|
27
|
+
::RubyParser.new.parse(string)
|
28
|
+
false # string was parsable, no multi-line
|
29
|
+
rescue ::Racc::ParseError
|
30
|
+
[:statement]
|
31
|
+
rescue SyntaxError => e
|
32
|
+
ERROR_MESSAGES.each{ |type, message|
|
33
|
+
return type if message === e.message
|
34
|
+
}
|
35
|
+
false # syntax error not multi-line relevant
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# J-_-L
|
data/test/shell_test.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
describe "Shell" do
|
4
|
+
before { reset_ripl }
|
5
|
+
|
6
|
+
def shell(options={})
|
7
|
+
Ripl.shell(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "multi-line behaviour" do
|
11
|
+
should_eval %| "m" * 2 |
|
12
|
+
should_eval %| "8989" |
|
13
|
+
should_eval %| "zzz" |
|
14
|
+
|
15
|
+
should_not_eval %| "m" * |
|
16
|
+
|
17
|
+
should_not_eval %| " |
|
18
|
+
should_not_eval %| ' |
|
19
|
+
should_not_eval %| / |
|
20
|
+
|
21
|
+
should_not_eval %| [ |
|
22
|
+
should_not_eval %| { |
|
23
|
+
should_not_eval %| ( |
|
24
|
+
|
25
|
+
should_not_eval %| def hey |
|
26
|
+
should_not_eval %| def hey; 89 |
|
27
|
+
should_not_eval %| begin |
|
28
|
+
should_eval %| for x in [2,3,4];7;end |
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO add misc real tests for everything
|
32
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'bacon'
|
2
|
+
require 'bacon/bits'
|
3
|
+
require 'rr'
|
4
|
+
require 'bacon/rr'
|
5
|
+
require 'stringio'
|
6
|
+
require 'ripl'
|
7
|
+
require 'ripl/multi_line'
|
8
|
+
include Ripl
|
9
|
+
|
10
|
+
module Helpers
|
11
|
+
def ripl(*args)
|
12
|
+
options = args[-1].is_a?(Hash) ? args.pop : {}
|
13
|
+
mock_riplrc unless options[:riplrc] == false
|
14
|
+
mock(Ripl.shell).loop unless options[:loop] == false
|
15
|
+
capture_stdout { Ripl::Runner.run(args) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def mock_riplrc(&block)
|
19
|
+
mock(Runner).load_rc(Ripl.config[:riplrc], &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def mock_shell(&block)
|
23
|
+
mock(Shell).create(anything) {|e|
|
24
|
+
shell = Shell.new(e)
|
25
|
+
block ? block.call(shell) : mock(shell).loop
|
26
|
+
shell
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_ripl
|
31
|
+
Ripl.instance_eval "@config = @shell = @riplrc = nil"
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset_shell
|
35
|
+
Ripl.send(:remove_const, :Shell)
|
36
|
+
$".delete $".grep(/shell\.rb/)[0]
|
37
|
+
require 'ripl/shell'
|
38
|
+
Ripl::Shell.include Ripl::History
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset_config
|
42
|
+
Ripl.config.merge! :history => '~/.irb_history', :completion => {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def capture_stdout(&block)
|
46
|
+
original_stdout = $stdout
|
47
|
+
$stdout = fake = StringIO.new
|
48
|
+
begin
|
49
|
+
yield
|
50
|
+
ensure
|
51
|
+
$stdout = original_stdout
|
52
|
+
end
|
53
|
+
fake.string
|
54
|
+
end
|
55
|
+
|
56
|
+
def capture_stderr(&block)
|
57
|
+
original_stderr = $stderr
|
58
|
+
$stderr = fake = StringIO.new
|
59
|
+
begin
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
$stderr = original_stderr
|
63
|
+
end
|
64
|
+
fake.string
|
65
|
+
end
|
66
|
+
|
67
|
+
# multi_line stuff
|
68
|
+
def should_eval(input)
|
69
|
+
it "should evaluate " + input do
|
70
|
+
mock(shell).get_input { input }
|
71
|
+
capture_stderr { capture_stdout { shell.loop_once }.should.not == '' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def should_not_eval(input)
|
76
|
+
it "should not evaluate " + input do
|
77
|
+
mock(shell).get_input { input }
|
78
|
+
capture_stderr { capture_stdout { shell.loop_once }.should == '' }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Bacon::Context.send :include, Helpers
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: ripl-multi_line
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.3.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jan Lelis
|
@@ -10,8 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
14
|
-
default_executable:
|
13
|
+
date: 2011-04-28 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: ripl
|
@@ -21,9 +20,53 @@ dependencies:
|
|
21
20
|
requirements:
|
22
21
|
- - ">="
|
23
22
|
- !ruby/object:Gem::Version
|
24
|
-
version: 0.3.
|
23
|
+
version: 0.3.6
|
25
24
|
type: :runtime
|
26
25
|
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bacon
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.1.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: bacon-bits
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: bacon-rr
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rr
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id005
|
27
70
|
description: This ripl plugin allows you to evaluate multiple lines of Ruby code.
|
28
71
|
email: mail@janlelis.de
|
29
72
|
executables: []
|
@@ -35,13 +78,19 @@ extra_rdoc_files:
|
|
35
78
|
- LICENSE.txt
|
36
79
|
files:
|
37
80
|
- lib/ripl/multi_line.rb
|
81
|
+
- lib/ripl/multi_line/live_error.rb
|
82
|
+
- lib/ripl/multi_line/error_check.rb
|
83
|
+
- lib/ripl/multi_line/ripper.rb
|
84
|
+
- lib/ripl/multi_line/ruby_parser.rb
|
85
|
+
- lib/ripl/multi_line/irb.rb
|
86
|
+
- test/test_helper.rb
|
87
|
+
- test/shell_test.rb
|
38
88
|
- LICENSE.txt
|
39
89
|
- README.rdoc
|
40
90
|
- CHANGELOG.rdoc
|
41
91
|
- deps.rip
|
42
92
|
- Rakefile
|
43
93
|
- .gemspec
|
44
|
-
has_rdoc: true
|
45
94
|
homepage: http://github.com/janlelis/ripl-multi_line
|
46
95
|
licenses:
|
47
96
|
- MIT
|
@@ -65,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
114
|
requirements: []
|
66
115
|
|
67
116
|
rubyforge_project:
|
68
|
-
rubygems_version: 1.
|
117
|
+
rubygems_version: 1.7.2
|
69
118
|
signing_key:
|
70
119
|
specification_version: 3
|
71
120
|
summary: A ripl plugin for multi-line eval.
|