vorax 0.1.0pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/LICENSE.txt +22 -0
- data/README.md +45 -0
- data/Rakefile +30 -0
- data/lib/vorax/base_funnel.rb +30 -0
- data/lib/vorax/output/html_convertor.rb +120 -0
- data/lib/vorax/output/html_funnel.rb +79 -0
- data/lib/vorax/output/pagezip_convertor.rb +20 -0
- data/lib/vorax/output/tablezip_convertor.rb +22 -0
- data/lib/vorax/output/vertical_convertor.rb +53 -0
- data/lib/vorax/output/zip_convertor.rb +117 -0
- data/lib/vorax/parser/argument.rb~ +125 -0
- data/lib/vorax/parser/body_split.rb +168 -0
- data/lib/vorax/parser/conn_string.rb +104 -0
- data/lib/vorax/parser/grammars/alias.rb +912 -0
- data/lib/vorax/parser/grammars/alias.rl +146 -0
- data/lib/vorax/parser/grammars/column.rb +454 -0
- data/lib/vorax/parser/grammars/column.rl +64 -0
- data/lib/vorax/parser/grammars/common.rl +98 -0
- data/lib/vorax/parser/grammars/package_spec.rb +1186 -0
- data/lib/vorax/parser/grammars/package_spec.rl +78 -0
- data/lib/vorax/parser/grammars/plsql_def.rb +469 -0
- data/lib/vorax/parser/grammars/plsql_def.rl +59 -0
- data/lib/vorax/parser/grammars/statement.rb +925 -0
- data/lib/vorax/parser/grammars/statement.rl +83 -0
- data/lib/vorax/parser/parser.rb +320 -0
- data/lib/vorax/parser/plsql_structure.rb +158 -0
- data/lib/vorax/parser/plsql_walker.rb +143 -0
- data/lib/vorax/parser/statement_inspector.rb~ +52 -0
- data/lib/vorax/parser/stmt_inspector.rb +78 -0
- data/lib/vorax/parser/target_ref.rb +110 -0
- data/lib/vorax/sqlplus.rb +281 -0
- data/lib/vorax/version.rb +7 -0
- data/lib/vorax/vorax_io.rb +70 -0
- data/lib/vorax.rb +60 -0
- data/spec/column_spec.rb +40 -0
- data/spec/conn_string_spec.rb +53 -0
- data/spec/package_spec_spec.rb +48 -0
- data/spec/pagezip_spec.rb +153 -0
- data/spec/parser_spec.rb +299 -0
- data/spec/plsql_structure_spec.rb +44 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/sql/create_objects.sql +69 -0
- data/spec/sql/dbms_crypto.spc +339 -0
- data/spec/sql/dbms_crypto.~spc +339 -0
- data/spec/sql/dbms_stats.spc +4097 -0
- data/spec/sql/drop_user.sql +10 -0
- data/spec/sql/muci.spc +24 -0
- data/spec/sql/setup_user.sql +22 -0
- data/spec/sql/test.pkg +67 -0
- data/spec/sqlplus_spec.rb +52 -0
- data/spec/stmt_inspector_spec.rb +84 -0
- data/spec/tablezip_spec.rb +111 -0
- data/spec/vertical_spec.rb +150 -0
- data/vorax.gemspec +21 -0
- metadata +139 -0
data/.gitignore
ADDED
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
|