ass_launcher 0.1.1.alpha
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.simplecov +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +122 -0
- data/Rakefile +10 -0
- data/ass_launcher.gemspec +32 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/ass_launcher/enterprise/binary_wrapper.rb +367 -0
- data/lib/ass_launcher/enterprise/cli/arguments_builder.rb +41 -0
- data/lib/ass_launcher/enterprise/cli/cli.spec +256 -0
- data/lib/ass_launcher/enterprise/cli/parameters.rb +289 -0
- data/lib/ass_launcher/enterprise/cli/spec_dsl/dsl_helpers.rb +66 -0
- data/lib/ass_launcher/enterprise/cli/spec_dsl.rb +209 -0
- data/lib/ass_launcher/enterprise/cli.rb +131 -0
- data/lib/ass_launcher/enterprise/ole/ole_binaries.rb +249 -0
- data/lib/ass_launcher/enterprise/ole/win32ole.rb +92 -0
- data/lib/ass_launcher/enterprise/ole.rb +188 -0
- data/lib/ass_launcher/enterprise/web_clients.rb +59 -0
- data/lib/ass_launcher/enterprise.rb +111 -0
- data/lib/ass_launcher/support/connection_string.rb +422 -0
- data/lib/ass_launcher/support/platforms.rb +232 -0
- data/lib/ass_launcher/support/shell/process_holder.rb +212 -0
- data/lib/ass_launcher/support/shell.rb +374 -0
- data/lib/ass_launcher/support/v8i_file.rb +66 -0
- data/lib/ass_launcher/support/v8i_section.rb +70 -0
- data/lib/ass_launcher/support.rb +9 -0
- data/lib/ass_launcher/version.rb +3 -0
- data/lib/ass_launcher.rb +6 -0
- metadata +202 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module AssLauncher
|
3
|
+
module Support
|
4
|
+
module Shell
|
5
|
+
# Class for running {Command} in subprocess and controlling
|
6
|
+
# him.
|
7
|
+
# Process run command in the thread. Thread waiting for process exit
|
8
|
+
# and call {Command#exit_handling}
|
9
|
+
# @example
|
10
|
+
# # Run command and witing for exit
|
11
|
+
# ph = ProcessHolder.run(command, options)
|
12
|
+
#
|
13
|
+
# result = ph.wait.result
|
14
|
+
# raise 'bang!' unless result.sucsess?
|
15
|
+
#
|
16
|
+
# # Run command and kill process when nidet
|
17
|
+
#
|
18
|
+
# ph = ProcessHolder.run(command, options)
|
19
|
+
#
|
20
|
+
# sleep 10 # for wakeup command
|
21
|
+
#
|
22
|
+
# if ! ph.alive?
|
23
|
+
# raise 'command onexpected exit'
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # doing something
|
27
|
+
#
|
28
|
+
# ph.kill
|
29
|
+
#
|
30
|
+
# # run and wait command
|
31
|
+
#
|
32
|
+
# ph = ProcessHolder.run(command, options).wait
|
33
|
+
#
|
34
|
+
# raise if ph.result.success?
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# @note WARNIG!!! not forgot kill of threads created and handled of
|
38
|
+
# ProcessHolder
|
39
|
+
#
|
40
|
+
# @note For run command used popen3 whith command_string and *args.
|
41
|
+
# It not shell running. If command.args.size == 0 in command.args array
|
42
|
+
# will be pushed one empty string.
|
43
|
+
# For more info see Process.spawn documentation
|
44
|
+
# @api private
|
45
|
+
class ProcessHolder
|
46
|
+
require 'open3'
|
47
|
+
require 'ass_launcher/support/platforms'
|
48
|
+
include Support::Platforms
|
49
|
+
class KillProcessError < StandardError; end
|
50
|
+
class RunProcessError < StandardError; end
|
51
|
+
class ProcessNotRunning < StandardError; end
|
52
|
+
# @api public
|
53
|
+
# @return [Fixnum] pid of runned process
|
54
|
+
attr_reader :pid
|
55
|
+
|
56
|
+
# @api public
|
57
|
+
# @return [RunAssResult] result of execution command
|
58
|
+
attr_reader :result
|
59
|
+
|
60
|
+
# @api public
|
61
|
+
# @return [Command, Script] command runned in process
|
62
|
+
attr_reader :command
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
# @return [Thread] thread waiting for process
|
66
|
+
attr_reader :thread
|
67
|
+
|
68
|
+
# @api private
|
69
|
+
attr_reader :options, :popen3_thread
|
70
|
+
|
71
|
+
Thread.abort_on_exception = true
|
72
|
+
|
73
|
+
# Keep of created instaces
|
74
|
+
# @return [Arry<ProcessHolder>]
|
75
|
+
# @api public
|
76
|
+
def self.process_list
|
77
|
+
@@process_slist ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.unreg_process(h)
|
81
|
+
process_list.delete(h)
|
82
|
+
end
|
83
|
+
private_class_method :unreg_process
|
84
|
+
|
85
|
+
def self.reg_process(h)
|
86
|
+
process_list << h
|
87
|
+
end
|
88
|
+
private_class_method :reg_process
|
89
|
+
|
90
|
+
# @note 'cmd /K command` not exit when exit command. Thread hangup
|
91
|
+
# @api private
|
92
|
+
def self.cmd_exe_with_k?(command)
|
93
|
+
shell_str = "#{command.cmd} #{command.args.join(' ')}"
|
94
|
+
! (shell_str =~ %r{(?<=\W|\A)cmd(.exe)?\s*(\/K)}i).nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
# @note 'cmd /C command` not kill command when cmd killed
|
98
|
+
# @api private
|
99
|
+
def self.cmd_exe_with_c?(command)
|
100
|
+
shell_str = "#{command.cmd} #{command.args.join(' ')}"
|
101
|
+
! (shell_str =~ %r{(?<=\W|\A)cmd(.exe)?\s*(\/C)}i).nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Run command subprocess in new Thread and return instace for process
|
105
|
+
# controlling
|
106
|
+
# Thread wait process and handling process exit wihth
|
107
|
+
# {Command#exit_handling}
|
108
|
+
# @param command [Command, Script] command runned in subprocess
|
109
|
+
# @param options [Hash] options for +Process.spawn+
|
110
|
+
# @return [ProcessHolder] instance with runned command
|
111
|
+
# @note (see ProcessHolder)
|
112
|
+
# @raise [RunProcessError] if command is cmd.exe with /K key see
|
113
|
+
# {cmd_exe_with_k?}
|
114
|
+
# @api public
|
115
|
+
# @raise (see initialize)
|
116
|
+
def self.run(command, options = {})
|
117
|
+
fail RunProcessError, 'Forbidden run cmd.exe with /K key'\
|
118
|
+
if cmd_exe_with_k? command
|
119
|
+
h = new(command, options)
|
120
|
+
reg_process h
|
121
|
+
h.run
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param (see run)
|
125
|
+
# @raise [ArgumentError] if command was already running
|
126
|
+
def initialize(command, options = {})
|
127
|
+
fail ArgumentError, 'Command was already running' if command.running?
|
128
|
+
@command = command
|
129
|
+
command.send(:process_holder=, self)
|
130
|
+
@options = options
|
131
|
+
options[:new_pgroup] = true if windows?
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [self]
|
135
|
+
# @raise [RunProcessError] if process was already running
|
136
|
+
def run
|
137
|
+
fail RunProcessError, "Process was run. Pid: #{pid}" if running?
|
138
|
+
@popen3_thread, stdout, stderr = run_process
|
139
|
+
@pid = @popen3_thread.pid
|
140
|
+
@thread = wait_process_in_thread(stdout, stderr)
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def running?
|
145
|
+
! pid.nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
def wait_process_in_thread(stdout, stderr)
|
149
|
+
Thread.new do
|
150
|
+
popen3_thread.join
|
151
|
+
begin
|
152
|
+
@result = command.exit_handling(exitstatus,\
|
153
|
+
stdout.read,\
|
154
|
+
stderr.read)
|
155
|
+
rescue StandardError => e
|
156
|
+
@result = e
|
157
|
+
end
|
158
|
+
self.class.send(:unreg_process, self)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
private :wait_process_in_thread
|
162
|
+
|
163
|
+
def exitstatus
|
164
|
+
popen3_thread.value.to_i
|
165
|
+
end
|
166
|
+
private :exitstatus
|
167
|
+
|
168
|
+
# Run new process
|
169
|
+
def run_process
|
170
|
+
command.args << '' if command.args.size == 0
|
171
|
+
_r1, r2, r3, thread = Open3.popen3 command.cmd, *command.args, options
|
172
|
+
[thread, r2, r3]
|
173
|
+
end
|
174
|
+
private :run_process
|
175
|
+
|
176
|
+
# Kill the process
|
177
|
+
# @return [self]
|
178
|
+
# @note WARNIG! for command runned as cmd /C commnd can't get pid of
|
179
|
+
# command process. In this case error raised
|
180
|
+
# @raise [KillProcessError] if command is cmd.exe with /C key see
|
181
|
+
# {cmd_exe_with_c?}
|
182
|
+
# @api public
|
183
|
+
# @raise (see alive?)
|
184
|
+
def kill
|
185
|
+
return self unless alive?
|
186
|
+
fail KillProcessError, 'Can\'t kill subprocess runned in cmd.exe '\
|
187
|
+
'on the windows machine' if self.class.cmd_exe_with_c? command
|
188
|
+
Process.kill('KILL', pid)
|
189
|
+
wait
|
190
|
+
end
|
191
|
+
|
192
|
+
# Wait for thread exit
|
193
|
+
# @return [self]
|
194
|
+
# @api public
|
195
|
+
# @raise (see alive?)
|
196
|
+
def wait
|
197
|
+
return self unless alive?
|
198
|
+
thread.join
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
# True if thread alive
|
203
|
+
# @api public
|
204
|
+
# @raise [ProcessNotRunning] unless process running
|
205
|
+
def alive?
|
206
|
+
fail ProcessNotRunning unless running?
|
207
|
+
thread.alive?
|
208
|
+
end
|
209
|
+
end # ProcessHolder
|
210
|
+
end # Shell
|
211
|
+
end # Support
|
212
|
+
end # AssLauncher
|
@@ -0,0 +1,374 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Monkey patch for [String]
|
4
|
+
class String
|
5
|
+
require 'shellwords'
|
6
|
+
def to_cmd
|
7
|
+
if AssLauncher::Support::Platforms.windows?\
|
8
|
+
|| AssLauncher::Support::Platforms.cygwin?
|
9
|
+
"\"#{self}\""
|
10
|
+
else
|
11
|
+
escape
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def escape
|
16
|
+
Shellwords.escape self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
module AssLauncher
|
22
|
+
class << self
|
23
|
+
def config
|
24
|
+
@config ||= Configuration.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.configure
|
29
|
+
yield(config)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Configuration for {AssLauncher}
|
33
|
+
class Configuration
|
34
|
+
attr_accessor :logger
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@logger = Loggining.default_logger
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger=(l)
|
41
|
+
fail ArgumentError, 'Logger may be valid logger' if l.nil?
|
42
|
+
@logger = l
|
43
|
+
end
|
44
|
+
end
|
45
|
+
# Loggining mixin
|
46
|
+
module Loggining
|
47
|
+
require 'logger'
|
48
|
+
|
49
|
+
DEFAULT_LEVEL = Logger::Severity::UNKNOWN
|
50
|
+
|
51
|
+
def self.included(k)
|
52
|
+
k.extend(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def logger
|
56
|
+
AssLauncher.config.logger
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
def self.default_logger
|
61
|
+
l = Logger.new($stderr)
|
62
|
+
l.level = DEFAULT_LEVEL
|
63
|
+
l
|
64
|
+
end
|
65
|
+
end
|
66
|
+
module Support
|
67
|
+
# Shell utils for run 1C:Enterprise binary
|
68
|
+
module Shell
|
69
|
+
# TODO: delete it see todo in platform #cygpath func
|
70
|
+
class RunError < StandardError; end
|
71
|
+
require 'methadone'
|
72
|
+
require 'tempfile'
|
73
|
+
require 'ass_launcher/support/shell/process_holder'
|
74
|
+
include Loggining
|
75
|
+
include Methadone::SH
|
76
|
+
extend Support::Platforms
|
77
|
+
|
78
|
+
# Command running directly as:
|
79
|
+
# popen3(command.cmd, *command.args, options)
|
80
|
+
#
|
81
|
+
# @note What reason for it? Reason for it:
|
82
|
+
#
|
83
|
+
# Fucking 1C binary often unexpected parse cmd arguments if run in
|
84
|
+
# shell like `1c.exe arguments`. For correction this invented two way run
|
85
|
+
# 1C binary: as command see {Shell::Command} or as script
|
86
|
+
# see {Shell::Script}. If run 1C as command we can control executing
|
87
|
+
# process wait exit or kill 1C binary process. If run 1C as script 1C
|
88
|
+
# more correctly parse arguments but we can't kill subprosess running
|
89
|
+
# in cmd.exe
|
90
|
+
#
|
91
|
+
# @note On default use silient execute 1C binary whit
|
92
|
+
# /DisableStartupDialogs,
|
93
|
+
# /DisableStartupMessages parameters and capture 1C output /OUT
|
94
|
+
# parameter. Read message from /OUT when 1C binary process exit and
|
95
|
+
# build instnce of RunAssResult.
|
96
|
+
#
|
97
|
+
# @note (see AssOutFile)
|
98
|
+
# @api private
|
99
|
+
class Command
|
100
|
+
attr_reader :cmd, :args, :ass_out_file, :options
|
101
|
+
attr_accessor :process_holder
|
102
|
+
private :process_holder=
|
103
|
+
private :ass_out_file
|
104
|
+
DEFAULT_OPTIONS = { silent_mode: true,
|
105
|
+
capture_assout: true
|
106
|
+
}
|
107
|
+
# @param cmd [String] path to 1C binary
|
108
|
+
# @param args [Array] arguments for 1C binary
|
109
|
+
# @option options [String] :assout_encoding encoding for assoutput file.
|
110
|
+
# Default 'cp1251'
|
111
|
+
# @option options [Boolean] :capture_assout capture assoutput.
|
112
|
+
# Default true
|
113
|
+
# @option options [Boolean]:silent_mode run 1C with
|
114
|
+
# /DisableStartupDialogs and /DisableStartupMessages parameters.
|
115
|
+
# Default true
|
116
|
+
def initialize(cmd, args = [], options = {})
|
117
|
+
@options = DEFAULT_OPTIONS.merge(options).freeze
|
118
|
+
@cmd = cmd
|
119
|
+
@args = args
|
120
|
+
@args += _silent_mode
|
121
|
+
@ass_out_file = _ass_out_file
|
122
|
+
end
|
123
|
+
|
124
|
+
# @return [true] if command was already running
|
125
|
+
def running?
|
126
|
+
! process_holder.nil?
|
127
|
+
end
|
128
|
+
|
129
|
+
# Run command
|
130
|
+
# @param options [Hash] options for Process.spawn
|
131
|
+
# @return [ProcessHolder]
|
132
|
+
def run(options = {})
|
133
|
+
return process_holder if running?
|
134
|
+
ProcessHolder.run(self, options)
|
135
|
+
end
|
136
|
+
|
137
|
+
def _silent_mode
|
138
|
+
if options[:silent_mode]
|
139
|
+
['/DisableStartupDialogs', '',
|
140
|
+
'/DisableStartupMessages', '']
|
141
|
+
else
|
142
|
+
[]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
private :_silent_mode
|
146
|
+
|
147
|
+
def _out_ass_argument(out_file)
|
148
|
+
@args += ['/OUT', out_file.to_s]
|
149
|
+
out_file
|
150
|
+
end
|
151
|
+
private :_out_ass_argument
|
152
|
+
|
153
|
+
def _ass_out_file
|
154
|
+
if options[:capture_assout]
|
155
|
+
out_file = AssOutFile.new(options[:assout_encoding])
|
156
|
+
_out_ass_argument out_file
|
157
|
+
else
|
158
|
+
StringIO.new
|
159
|
+
end
|
160
|
+
end
|
161
|
+
private :_ass_out_file
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
"#{cmd} #{args.join(' ')}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def exit_handling(exitstatus, out, err)
|
168
|
+
RunAssResult.new(exitstatus, encode_out(out),
|
169
|
+
encode_out(err), ass_out_file.read)
|
170
|
+
end
|
171
|
+
|
172
|
+
def encode_out(out)
|
173
|
+
out
|
174
|
+
end
|
175
|
+
private :encode_out
|
176
|
+
end
|
177
|
+
|
178
|
+
# class {Script} wraping cmd string in to script tempfile and running as:
|
179
|
+
# popen3('cmd.exe', '/C', 'tempfile' in cygwin or windows
|
180
|
+
# or popen3('sh', 'tempfile') in linux
|
181
|
+
#
|
182
|
+
# @note (see Command)
|
183
|
+
# @api private
|
184
|
+
class Script < Command
|
185
|
+
include Support::Platforms
|
186
|
+
# @param cmd [String] cmd string for executing as cmd.exe or sh script
|
187
|
+
# @option (see Command#initialize)
|
188
|
+
def initialize(cmd, options = {})
|
189
|
+
super cmd, [], options
|
190
|
+
end
|
191
|
+
|
192
|
+
def make_script
|
193
|
+
@file = Tempfile.new(%w( run_ass_script .cmd ))
|
194
|
+
@file.open
|
195
|
+
@file.write(encode)
|
196
|
+
@file.close
|
197
|
+
platform.path(@file.path)
|
198
|
+
end
|
199
|
+
private :make_script
|
200
|
+
|
201
|
+
# @note used @args variable for reason!
|
202
|
+
# In class {Script} methods {Script#cmd} and
|
203
|
+
# {Script#args} returns command and args for run
|
204
|
+
# script in sh or cmd.exe but @rgs varible use in {#to_s} for
|
205
|
+
# generate script content
|
206
|
+
# script
|
207
|
+
def _out_ass_argument(out_file)
|
208
|
+
@args += ['/OUT', "\"#{out_file}\""]
|
209
|
+
out_file
|
210
|
+
end
|
211
|
+
private :_out_ass_argument
|
212
|
+
|
213
|
+
def encode
|
214
|
+
if cygwin_or_windows?
|
215
|
+
# TODO: need to detect current win cmd encoding cp866 - may be wrong
|
216
|
+
return to_s.encode('cp866', 'utf-8')
|
217
|
+
end
|
218
|
+
to_s
|
219
|
+
end
|
220
|
+
private :encode
|
221
|
+
|
222
|
+
# @note used @cmd and @args variable for reason!
|
223
|
+
# In class {Script} methods {Script#cmd} and
|
224
|
+
# {Script#args} returns command and args for run
|
225
|
+
# script in sh or cmd.exe but {#to_s} return content for
|
226
|
+
# script
|
227
|
+
def to_s
|
228
|
+
"#{@cmd} #{@args.join(' ')}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def cygwin_or_windows?
|
232
|
+
cygwin? || windows?
|
233
|
+
end
|
234
|
+
private :cygwin_or_windows?
|
235
|
+
|
236
|
+
# Returm shell binary 'cmd.exe' or 'sh'
|
237
|
+
# @return [String]
|
238
|
+
def cmd
|
239
|
+
if cygwin_or_windows?
|
240
|
+
'cmd.exe'
|
241
|
+
else
|
242
|
+
'sh'
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Return args for run shell script
|
247
|
+
# @return [Array]
|
248
|
+
def args
|
249
|
+
if cygwin_or_windows?
|
250
|
+
['/C', make_script.win_string]
|
251
|
+
else
|
252
|
+
[make_script.to_s]
|
253
|
+
end.freeze
|
254
|
+
end
|
255
|
+
|
256
|
+
def encode_out(out)
|
257
|
+
# TODO: need to detect current win cmd encoding cp866 - may be wrong
|
258
|
+
begin
|
259
|
+
out.encode!('utf-8', 'cp866') if cygwin_or_windows?
|
260
|
+
rescue EncodingError => e
|
261
|
+
return "#{e.class}: #{out}"
|
262
|
+
end
|
263
|
+
out
|
264
|
+
end
|
265
|
+
private :encode_out
|
266
|
+
|
267
|
+
# Run script. Script wait process exit
|
268
|
+
# @param options [Hash] options for Process.spawn
|
269
|
+
# @return [ProcessHolder]
|
270
|
+
def run(options = {})
|
271
|
+
ph = super
|
272
|
+
ph.wait
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Contain result for execute 1C binary
|
277
|
+
# see {ProcessHolder#result}
|
278
|
+
# @api private
|
279
|
+
class RunAssResult
|
280
|
+
class UnexpectedAssOut < StandardError; end
|
281
|
+
class RunAssError < StandardError; end
|
282
|
+
attr_reader :out, :assout, :exitstatus, :err
|
283
|
+
attr_accessor :expected_assout
|
284
|
+
def initialize(exitstatus, out, err, assout)
|
285
|
+
@err = err
|
286
|
+
@out = out
|
287
|
+
@exitstatus = exitstatus
|
288
|
+
@assout = assout
|
289
|
+
end
|
290
|
+
|
291
|
+
# Verivfy of result and raises unless {#success?}
|
292
|
+
# @raise [UnexpectedAssOut] - exitstatus == 0 but taken unexpected
|
293
|
+
# assout {!#expected_assout?}
|
294
|
+
# @raise [RunAssError] - if other errors taken
|
295
|
+
# @api public
|
296
|
+
def verify!
|
297
|
+
fail UnexpectedAssOut, cut_assout unless expected_assout?
|
298
|
+
fail RunAssError, "#{err}#{cut_assout}" unless success?
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
def cut_assout
|
303
|
+
return assout if assout.size <= 80
|
304
|
+
"#{assout[0, 80]}..."
|
305
|
+
end
|
306
|
+
private :cut_assout
|
307
|
+
|
308
|
+
# @api public
|
309
|
+
def success?
|
310
|
+
exitstatus == 0 && expected_assout?
|
311
|
+
end
|
312
|
+
|
313
|
+
# Set regex for verify assout
|
314
|
+
# @note (see #expected_assout?)
|
315
|
+
# @param exp [nil, Regexp]
|
316
|
+
# @raise [ArgumentError] when bad expresion given
|
317
|
+
# @api public
|
318
|
+
def expected_assout=(exp)
|
319
|
+
return if exp.nil?
|
320
|
+
fail ArgumentError unless exp.is_a? Regexp
|
321
|
+
@expected_assout = exp
|
322
|
+
end
|
323
|
+
|
324
|
+
# @note Sometimes 1C does what we not expects. For example, we ask
|
325
|
+
# to create InfoBase File="tmp\tmp.ib" however 1C make files of
|
326
|
+
# infobase in root of 'tmp\' directory and exits with status 0. In this
|
327
|
+
# case we have to check assout for answer executed success? or not.
|
328
|
+
# Checkin {#assout} string
|
329
|
+
# If existstatus != 0 checking assout value skiped and return true
|
330
|
+
# It work when exitstatus == 0 but taken unexpected assout
|
331
|
+
# @return [Boolean]
|
332
|
+
# @api public
|
333
|
+
def expected_assout?
|
334
|
+
return true if expected_assout.nil?
|
335
|
+
return true if exitstatus != 0
|
336
|
+
! (expected_assout =~ assout).nil?
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# Hold, read and encode 1C output
|
341
|
+
#
|
342
|
+
# @note Fucking 1C not work with stdout and stderr
|
343
|
+
# For out 1C use /OUT"file" parameter and write message into. Message
|
344
|
+
# encoding 'cp1251' for windows and 'utf-8' for Linux
|
345
|
+
# @api private
|
346
|
+
class AssOutFile
|
347
|
+
include Support::Platforms
|
348
|
+
attr_reader :file, :path, :encoding
|
349
|
+
def initialize(encoding = nil)
|
350
|
+
@file = Tempfile.new('ass_out')
|
351
|
+
@file.close
|
352
|
+
@path = platform.path(@file.path)
|
353
|
+
@encoding = encoding || Encoding::CP1251
|
354
|
+
end
|
355
|
+
|
356
|
+
def to_s
|
357
|
+
@path.to_s
|
358
|
+
end
|
359
|
+
|
360
|
+
def read
|
361
|
+
begin
|
362
|
+
@file.open
|
363
|
+
s = @file.read
|
364
|
+
s.encode! Encoding::UTF_8, encoding unless linux?
|
365
|
+
ensure
|
366
|
+
@file.close
|
367
|
+
@file.unlink
|
368
|
+
end
|
369
|
+
s.to_s
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module AssLauncher
|
2
|
+
# Common objects and methods
|
3
|
+
module Support
|
4
|
+
EOF = "\r\n"
|
5
|
+
BOM = "\xEF\xBB\xBF".force_encoding('utf-8')
|
6
|
+
|
7
|
+
# v8i file reader-writer
|
8
|
+
module V8iFile
|
9
|
+
require 'inifile'
|
10
|
+
class ReadError < StandardError; end
|
11
|
+
|
12
|
+
# Read v8i content and return array of v8i sections
|
13
|
+
# @param io [IO] the input starem opened for read
|
14
|
+
# @return [Array<V8iSection>]
|
15
|
+
def self.read(io)
|
16
|
+
res = []
|
17
|
+
inifile = to_inifile(io.read)
|
18
|
+
inifile.each_section do |caption|
|
19
|
+
res << V8iSection.new(caption, inifile[caption])
|
20
|
+
end
|
21
|
+
res
|
22
|
+
end
|
23
|
+
|
24
|
+
# Read v8i file
|
25
|
+
# @param filename [String]
|
26
|
+
# @return (see read)
|
27
|
+
# @raise [ReadError] if file not exists
|
28
|
+
def self.load(filename)
|
29
|
+
fail ReadError, "File #{filename} not exist or not a file"\
|
30
|
+
unless File.file? filename
|
31
|
+
read File.new(filename, 'r:bom|utf-8')
|
32
|
+
end
|
33
|
+
|
34
|
+
# Write sections in to output stream
|
35
|
+
# @param io [IO] the output stream open for writing
|
36
|
+
# @param sections [Array<V8iSection>] sections for write
|
37
|
+
def self.write(io, sections)
|
38
|
+
sections.each do |s|
|
39
|
+
io.write(s.to_s + "\r\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Save sections in to v8i file
|
44
|
+
# @param filename [String]
|
45
|
+
# @param sections (see write)
|
46
|
+
def self.save(filename, sections)
|
47
|
+
write File.new(filename, 'w'), sections
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def self.to_inifile(content)
|
53
|
+
IniFile.new(content: "#{escape_content(content)}", comment: '')
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.escape_content(content)
|
57
|
+
content.gsub!(BOM, '')
|
58
|
+
content.gsub!(/\\\\/, '\\\\\\\\\\')
|
59
|
+
%w(r n t 0).each do |l|
|
60
|
+
content.gsub!(/\\#{l}/, "\\\\\\#{l}")
|
61
|
+
end
|
62
|
+
content
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|