vorax 0.1.0pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +1 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +45 -0
  5. data/Rakefile +30 -0
  6. data/lib/vorax/base_funnel.rb +30 -0
  7. data/lib/vorax/output/html_convertor.rb +120 -0
  8. data/lib/vorax/output/html_funnel.rb +79 -0
  9. data/lib/vorax/output/pagezip_convertor.rb +20 -0
  10. data/lib/vorax/output/tablezip_convertor.rb +22 -0
  11. data/lib/vorax/output/vertical_convertor.rb +53 -0
  12. data/lib/vorax/output/zip_convertor.rb +117 -0
  13. data/lib/vorax/parser/argument.rb~ +125 -0
  14. data/lib/vorax/parser/body_split.rb +168 -0
  15. data/lib/vorax/parser/conn_string.rb +104 -0
  16. data/lib/vorax/parser/grammars/alias.rb +912 -0
  17. data/lib/vorax/parser/grammars/alias.rl +146 -0
  18. data/lib/vorax/parser/grammars/column.rb +454 -0
  19. data/lib/vorax/parser/grammars/column.rl +64 -0
  20. data/lib/vorax/parser/grammars/common.rl +98 -0
  21. data/lib/vorax/parser/grammars/package_spec.rb +1186 -0
  22. data/lib/vorax/parser/grammars/package_spec.rl +78 -0
  23. data/lib/vorax/parser/grammars/plsql_def.rb +469 -0
  24. data/lib/vorax/parser/grammars/plsql_def.rl +59 -0
  25. data/lib/vorax/parser/grammars/statement.rb +925 -0
  26. data/lib/vorax/parser/grammars/statement.rl +83 -0
  27. data/lib/vorax/parser/parser.rb +320 -0
  28. data/lib/vorax/parser/plsql_structure.rb +158 -0
  29. data/lib/vorax/parser/plsql_walker.rb +143 -0
  30. data/lib/vorax/parser/statement_inspector.rb~ +52 -0
  31. data/lib/vorax/parser/stmt_inspector.rb +78 -0
  32. data/lib/vorax/parser/target_ref.rb +110 -0
  33. data/lib/vorax/sqlplus.rb +281 -0
  34. data/lib/vorax/version.rb +7 -0
  35. data/lib/vorax/vorax_io.rb +70 -0
  36. data/lib/vorax.rb +60 -0
  37. data/spec/column_spec.rb +40 -0
  38. data/spec/conn_string_spec.rb +53 -0
  39. data/spec/package_spec_spec.rb +48 -0
  40. data/spec/pagezip_spec.rb +153 -0
  41. data/spec/parser_spec.rb +299 -0
  42. data/spec/plsql_structure_spec.rb +44 -0
  43. data/spec/spec_helper.rb +13 -0
  44. data/spec/sql/create_objects.sql +69 -0
  45. data/spec/sql/dbms_crypto.spc +339 -0
  46. data/spec/sql/dbms_crypto.~spc +339 -0
  47. data/spec/sql/dbms_stats.spc +4097 -0
  48. data/spec/sql/drop_user.sql +10 -0
  49. data/spec/sql/muci.spc +24 -0
  50. data/spec/sql/setup_user.sql +22 -0
  51. data/spec/sql/test.pkg +67 -0
  52. data/spec/sqlplus_spec.rb +52 -0
  53. data/spec/stmt_inspector_spec.rb +84 -0
  54. data/spec/tablezip_spec.rb +111 -0
  55. data/spec/vertical_spec.rb +150 -0
  56. data/vorax.gemspec +21 -0
  57. metadata +139 -0
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ *.rbc
3
+ .config
4
+ .yardoc
5
+ _yardoc
6
+ doc/
7
+ spec/reports
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alexandru Tica
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Ruby::Vorax
2
+
3
+ Provides the logic required by Vorax, an Oracle IDE for geeks. Even
4
+ the main goal of this gem is to support Vorax, it can also be used
5
+ as it is to interact with a hidden SqlPLUS process or to parse
6
+ SQL/PLSQL code.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'ruby-vorax'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install ruby-vorax
21
+
22
+ ## Usage
23
+
24
+ To create a SqlPLUS connection and execute something:
25
+
26
+ sp = Sqlplus.new('sqlplus')
27
+ sp.exec('select table_name from all_tables;')
28
+ print sp.read_output(32767) while sp.busy?
29
+ sp.terminate
30
+
31
+ To get the structure of a PLSQL code:
32
+
33
+ text = File.open('spec/sql/test.pkg', 'rb') { |file| file.read }
34
+ structure = Parser::PlsqlStructure.new(text)
35
+ structure.print_tree
36
+
37
+ See the "YARD" generated documentation for additional info.
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require_relative 'spec/spec_helper.rb'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new
10
+
11
+ desc "Generate documentation"
12
+ YARD::Rake::YardocTask.new
13
+
14
+ desc 'Builds the gem'
15
+ task :build do
16
+ sh "gem build vorax.gemspec"
17
+ end
18
+
19
+ desc 'Builds and installs the gem'
20
+ task :install => :build do
21
+ sh "gem install vorax-#{Vorax::VERSION}"
22
+ end
23
+
24
+ desc 'Tags version, pushes to remote, and pushes gem'
25
+ task :release => :build do
26
+ sh "git tag v#{Vorax::VERSION}"
27
+ sh "git push origin master"
28
+ sh "git push origin v#{Vorax::VERSION}"
29
+ sh "gem push vorax-#{Vorax::VERSION}.gem"
30
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'nokogiri'
4
+
5
+ module Vorax
6
+
7
+ # A funnel is an abstraction for the idea of formatting text. You
8
+ # write some text into the funnel and then you read it formatted. It's
9
+ # up to every subclass to implement the transformation logic.
10
+ class BaseFunnel
11
+
12
+ # Write the text to be converted into the funnel. By default, this
13
+ # method does nothing, since it is intended to be overwritten by
14
+ # subclasses.
15
+ #
16
+ # @param text [String] the text to be formatted.
17
+ def write(text)
18
+ # do nothing: subclass responsability
19
+ end
20
+
21
+ # Read the converted text. By default, this method does nothing. It is
22
+ # intended to be overridden in subclasses.
23
+ def read
24
+ # do nothing: subclass responsability
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,120 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+ module Output
5
+
6
+ # An abstraction for converting an XML/HTML to something else. The idea is that
7
+ # sqlplus may be configured to spit HTML output so that we can easily parse
8
+ # it and transform it to something else. The io attribute is the pipe used to
9
+ # push HTML content and fetch the formatted text.
10
+ class HTMLConvertor < Nokogiri::XML::SAX::Document
11
+
12
+ attr_reader :io, :text
13
+
14
+ # Creates a new convertor.
15
+ def initialize()
16
+ super()
17
+ @io = StringIO.new("")
18
+ @tags_chain = []
19
+ @text = ''
20
+ end
21
+
22
+ # This callback must be defined in every subclass. It is
23
+ # invoked as soon as a new tag is detected.
24
+ #
25
+ # @param name [String] the detected tag name
26
+ # @param attrs [Array] an array with all defined attributes for
27
+ # the detected tag.
28
+ def start_hook name, attrs = []
29
+ # this should be implemented in sub-classes
30
+ end
31
+
32
+ # This callback must be defined in every subclass. It is
33
+ # invoked as soon as a end tag definition is detected.
34
+ #
35
+ # @param name [String] the name of the endding tag
36
+ def end_hook name
37
+ # this should be implemented in sub-classes
38
+ end
39
+
40
+ # Indent every line from the provided text with the given pad
41
+ # size.
42
+ #
43
+ # @param text [String] the multiline text to be indented
44
+ # @param pad_size [int] the padding size
45
+ #
46
+ # @return the indented text
47
+ def self.ml_indent(text, pad_size)
48
+ text.gsub(/\r?\n/, "\n#{' '*pad_size}")
49
+ end
50
+
51
+ # Get the N-th line from the provided multiline text.
52
+ #
53
+ # @param text [String] the multiline text
54
+ # @param lineno [int] the line number
55
+ # @return the N-th line
56
+ def self.ml_segment(text, lineno)
57
+ text.split(/\r?\n/)[lineno] || ""
58
+ end
59
+
60
+ # Get the size of the largest line from a multiline text.
61
+ #
62
+ # @param text [String] the multiline text.
63
+ # @return the size of the largest line
64
+ def self.ml_width(text)
65
+ text.split(/\r?\n/).inject(0) { |counter, elem| [elem.length, counter].max }
66
+ end
67
+
68
+ # Get the number of lines from a multiline text.
69
+ #
70
+ # @param text [String] the multiline text.
71
+ # @return the number of lines
72
+ def self.ml_count(text)
73
+ text.scan(/\r?\n/).size
74
+ end
75
+
76
+ # Get the HTML tag to be used to ping the Nokogiri parser.
77
+ # @return the ping tag
78
+ def self.ping_tag
79
+ "s"
80
+ end
81
+
82
+ # Whenever or not the convertor should spit plain text which is directly
83
+ # under <body> tag.
84
+ def should_spit_text?
85
+ @tags_chain.size > 0 &&
86
+ #(not @text.empty?) &&
87
+ ["body", "p", "br", HTMLConvertor.ping_tag].include?(@tags_chain.last[:name])
88
+ end
89
+
90
+ # The text accumulated within the current tag. This is automatically
91
+ # called from the Nokogiri engine.
92
+ def characters(str)
93
+ @text << str if str && !str.empty?
94
+ end
95
+
96
+ private
97
+
98
+ def start_element name, attrs = []
99
+ @tags_chain.push({:name => name.downcase, :attrs => attrs})
100
+ if should_spit_text?
101
+ chunk = @text
102
+ chunk.strip! unless @tags_chain.last[:name] == 'pre'
103
+ @io << chunk unless chunk.empty?
104
+ @text.clear
105
+ end
106
+ start_hook(name, attrs)
107
+ end
108
+
109
+ def end_element name
110
+ if name == 'pre'
111
+ @io << @text
112
+ end
113
+ end_hook(name.downcase)
114
+ @tags_chain.pop
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,79 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'nokogiri'
4
+
5
+ module Vorax
6
+
7
+ # A namespace for everything related to the sqlplus output processing.
8
+ module Output
9
+
10
+ # A special type of funnel to work with HTML convertors.
11
+ class HTMLFunnel < BaseFunnel
12
+
13
+ # Creates a new HTML funnel.
14
+ #
15
+ # @param convertor [HTMLConvertor] the convertor to be used
16
+ # to transform the HTML content.
17
+ def initialize(convertor)
18
+ @tail = ''
19
+ @parser = Nokogiri::HTML::SAX::PushParser.new(convertor)
20
+ end
21
+
22
+ # Put the html content into the funnel.
23
+ #
24
+ # @param text [String] a chunk of HTML text
25
+ def write(text)
26
+ if text && !text.empty?
27
+ @parser.write text
28
+ if @parser.document.should_spit_text?
29
+ @tail << text
30
+ # just to be sure we don't have stale text after
31
+ # the last end tag
32
+ ping
33
+ end
34
+ end
35
+ end
36
+
37
+ # Get the formatted text from the funnel, as it is transformed
38
+ # by the buddy convertor.
39
+ #
40
+ # @return the formatted text
41
+ def read
42
+ @parser.document.io.rewind
43
+ chunk = @parser.document.io.read
44
+ @parser.document.io.truncate(0)
45
+ @parser.document.io.seek(0)
46
+ return chunk
47
+ end
48
+
49
+ private
50
+
51
+ # This is a workaround. The Nokogiri pull parser doesn't spit anything
52
+ # if there's no endding tag. For example, if "<p>Text" is given, "Text" will not
53
+ # be spit because the end "</p>" is missing. In sqlplus this is common
54
+ # especially for "prompt" or "accept" commands which spit output without
55
+ # enclosing the text in any tags. The main problem is ACCEPT, where VoraX
56
+ # will wait for users input, but the prompt will not be shown which will
57
+ # make the poor user confused. The solution is to force a random tag into
58
+ # the HTML input stream so that the parser to move along.
59
+ def ping
60
+ unless @tail.empty?
61
+ # be carefull not to ping into incomplete tags
62
+ last_open_tag_position = (@tail.rindex('<') || -1)
63
+ last_close_tag_position = (@tail.rindex('>') || -1)
64
+ last_open_entity_position = (@tail.rindex('&') || -1)
65
+ last_close_entity_position = (@tail.rindex(';') || -1)
66
+ hwm = [last_close_tag_position, last_close_entity_position].max
67
+ @tail = @tail[hwm + 1 .. -1] if hwm >= 0
68
+ if last_close_tag_position >= last_open_tag_position &&
69
+ last_close_entity_position >= last_open_entity_position
70
+ @parser << "<#{HTMLConvertor.ping_tag}/>"
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+ module Output
5
+
6
+ # A convertor used to compress every page returned by sqlplus when executing
7
+ # a query.
8
+ class PagezipConvertor < ZipConvertor
9
+
10
+ # @see ZipConvertor.should_spit?
11
+ def should_spit?(end_tag)
12
+ ("table" == end_tag) ||
13
+ ("th" == end_tag && rows.size > 0)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+
5
+ module Output
6
+
7
+ # A convertor used to compress every HTML TABLE from the
8
+ # sqlplus output.
9
+ class TablezipConvertor < ZipConvertor
10
+
11
+ # see ZipConvertor.should_spit?
12
+ def should_spit?(current_tag)
13
+ current_tag == "table"
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+ module Vorax
3
+ module Output
4
+
5
+ class VerticalConvertor < HTMLConvertor
6
+
7
+ def initialize()
8
+ super()
9
+ reset_props()
10
+ end
11
+
12
+ def end_element name
13
+ super(name)
14
+ @io << "\n" if ['br', 'p'].include?(name)
15
+ if "th" == name and @collect_columns
16
+ column = @text.strip
17
+ @columns << column
18
+ @th_length = [column.length, @th_length].max
19
+ end
20
+ @values << @text.strip if "td" == name
21
+ if "tr" == name && (not @values.empty?)
22
+ @columns = Array.new(@values.size, "") if @columns.empty?
23
+ @columns.each_with_index do |column, i|
24
+ value = HTMLConvertor.ml_indent(@values[i], @th_length + 3)
25
+ @io.print("#{column.ljust(@th_length)} : #{value}\n")
26
+ end
27
+ @values.clear
28
+ @collect_columns = false
29
+ print_row_separator
30
+ end
31
+ @text.clear unless HTMLConvertor.ping_tag == name
32
+ reset_props() if name == "table"
33
+ end
34
+
35
+ def print_row_separator
36
+ @separator ||= '-' * 60
37
+ @io.print("#{'-' * 60}\n")
38
+ end
39
+
40
+ private
41
+
42
+ def reset_props
43
+ @columns = []
44
+ @values = []
45
+ @th_length = 0
46
+ @text = ""
47
+ @collect_columns = true
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+
5
+ module Output
6
+
7
+ # A special type of HTML convertor used for compressing HTML tables. This is an
8
+ # abstract class and every convertor which needs compressing utilities must
9
+ # inherit from this class.
10
+ class ZipConvertor < HTMLConvertor
11
+
12
+ attr_reader :rows, :last_close_tag, :last_open_tag
13
+
14
+ # Create a new ZipConvertor.
15
+ def initialize()
16
+ super()
17
+ reset_props()
18
+ end
19
+
20
+ # @see HTMLConvertor.start_hook
21
+ def start_hook name, attrs = []
22
+ @last_open_tag[:name] = name
23
+ @last_open_tag[:attrs] = attrs
24
+ end
25
+
26
+ # @see HTMLConvertor.end_hook
27
+ def end_hook name
28
+ @io << "\n" if ['br', 'p'].include?(name)
29
+ @record << {:text => text.strip,
30
+ :is_column => ("th" == name),
31
+ :align => get_align(@last_open_tag)} if ["td", "th"].include?(name)
32
+ if "tr" == name
33
+ @rows << @record.dup
34
+ @record.clear
35
+ end
36
+ if should_spit?(name)
37
+ spit
38
+ @rows.clear
39
+ end
40
+ text.clear unless HTMLConvertor.ping_tag == name
41
+ @last_close_tag.clear
42
+ @last_close_tag << name
43
+ reset_props() if "table" == name
44
+ end
45
+
46
+ # A method which tells if the accumulated compressed text should
47
+ # be spit or not. This is the only method which must be implemented
48
+ # into subclasses.
49
+ #
50
+ # @param name [String] the end HTML tag
51
+ def should_spit?(name)
52
+ raise RuntimeError.new "Implement me"
53
+ end
54
+
55
+ private
56
+
57
+ def columns_layout
58
+ layout = []
59
+ (0..@rows.first.size-1).each do |i|
60
+ width = @rows.inject(0) do |result, element|
61
+ [result, HTMLConvertor.ml_width(element[i][:text])].max
62
+ end
63
+ first_data_row = @rows.detect { |row| row[i][:is_column] == false }
64
+ align = first_data_row[i][:align] if first_data_row
65
+ layout << {:width => width, :align => (align && align == "right" ? "rjust" : "ljust")}
66
+ end
67
+ layout
68
+ end
69
+
70
+ def separator(layout)
71
+ # spit separator
72
+ layout.each_with_index do |column, i|
73
+ @io << "-" * column[:width]
74
+ @io << (i == layout.size - 1 ? "\n" : " ")
75
+ end
76
+ end
77
+
78
+ def spit
79
+ layout = columns_layout
80
+ # spit records
81
+ @rows.each do |record|
82
+ max_height = record.inject(0) { |result, element| [result, HTMLConvertor.ml_count(element[:text])].max }
83
+ (0..max_height).each do |j|
84
+ layout.length.times do |i|
85
+ @io << "\n" if record[i][:is_column] && i == 0 && @first_spit == false
86
+ @first_spit = false
87
+ @io << HTMLConvertor.ml_segment(record[i][:text], j).send(layout[i][:align], layout[i][:width])
88
+ @io << (i == layout.size - 1 ? "\n" : " ")
89
+ separator(layout) if i == layout.size - 1 && record[i][:is_column]
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def get_align(tag)
96
+ attrs = tag[:attrs]
97
+ if attrs
98
+ align_pair = attrs.find { |pair| pair[0] == "align" }
99
+ return align_pair[1] if align_pair
100
+ end
101
+ end
102
+
103
+ def reset_props
104
+ @record = []
105
+ @rows = []
106
+ @last_open_tag = {:name => '', :attrs => []}
107
+ @last_close_tag = ''
108
+ @first_spit = true
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+
3
+ module Vorax
4
+
5
+ module Parser
6
+
7
+ # Given a statement and a position we want to see if on that position we should
8
+ # provide code completion for an argument
9
+ class Argument
10
+
11
+ def initialize(statement)
12
+ @stmt = statement
13
+ # interesting points to search within statement
14
+ @marks = [BEGIN_PLSQL_SPECIAL_QUOTING, BEGIN_QUOTING,
15
+ CLOSE_PARAN, OPEN_PARAN, ANY]
16
+ # level referes to open/close brackets
17
+ @level = 0
18
+ end
19
+
20
+ # the function/procedure which owns the argument at the
21
+ # provided position
22
+ def belongs_to(position = @stmt.length)
23
+ @belongs_to = ''
24
+ stmt = @stmt[(0...position)]
25
+ # remove all comments
26
+ stmt = Parser::Comment.new.remove_all(stmt)
27
+ stmt.reverse!
28
+ @ss = StringScanner.new(stmt)
29
+ consume
30
+ return @belongs_to
31
+ end
32
+
33
+ private
34
+
35
+ def consume
36
+ while !@ss.eos?
37
+ @ss.skip_until(/#{@marks.join('|')}/im)
38
+
39
+ process_plsql_quoting
40
+ process_double_quotes
41
+ process_single_quotes
42
+ process_close_paran
43
+ process_open_paran
44
+
45
+ p @ss.rest
46
+ gets
47
+ end
48
+ end
49
+
50
+ def process_plsql_quoting
51
+ # pay attention, is reveresed
52
+ if @ss.matched =~ /'\]/
53
+ @ss.skip_until(/\['q/)
54
+ p 'a intrat'
55
+ end
56
+ #@ss.skip_until(/\{'q/) if @ss.matched =~ /'\}/
57
+ #@ss.skip_until(/\('q/) if @ss.matched =~ /'\)/
58
+ #@ss.skip_until(/\>'q/) if @ss.matched =~ /'\</
59
+ end
60
+
61
+ def process_double_quotes
62
+ @ss.skip_until(/"/) if @ss.matched == '"'
63
+ end
64
+
65
+ def process_single_quotes
66
+ if @ss.matched == "'"
67
+ begin
68
+ @ss.skip_until(/\'+/)
69
+ end while (@ss.matched != "'" && !@ss.eos?)
70
+ end
71
+ end
72
+
73
+ def process_close_paran
74
+ if @ss.matched =~ /#{CLOSE_PARAN}/
75
+ @level += 1
76
+ end
77
+ end
78
+
79
+ def process_open_paran
80
+ if @ss.matched =~ /#{OPEN_PARAN}/
81
+ if @level == 0
82
+ extract_module
83
+ else
84
+ @level -= 1
85
+ @ss.terminate if @level < 0 #give up, it's an invalid statement
86
+ end
87
+ end
88
+ end
89
+
90
+ def extract_module
91
+ while !@ss.eos?
92
+ # consume leading whitspaces
93
+ @ss.scan(/\s*/)
94
+ if @ss.check(/"/) == '"'
95
+ # we have a quoted identifier
96
+ @belongs_to << @ss.scan(/"/)
97
+ @belongs_to << @ss.scan_until(/"/)
98
+ else
99
+ # unquoted identifier
100
+ @belongs_to << @ss.scan(/\S+/)
101
+ end
102
+ # consume trailing whitespaces
103
+ @ss.scan(/\s*/)
104
+
105
+ # might be a dblink
106
+ if @ss.check(/@/) == '@'
107
+ @belongs_to << @ss.scan(/@/)
108
+ next
109
+ end
110
+
111
+ # might be package or a owner
112
+ if @ss.check(/\./) == '.'
113
+ @belongs_to << @ss.scan(/\./)
114
+ next
115
+ end
116
+ @ss.terminate
117
+ end
118
+ @belongs_to.reverse!
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+
125
+ end