vorax 0.4.2 → 5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/README.md +4 -29
  2. data/vorax.gemspec +3 -11
  3. metadata +4 -92
  4. data/.rspec +0 -1
  5. data/Rakefile +0 -30
  6. data/lib/vorax.rb +0 -60
  7. data/lib/vorax/base_funnel.rb +0 -30
  8. data/lib/vorax/output/html_convertor.rb +0 -120
  9. data/lib/vorax/output/html_funnel.rb +0 -79
  10. data/lib/vorax/output/pagezip_convertor.rb +0 -20
  11. data/lib/vorax/output/tablezip_convertor.rb +0 -22
  12. data/lib/vorax/output/vertical_convertor.rb +0 -53
  13. data/lib/vorax/output/zip_convertor.rb +0 -117
  14. data/lib/vorax/parser/argument.rb~ +0 -125
  15. data/lib/vorax/parser/conn_string.rb +0 -104
  16. data/lib/vorax/parser/grammars/alias.rb +0 -904
  17. data/lib/vorax/parser/grammars/alias.rl +0 -138
  18. data/lib/vorax/parser/grammars/column.rb +0 -454
  19. data/lib/vorax/parser/grammars/column.rl +0 -64
  20. data/lib/vorax/parser/grammars/common.rl +0 -107
  21. data/lib/vorax/parser/grammars/declare.rb +0 -9606
  22. data/lib/vorax/parser/grammars/declare.rl +0 -160
  23. data/lib/vorax/parser/grammars/for_block.rb +0 -440
  24. data/lib/vorax/parser/grammars/for_block.rl +0 -73
  25. data/lib/vorax/parser/grammars/plsql_def.rb +0 -539
  26. data/lib/vorax/parser/grammars/plsql_def.rl +0 -73
  27. data/lib/vorax/parser/grammars/statement.rb +0 -925
  28. data/lib/vorax/parser/grammars/statement.rl +0 -83
  29. data/lib/vorax/parser/parser.rb +0 -344
  30. data/lib/vorax/parser/plsql_structure.rb +0 -222
  31. data/lib/vorax/parser/plsql_walker.rb +0 -143
  32. data/lib/vorax/parser/statement_inspector.rb~ +0 -52
  33. data/lib/vorax/parser/stmt_inspector.rb +0 -78
  34. data/lib/vorax/parser/target_ref.rb +0 -110
  35. data/lib/vorax/sqlplus.rb +0 -273
  36. data/lib/vorax/version.rb +0 -7
  37. data/lib/vorax/vorax_io.rb +0 -70
  38. data/spec/column_spec.rb +0 -40
  39. data/spec/conn_string_spec.rb +0 -53
  40. data/spec/declare_spec.rb +0 -281
  41. data/spec/pagezip_spec.rb +0 -153
  42. data/spec/parser_spec.rb +0 -352
  43. data/spec/plsql_structure_spec.rb +0 -68
  44. data/spec/spec_helper.rb +0 -13
  45. data/spec/sql/create_objects.sql +0 -69
  46. data/spec/sql/dbms_crypto.spc +0 -339
  47. data/spec/sql/dbms_stats.spc +0 -4097
  48. data/spec/sql/drop_user.sql +0 -10
  49. data/spec/sql/muci.spc +0 -26
  50. data/spec/sql/setup_user.sql +0 -22
  51. data/spec/sql/test.fnc +0 -12
  52. data/spec/sql/test.pkg +0 -83
  53. data/spec/sqlplus_spec.rb +0 -52
  54. data/spec/stmt_inspector_spec.rb +0 -96
  55. data/spec/tablezip_spec.rb +0 -111
  56. data/spec/vertical_spec.rb +0 -150
@@ -1,143 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'strscan'
4
-
5
- module Vorax
6
-
7
- module Parser
8
-
9
- # A PLSQL string scanner which is looking for interesting points within
10
- # the provided source code. This is used instead of a fully fledged parser
11
- # for speed considerations.
12
- class PLSQLWalker
13
-
14
- BEGIN_ML_COMMENT = /\/\*/ unless defined?(BEGIN_ML_COMMENT)
15
- END_ML_COMMENT = /\*\// unless defined?(END_ML_COMMENT)
16
- BEGIN_SL_COMMENT = /--/ unless defined?(BEGIN_SL_COMMENT)
17
- END_SL_COMMENT = Parser::END_LINE unless defined?(END_SL_COMMENT)
18
- BEGIN_PLSQL_SPECIAL_QUOTING = /q'[!\[{(<]/ unless defined?(BEGIN_PLSQL_SPECIAL_QUOTING)
19
- BEGIN_DOUBLE_QUOTING = /[\"]/ unless defined?(BEGIN_DOUBLE_QUOTING)
20
- BEGIN_SINGLE_QUOTING = /[']/ unless defined?(BEGIN_SINGLE_QUOTING)
21
-
22
- # Create a new parse walker.
23
- #
24
- # @param text [String] the text to be walked/parsed
25
- # @param create_default_spots [boolean] whenever or not to create default
26
- # detection spots: multiline comments, singleline comments and quoted literals
27
- def initialize(text, create_default_spots=true)
28
- @text = text
29
- @matchers = []
30
- @ss = StringScanner.new(text)
31
- create_default_spots() if create_default_spots
32
- end
33
-
34
- # Returns the string scanner used for walking the string.
35
- #
36
- # @return [StringScanner] the string scanner
37
- def scanner
38
- @ss
39
- end
40
-
41
- # Register a new detection spot. The order of specifying these spots is important.
42
- #
43
- # @param pattern [Regexp] the spot regular expression
44
- # @param callback [Procedure] what to do when this spot is detected. The registered
45
- # block is always called with the string scanner object. Please do not use "return"
46
- # to exit from the defined block.
47
- def register_spot(pattern, &callback)
48
- @matchers << {:pattern => pattern, :callback => callback}
49
- end
50
-
51
- # Walk the text and trigger the registered callbacks. It returns the text which was
52
- # successfully walked.
53
- def walk
54
- global_matcher = Regexp.new(@matchers.map { |e| e[:pattern].to_s }.join('|'),
55
- Regexp::IGNORECASE)
56
- while !@ss.eos?
57
- if match = @ss.scan_until(global_matcher)
58
- current_pos = @ss.pos
59
- @matchers.each do |matcher|
60
- if @ss.matched =~ matcher[:pattern]
61
- matcher[:callback].call(@ss)
62
- end
63
- end
64
- else
65
- @ss.terminate
66
- end
67
- end
68
- end
69
-
70
- # Register a spot to walk a multiline comment.
71
- def register_default_ml_comment_spot
72
- register_spot(BEGIN_ML_COMMENT) do |scanner|
73
- scanner.scan_until(END_ML_COMMENT)
74
- end
75
- end
76
-
77
- # Register a spot to walk a single line comment.
78
- def register_default_sl_comment_spot
79
- register_spot(BEGIN_SL_COMMENT) do |scanner|
80
- scanner.scan_until(END_SL_COMMENT)
81
- end
82
- end
83
-
84
- # Register a spot to walk a plsql special quoting liternal.
85
- def register_default_plsql_quoting_spot
86
- register_spot(BEGIN_PLSQL_SPECIAL_QUOTING) do |scanner|
87
- scanner.scan_until(END_SL_COMMENT)
88
- if scanner.matched =~ /q'\[/
89
- scanner.scan_until(/\]'/)
90
- elsif scanner.matched =~ /q'[{]/
91
- scanner.scan_until(/[}]'/)
92
- elsif scanner.matched =~ /q'[(]/
93
- scanner.scan_until(/[)]'/)
94
- elsif scanner.matched =~ /q'[<]/
95
- scanner.scan_until(/[>]'/)
96
- end
97
- end
98
- end
99
-
100
- # Register a spot to walk a double quoted literal.
101
- def register_default_double_quoting_spot
102
- register_spot(BEGIN_DOUBLE_QUOTING) do |scanner|
103
- scanner.scan_until(/"/)
104
- end
105
- end
106
-
107
- # Register a spot to walk a single quoted literal.
108
- def register_default_single_quoting_spot
109
- register_spot(BEGIN_SINGLE_QUOTING) do |scanner|
110
- collector = ''
111
- begin
112
- if match = scanner.scan_until(/\'+/)
113
- collector << match
114
- end
115
- end while (scanner.matched != "'" && !scanner.eos?)
116
- collector
117
- end
118
- end
119
-
120
- private
121
-
122
- def create_default_spots
123
- # define a multiline comment spot
124
- register_default_ml_comment_spot()
125
-
126
- # define a single line comment spot
127
- register_default_sl_comment_spot()
128
-
129
- # define special PLSQL quotes spot
130
- register_default_plsql_quoting_spot()
131
-
132
- # register a double quoted string spot
133
- register_default_double_quoting_spot()
134
-
135
- # register a single quoted string spot
136
- register_default_single_quoting_spot()
137
- end
138
-
139
- end
140
-
141
- end
142
-
143
- end
@@ -1,52 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Vorax
4
-
5
- module Parser
6
-
7
- class StatementInspector
8
-
9
- def initialize(statement)
10
- @statement = statement
11
- end
12
-
13
- def type
14
- @type ||= Statement.new.type(statement)
15
- end
16
-
17
- def data_source
18
- descriptor.refs
19
- end
20
-
21
- def recursive_data_source(refs, collect, position)
22
- inner = refs.find { |r| r.respond_to?(:range) && r.range.include?(position) }
23
- collect.unshift(refs).flatten!
24
- if inner
25
- desc = Parser::Alias.new
26
- desc.walk(inner.base)
27
- data_source(desc.refs, collect, position - inner_expr.range.first)
28
- else
29
- return collect
30
- end
31
- end
32
-
33
- def query_fields
34
- @query_fields ||= Column.new.walk(descriptor.query_fields)
35
- end
36
-
37
- private
38
-
39
- def descriptor
40
- unless @desc
41
- @desc = Parser::Alias.new
42
- @desc.walk(@statement)
43
- end
44
- @desc
45
- end
46
-
47
- end
48
-
49
- end
50
-
51
- end
52
-
@@ -1,78 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Vorax
4
-
5
- module Parser
6
-
7
- # A class used to gather metadata information for an SQL statement. This is needed
8
- # especially for implementing VoraX code completion.
9
- class StmtInspector
10
-
11
- # Creates a new statement inspector.
12
- #
13
- # @param statement [String] the statement to be inspected
14
- def initialize(statement)
15
- @statement = statement
16
- end
17
-
18
- # Get the type of the statement
19
- #
20
- # (see Parser.statement_type)
21
- def type
22
- @type ||= Parser.statement_type(statement)
23
- end
24
-
25
- # Get all tableref/exprref for this statement, taking into account
26
- # the current position within that statement.
27
- #
28
- # @param position [int] the current position.
29
- # @return an array of TableRef/ExprRef objects
30
- def data_source(position=0)
31
- recursive_data_source(descriptor.refs, position)
32
- end
33
-
34
- # If it's a query, the corresponding columns are returned.
35
- #
36
- # @return an array of columns
37
- def query_fields
38
- @query_fields ||= Column.new.walk(descriptor.query_fields)
39
- end
40
-
41
- # Find the provided alias for the statement, taking into account the
42
- # current position.
43
- #
44
- # @param name [String] the alias name
45
- # @param position [int] the current position
46
- def find_alias(name, position=0)
47
- data_source(position).find { |r| r.pointer && r.pointer.upcase == name.upcase }
48
- end
49
-
50
- private
51
-
52
- def descriptor
53
- unless @desc
54
- @desc = Parser::Alias.new
55
- @desc.walk(@statement)
56
- end
57
- @desc
58
- end
59
-
60
- def recursive_data_source(refs, position, collect = [])
61
- inner = refs.find { |r| r.respond_to?(:range) && r.range.include?(position) }
62
- collect.unshift(refs).flatten!
63
- if inner
64
- desc = Parser::Alias.new
65
- desc.walk(inner.base)
66
- recursive_data_source(desc.refs, position - inner.range.first, collect)
67
- else
68
- return collect
69
- end
70
- end
71
-
72
-
73
- end
74
-
75
- end
76
-
77
- end
78
-
@@ -1,110 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module Vorax
4
-
5
- module Parser
6
-
7
- # An abstraction for a table reference within an SQL statement. This class is used
8
- # by the StmtInspector to model the FROM clause of a query or the target table of
9
- # an INSERT or UPDATE.
10
- class TableRef
11
-
12
- attr_reader :base, :pointer
13
-
14
- # Creates a new TableRef object.
15
- #
16
- # @param base [String] is the actual table/view name from the SQL statement.
17
- # @param pointer [String] is the alias of the table, if there's any
18
- def initialize(base, pointer = nil)
19
- @base = base
20
- @pointer = pointer
21
- end
22
-
23
- # Return the columns of this table ref.
24
- #
25
- # @return the columns of the table, in an unexpanded form (e.g. tbl.*)
26
- def columns
27
- ["#{@base}.*"]
28
- end
29
-
30
- # Compare too table ref objects.
31
- #
32
- # param obj [TableRef] the other tableref used for comparison.
33
- def ==(obj)
34
- self.base == obj.base && self.pointer == obj.pointer
35
- end
36
-
37
- end
38
-
39
- # This class is used to model a reference within a SQL statement, given as an
40
- # expression (e.g. "select * from (select * from dual);")
41
- class ExprRef
42
-
43
- attr_reader :base, :range, :pointer
44
-
45
- # Creates a new ExprRef object.
46
- #
47
- # @param base [String] the actual expresion
48
- # @param range [Range] the bounderies of this expression within the parent statement
49
- # @param pointer [String] the alias of the expresion, if there is any
50
- def initialize(base, range, pointer = nil)
51
- @base = base
52
- @range = range
53
- @pointer = pointer
54
- end
55
-
56
- # Get all columns for this expression.
57
- #
58
- # @return all columns of the query expression
59
- def columns
60
- collect = []
61
- recursive_columns(@base, collect)
62
- end
63
-
64
- # Compare too table ref objects.
65
- #
66
- # param obj [TableRef] the other tableref used for comparison.
67
- def ==(obj)
68
- self.base == obj.base && self.range == obj.range && self.pointer == obj.pointer
69
- end
70
-
71
- private
72
-
73
- def recursive_columns(statement, collect)
74
- inspector = StmtInspector.new(statement)
75
- columns_data = inspector.query_fields
76
- columns_data.each do |column|
77
- if column =~ /([a-z0-9#$\_]+\.)?\*/i
78
- #might be an alias
79
- alias_name = column[/[a-z0-9#$\_]+/i]
80
- ds = []
81
- if alias_name
82
- src = inspector.data_source.find do |r|
83
- (r.pointer && r.pointer.upcase == alias_name.upcase) || (r.base.upcase == alias_name.upcase)
84
- end
85
- ds << src if src
86
- elsif column == '*'
87
- ds = inspector.data_source
88
- end
89
- if ds.size > 0
90
- ds.each do |source|
91
- if source.respond_to?(:range)
92
- recursive_columns(source.base, collect)
93
- else
94
- collect << "#{source.base}.*"
95
- end
96
- end
97
- end
98
- else
99
- collect << column
100
- end
101
- end
102
- return collect
103
- end
104
-
105
- end
106
-
107
- end
108
-
109
- end
110
-
@@ -1,273 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module Vorax
4
-
5
- # Provides integration with Oracle SqlPlus CLI tool.
6
- class Sqlplus
7
-
8
- attr_reader :bin_file, :default_funnel_name, :process
9
-
10
- # Creates a new sqlplus instance.
11
- #
12
- # @param bin_file [String] the path to the SqlPlus executable. By
13
- # default is "sqlplus", which requires that the executable to be
14
- # in $PATH.
15
- def initialize(bin_file = "sqlplus")
16
- @bin_file = bin_file
17
- @busy = false
18
- @start_marker, @end_marker, @cancel_marker = [2.chr, 3.chr, 4.chr]
19
- @process = ChildProcess.build(@bin_file, "/nolog")
20
- # On Unix we may abort the currently executing query by sending a
21
- # INT signal to the Sqlplus process, but we need access to the
22
- # send_term private method.
23
- class << @process; public :send_signal; end if ChildProcess.unix?
24
- @process.duplex = true
25
- @process.detach = true
26
- @process.io.inherit!
27
- @io_read, @io_write = VoraxIO.pipe
28
- @process.io.stdout = @io_write
29
- @process.start
30
- @process.io.stdin.sync = true
31
- @current_funnel = nil
32
- @default_convertor_name = nil
33
- @registered_convertors = {:vertical => Output::VerticalConvertor,
34
- :pagezip => Output::PagezipConvertor,
35
- :tablezip => Output::TablezipConvertor}
36
- # warm up
37
- sleep 0.2
38
- # set the blockterm as the end_marker. The blockterm should
39
- # not be touch by the Vorax user, otherwise nasty things
40
- # may happen. This is also a workaround to mark the end of
41
- # output when the "echo" setting of sqlplus is "on". See the
42
- # implementation of pack().
43
- send_text("set blockterm \"#@end_marker\"\n")
44
- end
45
-
46
- # Set the default convertor for the output returned by sqlplus.
47
- #
48
- # @param convertor_name [Symbol] the default funnel name. The
49
- # valid values are: :vertical, :pagezip and :tablezip
50
- def default_convertor=(convertor_name)
51
- Vorax.debug("default_convertor=#{convertor_name.inspect}")
52
- @default_convertor_name = convertor_name
53
- end
54
-
55
- # Register a new convertor for the sqlplus output.
56
- #
57
- # @param convertor_name [Symbol] the name of the convertor (key)
58
- # @param convertor_class [Class] the class which implements the
59
- # convertor. It must be a subclass of BaseConvertor.
60
- def register_convertor(convertor_name, convertor_class)
61
- @registered_convertors[convertor_name] = convertor_class
62
- end
63
-
64
- # Execute an sqlplus command.
65
- #
66
- # @param command [String] the command to be executed.
67
- # @param params [Hash] additional parameters. You may use
68
- # the following options:
69
- # :prep => a string with commands to be executed just before
70
- # running the provided command. For example, you may
71
- # choose to set some sqlplus options.
72
- # :post => a string with commands to be executed after the
73
- # provided command was run. Here it's a good place
74
- # to put commands that restores some options affected
75
- # by the executed command.
76
- # :convertor => the convertor used to convert the output received
77
- # from Sqlplus. By default it's the convertor set with
78
- # "default_convertor=" method.
79
- # :pack_file => the file name into which the command(s) to be
80
- # executed are wrapped into and then sent for
81
- # execution to sqlplus using '@<file_name>'.
82
- # Providing this option may prove to be a good
83
- # thing for big commands. If this parameter is
84
- # not provided then the command is sent directly
85
- # to the input IO of the sqlplus process.
86
- def exec(command, params = {})
87
- Vorax.debug("exec: command=#{command.inspect} params=#{params.inspect}")
88
- raise AnotherExecRunning if busy?
89
- opts = {
90
- :prep => nil,
91
- :post => nil,
92
- :convertor => @default_convertor_name,
93
- :pack_file => nil,
94
- }.merge(params)
95
- @busy = true
96
- @look_for = @start_marker
97
- prepare_funnel(opts[:convertor])
98
- if @current_funnel && @current_funnel.is_a?(Output::HTMLFunnel)
99
- # all HTML funnels expects html format
100
- send_text("set markup html on\n")
101
- else
102
- send_text("set markup html off\n")
103
- end
104
- if opts[:pack_file]
105
- send_text("@#{pack(command, opts)}\n")
106
- else
107
- send_text("#{opts[:prep]}\n") if opts[:prep]
108
- capture { send_text("#{command}\n") }
109
- send_text("#{opts[:post]}\n") if opts[:post]
110
- end
111
- end
112
-
113
- # Send a text directly to the stdin of the sqlplus process.
114
- #
115
- # @param text [String] the text to be sent to sqlplus
116
- def send_text(text)
117
- @process.io.stdin.print(text)
118
- end
119
-
120
- # Check if the sqlplus process is busy executing something.
121
- #
122
- # @return true if the sqlplus is busy executing something,
123
- # false otherwise
124
- def busy?
125
- @busy
126
- end
127
-
128
- # Check if the output of a previous executed sqlpus command
129
- # was completely fetched out.
130
- #
131
- # @return true if the whole output was fetched, false otherwise
132
- def eof?
133
- not busy?
134
- end
135
-
136
- # Read the output spit by sqlplus process. If there is any default
137
- # convertor, the returned output will be formatted according to
138
- # that convertor.
139
- #
140
- # @param bytes [int] the maximum output chunk size
141
- # @return the output chunk
142
- def read_output(bytes=4086)
143
- output = ""
144
- raw_output = nil
145
- begin
146
- raw_output = @io_read.read_nonblock(bytes)
147
- rescue Errno::EAGAIN
148
- end
149
- if raw_output
150
- raw_output.gsub!(/\r/, '')
151
- scanner = StringScanner.new(raw_output)
152
- while not scanner.eos?
153
- if @look_for == @start_marker
154
- if text = scanner.scan_until(/#{@look_for}/)
155
- if text !~ /pro #{@look_for}/
156
- # Only if it's not part of a PROMPT sqlplus command.
157
- # This might happen when the "echo" sqlplus option
158
- # is ON and the begin marker is included into the
159
- # sql pack file. Because we are using big chunks to
160
- # read data it's very unlikely that the echoing of the
161
- # prompt command to be split in the middle.
162
- @look_for = @end_marker
163
- end
164
- else
165
- scanner.terminate
166
- end
167
- end
168
- if @look_for == @end_marker
169
- output = scanner.scan(/[^#{@look_for}]*/)
170
- if scanner.scan(/#{@look_for}/)
171
- # end of output reached
172
- scanner.terminate
173
- @busy = false
174
- end
175
- end
176
- end
177
- end
178
- chunk = output.force_encoding('UTF-8')
179
- if @current_funnel && !chunk.empty?
180
- # nokogiri may be confused about those unclosed <p> tags
181
- # sqlplus emits, so it's better to get rid of them and use
182
- # <br> instead.
183
- @current_funnel.write(br_only(chunk))
184
- chunk = @current_funnel.read
185
- end
186
- return chunk
187
- end
188
-
189
- # Cancel the currently executing statement. This is supported on Unix
190
- # only. On Windows there's no way to send a CTRL+C signal to Sqlplus
191
- # without aborting the process. There's an old enhancement request on
192
- # Oracle support:
193
- #
194
- # Bug 8890996: ENH: CONTROL-C SHOULD NOT EXIT WINDOWS CONSOLE SQLPLUS
195
- #
196
- # So, as soon as we have some fixes from the Oracle guys I will come
197
- # back to this method.
198
- def cancel
199
- raise PlatformNotSupported if ChildProcess.windows?
200
- if busy?
201
- @process.send_signal 'INT'
202
- mark_cancel
203
- # read until the cancel marker
204
- raw_output = ""
205
- until raw_output =~ /#{@cancel_marker}/
206
- begin
207
- raw_output = @io_read.read_nonblock(1024)
208
- yield if block_given?
209
- rescue Errno::EAGAIN
210
- sleep 0.1
211
- end
212
- end
213
- @busy = false
214
- end
215
- end
216
-
217
- # Kill the sqlplus process.
218
- def terminate
219
- @process.stop
220
- end
221
-
222
- private
223
-
224
- def br_only(chunk)
225
- # be prepared for chunks with <p> tag broken in the middle
226
- chunk.gsub(/<p>/, "<br>").gsub(/<p\z/, "<br").gsub(/\Ap>/, "br>")
227
- end
228
-
229
- def prepare_funnel(convertor_name)
230
- convertor = @registered_convertors[convertor_name]
231
- if convertor
232
- @current_funnel = Output::HTMLFunnel.new(convertor.new)
233
- else
234
- @current_funnel = nil
235
- end
236
- end
237
-
238
- def capture
239
- @process.io.stdin.puts("#pro #{@start_marker}")
240
- yield
241
- @process.io.stdin.puts("#pro #{@end_marker}")
242
- @process.io.stdin.puts(".")
243
- end
244
-
245
- def mark_cancel
246
- @process.io.stdin.puts
247
- @process.io.stdin.puts("pro #{@cancel_marker}")
248
- end
249
-
250
- def pack(command, opts)
251
- pack_file = opts[:pack_file]
252
- if pack_file
253
- File.open(pack_file, 'w') do |f|
254
- f.puts opts[:prep]
255
- f.puts "#pro #@start_marker"
256
- f.puts command.strip
257
- # we assume that the @end_marker is also
258
- # set as a block terminator. If "set echo on"
259
- # the output region will end here since the
260
- # block terminator command will be echoed. Otherwise,
261
- # the next prompt statement will do the job.
262
- f.puts "#{@end_marker}"
263
- f.puts "#pro #@end_marker"
264
- f.puts opts[:post]
265
- end
266
- end
267
- pack_file
268
- end
269
-
270
-
271
- end
272
-
273
- end