ass_launcher 0.1.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,422 @@
1
+ module AssLauncher
2
+ module Support
3
+ # Implement 1C connection string
4
+ # Mixin for connection string classes
5
+ # @note All connection string class have methods for get and set values
6
+ # of defined fields. Methods have name as fields but in downcase
7
+ # All fields defined for connection string class retutn {#fields}
8
+ # @example
9
+ # cs = AssLauncher::Support::\
10
+ # ConnectionString.new('File="\\fileserver\accounting.ib"')
11
+ # cs.is #-> :file
12
+ # cs.is? :file #-> true
13
+ # cs.usr = 'username'
14
+ # cs.pwd = 'password'
15
+ # cmd = "1civ8.exe enterprise #{cs.to_cmd}"
16
+ # run_result = AssLauncher::Support::Shell.run_ass(cmd)
17
+ module ConnectionString
18
+ class Error < StandardError; end
19
+ class ParseError < StandardError; end
20
+ # Commonn connection string fields
21
+ COMMON_FIELDS = %w(Usr Pwd LicDstr prmod Locale)
22
+ # Fields for server-infobase
23
+ SERVER_FIELDS = %w(Srvr Ref)
24
+ # Fields for file-infobase
25
+ FILE_FIELDS = %w(File)
26
+ # Fields for infobase published on http server
27
+ HTTP_FIELDS = %w(Ws)
28
+ HTTP_WEB_AUTH_FIELDS = %w(Wsn Wsp)
29
+ # Proxy fields for accsess to infobase published on http server via proxy
30
+ PROXY_FIELDS = %w(WspAuto WspSrv WspPort WspUser WspPwd)
31
+ # Fields for makes server-infobase
32
+ IB_MAKER_FIELDS = %w(DBMS DBSrvr DB
33
+ DBUID DBPwd SQLYOffs
34
+ CrSQLDB SchJobDn SUsr SPwd)
35
+ # Values for DBMS field
36
+ DBMS_VALUES = %w(MSSQLServer PostgreSQL IBMDB2 OracleDatabase)
37
+
38
+ # Analyzes connect string and build suitable class
39
+ # @param connstr (see parse)
40
+ # @return [Server | File | Http] instanse
41
+ def self.new(connstr)
42
+ case connstr
43
+ when /(\W|\A)File\s*=\s*"/i then File.new(parse(connstr))
44
+ when /(\W|\A)Srvr\s*=\s*"/i then Server.new(parse(connstr))
45
+ when /(\W|\A)Ws\s*=\s*"/i then Http.new(parse(connstr))
46
+ else
47
+ fail ParseError, "Uncknown connstr `#{connstr}'"
48
+ end
49
+ end
50
+
51
+ # Parse connect string into hash.
52
+ # Connect string have format:
53
+ # 'Field1="Value";Field2="Value";'
54
+ # Quotes ' " ' in value of field escape as doble quote ' "" '.
55
+ # Fields name convert to downcase [Symbol]
56
+ # @example
57
+ # parse 'Field="""Value"""' -> {field: '"Value"'}
58
+ # @param connstr [String]
59
+ # @return [Hash]
60
+ def self.parse(connstr)
61
+ res = {}
62
+ connstr.split(';').each do |str|
63
+ str.strip!
64
+ res.merge!(parse_key_value str) unless str.empty?
65
+ end
66
+ res
67
+ end
68
+
69
+ def self.parse_key_value(str)
70
+ fail ParseError, "Invalid string #{str}" unless\
71
+ /\A\s*(?<field>\w+)\s*=\s*"(?<value>.*)"\s*\z/i =~ str
72
+ { field.downcase.to_sym => value.gsub('""', '"') }
73
+ end
74
+ private_class_method :parse_key_value
75
+
76
+ # Return type of connection string
77
+ # :file, :server, :http
78
+ # @return [Symbol]
79
+ def is
80
+ self.class.name.split('::').last.downcase.to_sym
81
+ end
82
+
83
+ # Check connection string for type :file, :server, :http
84
+ # @param symbol [Symvol]
85
+ # @example
86
+ # if cs.is? :file
87
+ # #do for connect to the file infobase
88
+ # else
89
+ # raise "#{cs.is} unsupport
90
+ # end
91
+ def is?(symbol)
92
+ is == symbol
93
+ end
94
+
95
+ def to_hash
96
+ result = {}
97
+ fields.each do |f|
98
+ result[f.downcase.to_sym] = get_property(f)
99
+ end
100
+ result
101
+ end
102
+
103
+ def to_s(only_fields = nil)
104
+ only_fields ||= fields
105
+ result = ''
106
+ only_fields.each do |f|
107
+ result << "#{prop_to_s(f)};" unless get_property(f).to_s.empty?
108
+ end
109
+ result
110
+ end
111
+
112
+ # Convert connection string to array of 1C:Enterprise parameters.
113
+ # @return [Array] of 1C:Enterprise CLI parameters.
114
+ def to_args
115
+ to_args_common + to_args_private
116
+ end
117
+
118
+ def to_args_common
119
+ r = []
120
+ r += ['/N', usr] if usr
121
+ r += ['/P', pwd] if pwd
122
+ r += ['/UsePrivilegedMode', ''] if prmod.to_s == '1'
123
+ r += ['/L', locale] if locale
124
+ r
125
+ end
126
+ private :to_args_common
127
+
128
+ # Convert connection string to string of 1C:Enterprise parameters
129
+ # like /N"usr" /P"pwd" etc. See {#to_args}
130
+ # @return [String]
131
+ def to_cmd
132
+ r = ''
133
+ args = to_args
134
+ args.each_with_index do |v, i|
135
+ next unless i.even?
136
+ r << v
137
+ r << "\"#{args[i + 1]}\"" unless args[i + 1].to_s.empty?
138
+ r << ' '
139
+ end
140
+ r
141
+ end
142
+
143
+ # Fields required for new instance of connection string
144
+ def required_fields
145
+ self.class.required_fields
146
+ end
147
+
148
+ # All fields defined for connection string
149
+ def fields
150
+ self.class.fields
151
+ end
152
+
153
+ def self.included(base)
154
+ base.fields.each do |f|
155
+ base.send(:attr_accessor, f.downcase.to_sym)
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def _set_properties(hash)
162
+ hash.each do |key, value|
163
+ set_property(key, value)
164
+ end
165
+ end
166
+
167
+ def set_property(prop, value)
168
+ send("#{prop.downcase}=".to_sym, value)
169
+ end
170
+
171
+ def get_property(prop)
172
+ send(prop.downcase.to_sym)
173
+ end
174
+
175
+ def prop_to_s(prop)
176
+ "#{fields_to_hash[prop.downcase.to_sym]}="\
177
+ "\"#{get_property(prop).to_s.gsub('"', '""')}\""
178
+ end
179
+
180
+ def fields_to_hash
181
+ res = {}
182
+ fields.each do |f|
183
+ res[f.downcase.to_sym] = f
184
+ end
185
+ res
186
+ end
187
+
188
+ def required_fields_received?(received_fields)
189
+ (required_fields.map { |f| f.downcase.to_sym }\
190
+ & received_fields.keys.map { |k| k.downcase.to_sym }) == \
191
+ required_fields.map { |f| f.downcase.to_sym }
192
+ end
193
+
194
+ # Connection string for server-infobases
195
+ # @note (see ConnectionString)
196
+ class Server
197
+ # Simple class host:port
198
+ class ServerDescr
199
+ attr_reader :host, :port
200
+
201
+ # @param host [String] hostname
202
+ # @param port [String] port number
203
+ def initialize(host, port = nil)
204
+ @host = host.strip
205
+ @port = port.to_s.strip
206
+ end
207
+
208
+ # Parse sting <srv_string>
209
+ # @param srv_str [String] string like 'host:port,host:port'
210
+ # @return [Arry<ServerDescr>]
211
+ def self.parse(srv_str)
212
+ r = []
213
+ srv_str.split(',').each do |srv|
214
+ srv.strip!
215
+ r << new(* srv.chomp.split(':')) unless srv.empty?
216
+ end
217
+ r
218
+ end
219
+
220
+ # @return [String] formated 'host:port'
221
+ def to_s
222
+ "#{host}" + (port.empty? ? '' : ":#{port}")
223
+ end
224
+ end
225
+
226
+ def self.fields
227
+ required_fields | COMMON_FIELDS | IB_MAKER_FIELDS
228
+ end
229
+
230
+ def self.required_fields
231
+ SERVER_FIELDS
232
+ end
233
+
234
+ include ConnectionString
235
+
236
+ def initialize(hash)
237
+ fail ConnectionString::Error unless required_fields_received?(hash)
238
+ _set_properties(hash)
239
+ end
240
+
241
+ # @return [Array<ServerDescr>]
242
+ def servers
243
+ @servers ||= []
244
+ end
245
+
246
+ def srvr=(str)
247
+ fail ArgumentError if str.empty?
248
+ @servers = ServerDescr.parse(str)
249
+ @srvr = str
250
+ end
251
+
252
+ def ref=(str)
253
+ fail ArgumentError if str.empty?
254
+ @ref = str
255
+ end
256
+
257
+ def srvr
258
+ servers.join(',')
259
+ end
260
+
261
+ def srvr_raw
262
+ @srvr
263
+ end
264
+
265
+ # Build string suitable for
266
+ # :createinfibase runmode
267
+ # @todo validte createinfibase params
268
+ def createinfobase_cmd
269
+ to_s
270
+ end
271
+
272
+ # Build string suitable for Ole objects connecting.
273
+ def to_ole_string
274
+ "#{to_s(fields - IB_MAKER_FIELDS)}"
275
+ end
276
+
277
+ # Build args array suitable for
278
+ # :createinfibase runmode
279
+ def createinfobase_args
280
+ [createinfobase_cmd]
281
+ end
282
+
283
+ # (see DBMS_VALUES)
284
+ def dbms=(value)
285
+ @dbms = valid_value(value, DBMS_VALUES)
286
+ end
287
+
288
+ def crsqldb=(value)
289
+ @crsqldb = yes_or_not(value)
290
+ end
291
+
292
+ def schjobdn=(value)
293
+ @schjobdn = yes_or_not(value)
294
+ end
295
+
296
+ def yes_or_not(value)
297
+ valid_value(value, %w(Y N))
298
+ end
299
+ private :yes_or_not
300
+
301
+ def valid_value(v, av)
302
+ fail ArgumentError, "Bad value #{v}. Accepted values are `#{av}'"\
303
+ unless av.map(&:downcase).include?(v.to_s.downcase)
304
+ v
305
+ end
306
+ private :valid_value
307
+
308
+ def to_args_private
309
+ ['/S', "#{srvr}/#{ref}"]
310
+ end
311
+ private :to_args_private
312
+ end
313
+
314
+ # Connection string for file-infobases
315
+ # @note (see ConnectionString)
316
+ class File
317
+ def self.required_fields
318
+ FILE_FIELDS
319
+ end
320
+
321
+ def self.fields
322
+ required_fields | COMMON_FIELDS
323
+ end
324
+
325
+ include ConnectionString
326
+
327
+ def initialize(hash)
328
+ fail ConnectionString::Error unless required_fields_received?(hash)
329
+ _set_properties(hash)
330
+ end
331
+
332
+ def file=(str)
333
+ fail ArgumentError if str.empty?
334
+ @file = str
335
+ end
336
+
337
+ # Build string suitable for
338
+ # :createinfibase runmode
339
+ def createinfobase_cmd
340
+ "File=\"#{path.realdirpath.win_string}\""
341
+ end
342
+
343
+ # Build string suitable for Ole objects connecting.
344
+ def to_ole_string
345
+ "#{createinfobase_cmd};#{to_s(fields - ['File'])}"
346
+ end
347
+
348
+ # Build args array suitable for
349
+ # :createinfibase runmode
350
+ # Fucking 1C:
351
+ # - File="path" not work but work running as script
352
+ # - File='path' work correct
353
+ def createinfobase_args
354
+ ["File='#{path.realdirpath.win_string}'"]
355
+ end
356
+
357
+ def path
358
+ AssLauncher::Support::Platforms.path(file)
359
+ end
360
+
361
+ # Convert connection string to array of 1C:Enterprise parameters.
362
+ # @return [Array] of 1C:Enterprise CLI parameters.
363
+ def to_args_private
364
+ ['/F', path.realpath.to_s]
365
+ end
366
+ private :to_args_private
367
+ end
368
+
369
+ # Connection string for infobases published on http server
370
+ # @note (see ConnectionString)
371
+ class Http
372
+ def self.required_fields
373
+ HTTP_FIELDS
374
+ end
375
+
376
+ def self.fields
377
+ required_fields | COMMON_FIELDS | HTTP_WEB_AUTH_FIELDS | PROXY_FIELDS
378
+ end
379
+
380
+ include ConnectionString
381
+
382
+ def initialize(hash)
383
+ fail ConnectionString::Error unless required_fields_received?(hash)
384
+ _set_properties(hash)
385
+ end
386
+
387
+ def ws=(str)
388
+ fail ArgumentError if str.empty?
389
+ @ws = str
390
+ end
391
+
392
+ def uri
393
+ require 'uri'
394
+ uri = URI(ws)
395
+ uri.user = wsn
396
+ uri.password = wsp
397
+ uri
398
+ end
399
+
400
+ # Convert connection string to array of 1C:Enterprise parameters.
401
+ # @return [Array] of 1C:Enterprise CLI parameters.
402
+ def to_args_private
403
+ r = []
404
+ r += ['/WS', ws] if ws
405
+ r += ['/WSN', wsn] if wsn
406
+ r += ['/WSP', wsp] if wsp
407
+ to_args_private_proxy(r)
408
+ end
409
+ private :to_args_private
410
+
411
+ def to_args_private_proxy(r)
412
+ return r unless !wspauto && wspsrv
413
+ r += ['/Proxy', '', '-Psrv', wspsrv]
414
+ r += ['-PPort', wspport.to_s] if wspport
415
+ r += ['-PUser', wspuser] if wspuser
416
+ r += ['-PPwd', wsppwd] if wsppwd
417
+ r
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,232 @@
1
+ # encoding: utf-8
2
+
3
+ require 'ffi'
4
+
5
+ module FFI
6
+ # Monkey patch of [FFI::Platform]
7
+ module Platform
8
+ IS_CYGWIN = is_os('cygwin')
9
+
10
+ def self.cygwin?
11
+ IS_CYGWIN
12
+ end
13
+
14
+ def self.linux?
15
+ IS_LINUX
16
+ end
17
+ end
18
+ end
19
+
20
+ module AssLauncher
21
+ module Support
22
+ # OS-specific things
23
+ # Mixin module help work with things as paths and env in other plases
24
+ # @example
25
+ # include AssLauncher::Support::Platforms
26
+ #
27
+ # if cigwin?
28
+ # #do if run in Cygwin
29
+ # end
30
+ #
31
+ # # Find env value on regex
32
+ # pf = platform.env[/program\s*files/i]
33
+ # return if pf.size == 0
34
+ #
35
+ # # Use #path
36
+ # p = platform.path(pf[0])
37
+ # p.exists?
38
+ #
39
+ # # Use #path_class
40
+ # platform.path_class.glob('C:/*').each do |path|
41
+ # path.exists?
42
+ # end
43
+ #
44
+ # # Use #glob directly
45
+ # platform.glob('C:/*').each do |path|
46
+ # path.exists?
47
+ # end
48
+ #
49
+ #
50
+ module Platforms
51
+ # True if run in Cygwin
52
+ def cygwin?
53
+ FFI::Platform.cygwin?
54
+ end
55
+ module_function :cygwin?
56
+
57
+ # True if run in MinGW
58
+ def windows?
59
+ FFI::Platform.windows?
60
+ end
61
+ module_function :windows?
62
+
63
+ # True if run in Linux
64
+ def linux?
65
+ FFI::Platform.linux?
66
+ end
67
+ module_function :linux?
68
+
69
+ # Return module [Platforms] as helper
70
+ # @return [Platforms]
71
+ def platform
72
+ AssLauncher::Support::Platforms
73
+ end
74
+ private :platform
75
+
76
+ require 'pathname'
77
+
78
+ # Return suitable class
79
+ # @return [UnixPath | WinPath | CygPath]
80
+ def self.path_class
81
+ if cygwin?
82
+ PathnameExt::CygPath
83
+ elsif windows?
84
+ PathnameExt::WinPath
85
+ else
86
+ PathnameExt::UnixPath
87
+ end
88
+ end
89
+
90
+ # Return suitable class instance
91
+ # @return [UnixPath | WinPath | CygPath]
92
+ def self.path(string)
93
+ path_class.new(string)
94
+ end
95
+
96
+ # (see PathnameExt.glob)
97
+ def self.glob(p1, *args)
98
+ path_class.glob(p1, *args)
99
+ end
100
+
101
+ # Parent for OS-specific *Path classes
102
+ # @todo TRANSLATE THIS:
103
+ #
104
+ # rubocop:disable AsciiComments
105
+ # @note
106
+ # Класс предназначен для унификации работы с путями ФС в различных
107
+ # ОС.
108
+ # ОС зависимые методы будут переопределены в классах потомках
109
+ # [UnixPath | WinPath | CygPath].
110
+ #
111
+ # Пути могут приходить из следующих источников:
112
+ # - из консоли - при этом в Cygwin путь вида '/cygdrive/c' будет
113
+ # непонятен за пределами Cygwin
114
+ # - из ENV - при этом путь \\\\host\\share будет непонятен в Unix
115
+ #
116
+ # Общая мысль в следующем:
117
+ # - пути приводятся к mixed_path - /cygwin/c -> C:/, C:\\ -> C:/,
118
+ # \\\\host\\share -> //host/share
119
+ # - переопределяется метод glob класса [Pathname] при этом метод в
120
+ # Cygwin будет иметь свою реализацию т.к. в cygwin
121
+ # Dirname.glob('C:/') вернет пустой массив,
122
+ # а Dirname.glob('/cygdrive/c') отработает правильно.
123
+ # rubocop:enable AsciiComments
124
+ class PathnameExt < Pathname
125
+ # Override constructor for lead path to (#mixed_path)
126
+ # @param string [String] - string of path
127
+ def initialize(string)
128
+ @raw = string.to_s.strip
129
+ super mixed_path(@raw)
130
+ end
131
+
132
+ # This is fix (bug or featere)? of [Pathname] method. Called in
133
+ # chiled clesses returns not childe class instance but returns
134
+ # [Pathname] instance
135
+ def +(other)
136
+ self.class.new(super(other).to_s)
137
+ end
138
+
139
+ # Return mixed_path where delimiter is '/'
140
+ # @return [String]
141
+ def mixed_path(string)
142
+ string.tr('\\', '/')
143
+ end
144
+ private :mixed_path
145
+
146
+ # Return path suitable for windows apps. In Unix this method overridden
147
+ # @return [String]
148
+ def win_string
149
+ to_s.tr('/', '\\')
150
+ end
151
+
152
+ # Override (Pathname.glob) method for correct work with windows paths
153
+ # like a '\\\\host\\share', 'C:\\' and Cygwin paths like a '/cygdrive/c'
154
+ # @param (see Pathname.glob)
155
+ # @return [Array<PathnameExt>]
156
+ def self.glob(p1, *args)
157
+ super p1.tr('\\', '/'), *args
158
+ end
159
+
160
+ # Class for MinGW Ruby
161
+ class WinPath < PathnameExt; end
162
+
163
+ # Class for Unix Ruby
164
+ class UnixPath < PathnameExt
165
+ # (see PathnameExt#win_string)
166
+ def win_string
167
+ to_s
168
+ end
169
+ end
170
+
171
+ # Class for Cygwin Ruby
172
+ class CygPath < PathnameExt
173
+ # (cee PathnameExt#mixed_path)
174
+ def mixed_path(string)
175
+ cygpath(string, :m)
176
+ end
177
+
178
+ # (see PathnameExt.glob)
179
+ def self.glob(p1, *args)
180
+ super cygpath(p1, :u), *args
181
+ end
182
+
183
+ def self.cygpath(p1, flag)
184
+ fail ArgumentError, 'Only accepts :w | :m | :u flags'\
185
+ unless %w(w m u).include? flag.to_s
186
+ # TODO, extract shell call into Shell module
187
+ out = `cygpath -#{flag} #{p1.escape} 2>&1`.chomp
188
+ fail Shell::RunError, out unless exitstatus == 0
189
+ out
190
+ end
191
+
192
+ # TODO, extract shell call into Shell module
193
+ def self.exitstatus
194
+ # rubocop:disable all
195
+ fail Shell::Error, 'Unexpected $?.nil?' if $?.nil?
196
+ $?.exitstatus
197
+ # rubocop:enable all
198
+ end
199
+ private_class_method :exitstatus
200
+
201
+ def cygpath(p1, flag)
202
+ self.class.cygpath(p1, flag)
203
+ end
204
+ end
205
+ end
206
+
207
+ # Return suitable class
208
+ # @return [UnixEnv | WinEnv | CygEnv]
209
+ def self.env
210
+ if cygwin?
211
+ CygEnv
212
+ elsif windows?
213
+ WinEnv
214
+ else
215
+ UnixEnv
216
+ end
217
+ end
218
+
219
+ # Wrapper for ENV in Unix Ruby
220
+ class UnixEnv
221
+ # Return values ENV on regex
222
+ def self.[](regex)
223
+ ENV.map { |k, v| v if k =~ regex }.compact
224
+ end
225
+ end
226
+ # Wrapper for ENV in Cygwin Ruby
227
+ class CygEnv < UnixEnv; end
228
+ # Wrapper for ENV in MinGw Ruby
229
+ class WinEnv < UnixEnv; end
230
+ end
231
+ end
232
+ end