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