vorax 0.4.2 → 5.0

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