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