lib/ass_launcher/enterprise/binary_wrapper.rb
# encoding: utf-8
module AssLauncher
#
module Enterprise
require 'ass_launcher/enterprise/cli'
# Class for wrapping 1C:Enterprise platform binary executables such as
# 1cv8.exe and 1cv8c.exe.
# Class makes it easy to juggle with the different versions of
# 1C:Enterprise installations
#
# @abstract
# @api private
# @note (see #version)
# @note (see #arch)
class BinaryWrapper
include AssLauncher::Support::Platforms
attr_reader :path
V64_FILES = %w{version64.dat version64.txt}
# @api public
X86_64 = 'x86_64'
# @api public
I386 = 'i386'
def initialize(binpath)
@path = platform.path(binpath).realpath
fail ArgumentError, "Is not a file `#{binpath}'" unless @path.file?
fail ArgumentError,
"Invalid binary #{@path.basename} for #{self.class}" unless\
@path.basename.to_s.upcase == expects_basename.upcase
end
# Define version of 1C platform.
# @note In Windows version parsed from path and may
# content incorrect value - not
# real 1C platform version see {#extract_version}. For platform > 8.2
# if 1C platform instaled in standart directories it works correctly.
#
# In linux version returns from method
# {AssLauncher::Support::Linux.get_pkg_version}
#
# @api public
# @return [Gem::Version]
def version
@version ||= extract_version(path.to_s)
end
# Define arch on 1C platform.
# @api public
# @return [String] {X86_64} or {I386}
def arch
@arch ||= extract_arch
end
# True if {#arch} == {X86_64}
# @api public
def x86_64?
arch == X86_64
end
# Extract version from path
# @note
# - In windows 1C V > 8.2 default install into path:
# +bla/1cv?/8.3.8.1502/bin/1cv8.exe+
# - In Linux 1V default install into path:
# +/opt/1C/v8.3/i386/1cv8+
def extract_version(realpath)
return AssLauncher::Support::Linux.get_pkg_version(realpath) if\
linux?
extracted = realpath.to_s.split('/')[-3]
Gem::Version.new v8(extracted)
end
private :extract_version
def v8(extracted)
return '8.1.0.0' if extracted =~ %r{1cv81}i
extracted =~ /(\d+\.\d+\.?\d*\.?\d*)/i
(Regexp.last_match(1).to_s.split('.') + [0, 0, 0, 0])[0, 4].join('.')
end
private :v8
# Extract arch from path
# @note (see #extract_version)
def extract_arch
if linux?
extracted = path.to_s.split('/')[-2]
else
extracted = version64? ? X86_64 : I386
end
extracted
end
private :extract_arch
def version64?
V64_FILES.each do |f|
return true if version64_exist?(f)
end
false
end
private :version64?
def version64_exist?(file)
File.exist?(File.join(path.dirname, file))
end
private :version64_exist?
# Compare wrappers on version for sortable
# @param other [BinaryWrapper]
# @return [Bollean]
# @api public
def <=>(other)
version <=> other.version
end
def expects_basename
Enterprise.binaries(self.class)
end
private :expects_basename
# True if file exsists
# @api public
def exists?
path.file?
end
# Return 2 major digits from version
# @return [String]
# @api public
def major_v
version.to_s.split('.')[0, 2].join('.')
end
# Convert to {AssLauncher::Support::Shell::Command} instance
# @param args (see AssLauncher::Support::Shell::Command#initialize)
# @option options (see AssLauncher::Support::Shell::Command#initialize)
# @return [AssLauncher::Support::Shell::Command]
def to_command(args = [], options = {})
AssLauncher::Support::Shell::Command.new(path.to_s, args, options)
end
private :to_command
# Convert to {AssLauncher::Support::Shell::Script} instance
# @param args [String] string arguments for run 1C binary wrapped in
# +cmd.exe+ or +sh+ script like as: +'/Arg1 "Value" /Arg2 "value"'+
# @option options (see AssLauncher::Support::Shell::Script#initialize)
# @return [AssLauncher::Support::Shell::Script]
def to_script(args = '', options = {})
AssLauncher::Support::Shell::Script\
.new("#{path.win_string.to_cmd} #{args}", options)
end
private :to_script
def fail_if_wrong_mode(run_mode)
fail ArgumentError,
"Invalid run_mode `#{run_mode}' for #{self.class}" unless\
run_modes.include? run_mode
run_mode
end
private :fail_if_wrong_mode
# @param run_mode [Symbol]
# Valid values define in the {#run_modes}
# @raise [ArgumentError]
# @return [String] run mode for run 1C binary
def mode(run_mode)
fail_if_wrong_mode(run_mode).to_s.upcase
end
private :mode
# @api public
# @return (see Cli.defined_modes_for)
def self.run_modes
Cli.defined_modes_for(self)
end
# @api public
# @return (see Cli.defined_modes_for)
def run_modes
self.class.run_modes
end
# @api public
# @return [Cli::CliSpec]
def cli_spec
@cli_spec ||= Cli::CliSpec.for(self)
end
def build_args(run_mode, &block)
Cli::ArgumentsBuilder.build_args(self, run_mode, &block)
end
private :build_args
# Wrapper for 1C thick client binary
# @api public
# @example (see #script)
# @example (see #command)
#
class ThickClient < BinaryWrapper
# (see ThinClient#accepted_connstr)
def accepted_connstr
[:file, :server]
end
# Run 1C:Enterprise client as command.
# @note For correct pass cli parameters
# to 1C:Enterprise binary, you can passes block. Block will be eval in
# instance of {Cli::ArgumentsBuilder}. +ArgumentsBuilder+ use
# {Cli::CliSpec} and verify parameters and prameters values.
# Also you can pass arguments directly, without verify, uses +args+
# array.
#
# @note Command not wait while 1C:Enterprise execution. You can
# manipulate with many 1C clients runned at once.
#
# @param run_mode [Symbol] run mode 1C binary. It will be puts fierst
# parameter in +args+
# @param args (see BinaryWrapper#to_command)
# @option options (see BinaryWrapper#to_command)
# @return (see #to_command)
# @example
#
# # Get 1C:Enterprise last release for 8.3.6 version:
#
# cl = AssLauncher::Enterprise.thick_clients('~> 8.3.6').last
# raise 'Can\'t find 1C binary' if cl.nil?
#
# @example
#
# # Run 1C:Enterprise designer
# # Directly pass parameters:
#
# args = ['/F', 'path/to/file/infobase', '/L', 'en']
# ph = cl.command(:designer, args).run
#
# ph.wait.result.assout # => "Infobase not found!"
# ph.result.exitstatus # => 0
#
# # Fucking 1C: "Infobase not found!" but exit with 0 ????
#
# @example
#
# # Dump infobase
# # Directly pass parameters:
#
# args = ['/F', 'path/infobase', '/DumpIB', 'dump/path/file.dt', '/L',
# 'en']
# cm = cl.command(:designer, args)
#
# cm.run.wait.result.verify!
# #=> RunAssResult::RunAssError: Infobase not found!
#
# @example
#
# # Dump infobase
# # Uses Cli::ArgumentsBuilder:
#
# conn_str = AssLauncher::Support::ConnectionString.\
# new('File="//host/infobase"')
#
# command = cl.command(:designer) do
# connection_string conn_str
# DumpIB './infobase.dt'
# end
# ph = command.run.wait
#
# ph.result.verify!
# @example
# # Open thick client and attache into debuger
#
# conn_str = AssLauncher::Support::ConnectionString.\
# new('srvr="localhost"; ref="infobase"')
#
# command = cl.command(:enterprise) do
# connection_string conn_str
# debug
# debuggerUrl 'localhost'
# end
# ph = command.run.wait # Fucking 1C: If infobase not exists
# #will be opened GUI window with info similar 'Inforamation base
# #not found. Create new?" and exit whith status 0
#
# #Fucking 1C:
# #USES GUI DIALOG FOR ERROR REPORTING WHEN RUN IN NO GUI MODE
def command(run_mode, args = [], **options, &block)
args_ = args.dup
args_.unshift mode(run_mode)
args_ += build_args(run_mode, &block) if block_given?
verify_createinfobase_param_order! args_ if\
run_mode == :createinfobase
to_command(args_, options)
end
# Fucking 1C not check CLI parameter
# In create infobase mode create default infobase in user profile
# directory if the first parameter is not connection string!!!
def verify_createinfobase_param_order!(args)
cs = parse_cs args[1]
fail ArgumentError, ':createinfobase expects file or server'\
" connection string in first argument but given `#{args[1]}'" unless\
good_cs?(cs)
args
end
private :verify_createinfobase_param_order!
def good_cs?(cs)
return false unless cs
cs.is?(:file) || cs.is?(:server)
end
private :good_cs?
def parse_cs(string)
AssLauncher::Support::ConnectionString\
.new(string.to_s.tr('\'', '"'))
rescue AssLauncher::Support::ConnectionString::ParseError
nil
end
private :parse_cs
# Run 1C:Enterprise client as cmd or shell script.
# @note It waiting for script
# execution.
# @note It not use arguments builder and not expects of block.
# Arguments string make as you want
#
# @param run_mode (see #command)
# @param args (see #to_script)
# @option options (see #to_script)
# @example
#
# cl = AssLauncher::Enterprise.thick_clients('~> 8.3.6').last
# script = cl.script(:createinfobase, 'File="path\\new.ib"')
# ph = script.run # this waiting until process executing
# ph.result.expected_assout = /\("File="path\\new.ib";.*"\)/i
# ph.result.verify!
#
# @return (see #to_script)
def script(run_mode, args = '', **options)
args_ = "#{mode(run_mode)} #{args}"
to_script(args_, options)
end
end
# Wrapper for 1C thin client binary
# @api public
# @example (see #script)
# @example (see #command)
#
class ThinClient < ThickClient
# Define type of connection_string
# suitable for 1C binary
# @return [Array<Symbol>]
def accepted_connstr
[:file, :server, :http]
end
# Run 1C:Enterprise client as command.
# @note (see ThickClient#command)
# @param args (see ThickClient#command)
# @option options (see ThickClient#command)
# @return (see ThickClient#command)
# @example
#
# cl = AssLauncher::Enterprise.thin_clients('~> 8.3.6').last
# args = ['/F', 'path/to/file/infobase']
# ph = cl.command(args).run # Fucking 1C: If infobase not exists
# #will be opened GUI window with error info similar 'Inforamation base
# #not found"
#
# ph.wait # => waiting wile execiting
# ph.result.exitstatus # => 0
#
# # Uses Cli::ArgumentsBuilder:
#
# conn_str = AssLauncher::Support::ConnectionString.\
# new('File="//host/infobase"')
#
# command = cl.command do
# connection_string conn_str
# debug
# debuggerUrl 'localhost'
# end
# ph = command.run.wait # Fucking 1C: If infobase not exists
# #will be opened GUI window with error info similar 'Inforamation base
# #not found"
#
def command(args = [], **options, &block)
super(:enterprise, args, options, &block)
end
# Run 1C:Enterprise client as cmd or shell script.
# @note (see ThickClient#script)
# @param args (see ThickClient#script)
# @option options (see ThickClient#script)
# @return (see ThickClient#script)
# @example
# cl = AssLauncher::Enterprise.thin_clients('~> 8.3.6').last
# script = cl.script('File="path\\new.ib"')
# ph = script.run # this waiting until process executing
# # Fucking 1C: if infobase not exists will be opened GUI window
# #for infobase choice!
def script(args = '', **options)
super(:enterprise, args, options)
end
end
end
end
end