each_sql 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +14 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +7 -4
- data/README.markdown +23 -11
- data/Rakefile +0 -8
- data/VERSION +1 -1
- data/each_sql.gemspec +0 -3
- data/lib/each_sql.rb +45 -109
- data/lib/each_sql/each_sql.rb +79 -144
- data/lib/each_sql/parser.rb +39 -0
- data/lib/each_sql/parser/sql.citrus.erb +239 -0
- data/test/test_each_sql.rb +76 -276
- data/test/yml/common.yml +601 -0
- data/test/yml/default.yml +21 -0
- data/test/yml/mysql.yml +99 -0
- data/test/yml/oracle.yml +200 -0
- data/test/yml/postgres.yml +234 -0
- metadata +70 -16
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
+
### 0.3.0 / 2012/03/10
|
2
|
+
* Internal implementation revised.
|
3
|
+
* At first, I thought this would be trivial,
|
4
|
+
that I didn't need a real parser for just breaking SQL scripts
|
5
|
+
into individual executable units.
|
6
|
+
I couldn't be more wrong. Codes for handling a few exceptional cases
|
7
|
+
soon piled up and became unmaintainable.
|
8
|
+
The new version now employs Citrus parser for processing SQL scripts.
|
9
|
+
The output is not backward-compatible, for instance, comments before and after
|
10
|
+
each execution block are trimmed out.
|
11
|
+
* Supports PostgreSQL (experimental)
|
12
|
+
* `delimiter` command works for all types
|
13
|
+
|
1
14
|
### 0.2.5 / 2011/09/01
|
2
15
|
* Can pass block directly to EachSQL(script)
|
16
|
+
|
3
17
|
```ruby
|
4
18
|
EachSQL(script) do |sql|
|
5
19
|
# ...
|
data/Gemfile
CHANGED
@@ -2,11 +2,13 @@ source "http://rubygems.org"
|
|
2
2
|
# Add dependencies required to use your gem here.
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
5
|
+
gem 'citrus', '~> 2.4.1'
|
6
|
+
gem 'erubis', '~> 2.7.0'
|
7
|
+
gem 'quote_unquote', '~> 0.1.1'
|
5
8
|
|
6
9
|
# Add dependencies to develop your gem here.
|
7
10
|
# Include everything needed to run rake, tests, features, etc.
|
8
11
|
group :development do
|
9
12
|
gem "bundler", "~> 1.0.0"
|
10
13
|
gem "jeweler", "~> 1.6.2"
|
11
|
-
gem "rcov", ">= 0"
|
12
14
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
citrus (2.4.1)
|
5
|
+
erubis (2.7.0)
|
4
6
|
git (1.2.5)
|
5
7
|
jeweler (1.6.4)
|
6
8
|
bundler (~> 1.0)
|
7
9
|
git (>= 1.2.5)
|
8
10
|
rake
|
9
|
-
|
10
|
-
|
11
|
-
rcov (0.9.10-java)
|
11
|
+
quote_unquote (0.1.1)
|
12
|
+
rake (0.9.2.2)
|
12
13
|
|
13
14
|
PLATFORMS
|
14
15
|
java
|
@@ -16,5 +17,7 @@ PLATFORMS
|
|
16
17
|
|
17
18
|
DEPENDENCIES
|
18
19
|
bundler (~> 1.0.0)
|
20
|
+
citrus (~> 2.4.1)
|
21
|
+
erubis (~> 2.7.0)
|
19
22
|
jeweler (~> 1.6.2)
|
20
|
-
|
23
|
+
quote_unquote (~> 0.1.1)
|
data/README.markdown
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
|
1
|
+
each_sql
|
2
|
+
========
|
2
3
|
|
3
|
-
Enumerate
|
4
|
+
Enumerate executable blocks in the given SQL script.
|
4
5
|
|
5
|
-
|
6
|
+
Installation
|
7
|
+
------------
|
6
8
|
```
|
7
9
|
gem install 'each_sql'
|
8
10
|
```
|
9
11
|
|
10
|
-
|
12
|
+
Example
|
13
|
+
-------
|
11
14
|
### Basic
|
12
15
|
```ruby
|
13
16
|
require 'each_sql'
|
@@ -35,16 +38,24 @@ end
|
|
35
38
|
EachSQL(plsql_script, :oracle).each do |sql|
|
36
39
|
# ...
|
37
40
|
end
|
41
|
+
|
42
|
+
# For PostgreSQL scripts
|
43
|
+
EachSQL(plpgsql_script, :postgres).each do |sql|
|
44
|
+
# ...
|
45
|
+
end
|
38
46
|
```
|
39
47
|
|
40
|
-
|
41
|
-
|
42
|
-
-
|
48
|
+
TODO
|
49
|
+
----
|
50
|
+
- More tests.
|
51
|
+
- Support for other RDBMSs
|
43
52
|
|
44
|
-
|
53
|
+
Warning
|
54
|
+
-------
|
45
55
|
Stored procedure handling is at best incomplete. Use it at your own risk.
|
46
56
|
|
47
|
-
|
57
|
+
Contributing to each_sql
|
58
|
+
------------------------
|
48
59
|
|
49
60
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
50
61
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
@@ -54,8 +65,9 @@ Stored procedure handling is at best incomplete. Use it at your own risk.
|
|
54
65
|
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
55
66
|
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
56
67
|
|
57
|
-
|
68
|
+
Copyright
|
69
|
+
---------
|
58
70
|
|
59
|
-
Copyright (c)
|
71
|
+
Copyright (c) 2012 Junegunn Choi. See LICENSE.txt for
|
60
72
|
further details.
|
61
73
|
|
data/Rakefile
CHANGED
@@ -32,14 +32,6 @@ Rake::TestTask.new(:test) do |test|
|
|
32
32
|
test.verbose = true
|
33
33
|
end
|
34
34
|
|
35
|
-
require 'rcov/rcovtask'
|
36
|
-
Rcov::RcovTask.new do |test|
|
37
|
-
test.libs << 'test'
|
38
|
-
test.pattern = 'test/**/test_*.rb'
|
39
|
-
test.verbose = true
|
40
|
-
test.rcov_opts << '--exclude "gems/*"'
|
41
|
-
end
|
42
|
-
|
43
35
|
task :default => :test
|
44
36
|
|
45
37
|
require 'rake/rdoctask'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/each_sql.gemspec
CHANGED
@@ -43,16 +43,13 @@ Gem::Specification.new do |s|
|
|
43
43
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
44
44
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
45
45
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
46
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
47
46
|
else
|
48
47
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
49
48
|
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
50
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
51
49
|
end
|
52
50
|
else
|
53
51
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
54
52
|
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
55
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
56
53
|
end
|
57
54
|
end
|
58
55
|
|
data/lib/each_sql.rb
CHANGED
@@ -1,126 +1,62 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
# Junegunn Choi (junegunn.c@gmail.com)
|
3
3
|
|
4
|
+
require 'rubygems'
|
4
5
|
require 'each_sql/each_sql'
|
6
|
+
require 'each_sql/parser'
|
5
7
|
|
6
8
|
# Shortcut method for creating a Enumerable EachSQL object for the given input.
|
7
9
|
# @param[String] input Input script.
|
8
10
|
# @param[Symbol] The type of the input SQL script. :default, :mysql, and :oracle (or :plsql)
|
9
|
-
# @
|
11
|
+
# @yield[String] Executable SQL statement or block.
|
12
|
+
# @return[Array] Array of executable SQL statements and blocks.
|
10
13
|
def EachSQL input, type = :default
|
11
|
-
esql
|
14
|
+
esql = EachSQL.new(type)
|
15
|
+
ret = []
|
16
|
+
result = {}
|
17
|
+
|
18
|
+
process = lambda {
|
19
|
+
return if esql.empty?
|
20
|
+
result = esql.shift
|
21
|
+
sqls = result[:sqls]
|
22
|
+
sqls.each do |sql|
|
23
|
+
if block_given?
|
24
|
+
yield sql
|
25
|
+
else
|
26
|
+
ret << sql
|
27
|
+
end
|
28
|
+
end
|
29
|
+
}
|
12
30
|
|
13
|
-
|
14
|
-
|
15
|
-
|
31
|
+
input.to_s.each_line do |line|
|
32
|
+
case line
|
33
|
+
when /^\s*delimiter\s+(\S+)/i
|
34
|
+
process.call
|
35
|
+
if esql.empty?
|
36
|
+
esql.delimiter = $1
|
37
|
+
else
|
38
|
+
esql << line
|
39
|
+
end
|
40
|
+
when /#{Regexp.escape esql.delimiter}/
|
41
|
+
esql << line
|
42
|
+
process.call
|
43
|
+
else
|
44
|
+
esql << line
|
16
45
|
end
|
17
|
-
else
|
18
|
-
esql
|
19
46
|
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class EachSQL
|
23
|
-
# EachSQL::Default Hash is a set of pre-defined parsing rules
|
24
|
-
# - :default: Default parsing rules for vendor-independent SQL scripts
|
25
|
-
# - :mysql: Parsing rules for MySQL scripts. Understands `delimiter' statements.
|
26
|
-
# - :oracle: Parsing rules for Oracle scripts. Removes trailing slashes after begin-end blocks.
|
27
|
-
Defaults = {
|
28
|
-
:default => {
|
29
|
-
:delimiter => /;+/,
|
30
|
-
:blocks => {
|
31
|
-
/`/ => /`/,
|
32
|
-
/"/ => /"/,
|
33
|
-
/'/ => /'/,
|
34
|
-
/\/\*[^+]/ => /\*\//,
|
35
|
-
/--+/ => $/,
|
36
|
-
},
|
37
|
-
:nesting_blocks => {
|
38
|
-
/\bdeclare.*?;\s*?begin\b/im => /;\s*?end\b/i,
|
39
|
-
/\bbegin\b/i => /;\s*?end\b/i,
|
40
|
-
},
|
41
|
-
:nesting_context => [
|
42
|
-
/\A\s*(begin|declare|create\b[^;]+?\b(procedure|function|trigger|package))\b/im
|
43
|
-
],
|
44
|
-
:callbacks => {},
|
45
|
-
:ignore => [],
|
46
|
-
:replace => {},
|
47
|
-
# Let's assume we don't change delimiters within usual sql scripts
|
48
|
-
:strip_delimiter => lambda { |obj, stmt| stmt.sub(/\A;+/, '').sub(/;+\Z/, '') }
|
49
|
-
},
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
/`/ => /`/,
|
55
|
-
/"/ => /"/,
|
56
|
-
/'/ => /'/,
|
57
|
-
/\/\*[^+]/ => /\*\//,
|
58
|
-
/--+/ => $/,
|
59
|
-
},
|
60
|
-
:nesting_blocks => {
|
61
|
-
/\bbegin\b/i => /\bend\b/i
|
62
|
-
},
|
63
|
-
:nesting_context => [
|
64
|
-
/\A\s*(begin|create\b[^;]+?\b(procedure|function|trigger))\b/im
|
65
|
-
],
|
66
|
-
# We need to change delimiter on `delimiter' command
|
67
|
-
:callbacks => {
|
68
|
-
/^\s*delimiter\s+(\S+)/i => lambda { |obj, stmt, md|
|
69
|
-
new_delimiter = Regexp.new(Regexp.escape md[1])
|
70
|
-
obj.delimiter = /(#{new_delimiter})+|delimiter\s+\S+/i
|
71
|
-
obj.delimiter_string = md[1]
|
72
|
-
}
|
73
|
-
},
|
74
|
-
:ignore => [
|
75
|
-
/^delimiter\s+\S+$/i
|
76
|
-
],
|
77
|
-
:replace => {},
|
78
|
-
:strip_delimiter => lambda { |obj, stmt|
|
79
|
-
stmt.gsub(/(#{Regexp.escape(obj.delimiter_string || ';')})+\Z/, '')
|
80
|
-
}
|
81
|
-
},
|
48
|
+
if !esql.empty?
|
49
|
+
process.call
|
50
|
+
end
|
82
51
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
/--+/ => $/,
|
91
|
-
},
|
92
|
-
:nesting_blocks => {
|
93
|
-
/\bbegin\b/i => /\bend\b/i,
|
94
|
-
/\bdeclare.*?;\s*?begin\b/im => {
|
95
|
-
:closer => %r{;\s*/}m,
|
96
|
-
# Stops immediately
|
97
|
-
:pop => true
|
98
|
-
},
|
99
|
-
/\bcreate[^;]+?\b(procedure|function|trigger|package)\b/im => {
|
100
|
-
:closer => %r{;\s*/}m,
|
101
|
-
# Stops immediately
|
102
|
-
:pop => true
|
103
|
-
}
|
104
|
-
},
|
105
|
-
:nesting_context => [
|
106
|
-
/\A\s*(\/\s*)*(begin|declare|create\b[^;]+?\b(procedure|function|trigger|package))\b/im
|
107
|
-
],
|
108
|
-
:callbacks => {
|
109
|
-
/\Abegin\b/ => lambda { |obj, stmt, md|
|
110
|
-
# Oracle needs this
|
111
|
-
stmt << ';' if stmt !~ /;\Z/
|
112
|
-
}
|
113
|
-
},
|
114
|
-
:ignore => [],
|
115
|
-
:replace => { %r[\A/] => '' },
|
116
|
-
:strip_delimiter => lambda { |obj, stmt|
|
117
|
-
stmt.gsub(/(#{stmt =~ /;\s*\// ? '/' : ';'})+\Z/, '')
|
118
|
-
}
|
119
|
-
}
|
120
|
-
}
|
121
|
-
Defaults[:plsql] = Defaults[:oracle] # alias
|
52
|
+
if sql = result[:leftover]
|
53
|
+
if block_given?
|
54
|
+
yield sql
|
55
|
+
else
|
56
|
+
ret << sql
|
57
|
+
end
|
58
|
+
end
|
122
59
|
|
123
|
-
|
124
|
-
Defaults.freeze
|
60
|
+
ret
|
125
61
|
end
|
126
62
|
|
data/lib/each_sql/each_sql.rb
CHANGED
@@ -1,173 +1,108 @@
|
|
1
|
-
|
2
|
-
# Junegunn Choi (junegunn.c@gmail.com)
|
1
|
+
require 'stringio'
|
3
2
|
|
4
3
|
# Enumerable EachSQL object.
|
5
4
|
class EachSQL
|
6
5
|
include Enumerable
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@blocks = @options[:blocks]
|
14
|
-
@nblocks = @options[:nesting_blocks]
|
15
|
-
@all_blocks = @blocks.merge @nblocks
|
16
|
-
end
|
17
|
-
|
18
|
-
def each
|
19
|
-
return nil if @org_input.nil? || @org_input.empty?
|
20
|
-
@input = @org_input.dup
|
7
|
+
# @param[Symbol] type RDBMS type: :default|:mysql|:oracle|:postgres
|
8
|
+
def initialize type
|
9
|
+
@type = type
|
10
|
+
@data = ''
|
11
|
+
@sqls = []
|
21
12
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@delimiter = @options[:delimiter]
|
26
|
-
while @input && @input.length > 0
|
27
|
-
# Extract a statement
|
28
|
-
statement = next_statement
|
13
|
+
self.delimiter = ';'
|
14
|
+
end
|
29
15
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
statement.strip!
|
16
|
+
# @param[String] delim SQL delimiter
|
17
|
+
# @return[EachSQL]
|
18
|
+
def delimiter= delim
|
19
|
+
@delim = delim
|
20
|
+
@parser = EachSQL::Parser.parser_for @type, delim
|
21
|
+
self
|
22
|
+
end
|
38
23
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
callback.call self, statement, md if md
|
44
|
-
end
|
24
|
+
# @return[String] Current delimiter.
|
25
|
+
def delimiter
|
26
|
+
@delim
|
27
|
+
end
|
45
28
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
29
|
+
# Appends the given String to the buffer.
|
30
|
+
# @param[String] input String to append
|
31
|
+
def << input
|
32
|
+
if input
|
33
|
+
@data << input.sub(/\A#{[65279].pack('U*')}/, '') # BOM (FIXME)
|
52
34
|
end
|
53
|
-
|
35
|
+
self
|
54
36
|
end
|
55
37
|
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
break if block_start.nil?
|
70
|
-
|
71
|
-
md = match output, closer, block_start + opener_length
|
72
|
-
idx = block_end = md ? md[:end] : (output.length-1)
|
73
|
-
|
74
|
-
output[block_start...block_end] = ' ' * (block_end - block_start)
|
75
|
-
end
|
76
|
-
output
|
38
|
+
# Parses current buffer and returns the result in Hash.
|
39
|
+
# :sqls is an Array of processed executable SQL blocks,
|
40
|
+
# :leftover is the unparsed trailing data
|
41
|
+
# @return [Hash]
|
42
|
+
def shift
|
43
|
+
result = @parser.parse @data
|
44
|
+
@data = result.captures[:leftover].join
|
45
|
+
leftover = strip_sql(@data)
|
46
|
+
{
|
47
|
+
:sqls =>
|
48
|
+
result.captures[:execution_block].map { |b| strip_sql b },
|
49
|
+
:leftover => leftover.empty? ? nil : leftover
|
50
|
+
}
|
77
51
|
end
|
78
52
|
|
79
|
-
|
80
|
-
|
53
|
+
# Return is the buffer is empty
|
54
|
+
# @return [Boolean]
|
55
|
+
def empty?
|
56
|
+
@data.gsub(/\s/, '').empty?
|
57
|
+
end
|
81
58
|
|
82
|
-
|
59
|
+
# Parses the buffer and enumerates through the executable blocks.
|
60
|
+
# @yield [String]
|
61
|
+
# @return [NilClass]
|
62
|
+
def each
|
63
|
+
result = shift
|
64
|
+
sqls = (result[:sqls] + result[:leftover]).
|
65
|
+
map { |sql| strip_sql(sql) }.
|
66
|
+
reject(&:empty?)
|
67
|
+
sqls.each do |sql|
|
68
|
+
yield sql
|
83
69
|
end
|
84
|
-
|
85
|
-
ret = @input[0...@cur].strip
|
86
|
-
@input = @input[@cur..-1]
|
87
|
-
@input_c = @input_c[@cur..-1]
|
88
|
-
return ret
|
89
70
|
end
|
90
71
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# Look for the closest block depending on the current context
|
98
|
-
target_blocks =
|
99
|
-
if @options[:nesting_context].any? {|pat| @input_c.match pat }
|
100
|
-
@all_blocks
|
101
|
-
else
|
102
|
-
@blocks
|
103
|
-
end
|
104
|
-
|
105
|
-
block_start, body_start, opener, closer = target_blocks.map { |opener, closer|
|
106
|
-
closer = closer[:closer] if closer.is_a? Hash
|
107
|
-
md = match @input_c, opener, @cur
|
108
|
-
[md && md[:begin], md && md[:end], opener, closer]
|
109
|
-
}.reject { |e| e.first.nil? }.min_by(&:first)
|
110
|
-
|
111
|
-
# If we're nested, look for the parent's closer as well
|
112
|
-
if expect && (md = match @input_c, expect, @cur) &&
|
113
|
-
(block_start.nil? || md[:begin] < block_start)
|
114
|
-
|
115
|
-
@cur = md[:end]
|
116
|
-
return :nest_closer
|
72
|
+
private
|
73
|
+
def strip_sql sql
|
74
|
+
# Preprocess
|
75
|
+
case @type
|
76
|
+
when :oracle
|
77
|
+
sql = sql.sub(/\A[\s\/]+/, '').sub(/[\s\/]+\Z/, '')
|
117
78
|
end
|
118
79
|
|
119
|
-
#
|
120
|
-
|
121
|
-
|
122
|
-
|
80
|
+
# FIXME: Infinite loop?
|
81
|
+
# sql = sql.gsub(
|
82
|
+
# /
|
83
|
+
# (?:
|
84
|
+
# (?:\A(?:#{Regexp.escape @delim}|[\s]+)+)
|
85
|
+
# |
|
86
|
+
# (?:(?:#{Regexp.escape @delim}|[\s]+)+\Z)
|
87
|
+
# )+
|
88
|
+
# /x, '')
|
89
|
+
prev_sql = nil
|
90
|
+
delim = Regexp.escape @delim
|
91
|
+
while prev_sql != sql
|
92
|
+
prev_sql = sql
|
93
|
+
sql = sql.strip.sub(/\A(?:#{delim})+/, '').sub(/(?:#{delim})+\Z/, '')
|
123
94
|
end
|
124
95
|
|
125
|
-
#
|
126
|
-
@
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
while true
|
131
|
-
ret = process_next_block(closer)
|
132
|
-
break if ret == :nest_closer
|
133
|
-
throw_exception(closer) if @cur >= @input.length - 1
|
96
|
+
# Postprocess
|
97
|
+
case @type
|
98
|
+
when :oracle
|
99
|
+
if sql =~ /\bend(\s+\S+)?\Z/i
|
100
|
+
sql = sql + ';'
|
134
101
|
end
|
135
|
-
return :done if @nblocks[opener].is_a?(Hash) && @nblocks[opener][:pop]
|
136
|
-
|
137
|
-
# If non-nesting block, just skip through it
|
138
|
-
else
|
139
|
-
skip_through_block closer
|
140
102
|
end
|
141
103
|
|
142
|
-
|
104
|
+
sql
|
143
105
|
end
|
144
106
|
|
145
|
-
# For Ruby 1.8 compatibility
|
146
|
-
def match str, pat, idx
|
147
|
-
md = str[idx..-1].match(pat)
|
148
|
-
return nil if md.nil?
|
149
|
-
|
150
|
-
result = {
|
151
|
-
:begin => md && (md.begin(0) + idx),
|
152
|
-
:length => md && md[0].length,
|
153
|
-
:end => md && (md.end(0) + idx)
|
154
|
-
}
|
155
|
-
result
|
156
|
-
end
|
157
|
-
|
158
|
-
def skip_through_block closer
|
159
|
-
md = match @input_c, closer, @cur
|
160
|
-
throw_exception(closer) if md.nil?
|
161
|
-
|
162
|
-
@cur = md[:end]
|
163
|
-
end
|
164
|
-
|
165
|
-
def throw_exception closer
|
166
|
-
raise ArgumentError.new(
|
167
|
-
"Unclosed block: was expecting #{closer.inspect} " +
|
168
|
-
"while processing #{(@input[0, 60] + ' ... ').inspect}" +
|
169
|
-
(@prev_statement ?
|
170
|
-
" after #{@prev_statement.inspect}" : ""))
|
171
|
-
end
|
172
107
|
end#EachSQL
|
173
108
|
|