vorax 0.1.0pre

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.
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