ripl-multi_line 0.2.4 → 0.3.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/.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.
|