parent
92f77aceca
commit
d0da95dfc5
@ -0,0 +1,152 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
# info: action with extended modules
|
||||||
|
# options: COMMAND [COMMAND_OPTION | FORMAT] [FORMAT]
|
||||||
|
#
|
||||||
|
# example: v-ext-modules list json
|
||||||
|
#
|
||||||
|
# This function enables and disables additional modules
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Variables & Functions #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
# Argument definition
|
||||||
|
v_command = ARGV[0]
|
||||||
|
v_ext_option = ARGV[1]
|
||||||
|
v_format = ARGV[2]
|
||||||
|
|
||||||
|
require "/usr/local/hestia/func_ruby/global_options"
|
||||||
|
|
||||||
|
load_ruby_options_defaults
|
||||||
|
$HESTIA = load_hestia_default_path_from_env
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "modules"
|
||||||
|
|
||||||
|
hestia_check_privileged_user
|
||||||
|
|
||||||
|
load_global_bash_variables "/etc/hestiacp/hestia.conf"
|
||||||
|
if $HESTIA.nil?
|
||||||
|
hestia_print_error_message_to_cli "Can't find HESTIA base path"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Verifications #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
check_args 1, ARGV, "COMMAND [COMMAND_OPTION] [ACTION]"
|
||||||
|
|
||||||
|
# Perform verification if read-only mode is enabled
|
||||||
|
check_hestia_demo_mode
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Action #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
case v_command.to_sym
|
||||||
|
when :list, :state
|
||||||
|
info = []
|
||||||
|
pm = PluginManager.new
|
||||||
|
if v_command.to_sym == :state
|
||||||
|
if v_ext_option.nil?
|
||||||
|
hestia_print_error_message_to_cli "no module name specified"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
load_module = v_ext_option.to_s.strip.split(",")[0].to_s.strip
|
||||||
|
pm.load_plugins(nil, load_module)
|
||||||
|
else
|
||||||
|
pm.load_plugins
|
||||||
|
end
|
||||||
|
pm.get_loaded_plugins.each_key do |mod|
|
||||||
|
next if mod == "default"
|
||||||
|
|
||||||
|
inst = pm.get_instance(mod)
|
||||||
|
if inst.key != pm.get_key
|
||||||
|
hestia_print_error_message_to_cli "incorrect module with incorrect rights #{mod}"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
info_result = inst.info
|
||||||
|
info_result[:STATE] = hestia_ext_module_state_in_conf(info_result[:NAME], :get)
|
||||||
|
info << info_result
|
||||||
|
end
|
||||||
|
result_arr = info.sort do |a, b|
|
||||||
|
if a[:ID] < b[:ID]
|
||||||
|
-1
|
||||||
|
elsif a[:ID] > b[:ID]
|
||||||
|
1
|
||||||
|
else
|
||||||
|
a[:ID] < b[:ID]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if v_command.to_sym == :state
|
||||||
|
format = (v_format.nil? ? "shell" : v_format.strip)
|
||||||
|
else
|
||||||
|
format = (v_ext_option.nil? ? "shell" : v_ext_option.strip)
|
||||||
|
end
|
||||||
|
hestia_print_array_of_hashes(result_arr, format, "ID, NAME, DESCR, STATE, REQ")
|
||||||
|
when :enable
|
||||||
|
if v_ext_option.nil?
|
||||||
|
hestia_print_error_message_to_cli "no module name specified"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
pm = PluginManager.new
|
||||||
|
load_module = v_ext_option.to_s.strip.split(",")[0].to_s.strip
|
||||||
|
pm.load_plugins(nil, load_module)
|
||||||
|
if pm.get_loaded_plugins.key? load_module
|
||||||
|
if hestia_ext_module_state_in_conf(load_module, :get) == "disabled"
|
||||||
|
inst = pm.get_instance(load_module)
|
||||||
|
result = inst.enable()
|
||||||
|
if result == ""
|
||||||
|
hestia_ext_module_state_in_conf(load_module, :enable)
|
||||||
|
log_event OK, $ARGUMENTS
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "module #{load_module} return error #{result}"
|
||||||
|
log_event E_MODULE, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "no module with name #{load_module} found"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
when :disable
|
||||||
|
if v_ext_option.nil?
|
||||||
|
hestia_print_error_message_to_cli "no module name specified"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
pm = PluginManager.new
|
||||||
|
load_module = v_ext_option.to_s.strip.split(",")[0].to_s.strip
|
||||||
|
pm.load_plugins(nil, load_module)
|
||||||
|
if pm.get_loaded_plugins.key? load_module
|
||||||
|
if hestia_ext_module_state_in_conf(load_module, :get) == "enabled"
|
||||||
|
inst = pm.get_instance(load_module)
|
||||||
|
result = inst.disable()
|
||||||
|
if result == ""
|
||||||
|
hestia_ext_module_state_in_conf(load_module, :disable)
|
||||||
|
log_event OK, $ARGUMENTS
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "module #{load_module} return error #{result}"
|
||||||
|
log_event E_MODULE, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "no module with name #{load_module} found"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "unknown command"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
@ -0,0 +1,82 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
# info: action with extended modules
|
||||||
|
# options: MODULE_ID [MODULE_RELATED_COMMNDS]
|
||||||
|
#
|
||||||
|
# example: v-ext-modules list json
|
||||||
|
#
|
||||||
|
# This function enables and disables additional modules
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Variables & Functions #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
# Argument definition
|
||||||
|
v_id = ARGV[0]
|
||||||
|
|
||||||
|
require "/usr/local/hestia/func_ruby/global_options"
|
||||||
|
|
||||||
|
load_ruby_options_defaults
|
||||||
|
$HESTIA = load_hestia_default_path_from_env
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "modules"
|
||||||
|
|
||||||
|
hestia_check_privileged_user
|
||||||
|
|
||||||
|
load_global_bash_variables "/etc/hestiacp/hestia.conf"
|
||||||
|
if $HESTIA.nil?
|
||||||
|
hestia_print_error_message_to_cli "Can't find HESTIA base path"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
load_global_bash_variables "#{$HESTIA}/conf/hestia.conf"
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Verifications #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
check_args 1, ARGV, "MODULE_ID [MODULE_RELATED_COMMNDS]"
|
||||||
|
|
||||||
|
# Perform verification if read-only mode is enabled
|
||||||
|
check_hestia_demo_mode
|
||||||
|
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
# Action #
|
||||||
|
#----------------------------------------------------------#
|
||||||
|
|
||||||
|
if v_id.nil?
|
||||||
|
hestia_print_error_message_to_cli "no module name specified"
|
||||||
|
log_event E_ARGS, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
pm = PluginManager.new
|
||||||
|
load_module = v_id.strip
|
||||||
|
pm.load_plugins(nil, load_module)
|
||||||
|
if pm.get_loaded_plugins.key? load_module
|
||||||
|
if hestia_ext_module_state_in_conf(load_module, :get) == "enabled"
|
||||||
|
inst = pm.get_instance(load_module)
|
||||||
|
NEW_ARGV = if ARGV.length > 0
|
||||||
|
ARGV.drop(1)
|
||||||
|
else
|
||||||
|
ARGV
|
||||||
|
end
|
||||||
|
result = inst.command(NEW_ARGV)
|
||||||
|
if result == ""
|
||||||
|
log_event OK, $ARGUMENTS
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "module #{load_module} return error #{result}"
|
||||||
|
log_event E_MODULE, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "module #{load_module} disabled"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "no module with name #{load_module} found"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
@ -0,0 +1,2 @@
|
|||||||
|
---
|
||||||
|
BUNDLE_PATH: "gems"
|
@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "envbash"
|
||||||
|
gem "interface"
|
||||||
|
gem "shell"
|
@ -0,0 +1,22 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
e2mmap (0.1.0)
|
||||||
|
envbash (1.0.1)
|
||||||
|
interface (1.0.5)
|
||||||
|
shell (0.8.1)
|
||||||
|
e2mmap
|
||||||
|
sync
|
||||||
|
sync (0.5.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
x86_64-linux
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
envbash
|
||||||
|
interface
|
||||||
|
shell
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.5.16
|
@ -0,0 +1 @@
|
|||||||
|
0.0.1
|
@ -0,0 +1,30 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
class EmptyWorker < Kernel::ModuleCoreWorker
|
||||||
|
MODULE_ID = "empty_module"
|
||||||
|
|
||||||
|
def info
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
NAME: MODULE_ID,
|
||||||
|
DESCR: "Just empty module for storing max module id",
|
||||||
|
REQ: "",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
implements IPluginInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
module EmptyModule
|
||||||
|
def get_object
|
||||||
|
Proc.new { EmptyWorker.new }
|
||||||
|
end
|
||||||
|
|
||||||
|
module_function :get_object
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kernel::PluginConfiguration
|
||||||
|
include EmptyModule
|
||||||
|
|
||||||
|
@@loaded_plugins[EmptyWorker::MODULE_ID] = EmptyModule.get_object
|
||||||
|
end
|
@ -0,0 +1,108 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
require "shell"
|
||||||
|
|
||||||
|
class PassengerWorker < Kernel::ModuleCoreWorker
|
||||||
|
MODULE_ID = "passenger_manager"
|
||||||
|
|
||||||
|
def check_domains_with_passenger
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def info
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
NAME: MODULE_ID,
|
||||||
|
DESCR: "Added passenger support for nginx",
|
||||||
|
REQ: "puppet_installer",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable
|
||||||
|
log_file = get_log
|
||||||
|
f_inst_pp = get_module_paydata("passenger_installer.pp")
|
||||||
|
f_uninst_pp = get_module_paydata("passenger_uninstaller.pp")
|
||||||
|
if !check
|
||||||
|
inf = info
|
||||||
|
log("Req error, needed #{inf[:REQ]}")
|
||||||
|
"Req error, needed #{inf[:REQ]}"
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
log("install packages for passenger + nginx support: /usr/bin/puppet apply --detailed-exitcodes #{f_inst_pp}")
|
||||||
|
result_action = `/usr/bin/puppet apply --detailed-exitcodes "#{f_inst_pp}"`
|
||||||
|
ex_status = $?.exitstatus
|
||||||
|
if ex_status.to_i == 0 || ex_status.to_i == 2
|
||||||
|
log(result_action)
|
||||||
|
super
|
||||||
|
else
|
||||||
|
log(result_action)
|
||||||
|
log("Try to disable action: /usr/bin/puppet apply --detailed-exitcodes #{f_uninst_pp}")
|
||||||
|
result_action = `/usr/bin/puppet apply --detailed-exitcodes "#{f_uninst_pp}"`
|
||||||
|
"module installation error. See log #{log_file}"
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
log("module installation error #{e.message} #{e.backtrace.first}")
|
||||||
|
"module installation error. See log #{log_file}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable
|
||||||
|
log_file = get_log
|
||||||
|
f_uninst_pp = get_module_paydata("passenger_uninstaller.pp")
|
||||||
|
if !check_domains_with_passenger
|
||||||
|
return "Presents domains with passenger support disable it first"
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
log("uninstall packages for passenger + nginx support")
|
||||||
|
log("Try to disable action: /usr/bin/puppet apply --detailed-exitcodes #{f_uninst_pp}")
|
||||||
|
result_action = `/usr/bin/puppet apply --detailed-exitcodes "#{f_uninst_pp}"`
|
||||||
|
ex_status = $?.exitstatus
|
||||||
|
if ex_status.to_i == 0 || ex_status.to_i == 2
|
||||||
|
log(result_action)
|
||||||
|
super
|
||||||
|
else
|
||||||
|
log(result_action)
|
||||||
|
"module installation error. See log #{log_file}"
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
log("module installation error #{e.message} #{e.backtrace.first}")
|
||||||
|
"module installation error. See log #{log_file}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_default_ruby_conf()
|
||||||
|
#TODO
|
||||||
|
end
|
||||||
|
|
||||||
|
def command(args)
|
||||||
|
if args.length < 1
|
||||||
|
log("Not enough arguments. Needed command")
|
||||||
|
"Not enough arguments. Needed command"
|
||||||
|
end
|
||||||
|
m_command = args[0].strip
|
||||||
|
case m_command
|
||||||
|
when "get_rubys"
|
||||||
|
#TODO
|
||||||
|
else
|
||||||
|
log("Unknown commands. #{args}")
|
||||||
|
"Unknown commands. #{args}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
implements IPluginInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
module PassengerModule
|
||||||
|
def get_object
|
||||||
|
Proc.new { PassengerWorker.new }
|
||||||
|
end
|
||||||
|
|
||||||
|
module_function :get_object
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kernel::PluginConfiguration
|
||||||
|
include PassengerModule
|
||||||
|
|
||||||
|
@@loaded_plugins[PassengerWorker::MODULE_ID] = PassengerModule.get_object
|
||||||
|
end
|
@ -0,0 +1,29 @@
|
|||||||
|
package { 'passenger':
|
||||||
|
ensure => installed,
|
||||||
|
name => 'passenger',
|
||||||
|
provider => 'dnf',
|
||||||
|
}
|
||||||
|
-> package { 'nginx-passenger':
|
||||||
|
ensure => installed,
|
||||||
|
name => 'nginx-mod-http-passenger',
|
||||||
|
provider => 'dnf',
|
||||||
|
}
|
||||||
|
-> file { 'passenger.conf':
|
||||||
|
ensure => file,
|
||||||
|
path => '/etc/nginx/conf.d/passenger.conf',
|
||||||
|
content => 'passenger_root /usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini;
|
||||||
|
passenger_ruby /usr/bin/ruby;
|
||||||
|
passenger_instance_registry_dir /var/run/passenger-instreg;
|
||||||
|
passenger_user_switching on;',
|
||||||
|
}
|
||||||
|
-> file { 'passenger_includer.conf':
|
||||||
|
ensure => file,
|
||||||
|
content => 'load_module modules/ngx_http_passenger_module.so;',
|
||||||
|
path => '/etc/nginx/conf.d/main/passenger.conf',
|
||||||
|
}
|
||||||
|
~> service { 'nginx_service':
|
||||||
|
ensure => running,
|
||||||
|
name => 'nginx',
|
||||||
|
provider => 'systemd',
|
||||||
|
hasrestart => true,
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package { 'nginx-passenger':
|
||||||
|
ensure => absent,
|
||||||
|
name => 'nginx-mod-http-passenger',
|
||||||
|
provider => 'dnf',
|
||||||
|
}
|
||||||
|
-> package { 'passenger':
|
||||||
|
ensure => absent,
|
||||||
|
name => 'passenger',
|
||||||
|
provider => 'dnf',
|
||||||
|
}
|
||||||
|
-> file { 'passenger.conf':
|
||||||
|
ensure => absent,
|
||||||
|
path => '/etc/nginx/conf.d/passenger.conf',
|
||||||
|
}
|
||||||
|
-> file { 'passenger_includer.conf':
|
||||||
|
ensure => absent,
|
||||||
|
path => '/etc/nginx/conf.d/main/passenger.conf',
|
||||||
|
}
|
||||||
|
~> service { 'nginx_service':
|
||||||
|
ensure => running,
|
||||||
|
name => 'nginx',
|
||||||
|
provider => 'systemd',
|
||||||
|
hasrestart => true,
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
require "shell"
|
||||||
|
require "date"
|
||||||
|
|
||||||
|
class PuppetWorker < Kernel::ModuleCoreWorker
|
||||||
|
MODULE_ID = "puppet_installer"
|
||||||
|
|
||||||
|
def info
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
NAME: MODULE_ID,
|
||||||
|
DESCR: "Added puppet support, needed for another modules",
|
||||||
|
REQ: "",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable
|
||||||
|
log_file = get_log
|
||||||
|
date = DateTime.now
|
||||||
|
bkp_name = date.strftime("%Y_%m_%d_%H_%M_%S")
|
||||||
|
if !check
|
||||||
|
inf = info
|
||||||
|
log("Req error, needed #{inf[:REQ]}")
|
||||||
|
"Req error, needed #{inf[:REQ]}"
|
||||||
|
else
|
||||||
|
Shell.def_system_command("dnf", "/usr/bin/dnf")
|
||||||
|
Shell.def_system_command("gem", "/usr/bin/gem")
|
||||||
|
Shell.verbose = true
|
||||||
|
Shell.debug = false
|
||||||
|
sh = Shell.new
|
||||||
|
begin
|
||||||
|
%x( /usr/bin/rpm -q puppet )
|
||||||
|
unless $?.success?
|
||||||
|
log("install puppet packages")
|
||||||
|
sh.transact do
|
||||||
|
dnf("install", "-y", "puppet", "ruby", "rubygems", "puppet-stdlib") > log_file
|
||||||
|
gem("cleanup", "thor") > log_file
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log("puppet installed")
|
||||||
|
end
|
||||||
|
log("prepare puppet configuration")
|
||||||
|
if File.exist?("/etc/puppet/puppet.conf")
|
||||||
|
File.rename("/etc/puppet/puppet.conf", "/etc/puppet/puppet.conf.#{bkp_name}")
|
||||||
|
end
|
||||||
|
puppet_conf = <<~CONF
|
||||||
|
[main]
|
||||||
|
confdir=/etc/puppet
|
||||||
|
logdir=/var/log/puppet
|
||||||
|
vardir=/var/lib/puppet
|
||||||
|
ssldir=/var/lib/puppet/ssl
|
||||||
|
rundir=/var/run/puppet
|
||||||
|
factpath=$confdir/facter
|
||||||
|
environmentpath=$confdir/environments
|
||||||
|
basemodulepath=/usr/share/puppet/modules
|
||||||
|
default_manifest=$confdir/manifests
|
||||||
|
environment_timeout = unlimited
|
||||||
|
manifests_path =$confdir/manifests
|
||||||
|
CONF
|
||||||
|
File.open("/etc/puppet/puppet.conf", "w") do |f|
|
||||||
|
f.puts(puppet_conf)
|
||||||
|
end
|
||||||
|
log("prepare hiera configuration")
|
||||||
|
if File.exist?("/etc/puppet/hiera.yaml")
|
||||||
|
File.rename("/etc/puppet/hiera.yaml", "/etc/puppet/hiera.yaml.#{bkp_name}")
|
||||||
|
end
|
||||||
|
hiera_conf = <<~CONF
|
||||||
|
---
|
||||||
|
version: 5
|
||||||
|
hierarchy:
|
||||||
|
- name: "yaml"
|
||||||
|
datadir: /tmp/puppet/hieradata
|
||||||
|
# data is staged to a local directory by the puppet-manifest-apply.sh script
|
||||||
|
data_hash: yaml_data
|
||||||
|
paths:
|
||||||
|
- runtime.yaml
|
||||||
|
- host.yaml
|
||||||
|
- secure_system.yaml
|
||||||
|
- system.yaml
|
||||||
|
- secure_static.yaml
|
||||||
|
- static.yaml
|
||||||
|
- personality.yaml
|
||||||
|
- global.yaml
|
||||||
|
CONF
|
||||||
|
File.open("/etc/puppet/hiera.yaml", "w") do |f|
|
||||||
|
f.puts(hiera_conf)
|
||||||
|
end
|
||||||
|
log("create manifests directory")
|
||||||
|
sh.transact do
|
||||||
|
((mkdir("/etc/puppet/manifests")) > log_file) unless File.exist?("/etc/puppet/manifests")
|
||||||
|
end
|
||||||
|
super
|
||||||
|
rescue => e
|
||||||
|
log("module installation error #{e.message} #{e.backtrace.first}")
|
||||||
|
"module installation error. See log #{log_file}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
implements IPluginInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
module PuppetModule
|
||||||
|
def get_object
|
||||||
|
Proc.new { PuppetWorker.new }
|
||||||
|
end
|
||||||
|
|
||||||
|
module_function :get_object
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kernel::PluginConfiguration
|
||||||
|
include PuppetModule
|
||||||
|
|
||||||
|
@@loaded_plugins[PuppetWorker::MODULE_ID] = PuppetModule.get_object
|
||||||
|
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,6 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in e2mmap.gemspec
|
||||||
|
gemspec
|
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
@ -0,0 +1,85 @@
|
|||||||
|
# Exception2MessageMapper
|
||||||
|
|
||||||
|
Helper module for easily defining exceptions with predefined messages.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'e2mmap'
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
$ bundle
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
$ gem install e2mmap
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1.
|
||||||
|
|
||||||
|
```
|
||||||
|
class Foo
|
||||||
|
extend Exception2MessageMapper
|
||||||
|
def_e2message ExistingExceptionClass, "message..."
|
||||||
|
def_exception :NewExceptionClass, "message..."[, superclass]
|
||||||
|
...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
2.
|
||||||
|
|
||||||
|
```
|
||||||
|
module Error
|
||||||
|
extend Exception2MessageMapper
|
||||||
|
def_e2message ExistingExceptionClass, "message..."
|
||||||
|
def_exception :NewExceptionClass, "message..."[, superclass]
|
||||||
|
...
|
||||||
|
end
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
include Error
|
||||||
|
...
|
||||||
|
end
|
||||||
|
|
||||||
|
foo = Foo.new
|
||||||
|
foo.Fail ....
|
||||||
|
```
|
||||||
|
|
||||||
|
3.
|
||||||
|
|
||||||
|
```
|
||||||
|
module Error
|
||||||
|
extend Exception2MessageMapper
|
||||||
|
def_e2message ExistingExceptionClass, "message..."
|
||||||
|
def_exception :NewExceptionClass, "message..."[, superclass]
|
||||||
|
...
|
||||||
|
end
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
extend Exception2MessageMapper
|
||||||
|
include Error
|
||||||
|
...
|
||||||
|
end
|
||||||
|
|
||||||
|
Foo.Fail NewExceptionClass, arg...
|
||||||
|
Foo.Fail ExistingExceptionClass, arg...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||||
|
|
||||||
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/e2mmap.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause).
|
@ -0,0 +1 @@
|
|||||||
|
require "bundler/gem_tasks"
|
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require "bundler/setup"
|
||||||
|
require "e2mmap"
|
||||||
|
|
||||||
|
# You can add fixtures and/or initialization code here to make experimenting
|
||||||
|
# with your gem easier. You can also use a different console, if you like.
|
||||||
|
|
||||||
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||||
|
# require "pry"
|
||||||
|
# Pry.start
|
||||||
|
|
||||||
|
require "irb"
|
||||||
|
IRB.start(__FILE__)
|
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
set -vx
|
||||||
|
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
# Do any other automated setup that you need to do here
|
@ -0,0 +1,26 @@
|
|||||||
|
begin
|
||||||
|
require_relative "lib/e2mmap/version"
|
||||||
|
rescue LoadError
|
||||||
|
# for Ruby core repository
|
||||||
|
require_relative "e2mmap/version"
|
||||||
|
end
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "e2mmap"
|
||||||
|
spec.version = Exception2MessageMapper::VERSION
|
||||||
|
spec.authors = ["Keiju ISHITSUKA"]
|
||||||
|
spec.email = ["keiju@ruby-lang.org"]
|
||||||
|
|
||||||
|
spec.summary = %q{Module for defining custom exceptions with specific messages.}
|
||||||
|
spec.description = %q{Module for defining custom exceptions with specific messages.}
|
||||||
|
spec.homepage = "https://github.com/ruby/e2mmap"
|
||||||
|
spec.license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
spec.files = [".gitignore", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "e2mmap.gemspec", "lib/e2mmap.rb", "lib/e2mmap/version.rb"]
|
||||||
|
spec.bindir = "exe"
|
||||||
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
|
spec.add_development_dependency "bundler", "~> 1.16"
|
||||||
|
spec.add_development_dependency "rake", "~> 10.0"
|
||||||
|
end
|
@ -0,0 +1,177 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
#
|
||||||
|
#--
|
||||||
|
# e2mmap.rb - for Ruby 1.1
|
||||||
|
# $Release Version: 2.0$
|
||||||
|
# $Revision: 1.10 $
|
||||||
|
# by Keiju ISHITSUKA
|
||||||
|
#
|
||||||
|
#++
|
||||||
|
#
|
||||||
|
# Helper module for easily defining exceptions with predefined messages.
|
||||||
|
#
|
||||||
|
# == Usage
|
||||||
|
#
|
||||||
|
# 1.
|
||||||
|
# class Foo
|
||||||
|
# extend Exception2MessageMapper
|
||||||
|
# def_e2message ExistingExceptionClass, "message..."
|
||||||
|
# def_exception :NewExceptionClass, "message..."[, superclass]
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# 2.
|
||||||
|
# module Error
|
||||||
|
# extend Exception2MessageMapper
|
||||||
|
# def_e2message ExistingExceptionClass, "message..."
|
||||||
|
# def_exception :NewExceptionClass, "message..."[, superclass]
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# class Foo
|
||||||
|
# include Error
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# foo = Foo.new
|
||||||
|
# foo.Fail ....
|
||||||
|
#
|
||||||
|
# 3.
|
||||||
|
# module Error
|
||||||
|
# extend Exception2MessageMapper
|
||||||
|
# def_e2message ExistingExceptionClass, "message..."
|
||||||
|
# def_exception :NewExceptionClass, "message..."[, superclass]
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# class Foo
|
||||||
|
# extend Exception2MessageMapper
|
||||||
|
# include Error
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Foo.Fail NewExceptionClass, arg...
|
||||||
|
# Foo.Fail ExistingExceptionClass, arg...
|
||||||
|
#
|
||||||
|
#
|
||||||
|
module Exception2MessageMapper
|
||||||
|
|
||||||
|
E2MM = Exception2MessageMapper # :nodoc:
|
||||||
|
|
||||||
|
def E2MM.extend_object(cl)
|
||||||
|
super
|
||||||
|
cl.bind(self) unless cl < E2MM
|
||||||
|
end
|
||||||
|
|
||||||
|
def bind(cl)
|
||||||
|
self.module_eval "#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1
|
||||||
|
begin;
|
||||||
|
def Raise(err = nil, *rest)
|
||||||
|
Exception2MessageMapper.Raise(self.class, err, *rest)
|
||||||
|
end
|
||||||
|
alias Fail Raise
|
||||||
|
|
||||||
|
class << self
|
||||||
|
undef included
|
||||||
|
end
|
||||||
|
def self.included(mod)
|
||||||
|
mod.extend Exception2MessageMapper
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fail(err, *rest)
|
||||||
|
# err: exception
|
||||||
|
# rest: message arguments
|
||||||
|
#
|
||||||
|
def Raise(err = nil, *rest)
|
||||||
|
E2MM.Raise(self, err, *rest)
|
||||||
|
end
|
||||||
|
alias Fail Raise
|
||||||
|
alias fail Raise
|
||||||
|
|
||||||
|
# def_e2message(c, m)
|
||||||
|
# c: exception
|
||||||
|
# m: message_form
|
||||||
|
# define exception c with message m.
|
||||||
|
#
|
||||||
|
def def_e2message(c, m)
|
||||||
|
E2MM.def_e2message(self, c, m)
|
||||||
|
end
|
||||||
|
|
||||||
|
# def_exception(n, m, s)
|
||||||
|
# n: exception_name
|
||||||
|
# m: message_form
|
||||||
|
# s: superclass(default: StandardError)
|
||||||
|
# define exception named ``c'' with message m.
|
||||||
|
#
|
||||||
|
def def_exception(n, m, s = StandardError)
|
||||||
|
E2MM.def_exception(self, n, m, s)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Private definitions.
|
||||||
|
#
|
||||||
|
# {[class, exp] => message, ...}
|
||||||
|
@MessageMap = {}
|
||||||
|
|
||||||
|
# E2MM.def_e2message(k, e, m)
|
||||||
|
# k: class to define exception under.
|
||||||
|
# e: exception
|
||||||
|
# m: message_form
|
||||||
|
# define exception c with message m.
|
||||||
|
#
|
||||||
|
def E2MM.def_e2message(k, c, m)
|
||||||
|
E2MM.instance_eval{@MessageMap[[k, c]] = m}
|
||||||
|
c
|
||||||
|
end
|
||||||
|
|
||||||
|
# E2MM.def_exception(k, n, m, s)
|
||||||
|
# k: class to define exception under.
|
||||||
|
# n: exception_name
|
||||||
|
# m: message_form
|
||||||
|
# s: superclass(default: StandardError)
|
||||||
|
# define exception named ``c'' with message m.
|
||||||
|
#
|
||||||
|
def E2MM.def_exception(k, n, m, s = StandardError)
|
||||||
|
e = Class.new(s)
|
||||||
|
E2MM.instance_eval{@MessageMap[[k, e]] = m}
|
||||||
|
k.module_eval {remove_const(n)} if k.const_defined?(n, false)
|
||||||
|
k.const_set(n, e)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fail(klass, err, *rest)
|
||||||
|
# klass: class to define exception under.
|
||||||
|
# err: exception
|
||||||
|
# rest: message arguments
|
||||||
|
#
|
||||||
|
def E2MM.Raise(klass = E2MM, err = nil, *rest)
|
||||||
|
if form = e2mm_message(klass, err)
|
||||||
|
b = $@.nil? ? caller(1) : $@
|
||||||
|
b.shift if b[0] =~ /^#{Regexp.quote(__FILE__)}:/
|
||||||
|
raise err, sprintf(form, *rest), b
|
||||||
|
else
|
||||||
|
E2MM.Fail E2MM, ErrNotRegisteredException, err.inspect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class << E2MM
|
||||||
|
alias Fail Raise
|
||||||
|
end
|
||||||
|
|
||||||
|
def E2MM.e2mm_message(klass, exp)
|
||||||
|
for c in klass.ancestors
|
||||||
|
if mes = @MessageMap[[c,exp]]
|
||||||
|
m = klass.instance_eval('"' + mes + '"')
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
class << self
|
||||||
|
alias message e2mm_message
|
||||||
|
end
|
||||||
|
|
||||||
|
E2MM.def_exception(E2MM,
|
||||||
|
:ErrNotRegisteredException,
|
||||||
|
"not registered exception(%s)")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
module Exception2MessageMapper
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
@ -0,0 +1,18 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{json,rb,yaml,yml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Tab indents for Makefile
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
@ -0,0 +1,12 @@
|
|||||||
|
sudo: false
|
||||||
|
language: ruby
|
||||||
|
cache: bundler
|
||||||
|
rvm:
|
||||||
|
- 2.1
|
||||||
|
- 2.2
|
||||||
|
- 2.3.3
|
||||||
|
- 2.4.0
|
||||||
|
install:
|
||||||
|
- bundle
|
||||||
|
script:
|
||||||
|
- rake
|
@ -0,0 +1,2 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
gemspec
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Scampersand LLC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,71 @@
|
|||||||
|
# envbash
|
||||||
|
|
||||||
|
[](https://rubygems.org/gems/envbash)
|
||||||
|
[](https://travis-ci.org/scampersand/envbash-ruby?branch=master)
|
||||||
|
[](https://codecov.io/gh/scampersand/envbash-ruby/branch/master)
|
||||||
|
|
||||||
|
Ruby gem for sourcing a bash script to augment the environment.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
[12-factor apps](https://12factor.net/) require
|
||||||
|
[configuration loaded from the environment](https://12factor.net/config).
|
||||||
|
|
||||||
|
That's [easy on a platform like Heroku](https://devcenter.heroku.com/articles/config-vars),
|
||||||
|
where the environment is preset by the user with commands like
|
||||||
|
`heroku config:set`. But it's messier in development and non-Heroku
|
||||||
|
deployments, where the environment might need to be loaded from a file.
|
||||||
|
|
||||||
|
This package provides a mechanism for sourcing a Bash script to update
|
||||||
|
Ruby's environment (`ENV`). There are reasons for using a Bash script
|
||||||
|
instead of another configuration language:
|
||||||
|
|
||||||
|
1. Environment variable keys and values should always be strings. Using a Bash
|
||||||
|
script to update the environment enforces that restriction, so there won't
|
||||||
|
be surprises when you deploy into something like Heroku later on.
|
||||||
|
|
||||||
|
2. Using a script means that the values can be sourced into a Bash shell,
|
||||||
|
something that's non-trivial if you use a different config language.
|
||||||
|
|
||||||
|
3. For better or worse, using a script means that environment variables can be
|
||||||
|
set using the full power of the shell, including reading from other files.
|
||||||
|
|
||||||
|
Commonly the external file is called `env.bash`, hence the name of this project.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install from [RubyGems](https://rubygems.org/gems/envbash)
|
||||||
|
|
||||||
|
gem install envbash
|
||||||
|
|
||||||
|
or in your Gemfile:
|
||||||
|
|
||||||
|
gem 'envbash'
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Call `EnvBash.load` to source a Bash script into the current Ruby process.
|
||||||
|
Any variables that are set in the script, regardless of whether they are
|
||||||
|
explicitly exported, will be added to the process environment.
|
||||||
|
|
||||||
|
For example, given `env.bash` with the following content:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FOO='bar baz qux'
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be loaded into Ruby:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'envbash'
|
||||||
|
|
||||||
|
EnvBash.load('env.bash')
|
||||||
|
|
||||||
|
puts ENV['FOO'] #=> bar baz qux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Legal
|
||||||
|
|
||||||
|
Copyright 2017 [Scampersand LLC](https://scampersand.com)
|
||||||
|
|
||||||
|
Released under the [MIT license](https://github.com/scampersand/envbash-ruby/blob/master/LICENSE)
|
@ -0,0 +1,13 @@
|
|||||||
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test) do |test|
|
||||||
|
test.libs << 'lib' << 'test'
|
||||||
|
# make sure helper.rb is loaded first, to start simplecov
|
||||||
|
test.test_files = FileList['test/helper.rb', 'test/test*.rb']
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => :test
|
||||||
|
|
||||||
|
# this adds "rake build" to make pkg/envbash-*.gem
|
||||||
|
require 'bundler/setup'
|
||||||
|
Bundler::GemHelper.install_tasks
|
@ -0,0 +1,19 @@
|
|||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "envbash"
|
||||||
|
spec.summary = "Source env.bash script to update environment"
|
||||||
|
spec.version = "1.0.1"
|
||||||
|
spec.authors = ["Aron Griffis"]
|
||||||
|
spec.email = "aron@scampersand.com"
|
||||||
|
spec.homepage = "https://github.com/scampersand/envbash-ruby"
|
||||||
|
spec.licenses = ["MIT"]
|
||||||
|
|
||||||
|
spec.files = `git ls-files -z`.split("\x0")
|
||||||
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
||||||
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
|
spec.add_development_dependency "codecov"
|
||||||
|
spec.add_development_dependency "minitest"
|
||||||
|
spec.add_development_dependency "minitest-assert_errors"
|
||||||
|
spec.add_development_dependency "rake"
|
||||||
|
end
|
@ -0,0 +1,2 @@
|
|||||||
|
require_relative 'envbash/load'
|
||||||
|
require_relative 'envbash/read'
|
@ -0,0 +1,16 @@
|
|||||||
|
require_relative 'read'
|
||||||
|
|
||||||
|
|
||||||
|
module EnvBash
|
||||||
|
def EnvBash.load(envbash, into: ENV, override: false, remove: false, **kwargs)
|
||||||
|
loaded = read(envbash, **kwargs)
|
||||||
|
is_env = into.equal? ENV
|
||||||
|
into = into.to_h if is_env
|
||||||
|
if loaded
|
||||||
|
into.select! {|k| loaded.include? k} if remove
|
||||||
|
loaded.reject! {|k| into.include? k} unless override
|
||||||
|
into.merge! loaded
|
||||||
|
end
|
||||||
|
ENV.replace into if is_env
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,62 @@
|
|||||||
|
require 'open3'
|
||||||
|
require 'shellwords'
|
||||||
|
|
||||||
|
|
||||||
|
module EnvBash
|
||||||
|
|
||||||
|
FIXUPS = %w{_ OLDPWD PWD SHLVL}
|
||||||
|
|
||||||
|
class ScriptExitedEarly < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
def EnvBash.read(envbash, bash: 'bash', env: ENV, missing_ok: false, fixups: FIXUPS)
|
||||||
|
# make sure the file exists and is readable.
|
||||||
|
# alternatively we could test File.readable?(envbash) but this approach
|
||||||
|
# raises Errno::ENOENT or Errno::EACCES which is what we want.
|
||||||
|
begin
|
||||||
|
File.open(envbash).close
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
return if missing_ok
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
# construct an inline script which sources env.bash then prints the
|
||||||
|
# resulting environment so it can be eval'd back into this process.
|
||||||
|
inline = <<-EOT
|
||||||
|
set -a
|
||||||
|
source #{envbash.shellescape} >/dev/null
|
||||||
|
#{Gem.ruby.shellescape} -e 'p ENV'
|
||||||
|
EOT
|
||||||
|
|
||||||
|
# Process.spawn treats env as overriding ENV, and anything that should be
|
||||||
|
# omitted needs to have a nil value. If env == ENV then this is a noop.
|
||||||
|
env = Hash[ENV.keys.map {|k| [k, nil]}].merge(env)
|
||||||
|
|
||||||
|
# run the inline script with bash -c, capturing stdout. if there is any
|
||||||
|
# error output from env.bash, it will pass through to stderr.
|
||||||
|
# exit status is ignored.
|
||||||
|
output, _ = Open3.capture2(env, 'bash', '-c', inline, :in=>"/dev/null")
|
||||||
|
|
||||||
|
# the only stdout from the inline script should be
|
||||||
|
# `p ENV` so there should be no syntax errors eval'ing this. however there
|
||||||
|
# will be no output to eval if the sourced env.bash exited early, and that
|
||||||
|
# indicates script failure.
|
||||||
|
raise ScriptExitedEarly if output.empty?
|
||||||
|
|
||||||
|
# the eval'd output should return a hash.
|
||||||
|
nenv = eval(output)
|
||||||
|
|
||||||
|
# there are a few environment variables that vary between this process and
|
||||||
|
# running the inline script with bash -c, but are certainly not part of the
|
||||||
|
# intentional settings in env.bash.
|
||||||
|
for f in fixups
|
||||||
|
if env[f] # not .include? because env might have nil values
|
||||||
|
nenv[f] = env[f]
|
||||||
|
else
|
||||||
|
nenv.delete(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nenv
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,10 @@
|
|||||||
|
require 'simplecov'
|
||||||
|
SimpleCov.start
|
||||||
|
|
||||||
|
if ENV['CI'] == 'true'
|
||||||
|
require 'codecov'
|
||||||
|
SimpleCov.formatter = SimpleCov::Formatter::Codecov
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'minitest/autorun'
|
||||||
|
require 'minitest/assert_errors'
|
@ -0,0 +1,74 @@
|
|||||||
|
require_relative 'helper'
|
||||||
|
require 'envbash'
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoad < Minitest::Test
|
||||||
|
def setup
|
||||||
|
@orig = ENV.to_h
|
||||||
|
ENV['A'] = 'A'
|
||||||
|
ENV['B'] = 'B'
|
||||||
|
ENV['C'] = 'C'
|
||||||
|
ENV.delete('D')
|
||||||
|
@loaded = ENV.to_h.merge('A'=>'a', 'D'=>'d')
|
||||||
|
@loaded.delete('B')
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ENV.replace(@orig)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_load_no_override_no_remove
|
||||||
|
EnvBash.stub :read, @loaded do
|
||||||
|
# the first argument doesn't matter since read is stubbed
|
||||||
|
EnvBash.load('')
|
||||||
|
end
|
||||||
|
assert_equal ENV['A'], 'A' # NOT overridden
|
||||||
|
assert_equal ENV['B'], 'B' # NOT removed
|
||||||
|
assert_equal ENV['C'], 'C' # inherited
|
||||||
|
assert_equal ENV['D'], 'd' # loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_load_override_no_remove
|
||||||
|
EnvBash.stub :read, @loaded do
|
||||||
|
# the first argument doesn't matter since read is stubbed
|
||||||
|
EnvBash.load('', override: true)
|
||||||
|
end
|
||||||
|
assert_equal ENV['A'], 'a' # overridden
|
||||||
|
assert_equal ENV['B'], 'B' # NOT removed
|
||||||
|
assert_equal ENV['C'], 'C' # inherited
|
||||||
|
assert_equal ENV['D'], 'd' # loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_load_no_override_remove
|
||||||
|
EnvBash.stub :read, @loaded do
|
||||||
|
# the first argument doesn't matter since read is stubbed
|
||||||
|
EnvBash.load('', remove: true)
|
||||||
|
end
|
||||||
|
assert_equal ENV['A'], 'A' # NOT overridden
|
||||||
|
assert ! ENV.include?('B') # removed
|
||||||
|
assert_equal ENV['C'], 'C' # inherited
|
||||||
|
assert_equal ENV['D'], 'd' # loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_load_override_remove
|
||||||
|
EnvBash.stub :read, @loaded do
|
||||||
|
# the first argument doesn't matter since read is stubbed
|
||||||
|
EnvBash.load('', override: true, remove: true)
|
||||||
|
end
|
||||||
|
assert_equal ENV['A'], 'a' # overridden
|
||||||
|
assert ! ENV.include?('B') # removed
|
||||||
|
assert_equal ENV['C'], 'C' # inherited
|
||||||
|
assert_equal ENV['D'], 'd' # loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_load_into
|
||||||
|
orig = ENV.to_h
|
||||||
|
into = {}
|
||||||
|
EnvBash.stub :read, {'A'=>'B'} do
|
||||||
|
# the first argument doesn't matter since read is stubbed
|
||||||
|
EnvBash.load('', into: into)
|
||||||
|
end
|
||||||
|
assert_equal into, {'A'=>'B'}
|
||||||
|
assert_equal ENV.to_h, orig
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,95 @@
|
|||||||
|
require_relative 'helper'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'tmpdir'
|
||||||
|
require 'envbash'
|
||||||
|
|
||||||
|
|
||||||
|
class TestRead < Minitest::Test
|
||||||
|
def setup
|
||||||
|
@tmpdir = Dir.mktmpdir
|
||||||
|
@envbash = File.join @tmpdir, 'env.bash'
|
||||||
|
@orig = ENV.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
ENV.replace(@orig)
|
||||||
|
FileUtils.rm_rf(@tmpdir)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_missing_not_ok
|
||||||
|
assert_error_raised(nil, Errno::ENOENT) do
|
||||||
|
EnvBash.read @envbash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_missing_ok
|
||||||
|
assert_no_error do
|
||||||
|
EnvBash.read @envbash, missing_ok: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_permission_error
|
||||||
|
FileUtils.chmod 0, @tmpdir
|
||||||
|
assert_error_raised(nil, Errno::EACCES) do
|
||||||
|
EnvBash.read @envbash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_empty
|
||||||
|
FileUtils.touch @envbash
|
||||||
|
result = EnvBash.read @envbash
|
||||||
|
assert_equal result, @orig
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_normal
|
||||||
|
ENV.delete('FOO')
|
||||||
|
orig = ENV.to_h # separate from @orig
|
||||||
|
File.open(@envbash, 'w') do |f|
|
||||||
|
f.write 'FOO=BAR'
|
||||||
|
end
|
||||||
|
result = EnvBash.read @envbash
|
||||||
|
assert_equal result['FOO'], 'BAR'
|
||||||
|
result.delete('FOO')
|
||||||
|
assert_equal result, orig
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_error
|
||||||
|
File.open(@envbash, 'w') do |f|
|
||||||
|
# stderr doesn't matter, nor does final status.
|
||||||
|
f.write "echo 'okay!' >&2\nfalse"
|
||||||
|
end
|
||||||
|
result = EnvBash.read @envbash
|
||||||
|
assert_equal result, @orig
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_exit
|
||||||
|
File.open(@envbash, 'w') do |f|
|
||||||
|
f.write 'exit'
|
||||||
|
end
|
||||||
|
assert_error_raised(nil, EnvBash::ScriptExitedEarly) do
|
||||||
|
EnvBash.read @envbash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_env
|
||||||
|
File.open(@envbash, 'w') do |f|
|
||||||
|
f.write 'FOO=BAR'
|
||||||
|
end
|
||||||
|
result = EnvBash.read @envbash, env: {}
|
||||||
|
assert_equal result, {'FOO'=>'BAR'}
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read_fixups
|
||||||
|
File.open(@envbash, 'w') do |f|
|
||||||
|
f.write 'A=B; C=D; E=F; G=H'
|
||||||
|
end
|
||||||
|
myenv = {'A'=>'Z', 'E'=>'F'}
|
||||||
|
result = EnvBash.read @envbash, env: myenv, fixups: ['A', 'C']
|
||||||
|
# there will be extra stuff in result since fixups is overridden, so can't
|
||||||
|
# test strict equality.
|
||||||
|
assert_equal result['A'], 'Z' # fixups, myenv, env.bash
|
||||||
|
assert !result.include?('C') # fixups, not myenv, env.bash
|
||||||
|
assert_equal result['E'], 'F' # not fixups, myenv, env.bash
|
||||||
|
assert_equal result['G'], 'H' # not fixups, not myenv, env.bash
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,30 @@
|
|||||||
|
== 1.0.4 - 8-Jan-2016
|
||||||
|
* This gem is now signed.
|
||||||
|
* The gem related tasks in the Rakefile now assume Rubygems 2.x.
|
||||||
|
|
||||||
|
== 1.0.3 - 12-Oct-2014
|
||||||
|
* Rakefile, gemspec and README updates.
|
||||||
|
|
||||||
|
== 1.0.2 - 7-Oct-2009
|
||||||
|
* Fixed packaging bug in the gemspec, and made some other minor changes.
|
||||||
|
* Added the 'gem' rake task.
|
||||||
|
* Updated copyright and license in the README.
|
||||||
|
|
||||||
|
== 1.0.1 - 29-Jul-2009
|
||||||
|
* Now compatible with Ruby 1.9.x.
|
||||||
|
* Replaced the install.rb with a Rakefile and various tasks.
|
||||||
|
* Updated the license to Artistic 2.0.
|
||||||
|
* Added test-unit 2.x as a development dependency and refactored the test
|
||||||
|
suite to take advantage of some of its features.
|
||||||
|
* Renamed the test file to test_interface.rb.
|
||||||
|
* Renamed the example programs to all start with 'example_' to avoid any
|
||||||
|
possible confusion with actual test suites.
|
||||||
|
* Added rake tasks for running the example programs.
|
||||||
|
* Updated and refactored the gemspec.
|
||||||
|
|
||||||
|
== 1.0.0 - 5-Jun-2005
|
||||||
|
* Re-released on RubyForge.
|
||||||
|
* Some test suite and doc changes.
|
||||||
|
|
||||||
|
== 0.1.0 - 9-May-2004
|
||||||
|
* Initial release
|
@ -0,0 +1,12 @@
|
|||||||
|
* CHANGES
|
||||||
|
* MANIFEST
|
||||||
|
* README
|
||||||
|
* Rakefile
|
||||||
|
* interface.gemspec
|
||||||
|
* certs/djberg96_pub.pem
|
||||||
|
* examples/example_instance.rb
|
||||||
|
* examples/example_interface.rb
|
||||||
|
* examples/example_sub.rb
|
||||||
|
* examples/example_unrequire.rb
|
||||||
|
* lib/interface.rb
|
||||||
|
* test/test_interface.rb
|
@ -0,0 +1,74 @@
|
|||||||
|
== Description
|
||||||
|
This module provides Java style interfaces for Ruby, including a fairly
|
||||||
|
similar syntax. I don't necessarily believe in interfaces, but I wanted to
|
||||||
|
put it out there as proof that it could be done. Frankly, Java needs mixins
|
||||||
|
more than Ruby needs interfaces, but here you go.
|
||||||
|
|
||||||
|
== Installation
|
||||||
|
gem install interface
|
||||||
|
|
||||||
|
== Synopsis
|
||||||
|
require 'interface'
|
||||||
|
|
||||||
|
MyInterface = interface{
|
||||||
|
required_methods :foo, :bar, :baz
|
||||||
|
}
|
||||||
|
|
||||||
|
# Raises an error until 'baz' is defined
|
||||||
|
class MyClass
|
||||||
|
def foo
|
||||||
|
puts "foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
def bar
|
||||||
|
puts "bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
implements MyInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
== General Notes
|
||||||
|
Subinterfaces work as well. See the test_sub.rb file under the 'test'
|
||||||
|
directory for a sample.
|
||||||
|
|
||||||
|
== Developer's Notes
|
||||||
|
A discussion on IRC with Mauricio Fernandez got us talking about traits.
|
||||||
|
During that discussion I remembered a blog entry by David Naseby. I
|
||||||
|
revisited his blog entry and took a closer look:
|
||||||
|
|
||||||
|
http://ruby-naseby.blogspot.com/2008/11/traits-in-ruby.html
|
||||||
|
|
||||||
|
Keep in mind that I also happened to be thinking about Java at the moment
|
||||||
|
because of a recent job switch that involved coding in Java. I was also
|
||||||
|
trying to figure out what the purpose of interfaces were.
|
||||||
|
|
||||||
|
As I read the first page of David Naseby's article I realized that,
|
||||||
|
whether intended or not, he had implemented a rudimentary form of interfaces
|
||||||
|
for Ruby. When I discovered this, I talked about it some more with Mauricio
|
||||||
|
and he and I (mostly him) fleshed out the rest of the module, including some
|
||||||
|
syntax improvements. The result is syntax and functionality that is nearly
|
||||||
|
identical to Java.
|
||||||
|
|
||||||
|
I should note that, although I am listed as the author, this was mostly the
|
||||||
|
combined work of David Naseby and Mauricio Fernandez. I just happened to be
|
||||||
|
the guy that put it all together.
|
||||||
|
|
||||||
|
== Acknowledgements
|
||||||
|
This module was largely inspired and somewhat copied from a post by
|
||||||
|
David Naseby (see URL above). It was subsequently modified almost entirely
|
||||||
|
by Mauricio Fernandez through a series of discussions on IRC.
|
||||||
|
|
||||||
|
== Copyright
|
||||||
|
(C) 2004-2016 Daniel J. Berger
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
== Warranty
|
||||||
|
This package is provided "as is" and without any express or
|
||||||
|
implied warranties, including, without limitation, the implied
|
||||||
|
warranties of merchantability and fitness for a particular purpose.
|
||||||
|
|
||||||
|
== License
|
||||||
|
Artistic 2.0
|
||||||
|
|
||||||
|
== Author
|
||||||
|
Daniel J. Berger
|
@ -0,0 +1,50 @@
|
|||||||
|
require 'rake'
|
||||||
|
require 'rake/clean'
|
||||||
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
CLEAN.include("**/*.gem", "**/*.rbc")
|
||||||
|
|
||||||
|
namespace :gem do
|
||||||
|
desc "Create the interface gem"
|
||||||
|
task :create => [:clean] do
|
||||||
|
require 'rubygems/package'
|
||||||
|
spec = eval(IO.read('interface.gemspec'))
|
||||||
|
spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
|
||||||
|
Gem::Package.build(spec, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Install the interface gem"
|
||||||
|
task :install => [:create] do
|
||||||
|
file = Dir["*.gem"].first
|
||||||
|
sh "gem install -l #{file}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :example do
|
||||||
|
desc 'Run the example_instance.rb sample program'
|
||||||
|
task :instance do
|
||||||
|
ruby '-Ilib examples/example_instance.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run the example_interface.rb sample program'
|
||||||
|
task :interface do
|
||||||
|
ruby '-Ilib examples/example_interface.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run the example_sub.rb sample program'
|
||||||
|
task :sub do
|
||||||
|
ruby '-Ilib examples/example_sub.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run the example_unrequire.rb sample program'
|
||||||
|
task :unrequire do
|
||||||
|
ruby '-Ilib examples/example_unrequire.rb'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Rake::TestTask.new do |t|
|
||||||
|
t.verbose = true
|
||||||
|
t.warning = true
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => :test
|
@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl
|
||||||
|
cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
|
||||||
|
MB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi
|
||||||
|
ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
|
||||||
|
bTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALgfaroVM6CI06cxr0/h
|
||||||
|
A+j+pc8fgpRgBVmHFaFunq28GPC3IvW7Nvc3Y8SnAW7pP1EQIbhlwRIaQzJ93/yj
|
||||||
|
u95KpkP7tA9erypnV7dpzBkzNlX14ACaFD/6pHoXoe2ltBxk3CCyyzx70mTqJpph
|
||||||
|
75IB03ni9a8yqn8pmse+s83bFJOAqddSj009sGPcQO+QOWiNxqYv1n5EHcvj2ebO
|
||||||
|
6hN7YTmhx7aSia4qL/quc4DlIaGMWoAhvML7u1fmo53CYxkKskfN8MOecq2vfEmL
|
||||||
|
iLu+SsVVEAufMDDFMXMJlvDsviolUSGMSNRTujkyCcJoXKYYxZSNtIiyd9etI0X3
|
||||||
|
ctu0uhrFyrMZXCedutvXNjUolD5r9KGBFSWH1R9u2I3n3SAyFF2yzv/7idQHLJJq
|
||||||
|
74BMnx0FIq6fCpu5slAipvxZ3ZkZpEXZFr3cIBtO1gFvQWW7E/Y3ijliWJS1GQFq
|
||||||
|
058qERadHGu1yu1dojmFRo6W2KZvY9al2yIlbkpDrD5MYQIDAQABo3cwdTAJBgNV
|
||||||
|
HRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUFZsMapgzJimzsbaBG2Tm8j5e
|
||||||
|
AzgwHQYDVR0RBBYwFIESZGpiZXJnOTZAZ21haWwuY29tMB0GA1UdEgQWMBSBEmRq
|
||||||
|
YmVyZzk2QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAW2tnYixXQtKxgGXq
|
||||||
|
/3iSWG2bLwvxS4go3srO+aRXZHrFUMlJ5W0mCxl03aazxxKTsVVpZD8QZxvK91OQ
|
||||||
|
h9zr9JBYqCLcCVbr8SkmYCi/laxIZxsNE5YI8cC8vvlLI7AMgSfPSnn/Epq1GjGY
|
||||||
|
6L1iRcEDtanGCIvjqlCXO9+BmsnCfEVehqZkQHeYczA03tpOWb6pon2wzvMKSsKH
|
||||||
|
ks0ApVdstSLz1kzzAqem/uHdG9FyXdbTAwH1G4ZPv69sQAFAOCgAqYmdnzedsQtE
|
||||||
|
1LQfaQrx0twO+CZJPcRLEESjq8ScQxWRRkfuh2VeR7cEU7L7KqT10mtUwrvw7APf
|
||||||
|
DYoeCY9KyjIBjQXfbj2ke5u1hZj94Fsq9FfbEQg8ygCgwThnmkTrrKEiMSs3alYR
|
||||||
|
ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
|
||||||
|
WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,30 @@
|
|||||||
|
#######################################################################
|
||||||
|
# example_instance.rb
|
||||||
|
#
|
||||||
|
# Sample program to demonstrate extending an interface to an instance
|
||||||
|
# of a class. You can run this program via the 'rake example:instance'
|
||||||
|
# task. Modify as you see fit.
|
||||||
|
#######################################################################
|
||||||
|
require 'interface'
|
||||||
|
|
||||||
|
MyInterface = interface{
|
||||||
|
required_methods :foo, :bar
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
def foo; end
|
||||||
|
def bar; end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Bar
|
||||||
|
end
|
||||||
|
|
||||||
|
f = Foo.new
|
||||||
|
f.extend(MyInterface)
|
||||||
|
|
||||||
|
b = Bar.new
|
||||||
|
|
||||||
|
# This will blow up
|
||||||
|
class << b
|
||||||
|
include MyInterface
|
||||||
|
end
|
@ -0,0 +1,28 @@
|
|||||||
|
#######################################################################
|
||||||
|
# example_interface.rb
|
||||||
|
#
|
||||||
|
# Sample test script that demonstrates a typical interface. You can
|
||||||
|
# run this example via the 'rake example:interface' task. Modify this
|
||||||
|
# code as you see fit.
|
||||||
|
#######################################################################
|
||||||
|
require 'interface'
|
||||||
|
|
||||||
|
MyInterface = interface{
|
||||||
|
required_methods :foo, :bar
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyClass
|
||||||
|
def foo; end
|
||||||
|
def bar; end
|
||||||
|
include MyInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
# Raises an error until bar is defined
|
||||||
|
class Foo
|
||||||
|
def foo
|
||||||
|
puts "foo"
|
||||||
|
end
|
||||||
|
include MyInterface
|
||||||
|
end
|
||||||
|
=end
|
@ -0,0 +1,34 @@
|
|||||||
|
#######################################################################
|
||||||
|
# example_sub.rb
|
||||||
|
#
|
||||||
|
# Sample program to demonstrate extending a sub-interface. You can
|
||||||
|
# run this program via the 'rake example:sub' task. Modify this code
|
||||||
|
# as you see fit.
|
||||||
|
#######################################################################
|
||||||
|
require 'interface'
|
||||||
|
|
||||||
|
module MyInterface
|
||||||
|
extend Interface
|
||||||
|
required_methods :foo, :bar
|
||||||
|
end
|
||||||
|
|
||||||
|
module MySubInterface
|
||||||
|
extend Interface
|
||||||
|
extend MyInterface
|
||||||
|
required_methods :baz
|
||||||
|
end
|
||||||
|
|
||||||
|
class MyClass
|
||||||
|
def baz; end
|
||||||
|
def bar; end
|
||||||
|
def foo; end
|
||||||
|
include MySubInterface
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
# Raises an error
|
||||||
|
class MyClass
|
||||||
|
def baz; end
|
||||||
|
include MyInterface
|
||||||
|
end
|
||||||
|
=end
|
@ -0,0 +1,26 @@
|
|||||||
|
###########################################################################
|
||||||
|
# example_unrequire.rb
|
||||||
|
#
|
||||||
|
# Sample test script for to verify that unrequired_methods works properly.
|
||||||
|
# You can run this code via the 'rake example:unrequire' rake task. Modify
|
||||||
|
# this code as you see fit.
|
||||||
|
###########################################################################
|
||||||
|
require 'interface'
|
||||||
|
|
||||||
|
MyInterface = interface{
|
||||||
|
required_methods :foo, :bar
|
||||||
|
}
|
||||||
|
|
||||||
|
# require foo and baz, but not bar
|
||||||
|
MySubInterface = interface{
|
||||||
|
extends MyInterface
|
||||||
|
required_methods :baz
|
||||||
|
unrequired_methods :bar
|
||||||
|
}
|
||||||
|
|
||||||
|
# No error
|
||||||
|
class MyClass
|
||||||
|
def foo; end
|
||||||
|
def baz; end
|
||||||
|
include MySubInterface
|
||||||
|
end
|
@ -0,0 +1,34 @@
|
|||||||
|
require 'rubygems'
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = 'interface'
|
||||||
|
spec.version = '1.0.5'
|
||||||
|
spec.author = 'Daniel J. Berger'
|
||||||
|
spec.license = 'Artistic-2.0'
|
||||||
|
spec.email = 'djberg96@gmail.com'
|
||||||
|
spec.homepage = 'http://github.com/djberg96/interface'
|
||||||
|
spec.summary = 'Java style interfaces for Ruby'
|
||||||
|
spec.test_file = 'test/test_interface.rb'
|
||||||
|
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
||||||
|
spec.cert_chain = Dir['certs/*']
|
||||||
|
|
||||||
|
spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
|
||||||
|
|
||||||
|
spec.add_development_dependency('test-unit')
|
||||||
|
spec.add_development_dependency('rake')
|
||||||
|
|
||||||
|
spec.metadata = {
|
||||||
|
'homepage_uri' => 'https://github.com/djberg96/interface',
|
||||||
|
'bug_tracker_uri' => 'https://github.com/djberg96/interface/issues',
|
||||||
|
'changelog_uri' => 'https://github.com/djberg96/interface/blob/master/CHANGES',
|
||||||
|
'documentation_uri' => 'https://github.com/djberg96/interface/wiki',
|
||||||
|
'source_code_uri' => 'https://github.com/djberg96/interface',
|
||||||
|
'wiki_uri' => 'https://github.com/djberg96/interface/wiki'
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.description = <<-EOF
|
||||||
|
The interface library implements Java style interfaces for Ruby.
|
||||||
|
It lets you define a set a methods that must be defined in the
|
||||||
|
including class or module, or an error is raised.
|
||||||
|
EOF
|
||||||
|
end
|
@ -0,0 +1,106 @@
|
|||||||
|
# A module for implementing Java style interfaces in Ruby. For more information
|
||||||
|
# about Java interfaces, please see:
|
||||||
|
#
|
||||||
|
# http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
|
||||||
|
#
|
||||||
|
module Interface
|
||||||
|
# The version of the interface library.
|
||||||
|
Interface::VERSION = '1.0.5'.freeze
|
||||||
|
|
||||||
|
# Raised if a class or instance does not meet the interface requirements.
|
||||||
|
class MethodMissing < RuntimeError; end
|
||||||
|
|
||||||
|
alias :extends :extend
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def extend_object(obj)
|
||||||
|
return append_features(obj) if Interface === obj
|
||||||
|
append_features(class << obj; self end)
|
||||||
|
included(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def append_features(mod)
|
||||||
|
return super if Interface === mod
|
||||||
|
|
||||||
|
# Is this a sub-interface?
|
||||||
|
inherited = (self.ancestors-[self]).select{ |x| Interface === x }
|
||||||
|
inherited = inherited.map{ |x| x.instance_variable_get('@ids') }
|
||||||
|
|
||||||
|
# Store required method ids
|
||||||
|
ids = @ids + inherited.flatten
|
||||||
|
@unreq ||= []
|
||||||
|
|
||||||
|
# Iterate over the methods, minus the unrequired methods, and raise
|
||||||
|
# an error if the method has not been defined.
|
||||||
|
(ids - @unreq).uniq.each do |id|
|
||||||
|
unless mod.instance_methods(true).include?(id)
|
||||||
|
raise Interface::MethodMissing, id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
super mod
|
||||||
|
end
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
|
# Accepts an array of method names that define the interface. When this
|
||||||
|
# module is included/implemented, those method names must have already been
|
||||||
|
# defined.
|
||||||
|
#
|
||||||
|
def required_methods(*ids)
|
||||||
|
@ids = ids
|
||||||
|
end
|
||||||
|
|
||||||
|
# Accepts an array of method names that are removed as a requirement for
|
||||||
|
# implementation. Presumably you would use this in a sub-interface where
|
||||||
|
# you only wanted a partial implementation of an existing interface.
|
||||||
|
#
|
||||||
|
def unrequired_methods(*ids)
|
||||||
|
@unreq ||= []
|
||||||
|
@unreq += ids
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Object
|
||||||
|
# The interface method creates an interface module which typically sets
|
||||||
|
# a list of methods that must be defined in the including class or module.
|
||||||
|
# If the methods are not defined, an Interface::MethodMissing error is raised.
|
||||||
|
#
|
||||||
|
# A interface can extend an existing interface as well. These are called
|
||||||
|
# sub-interfaces, and they can included the rules for their parent interface
|
||||||
|
# by simply extending it.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# # Require 'alpha' and 'beta' methods
|
||||||
|
# AlphaInterface = interface{
|
||||||
|
# required_methods :alpha, :beta
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # A sub-interface that requires 'beta' and 'gamma' only
|
||||||
|
# GammaInterface = interface{
|
||||||
|
# extends AlphaInterface
|
||||||
|
# required_methods :gamma
|
||||||
|
# unrequired_methods :alpha
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Raises an Interface::MethodMissing error because :beta is not defined.
|
||||||
|
# class MyClass
|
||||||
|
# def alpha
|
||||||
|
# # ...
|
||||||
|
# end
|
||||||
|
# implements AlphaInterface
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def interface(&block)
|
||||||
|
Module.new do |mod|
|
||||||
|
mod.extend(Interface)
|
||||||
|
mod.instance_eval(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Module
|
||||||
|
alias :implements :include
|
||||||
|
end
|
@ -0,0 +1,64 @@
|
|||||||
|
#####################################################
|
||||||
|
# test_interface.rb
|
||||||
|
#
|
||||||
|
# Test suite for the Interface module.
|
||||||
|
#####################################################
|
||||||
|
require 'test-unit'
|
||||||
|
require 'interface'
|
||||||
|
|
||||||
|
class TC_Interface < Test::Unit::TestCase
|
||||||
|
def self.startup
|
||||||
|
alpha_interface = interface{
|
||||||
|
required_methods :alpha, :beta
|
||||||
|
}
|
||||||
|
|
||||||
|
gamma_interface = interface{
|
||||||
|
extends alpha_interface
|
||||||
|
required_methods :gamma
|
||||||
|
unrequired_methods :alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
# Workaround for 1.9.x
|
||||||
|
@@alpha_interface = alpha_interface
|
||||||
|
@@gamma_interface = gamma_interface
|
||||||
|
|
||||||
|
eval("class A; end")
|
||||||
|
|
||||||
|
eval("
|
||||||
|
class B
|
||||||
|
def alpha; end
|
||||||
|
def beta; end
|
||||||
|
end
|
||||||
|
")
|
||||||
|
|
||||||
|
eval("
|
||||||
|
class C
|
||||||
|
def beta; end
|
||||||
|
def gamma; end
|
||||||
|
end
|
||||||
|
")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_version
|
||||||
|
assert_equal('1.0.5', Interface::VERSION)
|
||||||
|
assert_true(Interface::VERSION.frozen?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_interface_requirements_not_met
|
||||||
|
assert_raise(Interface::MethodMissing){ A.extend(@@alpha_interface) }
|
||||||
|
assert_raise(Interface::MethodMissing){ A.new.extend(@@alpha_interface) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sub_interface_requirements_not_met
|
||||||
|
assert_raise(Interface::MethodMissing){ B.extend(@@gamma_interface) }
|
||||||
|
assert_raise(Interface::MethodMissing){ B.new.extend(@@gamma_interface) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_alpha_interface_requirements_met
|
||||||
|
assert_nothing_raised{ B.new.extend(@@alpha_interface) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gamma_interface_requirements_met
|
||||||
|
assert_nothing_raised{ C.new.extend(@@gamma_interface) }
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,11 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
||||||
|
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
group :development do
|
||||||
|
gem "bundler"
|
||||||
|
gem "rake"
|
||||||
|
gem "test-unit"
|
||||||
|
end
|
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
@ -0,0 +1,97 @@
|
|||||||
|
# Shell
|
||||||
|
|
||||||
|
Shell implements an idiomatic Ruby interface for common UNIX shell commands.
|
||||||
|
|
||||||
|
It provides users the ability to execute commands with filters and pipes, like +sh+/+csh+ by using native facilities of Ruby.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'shell'
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
$ bundle
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
$ gem install shell
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Temp file creation
|
||||||
|
|
||||||
|
In this example we will create three +tmpFile+'s in three different folders under the +/tmp+ directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
sh = Shell.cd("/tmp") # Change to the /tmp directory
|
||||||
|
sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1")
|
||||||
|
# make the 'shell-test-1' directory if it doesn't already exist
|
||||||
|
sh.cd("shell-test-1") # Change to the /tmp/shell-test-1 directory
|
||||||
|
for dir in ["dir1", "dir3", "dir5"]
|
||||||
|
if !sh.exists?(dir)
|
||||||
|
sh.mkdir dir # make dir if it doesn't already exist
|
||||||
|
sh.cd(dir) do
|
||||||
|
# change to the `dir` directory
|
||||||
|
f = sh.open("tmpFile", "w") # open a new file in write mode
|
||||||
|
f.print "TEST\n" # write to the file
|
||||||
|
f.close # close the file handler
|
||||||
|
end
|
||||||
|
print sh.pwd # output the process working directory
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Temp file creation with self
|
||||||
|
|
||||||
|
This example is identical to the first, except we're using CommandProcessor#transact.
|
||||||
|
|
||||||
|
CommandProcessor#transact executes the given block against self, in this case +sh+; our Shell object. Within the block we can substitute +sh.cd+ to +cd+, because the scope within the block uses +sh+ already.
|
||||||
|
|
||||||
|
```
|
||||||
|
sh = Shell.cd("/tmp")
|
||||||
|
sh.transact do
|
||||||
|
mkdir "shell-test-1" unless exists?("shell-test-1")
|
||||||
|
cd("shell-test-1")
|
||||||
|
for dir in ["dir1", "dir3", "dir5"]
|
||||||
|
if !exists?(dir)
|
||||||
|
mkdir dir
|
||||||
|
cd(dir) do
|
||||||
|
f = open("tmpFile", "w")
|
||||||
|
f.print "TEST\n"
|
||||||
|
f.close
|
||||||
|
end
|
||||||
|
print pwd
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pipe /etc/printcap into a file
|
||||||
|
|
||||||
|
In this example we will read the operating system file +/etc/printcap+, generated by +cupsd+, and then output it to a new file relative to the +pwd+ of +sh+.
|
||||||
|
|
||||||
|
```
|
||||||
|
sh = Shell.new
|
||||||
|
sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2"
|
||||||
|
(sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12"
|
||||||
|
sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2"
|
||||||
|
(sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||||
|
|
||||||
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/shell.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause).
|
@ -0,0 +1,17 @@
|
|||||||
|
require "bundler/gem_tasks"
|
||||||
|
require "rake/testtask"
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
t.libs << "test/lib"
|
||||||
|
t.ruby_opts << "-rhelper"
|
||||||
|
t.test_files = FileList["test/**/test_*.rb"]
|
||||||
|
end
|
||||||
|
|
||||||
|
task :sync_tool do
|
||||||
|
require 'fileutils'
|
||||||
|
FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib"
|
||||||
|
FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
|
||||||
|
FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => :test
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require "bundler/setup"
|
||||||
|
require_relative "../lib/shell"
|
||||||
|
|
||||||
|
require "irb"
|
||||||
|
IRB.start(__FILE__)
|
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
set -vx
|
||||||
|
|
||||||
|
bundle install
|
@ -0,0 +1,462 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision: 1.9 $
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
require "e2mmap"
|
||||||
|
|
||||||
|
require "forwardable"
|
||||||
|
|
||||||
|
require "shell/error"
|
||||||
|
require "shell/command-processor"
|
||||||
|
require "shell/process-controller"
|
||||||
|
require "shell/version"
|
||||||
|
|
||||||
|
# Shell implements an idiomatic Ruby interface for common UNIX shell commands.
|
||||||
|
#
|
||||||
|
# It provides users the ability to execute commands with filters and pipes,
|
||||||
|
# like +sh+/+csh+ by using native facilities of Ruby.
|
||||||
|
#
|
||||||
|
# == Examples
|
||||||
|
#
|
||||||
|
# === Temp file creation
|
||||||
|
#
|
||||||
|
# In this example we will create three +tmpFile+'s in three different folders
|
||||||
|
# under the +/tmp+ directory.
|
||||||
|
#
|
||||||
|
# sh = Shell.cd("/tmp") # Change to the /tmp directory
|
||||||
|
# sh.mkdir "shell-test-1" unless sh.exists?("shell-test-1")
|
||||||
|
# # make the 'shell-test-1' directory if it doesn't already exist
|
||||||
|
# sh.cd("shell-test-1") # Change to the /tmp/shell-test-1 directory
|
||||||
|
# for dir in ["dir1", "dir3", "dir5"]
|
||||||
|
# if !sh.exists?(dir)
|
||||||
|
# sh.mkdir dir # make dir if it doesn't already exist
|
||||||
|
# sh.cd(dir) do
|
||||||
|
# # change to the `dir` directory
|
||||||
|
# f = sh.open("tmpFile", "w") # open a new file in write mode
|
||||||
|
# f.print "TEST\n" # write to the file
|
||||||
|
# f.close # close the file handler
|
||||||
|
# end
|
||||||
|
# print sh.pwd # output the process working directory
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# === Temp file creation with self
|
||||||
|
#
|
||||||
|
# This example is identical to the first, except we're using
|
||||||
|
# CommandProcessor#transact.
|
||||||
|
#
|
||||||
|
# CommandProcessor#transact executes the given block against self, in this case
|
||||||
|
# +sh+; our Shell object. Within the block we can substitute +sh.cd+ to +cd+,
|
||||||
|
# because the scope within the block uses +sh+ already.
|
||||||
|
#
|
||||||
|
# sh = Shell.cd("/tmp")
|
||||||
|
# sh.transact do
|
||||||
|
# mkdir "shell-test-1" unless exists?("shell-test-1")
|
||||||
|
# cd("shell-test-1")
|
||||||
|
# for dir in ["dir1", "dir3", "dir5"]
|
||||||
|
# if !exists?(dir)
|
||||||
|
# mkdir dir
|
||||||
|
# cd(dir) do
|
||||||
|
# f = open("tmpFile", "w")
|
||||||
|
# f.print "TEST\n"
|
||||||
|
# f.close
|
||||||
|
# end
|
||||||
|
# print pwd
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# === Pipe /etc/printcap into a file
|
||||||
|
#
|
||||||
|
# In this example we will read the operating system file +/etc/printcap+,
|
||||||
|
# generated by +cupsd+, and then output it to a new file relative to the +pwd+
|
||||||
|
# of +sh+.
|
||||||
|
#
|
||||||
|
# sh = Shell.new
|
||||||
|
# sh.cat("/etc/printcap") | sh.tee("tee1") > "tee2"
|
||||||
|
# (sh.cat < "/etc/printcap") | sh.tee("tee11") > "tee12"
|
||||||
|
# sh.cat("/etc/printcap") | sh.tee("tee1") >> "tee2"
|
||||||
|
# (sh.cat < "/etc/printcap") | sh.tee("tee11") >> "tee12"
|
||||||
|
#
|
||||||
|
class Shell
|
||||||
|
|
||||||
|
include Error
|
||||||
|
extend Exception2MessageMapper
|
||||||
|
|
||||||
|
# debug: true -> normal debug
|
||||||
|
# debug: 1 -> eval definition debug
|
||||||
|
# debug: 2 -> detail inspect debug
|
||||||
|
@debug = false
|
||||||
|
@verbose = true
|
||||||
|
|
||||||
|
@debug_display_process_id = false
|
||||||
|
@debug_display_thread_id = true
|
||||||
|
@debug_output_mutex = Thread::Mutex.new
|
||||||
|
@default_system_path = nil
|
||||||
|
@default_record_separator = nil
|
||||||
|
|
||||||
|
class << Shell
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
attr_accessor :cascade, :verbose
|
||||||
|
attr_reader :debug
|
||||||
|
|
||||||
|
alias debug? debug
|
||||||
|
alias verbose? verbose
|
||||||
|
@verbose = true
|
||||||
|
|
||||||
|
def debug=(val)
|
||||||
|
@debug = val
|
||||||
|
@verbose = val if val
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# Shell.cd(path)
|
||||||
|
#
|
||||||
|
# Creates a new Shell instance with the current working directory
|
||||||
|
# set to +path+.
|
||||||
|
def cd(path)
|
||||||
|
new(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the directories in the current shell's PATH environment variable
|
||||||
|
# as an array of directory names. This sets the system_path for all
|
||||||
|
# instances of Shell.
|
||||||
|
#
|
||||||
|
# Example: If in your current shell, you did:
|
||||||
|
#
|
||||||
|
# $ echo $PATH
|
||||||
|
# /usr/bin:/bin:/usr/local/bin
|
||||||
|
#
|
||||||
|
# Running this method in the above shell would then return:
|
||||||
|
#
|
||||||
|
# ["/usr/bin", "/bin", "/usr/local/bin"]
|
||||||
|
#
|
||||||
|
def default_system_path
|
||||||
|
if @default_system_path
|
||||||
|
@default_system_path
|
||||||
|
else
|
||||||
|
ENV["PATH"].split(":")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the system_path that new instances of Shell should have as their
|
||||||
|
# initial system_path.
|
||||||
|
#
|
||||||
|
# +path+ should be an array of directory name strings.
|
||||||
|
def default_system_path=(path)
|
||||||
|
@default_system_path = path
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_record_separator
|
||||||
|
if @default_record_separator
|
||||||
|
@default_record_separator
|
||||||
|
else
|
||||||
|
$/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_record_separator=(rs)
|
||||||
|
@default_record_separator = rs
|
||||||
|
end
|
||||||
|
|
||||||
|
# os resource mutex
|
||||||
|
mutex_methods = ["unlock", "lock", "locked?", "synchronize", "try_lock"]
|
||||||
|
for m in mutex_methods
|
||||||
|
def_delegator("@debug_output_mutex", m, "debug_output_"+m.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# Shell.new(pwd, umask) -> obj
|
||||||
|
#
|
||||||
|
# Creates a Shell object which current directory is set to the process
|
||||||
|
# current directory, unless otherwise specified by the +pwd+ argument.
|
||||||
|
def initialize(pwd = Dir.pwd, umask = nil)
|
||||||
|
@cwd = File.expand_path(pwd)
|
||||||
|
@dir_stack = []
|
||||||
|
@umask = umask
|
||||||
|
|
||||||
|
@system_path = Shell.default_system_path
|
||||||
|
@record_separator = Shell.default_record_separator
|
||||||
|
|
||||||
|
@command_processor = CommandProcessor.new(self)
|
||||||
|
@process_controller = ProcessController.new(self)
|
||||||
|
|
||||||
|
@verbose = Shell.verbose
|
||||||
|
@debug = Shell.debug
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the command search path in an array
|
||||||
|
attr_reader :system_path
|
||||||
|
|
||||||
|
# Sets the system path (the Shell instance's PATH environment variable).
|
||||||
|
#
|
||||||
|
# +path+ should be an array of directory name strings.
|
||||||
|
def system_path=(path)
|
||||||
|
@system_path = path
|
||||||
|
rehash
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Returns the umask
|
||||||
|
attr_accessor :umask
|
||||||
|
attr_accessor :record_separator
|
||||||
|
attr_accessor :verbose
|
||||||
|
attr_reader :debug
|
||||||
|
|
||||||
|
def debug=(val)
|
||||||
|
@debug = val
|
||||||
|
@verbose = val if val
|
||||||
|
end
|
||||||
|
|
||||||
|
alias verbose? verbose
|
||||||
|
alias debug? debug
|
||||||
|
|
||||||
|
attr_reader :command_processor
|
||||||
|
attr_reader :process_controller
|
||||||
|
|
||||||
|
def expand_path(path)
|
||||||
|
File.expand_path(path, @cwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Most Shell commands are defined via CommandProcessor
|
||||||
|
|
||||||
|
#
|
||||||
|
# Dir related methods
|
||||||
|
#
|
||||||
|
# Shell#cwd/dir/getwd/pwd
|
||||||
|
# Shell#chdir/cd
|
||||||
|
# Shell#pushdir/pushd
|
||||||
|
# Shell#popdir/popd
|
||||||
|
# Shell#mkdir
|
||||||
|
# Shell#rmdir
|
||||||
|
|
||||||
|
# Returns the current working directory.
|
||||||
|
attr_reader :cwd
|
||||||
|
alias dir cwd
|
||||||
|
alias getwd cwd
|
||||||
|
alias pwd cwd
|
||||||
|
|
||||||
|
attr_reader :dir_stack
|
||||||
|
alias dirs dir_stack
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# Shell.chdir(path)
|
||||||
|
#
|
||||||
|
# Creates a Shell object which current directory is set to +path+.
|
||||||
|
#
|
||||||
|
# If a block is given, it restores the current directory when the block ends.
|
||||||
|
#
|
||||||
|
# If called as iterator, it restores the current directory when the
|
||||||
|
# block ends.
|
||||||
|
def chdir(path = nil, verbose = @verbose)
|
||||||
|
check_point
|
||||||
|
|
||||||
|
if block_given?
|
||||||
|
notify("chdir(with block) #{path}") if verbose
|
||||||
|
cwd_old = @cwd
|
||||||
|
begin
|
||||||
|
chdir(path, nil)
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
chdir(cwd_old, nil)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
notify("chdir #{path}") if verbose
|
||||||
|
path = "~" unless path
|
||||||
|
@cwd = expand_path(path)
|
||||||
|
notify "current dir: #{@cwd}"
|
||||||
|
rehash
|
||||||
|
Void.new(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias cd chdir
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# pushdir(path)
|
||||||
|
# pushdir(path) { &block }
|
||||||
|
#
|
||||||
|
# Pushes the current directory to the directory stack, changing the current
|
||||||
|
# directory to +path+.
|
||||||
|
#
|
||||||
|
# If +path+ is omitted, it exchanges its current directory and the top of its
|
||||||
|
# directory stack.
|
||||||
|
#
|
||||||
|
# If a block is given, it restores the current directory when the block ends.
|
||||||
|
def pushdir(path = nil, verbose = @verbose)
|
||||||
|
check_point
|
||||||
|
|
||||||
|
if block_given?
|
||||||
|
notify("pushdir(with block) #{path}") if verbose
|
||||||
|
pushdir(path, nil)
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
popdir
|
||||||
|
end
|
||||||
|
elsif path
|
||||||
|
notify("pushdir #{path}") if verbose
|
||||||
|
@dir_stack.push @cwd
|
||||||
|
chdir(path, nil)
|
||||||
|
notify "dir stack: [#{@dir_stack.join ', '}]"
|
||||||
|
self
|
||||||
|
else
|
||||||
|
notify("pushdir") if verbose
|
||||||
|
if pop = @dir_stack.pop
|
||||||
|
@dir_stack.push @cwd
|
||||||
|
chdir pop
|
||||||
|
notify "dir stack: [#{@dir_stack.join ', '}]"
|
||||||
|
self
|
||||||
|
else
|
||||||
|
Shell.Fail DirStackEmpty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Void.new(self)
|
||||||
|
end
|
||||||
|
alias pushd pushdir
|
||||||
|
|
||||||
|
# Pops a directory from the directory stack, and sets the current directory
|
||||||
|
# to it.
|
||||||
|
def popdir
|
||||||
|
check_point
|
||||||
|
|
||||||
|
notify("popdir")
|
||||||
|
if pop = @dir_stack.pop
|
||||||
|
chdir pop
|
||||||
|
notify "dir stack: [#{@dir_stack.join ', '}]"
|
||||||
|
self
|
||||||
|
else
|
||||||
|
Shell.Fail DirStackEmpty
|
||||||
|
end
|
||||||
|
Void.new(self)
|
||||||
|
end
|
||||||
|
alias popd popdir
|
||||||
|
|
||||||
|
# Returns a list of scheduled jobs.
|
||||||
|
def jobs
|
||||||
|
@process_controller.jobs
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# kill(signal, job)
|
||||||
|
#
|
||||||
|
# Sends the given +signal+ to the given +job+
|
||||||
|
def kill(sig, command)
|
||||||
|
@process_controller.kill_job(sig, command)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# def_system_command(command, path = command)
|
||||||
|
#
|
||||||
|
# Convenience method for Shell::CommandProcessor.def_system_command.
|
||||||
|
# Defines an instance method which will execute the given shell command.
|
||||||
|
# If the executable is not in Shell.default_system_path, you must
|
||||||
|
# supply the path to it.
|
||||||
|
#
|
||||||
|
# Shell.def_system_command('hostname')
|
||||||
|
# Shell.new.hostname # => localhost
|
||||||
|
#
|
||||||
|
# # How to use an executable that's not in the default path
|
||||||
|
#
|
||||||
|
# Shell.def_system_command('run_my_program', "~/hello")
|
||||||
|
# Shell.new.run_my_program # prints "Hello from a C program!"
|
||||||
|
#
|
||||||
|
def Shell.def_system_command(command, path = command)
|
||||||
|
CommandProcessor.def_system_command(command, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience method for Shell::CommandProcessor.undef_system_command
|
||||||
|
def Shell.undef_system_command(command)
|
||||||
|
CommandProcessor.undef_system_command(command)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# alias_command(alias, command, *opts, &block)
|
||||||
|
#
|
||||||
|
# Convenience method for Shell::CommandProcessor.alias_command.
|
||||||
|
# Defines an instance method which will execute a command under
|
||||||
|
# an alternative name.
|
||||||
|
#
|
||||||
|
# Shell.def_system_command('date')
|
||||||
|
# Shell.alias_command('date_in_utc', 'date', '-u')
|
||||||
|
# Shell.new.date_in_utc # => Sat Jan 25 16:59:57 UTC 2014
|
||||||
|
#
|
||||||
|
def Shell.alias_command(ali, command, *opts, &block)
|
||||||
|
CommandProcessor.alias_command(ali, command, *opts, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience method for Shell::CommandProcessor.unalias_command
|
||||||
|
def Shell.unalias_command(ali)
|
||||||
|
CommandProcessor.unalias_command(ali)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# install_system_commands(pre = "sys_")
|
||||||
|
#
|
||||||
|
# Convenience method for Shell::CommandProcessor.install_system_commands.
|
||||||
|
# Defines instance methods representing all the executable files found in
|
||||||
|
# Shell.default_system_path, with the given prefix prepended to their
|
||||||
|
# names.
|
||||||
|
#
|
||||||
|
# Shell.install_system_commands
|
||||||
|
# Shell.new.sys_echo("hello") # => hello
|
||||||
|
#
|
||||||
|
def Shell.install_system_commands(pre = "sys_")
|
||||||
|
CommandProcessor.install_system_commands(pre)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
def inspect
|
||||||
|
if debug.kind_of?(Integer) && debug > 2
|
||||||
|
super
|
||||||
|
else
|
||||||
|
to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.notify(*opts)
|
||||||
|
Shell::debug_output_synchronize do
|
||||||
|
if opts[-1].kind_of?(String)
|
||||||
|
yorn = verbose?
|
||||||
|
else
|
||||||
|
yorn = opts.pop
|
||||||
|
end
|
||||||
|
return unless yorn
|
||||||
|
|
||||||
|
if @debug_display_thread_id
|
||||||
|
if @debug_display_process_id
|
||||||
|
prefix = "shell(##{Process.pid}:#{Thread.current.to_s.sub("Thread", "Th")}): "
|
||||||
|
else
|
||||||
|
prefix = "shell(#{Thread.current.to_s.sub("Thread", "Th")}): "
|
||||||
|
end
|
||||||
|
else
|
||||||
|
prefix = "shell: "
|
||||||
|
end
|
||||||
|
_head = true
|
||||||
|
STDERR.print opts.collect{|mes|
|
||||||
|
mes = mes.dup
|
||||||
|
yield mes if block_given?
|
||||||
|
if _head
|
||||||
|
_head = false
|
||||||
|
prefix + mes
|
||||||
|
else
|
||||||
|
" "* prefix.size + mes
|
||||||
|
end
|
||||||
|
}.join("\n")+"\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CommandProcessor.initialize
|
||||||
|
CommandProcessor.run_config
|
||||||
|
end
|
@ -0,0 +1,147 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell/builtin-command.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
require_relative "filter"
|
||||||
|
|
||||||
|
class Shell
|
||||||
|
class BuiltInCommand < Filter
|
||||||
|
def wait?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
def active?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Void < BuiltInCommand
|
||||||
|
def initialize(sh, *opts)
|
||||||
|
super sh
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(rs = nil)
|
||||||
|
# do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Echo < BuiltInCommand
|
||||||
|
def initialize(sh, *strings)
|
||||||
|
super sh
|
||||||
|
@strings = strings
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(rs = nil)
|
||||||
|
rs = @shell.record_separator unless rs
|
||||||
|
for str in @strings
|
||||||
|
yield str + rs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Cat < BuiltInCommand
|
||||||
|
def initialize(sh, *filenames)
|
||||||
|
super sh
|
||||||
|
@cat_files = filenames
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(rs = nil)
|
||||||
|
if @cat_files.empty?
|
||||||
|
super
|
||||||
|
else
|
||||||
|
for src in @cat_files
|
||||||
|
@shell.foreach(src, rs){|l| yield l}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Glob < BuiltInCommand
|
||||||
|
def initialize(sh, pattern)
|
||||||
|
super sh
|
||||||
|
|
||||||
|
@pattern = pattern
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(rs = nil)
|
||||||
|
if @pattern[0] == ?/
|
||||||
|
@files = Dir[@pattern]
|
||||||
|
else
|
||||||
|
prefix = @shell.pwd+"/"
|
||||||
|
@files = Dir[prefix+@pattern].collect{|p| p.sub(prefix, "")}
|
||||||
|
end
|
||||||
|
rs = @shell.record_separator unless rs
|
||||||
|
for f in @files
|
||||||
|
yield f+rs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class AppendIO < BuiltInCommand
|
||||||
|
def initialize(sh, io, filter)
|
||||||
|
super sh
|
||||||
|
@input = filter
|
||||||
|
@io = io
|
||||||
|
end
|
||||||
|
|
||||||
|
def input=(filter)
|
||||||
|
@input.input=filter
|
||||||
|
for l in @input
|
||||||
|
@io << l
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class AppendFile < AppendIO
|
||||||
|
def initialize(sh, to_filename, filter)
|
||||||
|
@file_name = to_filename
|
||||||
|
io = sh.open(to_filename, "a")
|
||||||
|
super(sh, io, filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def input=(filter)
|
||||||
|
begin
|
||||||
|
super
|
||||||
|
ensure
|
||||||
|
@io.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Tee < BuiltInCommand
|
||||||
|
def initialize(sh, filename)
|
||||||
|
super sh
|
||||||
|
@to_filename = filename
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(rs = nil)
|
||||||
|
to = @shell.open(@to_filename, "w")
|
||||||
|
begin
|
||||||
|
super{|l| to << l; yield l}
|
||||||
|
ensure
|
||||||
|
to.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Concat < BuiltInCommand
|
||||||
|
def initialize(sh, *jobs)
|
||||||
|
super(sh)
|
||||||
|
@jobs = jobs
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(rs = nil)
|
||||||
|
while job = @jobs.shift
|
||||||
|
job.each{|l| yield l}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,671 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell/command-controller.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
require "e2mmap"
|
||||||
|
|
||||||
|
require_relative "error"
|
||||||
|
require_relative "filter"
|
||||||
|
require_relative "system-command"
|
||||||
|
require_relative "builtin-command"
|
||||||
|
|
||||||
|
class Shell
|
||||||
|
# In order to execute a command on your OS, you need to define it as a
|
||||||
|
# Shell method.
|
||||||
|
#
|
||||||
|
# Alternatively, you can execute any command via
|
||||||
|
# Shell::CommandProcessor#system even if it is not defined.
|
||||||
|
class CommandProcessor
|
||||||
|
|
||||||
|
#
|
||||||
|
# initialize of Shell and related classes.
|
||||||
|
#
|
||||||
|
m = [:initialize, :expand_path]
|
||||||
|
if Object.methods.first.kind_of?(String)
|
||||||
|
NoDelegateMethods = m.collect{|x| x.id2name}
|
||||||
|
else
|
||||||
|
NoDelegateMethods = m
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.initialize
|
||||||
|
|
||||||
|
install_builtin_commands
|
||||||
|
|
||||||
|
# define CommandProcessor#methods to Shell#methods and Filter#methods
|
||||||
|
for m in CommandProcessor.instance_methods(false) - NoDelegateMethods
|
||||||
|
add_delegate_command_to_shell(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.method_added(id)
|
||||||
|
add_delegate_command_to_shell(id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# include run file.
|
||||||
|
#
|
||||||
|
def self.run_config
|
||||||
|
rc = "~/.rb_shell"
|
||||||
|
begin
|
||||||
|
load File.expand_path(rc) if ENV.key?("HOME")
|
||||||
|
rescue LoadError, Errno::ENOENT
|
||||||
|
rescue
|
||||||
|
print "load error: #{rc}\n"
|
||||||
|
print $!.class, ": ", $!, "\n"
|
||||||
|
for err in $@[0, $@.size - 2]
|
||||||
|
print "\t", err, "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(shell)
|
||||||
|
@shell = shell
|
||||||
|
@system_commands = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# CommandProcessor#expand_path(path)
|
||||||
|
# path: String
|
||||||
|
# return: String
|
||||||
|
# returns the absolute path for <path>
|
||||||
|
#
|
||||||
|
def expand_path(path)
|
||||||
|
@shell.expand_path(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# foreach(path, record_separator) -> Enumerator
|
||||||
|
# foreach(path, record_separator) { block }
|
||||||
|
#
|
||||||
|
# See IO.foreach when +path+ is a file.
|
||||||
|
#
|
||||||
|
# See Dir.foreach when +path+ is a directory.
|
||||||
|
#
|
||||||
|
def foreach(path = nil, *rs)
|
||||||
|
path = "." unless path
|
||||||
|
path = expand_path(path)
|
||||||
|
|
||||||
|
if File.directory?(path)
|
||||||
|
Dir.foreach(path){|fn| yield fn}
|
||||||
|
else
|
||||||
|
IO.foreach(path, *rs){|l| yield l}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# open(path, mode, permissions) -> Enumerator
|
||||||
|
# open(path, mode, permissions) { block }
|
||||||
|
#
|
||||||
|
# See IO.open when +path+ is a file.
|
||||||
|
#
|
||||||
|
# See Dir.open when +path+ is a directory.
|
||||||
|
#
|
||||||
|
def open(path, mode = nil, perm = 0666, &b)
|
||||||
|
path = expand_path(path)
|
||||||
|
if File.directory?(path)
|
||||||
|
Dir.open(path, &b)
|
||||||
|
else
|
||||||
|
if @shell.umask
|
||||||
|
f = File.open(path, mode, perm)
|
||||||
|
File.chmod(perm & ~@shell.umask, path)
|
||||||
|
if block_given?
|
||||||
|
f.each(&b)
|
||||||
|
end
|
||||||
|
f
|
||||||
|
else
|
||||||
|
File.open(path, mode, perm, &b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# unlink(path)
|
||||||
|
#
|
||||||
|
# See IO.unlink when +path+ is a file.
|
||||||
|
#
|
||||||
|
# See Dir.unlink when +path+ is a directory.
|
||||||
|
#
|
||||||
|
def unlink(path)
|
||||||
|
@shell.check_point
|
||||||
|
|
||||||
|
path = expand_path(path)
|
||||||
|
if File.directory?(path)
|
||||||
|
Dir.unlink(path)
|
||||||
|
else
|
||||||
|
IO.unlink(path)
|
||||||
|
end
|
||||||
|
Void.new(@shell)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See Shell::CommandProcessor#test
|
||||||
|
alias top_level_test test
|
||||||
|
# call-seq:
|
||||||
|
# test(command, file1, file2) -> true or false
|
||||||
|
# [command, file1, file2] -> true or false
|
||||||
|
#
|
||||||
|
# Tests if the given +command+ exists in +file1+, or optionally +file2+.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# sh[?e, "foo"]
|
||||||
|
# sh[:e, "foo"]
|
||||||
|
# sh["e", "foo"]
|
||||||
|
# sh[:exists?, "foo"]
|
||||||
|
# sh["exists?", "foo"]
|
||||||
|
#
|
||||||
|
def test(command, file1, file2=nil)
|
||||||
|
file1 = expand_path(file1)
|
||||||
|
file2 = expand_path(file2) if file2
|
||||||
|
command = command.id2name if command.kind_of?(Symbol)
|
||||||
|
|
||||||
|
case command
|
||||||
|
when Integer
|
||||||
|
if file2
|
||||||
|
top_level_test(command, file1, file2)
|
||||||
|
else
|
||||||
|
top_level_test(command, file1)
|
||||||
|
end
|
||||||
|
when String
|
||||||
|
if command.size == 1
|
||||||
|
if file2
|
||||||
|
top_level_test(command, file1, file2)
|
||||||
|
else
|
||||||
|
top_level_test(command, file1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unless FileTest.methods(false).include?(command.to_sym)
|
||||||
|
raise "unsupported command: #{ command }"
|
||||||
|
end
|
||||||
|
if file2
|
||||||
|
FileTest.send(command, file1, file2)
|
||||||
|
else
|
||||||
|
FileTest.send(command, file1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# See Shell::CommandProcessor#test
|
||||||
|
alias [] test
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# mkdir(path)
|
||||||
|
#
|
||||||
|
# Same as Dir.mkdir, except multiple directories are allowed.
|
||||||
|
def mkdir(*path)
|
||||||
|
@shell.check_point
|
||||||
|
notify("mkdir #{path.join(' ')}")
|
||||||
|
|
||||||
|
perm = nil
|
||||||
|
if path.last.kind_of?(Integer)
|
||||||
|
perm = path.pop
|
||||||
|
end
|
||||||
|
for dir in path
|
||||||
|
d = expand_path(dir)
|
||||||
|
if perm
|
||||||
|
Dir.mkdir(d, perm)
|
||||||
|
else
|
||||||
|
Dir.mkdir(d)
|
||||||
|
end
|
||||||
|
File.chmod(d, 0666 & ~@shell.umask) if @shell.umask
|
||||||
|
end
|
||||||
|
Void.new(@shell)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# rmdir(path)
|
||||||
|
#
|
||||||
|
# Same as Dir.rmdir, except multiple directories are allowed.
|
||||||
|
def rmdir(*path)
|
||||||
|
@shell.check_point
|
||||||
|
notify("rmdir #{path.join(' ')}")
|
||||||
|
|
||||||
|
for dir in path
|
||||||
|
Dir.rmdir(expand_path(dir))
|
||||||
|
end
|
||||||
|
Void.new(@shell)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# system(command, *options) -> SystemCommand
|
||||||
|
#
|
||||||
|
# Executes the given +command+ with the +options+ parameter.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# print sh.system("ls", "-l")
|
||||||
|
# sh.system("ls", "-l") | sh.head > STDOUT
|
||||||
|
#
|
||||||
|
def system(command, *opts)
|
||||||
|
if opts.empty?
|
||||||
|
if command =~ /\*|\?|\{|\}|\[|\]|<|>|\(|\)|~|&|\||\\|\$|;|'|`|"|\n/
|
||||||
|
return SystemCommand.new(@shell, find_system_command("sh"), "-c", command)
|
||||||
|
else
|
||||||
|
command, *opts = command.split(/\s+/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
SystemCommand.new(@shell, find_system_command(command), *opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# rehash
|
||||||
|
#
|
||||||
|
# Clears the command hash table.
|
||||||
|
def rehash
|
||||||
|
@system_commands = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_point # :nodoc:
|
||||||
|
@shell.process_controller.wait_all_jobs_execution
|
||||||
|
end
|
||||||
|
alias finish_all_jobs check_point # :nodoc:
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# transact { block }
|
||||||
|
#
|
||||||
|
# Executes a block as self
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# sh.transact { system("ls", "-l") | head > STDOUT }
|
||||||
|
def transact(&block)
|
||||||
|
begin
|
||||||
|
@shell.instance_eval(&block)
|
||||||
|
ensure
|
||||||
|
check_point
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# out(device) { block }
|
||||||
|
#
|
||||||
|
# Calls <code>device.print</code> on the result passing the _block_ to
|
||||||
|
# #transact
|
||||||
|
def out(dev = STDOUT, &block)
|
||||||
|
dev.print transact(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# echo(*strings) -> Echo
|
||||||
|
#
|
||||||
|
# Returns a Echo object, for the given +strings+
|
||||||
|
def echo(*strings)
|
||||||
|
Echo.new(@shell, *strings)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# cat(*filename) -> Cat
|
||||||
|
#
|
||||||
|
# Returns a Cat object, for the given +filenames+
|
||||||
|
def cat(*filenames)
|
||||||
|
Cat.new(@shell, *filenames)
|
||||||
|
end
|
||||||
|
|
||||||
|
# def sort(*filenames)
|
||||||
|
# Sort.new(self, *filenames)
|
||||||
|
# end
|
||||||
|
# call-seq:
|
||||||
|
# glob(pattern) -> Glob
|
||||||
|
#
|
||||||
|
# Returns a Glob filter object, with the given +pattern+ object
|
||||||
|
def glob(pattern)
|
||||||
|
Glob.new(@shell, pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
def append(to, filter)
|
||||||
|
case to
|
||||||
|
when String
|
||||||
|
AppendFile.new(@shell, to, filter)
|
||||||
|
when IO
|
||||||
|
AppendIO.new(@shell, to, filter)
|
||||||
|
else
|
||||||
|
Shell.Fail Error::CantApplyMethod, "append", to.class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# tee(file) -> Tee
|
||||||
|
#
|
||||||
|
# Returns a Tee filter object, with the given +file+ command
|
||||||
|
def tee(file)
|
||||||
|
Tee.new(@shell, file)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# concat(*jobs) -> Concat
|
||||||
|
#
|
||||||
|
# Returns a Concat object, for the given +jobs+
|
||||||
|
def concat(*jobs)
|
||||||
|
Concat.new(@shell, *jobs)
|
||||||
|
end
|
||||||
|
|
||||||
|
# %pwd, %cwd -> @pwd
|
||||||
|
def notify(*opts)
|
||||||
|
Shell.notify(*opts) {|mes|
|
||||||
|
yield mes if block_given?
|
||||||
|
|
||||||
|
mes.gsub!("%pwd", "#{@cwd}")
|
||||||
|
mes.gsub!("%cwd", "#{@cwd}")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# private functions
|
||||||
|
#
|
||||||
|
def find_system_command(command)
|
||||||
|
return command if /^\// =~ command
|
||||||
|
case path = @system_commands[command]
|
||||||
|
when String
|
||||||
|
if exists?(path)
|
||||||
|
return path
|
||||||
|
else
|
||||||
|
Shell.Fail Error::CommandNotFound, command
|
||||||
|
end
|
||||||
|
when false
|
||||||
|
Shell.Fail Error::CommandNotFound, command
|
||||||
|
end
|
||||||
|
|
||||||
|
for p in @shell.system_path
|
||||||
|
path = join(p, command)
|
||||||
|
begin
|
||||||
|
st = File.stat(path)
|
||||||
|
rescue SystemCallError
|
||||||
|
next
|
||||||
|
else
|
||||||
|
next unless st.executable? and !st.directory?
|
||||||
|
@system_commands[command] = path
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@system_commands[command] = false
|
||||||
|
Shell.Fail Error::CommandNotFound, command
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# def_system_command(command, path) -> Shell::SystemCommand
|
||||||
|
#
|
||||||
|
# Defines a command, registering +path+ as a Shell method for the given
|
||||||
|
# +command+.
|
||||||
|
#
|
||||||
|
# Shell::CommandProcessor.def_system_command "ls"
|
||||||
|
# #=> Defines ls.
|
||||||
|
#
|
||||||
|
# Shell::CommandProcessor.def_system_command "sys_sort", "sort"
|
||||||
|
# #=> Defines sys_sort as sort
|
||||||
|
#
|
||||||
|
def self.def_system_command(command, path = command)
|
||||||
|
begin
|
||||||
|
eval((d = %Q[def #{command}(*opts)
|
||||||
|
SystemCommand.new(@shell, '#{path}', *opts)
|
||||||
|
end]), nil, __FILE__, __LINE__ - 1)
|
||||||
|
rescue SyntaxError
|
||||||
|
Shell.notify "warn: Can't define #{command} path: #{path}."
|
||||||
|
end
|
||||||
|
Shell.notify "Define #{command} path: #{path}.", Shell.debug?
|
||||||
|
Shell.notify("Definition of #{command}: ", d,
|
||||||
|
Shell.debug.kind_of?(Integer) && Shell.debug > 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# undef_system_command(command) -> self
|
||||||
|
#
|
||||||
|
# Undefines a command
|
||||||
|
def self.undef_system_command(command)
|
||||||
|
command = command.id2name if command.kind_of?(Symbol)
|
||||||
|
remove_method(command)
|
||||||
|
Shell.module_eval{remove_method(command)}
|
||||||
|
Filter.module_eval{remove_method(command)}
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
@alias_map = {}
|
||||||
|
# Returns a list of aliased commands
|
||||||
|
def self.alias_map
|
||||||
|
@alias_map
|
||||||
|
end
|
||||||
|
# call-seq:
|
||||||
|
# alias_command(alias, command, *options) -> self
|
||||||
|
#
|
||||||
|
# Creates a command alias at the given +alias+ for the given +command+,
|
||||||
|
# passing any +options+ along with it.
|
||||||
|
#
|
||||||
|
# Shell::CommandProcessor.alias_command "lsC", "ls", "-CBF", "--show-control-chars"
|
||||||
|
# Shell::CommandProcessor.alias_command("lsC", "ls"){|*opts| ["-CBF", "--show-control-chars", *opts]}
|
||||||
|
#
|
||||||
|
def self.alias_command(ali, command, *opts)
|
||||||
|
ali = ali.id2name if ali.kind_of?(Symbol)
|
||||||
|
command = command.id2name if command.kind_of?(Symbol)
|
||||||
|
begin
|
||||||
|
if block_given?
|
||||||
|
@alias_map[ali.intern] = proc
|
||||||
|
|
||||||
|
eval((d = %Q[def #{ali}(*opts)
|
||||||
|
@shell.__send__(:#{command},
|
||||||
|
*(CommandProcessor.alias_map[:#{ali}].call *opts))
|
||||||
|
end]), nil, __FILE__, __LINE__ - 1)
|
||||||
|
|
||||||
|
else
|
||||||
|
args = opts.collect{|opt| '"' + opt + '"'}.join(",")
|
||||||
|
eval((d = %Q[def #{ali}(*opts)
|
||||||
|
@shell.__send__(:#{command}, #{args}, *opts)
|
||||||
|
end]), nil, __FILE__, __LINE__ - 1)
|
||||||
|
end
|
||||||
|
rescue SyntaxError
|
||||||
|
Shell.notify "warn: Can't alias #{ali} command: #{command}."
|
||||||
|
Shell.notify("Definition of #{ali}: ", d)
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
Shell.notify "Define #{ali} command: #{command}.", Shell.debug?
|
||||||
|
Shell.notify("Definition of #{ali}: ", d,
|
||||||
|
Shell.debug.kind_of?(Integer) && Shell.debug > 1)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# unalias_command(alias) -> self
|
||||||
|
#
|
||||||
|
# Unaliases the given +alias+ command.
|
||||||
|
def self.unalias_command(ali)
|
||||||
|
ali = ali.id2name if ali.kind_of?(Symbol)
|
||||||
|
@alias_map.delete ali.intern
|
||||||
|
undef_system_command(ali)
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
#
|
||||||
|
# Delegates File and FileTest methods into Shell, including the following
|
||||||
|
# commands:
|
||||||
|
#
|
||||||
|
# * Shell#blockdev?(file)
|
||||||
|
# * Shell#chardev?(file)
|
||||||
|
# * Shell#directory?(file)
|
||||||
|
# * Shell#executable?(file)
|
||||||
|
# * Shell#executable_real?(file)
|
||||||
|
# * Shell#exist?(file)/Shell#exists?(file)
|
||||||
|
# * Shell#file?(file)
|
||||||
|
# * Shell#grpowned?(file)
|
||||||
|
# * Shell#owned?(file)
|
||||||
|
# * Shell#pipe?(file)
|
||||||
|
# * Shell#readable?(file)
|
||||||
|
# * Shell#readable_real?(file)
|
||||||
|
# * Shell#setgid?(file)
|
||||||
|
# * Shell#setuid?(file)
|
||||||
|
# * Shell#size(file)/Shell#size?(file)
|
||||||
|
# * Shell#socket?(file)
|
||||||
|
# * Shell#sticky?(file)
|
||||||
|
# * Shell#symlink?(file)
|
||||||
|
# * Shell#writable?(file)
|
||||||
|
# * Shell#writable_real?(file)
|
||||||
|
# * Shell#zero?(file)
|
||||||
|
# * Shell#syscopy(filename_from, filename_to)
|
||||||
|
# * Shell#copy(filename_from, filename_to)
|
||||||
|
# * Shell#move(filename_from, filename_to)
|
||||||
|
# * Shell#compare(filename_from, filename_to)
|
||||||
|
# * Shell#safe_unlink(*filenames)
|
||||||
|
# * Shell#makedirs(*filenames)
|
||||||
|
# * Shell#install(filename_from, filename_to, mode)
|
||||||
|
#
|
||||||
|
# And also, there are some aliases for convenience:
|
||||||
|
#
|
||||||
|
# * Shell#cmp <- Shell#compare
|
||||||
|
# * Shell#mv <- Shell#move
|
||||||
|
# * Shell#cp <- Shell#copy
|
||||||
|
# * Shell#rm_f <- Shell#safe_unlink
|
||||||
|
# * Shell#mkpath <- Shell#makedirs
|
||||||
|
#
|
||||||
|
def self.def_builtin_commands(delegation_class, command_specs)
|
||||||
|
for meth, args in command_specs
|
||||||
|
arg_str = args.collect{|arg| arg.downcase}.join(", ")
|
||||||
|
call_arg_str = args.collect{
|
||||||
|
|arg|
|
||||||
|
case arg
|
||||||
|
when /^(FILENAME.*)$/
|
||||||
|
format("expand_path(%s)", $1.downcase)
|
||||||
|
when /^(\*FILENAME.*)$/
|
||||||
|
# \*FILENAME* -> filenames.collect{|fn| expand_path(fn)}.join(", ")
|
||||||
|
$1.downcase + '.collect{|fn| expand_path(fn)}'
|
||||||
|
else
|
||||||
|
arg
|
||||||
|
end
|
||||||
|
}.join(", ")
|
||||||
|
d = %Q[def #{meth}(#{arg_str})
|
||||||
|
#{delegation_class}.#{meth}(#{call_arg_str})
|
||||||
|
end]
|
||||||
|
Shell.notify "Define #{meth}(#{arg_str})", Shell.debug?
|
||||||
|
Shell.notify("Definition of #{meth}: ", d,
|
||||||
|
Shell.debug.kind_of?(Integer) && Shell.debug > 1)
|
||||||
|
eval d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# install_system_commands(prefix = "sys_")
|
||||||
|
#
|
||||||
|
# Defines all commands in the Shell.default_system_path as Shell method,
|
||||||
|
# all with given +prefix+ appended to their names.
|
||||||
|
#
|
||||||
|
# Any invalid character names are converted to +_+, and errors are passed
|
||||||
|
# to Shell.notify.
|
||||||
|
#
|
||||||
|
# Methods already defined are skipped.
|
||||||
|
def self.install_system_commands(pre = "sys_")
|
||||||
|
defined_meth = {}
|
||||||
|
for m in Shell.methods
|
||||||
|
defined_meth[m] = true
|
||||||
|
end
|
||||||
|
sh = Shell.new
|
||||||
|
for path in Shell.default_system_path
|
||||||
|
next unless sh.directory? path
|
||||||
|
sh.cd path
|
||||||
|
sh.foreach do
|
||||||
|
|cn|
|
||||||
|
if !defined_meth[pre + cn] && sh.file?(cn) && sh.executable?(cn)
|
||||||
|
command = (pre + cn).gsub(/\W/, "_").sub(/^([0-9])/, '_\1')
|
||||||
|
begin
|
||||||
|
def_system_command(command, sh.expand_path(cn))
|
||||||
|
rescue
|
||||||
|
Shell.notify "warn: Can't define #{command} path: #{cn}"
|
||||||
|
end
|
||||||
|
defined_meth[command] = command
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.add_delegate_command_to_shell(id) # :nodoc:
|
||||||
|
id = id.intern if id.kind_of?(String)
|
||||||
|
name = id.id2name
|
||||||
|
if Shell.method_defined?(id)
|
||||||
|
Shell.notify "warn: override definition of Shell##{name}."
|
||||||
|
Shell.notify "warn: alias Shell##{name} to Shell##{name}_org.\n"
|
||||||
|
Shell.module_eval "alias #{name}_org #{name}"
|
||||||
|
end
|
||||||
|
Shell.notify "method added: Shell##{name}.", Shell.debug?
|
||||||
|
Shell.module_eval(%Q[def #{name}(*args, &block)
|
||||||
|
begin
|
||||||
|
@command_processor.__send__(:#{name}, *args, &block)
|
||||||
|
rescue Exception
|
||||||
|
$@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
|
||||||
|
$@.delete_if{|s| /^\\(eval\\):/ =~ s}
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end], __FILE__, __LINE__)
|
||||||
|
|
||||||
|
if Shell::Filter.method_defined?(id)
|
||||||
|
Shell.notify "warn: override definition of Shell::Filter##{name}."
|
||||||
|
Shell.notify "warn: alias Shell##{name} to Shell::Filter##{name}_org."
|
||||||
|
Filter.module_eval "alias #{name}_org #{name}"
|
||||||
|
end
|
||||||
|
Shell.notify "method added: Shell::Filter##{name}.", Shell.debug?
|
||||||
|
Filter.module_eval(%Q[def #{name}(*args, &block)
|
||||||
|
begin
|
||||||
|
self | @shell.__send__(:#{name}, *args, &block)
|
||||||
|
rescue Exception
|
||||||
|
$@.delete_if{|s| /:in `__getobj__'$/ =~ s} #`
|
||||||
|
$@.delete_if{|s| /^\\(eval\\):/ =~ s}
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end], __FILE__, __LINE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Delegates File methods into Shell, including the following commands:
|
||||||
|
#
|
||||||
|
# * Shell#atime(file)
|
||||||
|
# * Shell#basename(file, *opt)
|
||||||
|
# * Shell#chmod(mode, *files)
|
||||||
|
# * Shell#chown(owner, group, *file)
|
||||||
|
# * Shell#ctime(file)
|
||||||
|
# * Shell#delete(*file)
|
||||||
|
# * Shell#dirname(file)
|
||||||
|
# * Shell#ftype(file)
|
||||||
|
# * Shell#join(*file)
|
||||||
|
# * Shell#link(file_from, file_to)
|
||||||
|
# * Shell#lstat(file)
|
||||||
|
# * Shell#mtime(file)
|
||||||
|
# * Shell#readlink(file)
|
||||||
|
# * Shell#rename(file_from, file_to)
|
||||||
|
# * Shell#split(file)
|
||||||
|
# * Shell#stat(file)
|
||||||
|
# * Shell#symlink(file_from, file_to)
|
||||||
|
# * Shell#truncate(file, length)
|
||||||
|
# * Shell#utime(atime, mtime, *file)
|
||||||
|
#
|
||||||
|
def self.install_builtin_commands
|
||||||
|
# method related File.
|
||||||
|
# (exclude open/foreach/unlink)
|
||||||
|
normal_delegation_file_methods = [
|
||||||
|
["atime", ["FILENAME"]],
|
||||||
|
["basename", ["fn", "*opts"]],
|
||||||
|
["chmod", ["mode", "*FILENAMES"]],
|
||||||
|
["chown", ["owner", "group", "*FILENAME"]],
|
||||||
|
["ctime", ["FILENAMES"]],
|
||||||
|
["delete", ["*FILENAMES"]],
|
||||||
|
["dirname", ["FILENAME"]],
|
||||||
|
["ftype", ["FILENAME"]],
|
||||||
|
["join", ["*items"]],
|
||||||
|
["link", ["FILENAME_O", "FILENAME_N"]],
|
||||||
|
["lstat", ["FILENAME"]],
|
||||||
|
["mtime", ["FILENAME"]],
|
||||||
|
["readlink", ["FILENAME"]],
|
||||||
|
["rename", ["FILENAME_FROM", "FILENAME_TO"]],
|
||||||
|
["split", ["pathname"]],
|
||||||
|
["stat", ["FILENAME"]],
|
||||||
|
["symlink", ["FILENAME_O", "FILENAME_N"]],
|
||||||
|
["truncate", ["FILENAME", "length"]],
|
||||||
|
["utime", ["atime", "mtime", "*FILENAMES"]]]
|
||||||
|
|
||||||
|
def_builtin_commands(File, normal_delegation_file_methods)
|
||||||
|
alias_method :rm, :delete
|
||||||
|
|
||||||
|
# method related FileTest
|
||||||
|
def_builtin_commands(FileTest,
|
||||||
|
FileTest.singleton_methods(false).collect{|m| [m, ["FILENAME"]]})
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,26 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell/error.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
require "e2mmap"
|
||||||
|
|
||||||
|
class Shell
|
||||||
|
module Error
|
||||||
|
extend Exception2MessageMapper
|
||||||
|
def_e2message TypeError, "wrong argument type %s (expected %s)"
|
||||||
|
|
||||||
|
def_exception :DirStackEmpty, "Directory stack empty."
|
||||||
|
def_exception :CantDefine, "Can't define method(%s, %s)."
|
||||||
|
def_exception :CantApplyMethod, "This method(%s) does not apply to this type(%s)."
|
||||||
|
def_exception :CommandNotFound, "Command not found(%s)."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,138 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell/filter.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
class Shell #:nodoc:
|
||||||
|
# Any result of command execution is a Filter.
|
||||||
|
#
|
||||||
|
# This class includes Enumerable, therefore a Filter object can use all
|
||||||
|
# Enumerable
|
||||||
|
# facilities.
|
||||||
|
#
|
||||||
|
class Filter
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
def initialize(sh)
|
||||||
|
@shell = sh # parent shell
|
||||||
|
@input = nil # input filter
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :input
|
||||||
|
|
||||||
|
def input=(filter)
|
||||||
|
@input = filter
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# each(record_separator=nil) { block }
|
||||||
|
#
|
||||||
|
# Iterates a block for each line.
|
||||||
|
def each(rs = nil)
|
||||||
|
rs = @shell.record_separator unless rs
|
||||||
|
if @input
|
||||||
|
@input.each(rs){|l| yield l}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# < source
|
||||||
|
#
|
||||||
|
# Inputs from +source+, which is either a string of a file name or an IO
|
||||||
|
# object.
|
||||||
|
def <(src)
|
||||||
|
case src
|
||||||
|
when String
|
||||||
|
cat = Cat.new(@shell, src)
|
||||||
|
cat | self
|
||||||
|
when IO
|
||||||
|
self.input = src
|
||||||
|
self
|
||||||
|
else
|
||||||
|
Shell.Fail Error::CantApplyMethod, "<", src.class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# > source
|
||||||
|
#
|
||||||
|
# Outputs from +source+, which is either a string of a file name or an IO
|
||||||
|
# object.
|
||||||
|
def >(to)
|
||||||
|
case to
|
||||||
|
when String
|
||||||
|
dst = @shell.open(to, "w")
|
||||||
|
begin
|
||||||
|
each(){|l| dst << l}
|
||||||
|
ensure
|
||||||
|
dst.close
|
||||||
|
end
|
||||||
|
when IO
|
||||||
|
each(){|l| to << l}
|
||||||
|
else
|
||||||
|
Shell.Fail Error::CantApplyMethod, ">", to.class
|
||||||
|
end
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# >> source
|
||||||
|
#
|
||||||
|
# Appends the output to +source+, which is either a string of a file name
|
||||||
|
# or an IO object.
|
||||||
|
def >>(to)
|
||||||
|
begin
|
||||||
|
Shell.cd(@shell.pwd).append(to, self)
|
||||||
|
rescue CantApplyMethod
|
||||||
|
Shell.Fail Error::CantApplyMethod, ">>", to.class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# | filter
|
||||||
|
#
|
||||||
|
# Processes a pipeline.
|
||||||
|
def |(filter)
|
||||||
|
filter.input = self
|
||||||
|
if active?
|
||||||
|
@shell.process_controller.start_job filter
|
||||||
|
end
|
||||||
|
filter
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# filter1 + filter2
|
||||||
|
#
|
||||||
|
# Outputs +filter1+, and then +filter2+ using Join.new
|
||||||
|
def +(filter)
|
||||||
|
Join.new(@shell, self, filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_a
|
||||||
|
ary = []
|
||||||
|
each(){|l| ary.push l}
|
||||||
|
ary
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
str = ""
|
||||||
|
each(){|l| str.concat l}
|
||||||
|
str
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
if @shell.debug.kind_of?(Integer) && @shell.debug > 2
|
||||||
|
super
|
||||||
|
else
|
||||||
|
to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,309 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell/process-controller.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
require "forwardable"
|
||||||
|
require "sync"
|
||||||
|
|
||||||
|
class Shell
|
||||||
|
class ProcessController
|
||||||
|
|
||||||
|
@ProcessControllers = {}
|
||||||
|
@ProcessControllersMonitor = Thread::Mutex.new
|
||||||
|
@ProcessControllersCV = Thread::ConditionVariable.new
|
||||||
|
|
||||||
|
@BlockOutputMonitor = Thread::Mutex.new
|
||||||
|
@BlockOutputCV = Thread::ConditionVariable.new
|
||||||
|
|
||||||
|
class << self
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegator("@ProcessControllersMonitor",
|
||||||
|
"synchronize", "process_controllers_exclusive")
|
||||||
|
|
||||||
|
def active_process_controllers
|
||||||
|
process_controllers_exclusive do
|
||||||
|
@ProcessControllers.dup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def activate(pc)
|
||||||
|
process_controllers_exclusive do
|
||||||
|
@ProcessControllers[pc] ||= 0
|
||||||
|
@ProcessControllers[pc] += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inactivate(pc)
|
||||||
|
process_controllers_exclusive do
|
||||||
|
if @ProcessControllers[pc]
|
||||||
|
if (@ProcessControllers[pc] -= 1) == 0
|
||||||
|
@ProcessControllers.delete(pc)
|
||||||
|
@ProcessControllersCV.signal
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_active_object
|
||||||
|
process_controllers_exclusive do
|
||||||
|
for ref in @ProcessControllers.keys
|
||||||
|
yield ref
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def block_output_synchronize(&b)
|
||||||
|
@BlockOutputMonitor.synchronize(&b)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wait_to_finish_all_process_controllers
|
||||||
|
process_controllers_exclusive do
|
||||||
|
while !@ProcessControllers.empty?
|
||||||
|
Shell::notify("Process finishing, but active shell exists",
|
||||||
|
"You can use Shell#transact or Shell#check_point for more safe execution.")
|
||||||
|
if Shell.debug?
|
||||||
|
for pc in @ProcessControllers.keys
|
||||||
|
Shell::notify(" Not finished jobs in "+pc.shell.to_s)
|
||||||
|
for com in pc.jobs
|
||||||
|
com.notify(" Jobs: %id")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@ProcessControllersCV.wait(@ProcessControllersMonitor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# for shell-command complete finish at this process exit.
|
||||||
|
USING_AT_EXIT_WHEN_PROCESS_EXIT = true
|
||||||
|
at_exit do
|
||||||
|
wait_to_finish_all_process_controllers unless $@
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(shell)
|
||||||
|
@shell = shell
|
||||||
|
@waiting_jobs = []
|
||||||
|
@active_jobs = []
|
||||||
|
@jobs_sync = Sync.new
|
||||||
|
|
||||||
|
@job_monitor = Thread::Mutex.new
|
||||||
|
@job_condition = Thread::ConditionVariable.new
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :shell
|
||||||
|
|
||||||
|
def jobs
|
||||||
|
jobs = []
|
||||||
|
@jobs_sync.synchronize(:SH) do
|
||||||
|
jobs.concat @waiting_jobs
|
||||||
|
jobs.concat @active_jobs
|
||||||
|
end
|
||||||
|
jobs
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_jobs
|
||||||
|
@active_jobs
|
||||||
|
end
|
||||||
|
|
||||||
|
def waiting_jobs
|
||||||
|
@waiting_jobs
|
||||||
|
end
|
||||||
|
|
||||||
|
def jobs_exist?
|
||||||
|
@jobs_sync.synchronize(:SH) do
|
||||||
|
@active_jobs.empty? or @waiting_jobs.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_jobs_exist?
|
||||||
|
@jobs_sync.synchronize(:SH) do
|
||||||
|
@active_jobs.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def waiting_jobs_exist?
|
||||||
|
@jobs_sync.synchronize(:SH) do
|
||||||
|
@waiting_jobs.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# schedule a command
|
||||||
|
def add_schedule(command)
|
||||||
|
@jobs_sync.synchronize(:EX) do
|
||||||
|
ProcessController.activate(self)
|
||||||
|
if @active_jobs.empty?
|
||||||
|
start_job command
|
||||||
|
else
|
||||||
|
@waiting_jobs.push(command)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# start a job
|
||||||
|
def start_job(command = nil)
|
||||||
|
@jobs_sync.synchronize(:EX) do
|
||||||
|
if command
|
||||||
|
return if command.active?
|
||||||
|
@waiting_jobs.delete command
|
||||||
|
else
|
||||||
|
command = @waiting_jobs.shift
|
||||||
|
|
||||||
|
return unless command
|
||||||
|
end
|
||||||
|
@active_jobs.push command
|
||||||
|
command.start
|
||||||
|
|
||||||
|
# start all jobs that input from the job
|
||||||
|
for job in @waiting_jobs.dup
|
||||||
|
start_job(job) if job.input == command
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def waiting_job?(job)
|
||||||
|
@jobs_sync.synchronize(:SH) do
|
||||||
|
@waiting_jobs.include?(job)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_job?(job)
|
||||||
|
@jobs_sync.synchronize(:SH) do
|
||||||
|
@active_jobs.include?(job)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# terminate a job
|
||||||
|
def terminate_job(command)
|
||||||
|
@jobs_sync.synchronize(:EX) do
|
||||||
|
@active_jobs.delete command
|
||||||
|
ProcessController.inactivate(self)
|
||||||
|
if @active_jobs.empty?
|
||||||
|
command.notify("start_job in terminate_job(%id)", Shell::debug?)
|
||||||
|
start_job
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# kill a job
|
||||||
|
def kill_job(sig, command)
|
||||||
|
@jobs_sync.synchronize(:EX) do
|
||||||
|
if @waiting_jobs.delete command
|
||||||
|
ProcessController.inactivate(self)
|
||||||
|
return
|
||||||
|
elsif @active_jobs.include?(command)
|
||||||
|
begin
|
||||||
|
r = command.kill(sig)
|
||||||
|
ProcessController.inactivate(self)
|
||||||
|
rescue
|
||||||
|
print "Shell: Warn: $!\n" if @shell.verbose?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
@active_jobs.delete command
|
||||||
|
r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# wait for all jobs to terminate
|
||||||
|
def wait_all_jobs_execution
|
||||||
|
@job_monitor.synchronize do
|
||||||
|
begin
|
||||||
|
while !jobs.empty?
|
||||||
|
@job_condition.wait(@job_monitor)
|
||||||
|
for job in jobs
|
||||||
|
job.notify("waiting job(%id)", Shell::debug?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
redo unless jobs.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# simple fork
|
||||||
|
def sfork(command)
|
||||||
|
pipe_me_in, pipe_peer_out = IO.pipe
|
||||||
|
pipe_peer_in, pipe_me_out = IO.pipe
|
||||||
|
|
||||||
|
|
||||||
|
pid = nil
|
||||||
|
pid_mutex = Thread::Mutex.new
|
||||||
|
pid_cv = Thread::ConditionVariable.new
|
||||||
|
|
||||||
|
Thread.start do
|
||||||
|
ProcessController.block_output_synchronize do
|
||||||
|
STDOUT.flush
|
||||||
|
ProcessController.each_active_object do |pc|
|
||||||
|
for jobs in pc.active_jobs
|
||||||
|
jobs.flush
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pid = fork {
|
||||||
|
Thread.list.each do |th|
|
||||||
|
th.kill unless Thread.current == th
|
||||||
|
end
|
||||||
|
|
||||||
|
STDIN.reopen(pipe_peer_in)
|
||||||
|
STDOUT.reopen(pipe_peer_out)
|
||||||
|
|
||||||
|
ObjectSpace.each_object(IO) do |io|
|
||||||
|
if ![STDIN, STDOUT, STDERR].include?(io)
|
||||||
|
io.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
yield
|
||||||
|
}
|
||||||
|
end
|
||||||
|
pid_cv.signal
|
||||||
|
|
||||||
|
pipe_peer_in.close
|
||||||
|
pipe_peer_out.close
|
||||||
|
command.notify "job(%name:##{pid}) start", @shell.debug?
|
||||||
|
|
||||||
|
begin
|
||||||
|
_pid = nil
|
||||||
|
command.notify("job(%id) start to waiting finish.", @shell.debug?)
|
||||||
|
_pid = Process.waitpid(pid, nil)
|
||||||
|
rescue Errno::ECHILD
|
||||||
|
command.notify "warn: job(%id) was done already waitpid."
|
||||||
|
_pid = true
|
||||||
|
ensure
|
||||||
|
command.notify("Job(%id): Wait to finish when Process finished.", @shell.debug?)
|
||||||
|
# when the process ends, wait until the command terminates
|
||||||
|
if USING_AT_EXIT_WHEN_PROCESS_EXIT or _pid
|
||||||
|
else
|
||||||
|
command.notify("notice: Process finishing...",
|
||||||
|
"wait for Job[%id] to finish.",
|
||||||
|
"You can use Shell#transact or Shell#check_point for more safe execution.")
|
||||||
|
redo
|
||||||
|
end
|
||||||
|
|
||||||
|
@job_monitor.synchronize do
|
||||||
|
terminate_job(command)
|
||||||
|
@job_condition.signal
|
||||||
|
command.notify "job(%id) finish.", @shell.debug?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pid_mutex.synchronize do
|
||||||
|
while !pid
|
||||||
|
pid_cv.wait(pid_mutex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return pid, pipe_me_in, pipe_me_out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,159 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# shell/system-command.rb -
|
||||||
|
# $Release Version: 0.7 $
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
require_relative "filter"
|
||||||
|
|
||||||
|
class Shell
|
||||||
|
class SystemCommand < Filter
|
||||||
|
def initialize(sh, command, *opts)
|
||||||
|
if t = opts.find{|opt| !opt.kind_of?(String) && opt.class}
|
||||||
|
Shell.Fail TypeError, t.class, "String"
|
||||||
|
end
|
||||||
|
super(sh)
|
||||||
|
@command = command
|
||||||
|
@opts = opts
|
||||||
|
|
||||||
|
@input_queue = Thread::Queue.new
|
||||||
|
@pid = nil
|
||||||
|
|
||||||
|
sh.process_controller.add_schedule(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :command
|
||||||
|
alias name command
|
||||||
|
|
||||||
|
def wait?
|
||||||
|
@shell.process_controller.waiting_job?(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
@shell.process_controller.active_job?(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def input=(inp)
|
||||||
|
super
|
||||||
|
if active?
|
||||||
|
start_export
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
notify([@command, *@opts].join(" "))
|
||||||
|
|
||||||
|
@pid, @pipe_in, @pipe_out = @shell.process_controller.sfork(self) {
|
||||||
|
Dir.chdir @shell.pwd
|
||||||
|
$0 = @command
|
||||||
|
exec(@command, *@opts)
|
||||||
|
}
|
||||||
|
if @input
|
||||||
|
start_export
|
||||||
|
end
|
||||||
|
start_import
|
||||||
|
end
|
||||||
|
|
||||||
|
def flush
|
||||||
|
@pipe_out.flush if @pipe_out and !@pipe_out.closed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def terminate
|
||||||
|
begin
|
||||||
|
@pipe_in.close
|
||||||
|
rescue IOError
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
@pipe_out.close
|
||||||
|
rescue IOError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def kill(sig)
|
||||||
|
if @pid
|
||||||
|
Process.kill(sig, @pid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_import
|
||||||
|
notify "Job(%id) start imp-pipe.", @shell.debug?
|
||||||
|
_eop = true
|
||||||
|
Thread.start {
|
||||||
|
begin
|
||||||
|
while l = @pipe_in.gets
|
||||||
|
@input_queue.push l
|
||||||
|
end
|
||||||
|
_eop = false
|
||||||
|
rescue Errno::EPIPE
|
||||||
|
_eop = false
|
||||||
|
ensure
|
||||||
|
if !ProcessController::USING_AT_EXIT_WHEN_PROCESS_EXIT and _eop
|
||||||
|
notify("warn: Process finishing...",
|
||||||
|
"wait for Job[%id] to finish pipe importing.",
|
||||||
|
"You can use Shell#transact or Shell#check_point for more safe execution.")
|
||||||
|
redo
|
||||||
|
end
|
||||||
|
notify "job(%id}) close imp-pipe.", @shell.debug?
|
||||||
|
@input_queue.push :EOF
|
||||||
|
@pipe_in.close
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_export
|
||||||
|
notify "job(%id) start exp-pipe.", @shell.debug?
|
||||||
|
_eop = true
|
||||||
|
Thread.start{
|
||||||
|
begin
|
||||||
|
@input.each do |l|
|
||||||
|
ProcessController::block_output_synchronize do
|
||||||
|
@pipe_out.print l
|
||||||
|
end
|
||||||
|
end
|
||||||
|
_eop = false
|
||||||
|
rescue Errno::EPIPE, Errno::EIO
|
||||||
|
_eop = false
|
||||||
|
ensure
|
||||||
|
if !ProcessController::USING_AT_EXIT_WHEN_PROCESS_EXIT and _eop
|
||||||
|
notify("shell: warn: Process finishing...",
|
||||||
|
"wait for Job(%id) to finish pipe exporting.",
|
||||||
|
"You can use Shell#transact or Shell#check_point for more safe execution.")
|
||||||
|
redo
|
||||||
|
end
|
||||||
|
notify "job(%id) close exp-pipe.", @shell.debug?
|
||||||
|
@pipe_out.close
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
alias super_each each
|
||||||
|
def each(rs = nil)
|
||||||
|
while (l = @input_queue.pop) != :EOF
|
||||||
|
yield l
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ex)
|
||||||
|
# if you wish to output:
|
||||||
|
# "shell: job(#{@command}:#{@pid}) close pipe-out."
|
||||||
|
# then
|
||||||
|
# mes: "job(%id) close pipe-out."
|
||||||
|
# yorn: Boolean(@shell.debug? or @shell.verbose?)
|
||||||
|
def notify(*opts)
|
||||||
|
@shell.notify(*opts) do |mes|
|
||||||
|
yield mes if block_given?
|
||||||
|
|
||||||
|
mes.gsub!("%id", "#{@command}:##{@pid}")
|
||||||
|
mes.gsub!("%name", "#{@command}")
|
||||||
|
mes.gsub!("%pid", "#{@pid}")
|
||||||
|
mes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# version.rb - shell version definition file
|
||||||
|
# $Release Version: 0.7$
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
class Shell # :nodoc:
|
||||||
|
VERSION = "0.8.1"
|
||||||
|
@RELEASE_VERSION = VERSION
|
||||||
|
@LAST_UPDATE_DATE = "07/03/20"
|
||||||
|
end
|
@ -0,0 +1,42 @@
|
|||||||
|
begin
|
||||||
|
require_relative "lib/shell/version"
|
||||||
|
rescue LoadError
|
||||||
|
# for Ruby core repository
|
||||||
|
require_relative "version"
|
||||||
|
end
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "shell"
|
||||||
|
spec.version = Shell::VERSION
|
||||||
|
spec.authors = ["Keiju ISHITSUKA"]
|
||||||
|
spec.email = ["keiju@ruby-lang.org"]
|
||||||
|
|
||||||
|
spec.summary = %q{An idiomatic Ruby interface for common UNIX shell commands.}
|
||||||
|
spec.description = %q{An idiomatic Ruby interface for common UNIX shell commands.}
|
||||||
|
spec.homepage = "https://github.com/ruby/shell"
|
||||||
|
spec.license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
spec.files = [
|
||||||
|
"Gemfile",
|
||||||
|
"LICENSE.txt",
|
||||||
|
"README.md",
|
||||||
|
"Rakefile",
|
||||||
|
"bin/console",
|
||||||
|
"bin/setup",
|
||||||
|
"lib/shell.rb",
|
||||||
|
"lib/shell/builtin-command.rb",
|
||||||
|
"lib/shell/command-processor.rb",
|
||||||
|
"lib/shell/error.rb",
|
||||||
|
"lib/shell/filter.rb",
|
||||||
|
"lib/shell/process-controller.rb",
|
||||||
|
"lib/shell/system-command.rb",
|
||||||
|
"lib/shell/version.rb",
|
||||||
|
"shell.gemspec",
|
||||||
|
]
|
||||||
|
spec.bindir = "exe"
|
||||||
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
|
spec.add_runtime_dependency "e2mmap"
|
||||||
|
spec.add_runtime_dependency "sync"
|
||||||
|
end
|
@ -0,0 +1,5 @@
|
|||||||
|
sudo: false
|
||||||
|
language: ruby
|
||||||
|
rvm:
|
||||||
|
- 2.6.0
|
||||||
|
before_install: gem install bundler -v 1.16.2
|
@ -0,0 +1,5 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
||||||
|
|
||||||
|
gemspec
|
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
@ -0,0 +1,77 @@
|
|||||||
|
# Sync
|
||||||
|
|
||||||
|
A module that provides a two-phase lock with a counter.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'sync'
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
$ bundle
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
$ gem install sync
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Sync_m, Synchronizer_m
|
||||||
|
|
||||||
|
```
|
||||||
|
obj.extend(Sync_m)
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
class Foo
|
||||||
|
include Sync_m
|
||||||
|
:
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Sync_m#sync_mode
|
||||||
|
Sync_m#sync_locked?, locked?
|
||||||
|
Sync_m#sync_shared?, shared?
|
||||||
|
Sync_m#sync_exclusive?, sync_exclusive?
|
||||||
|
Sync_m#sync_try_lock, try_lock
|
||||||
|
Sync_m#sync_lock, lock
|
||||||
|
Sync_m#sync_unlock, unlock
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync, Synchronizer:
|
||||||
|
|
||||||
|
```
|
||||||
|
sync = Sync.new
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Sync#mode
|
||||||
|
Sync#locked?
|
||||||
|
Sync#shared?
|
||||||
|
Sync#exclusive?
|
||||||
|
Sync#try_lock(mode) -- mode = :EX, :SH, :UN
|
||||||
|
Sync#lock(mode) -- mode = :EX, :SH, :UN
|
||||||
|
Sync#unlock
|
||||||
|
Sync#synchronize(mode) {...}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||||
|
|
||||||
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/sync.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause).
|
@ -0,0 +1,10 @@
|
|||||||
|
require "bundler/gem_tasks"
|
||||||
|
require "rake/testtask"
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
t.libs << "test"
|
||||||
|
t.libs << "lib"
|
||||||
|
t.test_files = FileList["test/**/test_*.rb"]
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => :test
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require "bundler/setup"
|
||||||
|
require_relative "../lib/sync"
|
||||||
|
|
||||||
|
require "irb"
|
||||||
|
IRB.start(__FILE__)
|
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
set -vx
|
||||||
|
|
||||||
|
bundle install
|
@ -0,0 +1,328 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
#
|
||||||
|
# sync.rb - 2 phase lock with counter
|
||||||
|
# $Release Version: 1.0$
|
||||||
|
# $Revision$
|
||||||
|
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
|
||||||
|
#
|
||||||
|
# --
|
||||||
|
# Sync_m, Synchronizer_m
|
||||||
|
# Usage:
|
||||||
|
# obj.extend(Sync_m)
|
||||||
|
# or
|
||||||
|
# class Foo
|
||||||
|
# include Sync_m
|
||||||
|
# :
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Sync_m#sync_mode
|
||||||
|
# Sync_m#sync_locked?, locked?
|
||||||
|
# Sync_m#sync_shared?, shared?
|
||||||
|
# Sync_m#sync_exclusive?, sync_exclusive?
|
||||||
|
# Sync_m#sync_try_lock, try_lock
|
||||||
|
# Sync_m#sync_lock, lock
|
||||||
|
# Sync_m#sync_unlock, unlock
|
||||||
|
#
|
||||||
|
# Sync, Synchronizer:
|
||||||
|
# Usage:
|
||||||
|
# sync = Sync.new
|
||||||
|
#
|
||||||
|
# Sync#mode
|
||||||
|
# Sync#locked?
|
||||||
|
# Sync#shared?
|
||||||
|
# Sync#exclusive?
|
||||||
|
# Sync#try_lock(mode) -- mode = :EX, :SH, :UN
|
||||||
|
# Sync#lock(mode) -- mode = :EX, :SH, :UN
|
||||||
|
# Sync#unlock
|
||||||
|
# Sync#synchronize(mode) {...}
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
##
|
||||||
|
# A module that provides a two-phase lock with a counter.
|
||||||
|
|
||||||
|
module Sync_m
|
||||||
|
# lock mode
|
||||||
|
UN = :UN
|
||||||
|
SH = :SH
|
||||||
|
EX = :EX
|
||||||
|
|
||||||
|
# exceptions
|
||||||
|
class Err < StandardError
|
||||||
|
def Err.Fail(*opt)
|
||||||
|
fail self, sprintf(self::Message, *opt)
|
||||||
|
end
|
||||||
|
|
||||||
|
class UnknownLocker < Err
|
||||||
|
Message = "Thread(%s) not locked."
|
||||||
|
def UnknownLocker.Fail(th)
|
||||||
|
super(th.inspect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class LockModeFailer < Err
|
||||||
|
Message = "Unknown lock mode(%s)"
|
||||||
|
def LockModeFailer.Fail(mode)
|
||||||
|
if mode.id2name
|
||||||
|
mode = mode.id2name
|
||||||
|
end
|
||||||
|
super(mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def Sync_m.define_aliases(cl)
|
||||||
|
cl.module_eval %q{
|
||||||
|
alias locked? sync_locked?
|
||||||
|
alias shared? sync_shared?
|
||||||
|
alias exclusive? sync_exclusive?
|
||||||
|
alias lock sync_lock
|
||||||
|
alias unlock sync_unlock
|
||||||
|
alias try_lock sync_try_lock
|
||||||
|
alias synchronize sync_synchronize
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def Sync_m.append_features(cl)
|
||||||
|
super
|
||||||
|
# do nothing for Modules
|
||||||
|
# make aliases for Classes.
|
||||||
|
define_aliases(cl) unless cl.instance_of?(Module)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def Sync_m.extend_object(obj)
|
||||||
|
super
|
||||||
|
obj.sync_extend
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_extend
|
||||||
|
unless (defined? locked? and
|
||||||
|
defined? shared? and
|
||||||
|
defined? exclusive? and
|
||||||
|
defined? lock and
|
||||||
|
defined? unlock and
|
||||||
|
defined? try_lock and
|
||||||
|
defined? synchronize)
|
||||||
|
Sync_m.define_aliases(singleton_class)
|
||||||
|
end
|
||||||
|
sync_initialize
|
||||||
|
end
|
||||||
|
|
||||||
|
# accessing
|
||||||
|
def sync_locked?
|
||||||
|
sync_mode != UN
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_shared?
|
||||||
|
sync_mode == SH
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_exclusive?
|
||||||
|
sync_mode == EX
|
||||||
|
end
|
||||||
|
|
||||||
|
# locking methods.
|
||||||
|
def sync_try_lock(mode = EX)
|
||||||
|
return unlock if mode == UN
|
||||||
|
@sync_mutex.synchronize do
|
||||||
|
sync_try_lock_sub(mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_lock(m = EX)
|
||||||
|
return unlock if m == UN
|
||||||
|
Thread.handle_interrupt(StandardError => :on_blocking) do
|
||||||
|
while true
|
||||||
|
@sync_mutex.synchronize do
|
||||||
|
begin
|
||||||
|
if sync_try_lock_sub(m)
|
||||||
|
return self
|
||||||
|
else
|
||||||
|
if sync_sh_locker[Thread.current]
|
||||||
|
sync_upgrade_waiting.push [Thread.current, sync_sh_locker[Thread.current]]
|
||||||
|
sync_sh_locker.delete(Thread.current)
|
||||||
|
else
|
||||||
|
unless sync_waiting.include?(Thread.current) || sync_upgrade_waiting.reverse_each.any?{|w| w.first == Thread.current }
|
||||||
|
sync_waiting.push Thread.current
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@sync_mutex.sleep
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
sync_waiting.delete(Thread.current)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_unlock(m = EX)
|
||||||
|
wakeup_threads = []
|
||||||
|
@sync_mutex.synchronize do
|
||||||
|
if sync_mode == UN
|
||||||
|
Err::UnknownLocker.Fail(Thread.current)
|
||||||
|
end
|
||||||
|
|
||||||
|
m = sync_mode if m == EX and sync_mode == SH
|
||||||
|
|
||||||
|
runnable = false
|
||||||
|
case m
|
||||||
|
when UN
|
||||||
|
Err::UnknownLocker.Fail(Thread.current)
|
||||||
|
|
||||||
|
when EX
|
||||||
|
if sync_ex_locker == Thread.current
|
||||||
|
if (self.sync_ex_count = sync_ex_count - 1) == 0
|
||||||
|
self.sync_ex_locker = nil
|
||||||
|
if sync_sh_locker.include?(Thread.current)
|
||||||
|
self.sync_mode = SH
|
||||||
|
else
|
||||||
|
self.sync_mode = UN
|
||||||
|
end
|
||||||
|
runnable = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Err::UnknownLocker.Fail(Thread.current)
|
||||||
|
end
|
||||||
|
|
||||||
|
when SH
|
||||||
|
if (count = sync_sh_locker[Thread.current]).nil?
|
||||||
|
Err::UnknownLocker.Fail(Thread.current)
|
||||||
|
else
|
||||||
|
if (sync_sh_locker[Thread.current] = count - 1) == 0
|
||||||
|
sync_sh_locker.delete(Thread.current)
|
||||||
|
if sync_sh_locker.empty? and sync_ex_count == 0
|
||||||
|
self.sync_mode = UN
|
||||||
|
runnable = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if runnable
|
||||||
|
if sync_upgrade_waiting.size > 0
|
||||||
|
th, count = sync_upgrade_waiting.shift
|
||||||
|
sync_sh_locker[th] = count
|
||||||
|
th.wakeup
|
||||||
|
wakeup_threads.push th
|
||||||
|
else
|
||||||
|
wait = sync_waiting
|
||||||
|
self.sync_waiting = []
|
||||||
|
for th in wait
|
||||||
|
th.wakeup
|
||||||
|
wakeup_threads.push th
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for th in wakeup_threads
|
||||||
|
th.run
|
||||||
|
end
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_synchronize(mode = EX)
|
||||||
|
Thread.handle_interrupt(StandardError => :on_blocking) do
|
||||||
|
sync_lock(mode)
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
sync_unlock
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :sync_mode
|
||||||
|
|
||||||
|
attr_accessor :sync_waiting
|
||||||
|
attr_accessor :sync_upgrade_waiting
|
||||||
|
attr_accessor :sync_sh_locker
|
||||||
|
attr_accessor :sync_ex_locker
|
||||||
|
attr_accessor :sync_ex_count
|
||||||
|
|
||||||
|
def sync_inspect
|
||||||
|
sync_iv = instance_variables.select{|iv| /^@sync_/ =~ iv.id2name}.collect{|iv| iv.id2name + '=' + instance_eval(iv.id2name).inspect}.join(",")
|
||||||
|
print "<#{self.class}.extend Sync_m: #{inspect}, <Sync_m: #{sync_iv}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def sync_initialize
|
||||||
|
@sync_mode = UN
|
||||||
|
@sync_waiting = []
|
||||||
|
@sync_upgrade_waiting = []
|
||||||
|
@sync_sh_locker = Hash.new
|
||||||
|
@sync_ex_locker = nil
|
||||||
|
@sync_ex_count = 0
|
||||||
|
|
||||||
|
@sync_mutex = Thread::Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(*args)
|
||||||
|
super
|
||||||
|
sync_initialize
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_try_lock_sub(m)
|
||||||
|
case m
|
||||||
|
when SH
|
||||||
|
case sync_mode
|
||||||
|
when UN
|
||||||
|
self.sync_mode = m
|
||||||
|
sync_sh_locker[Thread.current] = 1
|
||||||
|
ret = true
|
||||||
|
when SH
|
||||||
|
count = 0 unless count = sync_sh_locker[Thread.current]
|
||||||
|
sync_sh_locker[Thread.current] = count + 1
|
||||||
|
ret = true
|
||||||
|
when EX
|
||||||
|
# in EX mode, lock will upgrade to EX lock
|
||||||
|
if sync_ex_locker == Thread.current
|
||||||
|
self.sync_ex_count = sync_ex_count + 1
|
||||||
|
ret = true
|
||||||
|
else
|
||||||
|
ret = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when EX
|
||||||
|
if sync_mode == UN or
|
||||||
|
sync_mode == SH && sync_sh_locker.size == 1 && sync_sh_locker.include?(Thread.current)
|
||||||
|
self.sync_mode = m
|
||||||
|
self.sync_ex_locker = Thread.current
|
||||||
|
self.sync_ex_count = 1
|
||||||
|
ret = true
|
||||||
|
elsif sync_mode == EX && sync_ex_locker == Thread.current
|
||||||
|
self.sync_ex_count = sync_ex_count + 1
|
||||||
|
ret = true
|
||||||
|
else
|
||||||
|
ret = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Err::LockModeFailer.Fail m
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# An alias for Sync_m from sync.rb
|
||||||
|
|
||||||
|
Synchronizer_m = Sync_m
|
||||||
|
|
||||||
|
##
|
||||||
|
# A class that provides two-phase lock with a counter. See Sync_m for
|
||||||
|
# details.
|
||||||
|
|
||||||
|
class Sync
|
||||||
|
|
||||||
|
VERSION = "0.5.0"
|
||||||
|
|
||||||
|
include Sync_m
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# An alias for Sync from sync.rb. See Sync_m for details.
|
||||||
|
|
||||||
|
Synchronizer = Sync
|
@ -0,0 +1,27 @@
|
|||||||
|
begin
|
||||||
|
require_relative "lib/sync"
|
||||||
|
rescue LoadError
|
||||||
|
# for Ruby core repository
|
||||||
|
require_relative "sync"
|
||||||
|
end
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "sync"
|
||||||
|
spec.version = Sync::VERSION
|
||||||
|
spec.authors = ["Keiju ISHITSUKA"]
|
||||||
|
spec.email = ["keiju@ruby-lang.org"]
|
||||||
|
|
||||||
|
spec.summary = %q{A module that provides a two-phase lock with a counter.}
|
||||||
|
spec.description = %q{A module that provides a two-phase lock with a counter.}
|
||||||
|
spec.homepage = "https://github.com/ruby/sync"
|
||||||
|
spec.license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
spec.files = [".gitignore", ".travis.yml", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "lib/sync.rb", "sync.gemspec"]
|
||||||
|
spec.bindir = "exe"
|
||||||
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
|
spec.add_development_dependency "bundler"
|
||||||
|
spec.add_development_dependency "rake"
|
||||||
|
spec.add_development_dependency "test-unit"
|
||||||
|
end
|
@ -0,0 +1,26 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# stub: e2mmap 0.1.0 ruby lib
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "e2mmap".freeze
|
||||||
|
s.version = "0.1.0".freeze
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
|
s.require_paths = ["lib".freeze]
|
||||||
|
s.authors = ["Keiju ISHITSUKA".freeze]
|
||||||
|
s.bindir = "exe".freeze
|
||||||
|
s.date = "2018-12-04"
|
||||||
|
s.description = "Module for defining custom exceptions with specific messages.".freeze
|
||||||
|
s.email = ["keiju@ruby-lang.org".freeze]
|
||||||
|
s.homepage = "https://github.com/ruby/e2mmap".freeze
|
||||||
|
s.licenses = ["BSD-2-Clause".freeze]
|
||||||
|
s.rubygems_version = "2.7.6".freeze
|
||||||
|
s.summary = "Module for defining custom exceptions with specific messages.".freeze
|
||||||
|
|
||||||
|
s.installed_by_version = "3.5.16".freeze if s.respond_to? :installed_by_version
|
||||||
|
|
||||||
|
s.specification_version = 4
|
||||||
|
|
||||||
|
s.add_development_dependency(%q<bundler>.freeze, ["~> 1.16".freeze])
|
||||||
|
s.add_development_dependency(%q<rake>.freeze, ["~> 10.0".freeze])
|
||||||
|
end
|
@ -0,0 +1,26 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# stub: envbash 1.0.1 ruby lib
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "envbash".freeze
|
||||||
|
s.version = "1.0.1".freeze
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
|
s.require_paths = ["lib".freeze]
|
||||||
|
s.authors = ["Aron Griffis".freeze]
|
||||||
|
s.date = "2017-02-18"
|
||||||
|
s.email = "aron@scampersand.com".freeze
|
||||||
|
s.homepage = "https://github.com/scampersand/envbash-ruby".freeze
|
||||||
|
s.licenses = ["MIT".freeze]
|
||||||
|
s.rubygems_version = "2.5.2".freeze
|
||||||
|
s.summary = "Source env.bash script to update environment".freeze
|
||||||
|
|
||||||
|
s.installed_by_version = "3.5.16".freeze if s.respond_to? :installed_by_version
|
||||||
|
|
||||||
|
s.specification_version = 4
|
||||||
|
|
||||||
|
s.add_development_dependency(%q<codecov>.freeze, [">= 0".freeze])
|
||||||
|
s.add_development_dependency(%q<minitest>.freeze, [">= 0".freeze])
|
||||||
|
s.add_development_dependency(%q<minitest-assert_errors>.freeze, [">= 0".freeze])
|
||||||
|
s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
|
||||||
|
end
|
@ -0,0 +1,29 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# stub: interface 1.0.5 ruby lib
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "interface".freeze
|
||||||
|
s.version = "1.0.5".freeze
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
|
s.metadata = { "bug_tracker_uri" => "https://github.com/djberg96/interface/issues", "changelog_uri" => "https://github.com/djberg96/interface/blob/master/CHANGES", "documentation_uri" => "https://github.com/djberg96/interface/wiki", "homepage_uri" => "https://github.com/djberg96/interface", "source_code_uri" => "https://github.com/djberg96/interface", "wiki_uri" => "https://github.com/djberg96/interface/wiki" } if s.respond_to? :metadata=
|
||||||
|
s.require_paths = ["lib".freeze]
|
||||||
|
s.authors = ["Daniel J. Berger".freeze]
|
||||||
|
s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl\ncmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t\nMB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi\nZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv\nbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALgfaroVM6CI06cxr0/h\nA+j+pc8fgpRgBVmHFaFunq28GPC3IvW7Nvc3Y8SnAW7pP1EQIbhlwRIaQzJ93/yj\nu95KpkP7tA9erypnV7dpzBkzNlX14ACaFD/6pHoXoe2ltBxk3CCyyzx70mTqJpph\n75IB03ni9a8yqn8pmse+s83bFJOAqddSj009sGPcQO+QOWiNxqYv1n5EHcvj2ebO\n6hN7YTmhx7aSia4qL/quc4DlIaGMWoAhvML7u1fmo53CYxkKskfN8MOecq2vfEmL\niLu+SsVVEAufMDDFMXMJlvDsviolUSGMSNRTujkyCcJoXKYYxZSNtIiyd9etI0X3\nctu0uhrFyrMZXCedutvXNjUolD5r9KGBFSWH1R9u2I3n3SAyFF2yzv/7idQHLJJq\n74BMnx0FIq6fCpu5slAipvxZ3ZkZpEXZFr3cIBtO1gFvQWW7E/Y3ijliWJS1GQFq\n058qERadHGu1yu1dojmFRo6W2KZvY9al2yIlbkpDrD5MYQIDAQABo3cwdTAJBgNV\nHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUFZsMapgzJimzsbaBG2Tm8j5e\nAzgwHQYDVR0RBBYwFIESZGpiZXJnOTZAZ21haWwuY29tMB0GA1UdEgQWMBSBEmRq\nYmVyZzk2QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAW2tnYixXQtKxgGXq\n/3iSWG2bLwvxS4go3srO+aRXZHrFUMlJ5W0mCxl03aazxxKTsVVpZD8QZxvK91OQ\nh9zr9JBYqCLcCVbr8SkmYCi/laxIZxsNE5YI8cC8vvlLI7AMgSfPSnn/Epq1GjGY\n6L1iRcEDtanGCIvjqlCXO9+BmsnCfEVehqZkQHeYczA03tpOWb6pon2wzvMKSsKH\nks0ApVdstSLz1kzzAqem/uHdG9FyXdbTAwH1G4ZPv69sQAFAOCgAqYmdnzedsQtE\n1LQfaQrx0twO+CZJPcRLEESjq8ScQxWRRkfuh2VeR7cEU7L7KqT10mtUwrvw7APf\nDYoeCY9KyjIBjQXfbj2ke5u1hZj94Fsq9FfbEQg8ygCgwThnmkTrrKEiMSs3alYR\nORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM\nWZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh\n-----END CERTIFICATE-----\n".freeze]
|
||||||
|
s.date = "2024-11-13"
|
||||||
|
s.description = " The interface library implements Java style interfaces for Ruby.\n It lets you define a set a methods that must be defined in the\n including class or module, or an error is raised.\n".freeze
|
||||||
|
s.email = "djberg96@gmail.com".freeze
|
||||||
|
s.extra_rdoc_files = ["README".freeze, "CHANGES".freeze, "MANIFEST".freeze]
|
||||||
|
s.files = ["CHANGES".freeze, "MANIFEST".freeze, "README".freeze]
|
||||||
|
s.homepage = "http://github.com/djberg96/interface".freeze
|
||||||
|
s.licenses = ["Artistic-2.0".freeze]
|
||||||
|
s.rubygems_version = "3.0.6".freeze
|
||||||
|
s.summary = "Java style interfaces for Ruby".freeze
|
||||||
|
|
||||||
|
s.installed_by_version = "3.5.16".freeze if s.respond_to? :installed_by_version
|
||||||
|
|
||||||
|
s.specification_version = 4
|
||||||
|
|
||||||
|
s.add_development_dependency(%q<test-unit>.freeze, [">= 0".freeze])
|
||||||
|
s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
|
||||||
|
end
|
@ -0,0 +1,26 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# stub: shell 0.8.1 ruby lib
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "shell".freeze
|
||||||
|
s.version = "0.8.1".freeze
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
|
s.require_paths = ["lib".freeze]
|
||||||
|
s.authors = ["Keiju ISHITSUKA".freeze]
|
||||||
|
s.bindir = "exe".freeze
|
||||||
|
s.date = "2020-07-16"
|
||||||
|
s.description = "An idiomatic Ruby interface for common UNIX shell commands.".freeze
|
||||||
|
s.email = ["keiju@ruby-lang.org".freeze]
|
||||||
|
s.homepage = "https://github.com/ruby/shell".freeze
|
||||||
|
s.licenses = ["BSD-2-Clause".freeze]
|
||||||
|
s.rubygems_version = "3.2.0.pre1".freeze
|
||||||
|
s.summary = "An idiomatic Ruby interface for common UNIX shell commands.".freeze
|
||||||
|
|
||||||
|
s.installed_by_version = "3.5.16".freeze if s.respond_to? :installed_by_version
|
||||||
|
|
||||||
|
s.specification_version = 4
|
||||||
|
|
||||||
|
s.add_runtime_dependency(%q<e2mmap>.freeze, [">= 0".freeze])
|
||||||
|
s.add_runtime_dependency(%q<sync>.freeze, [">= 0".freeze])
|
||||||
|
end
|
@ -0,0 +1,27 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# stub: sync 0.5.0 ruby lib
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "sync".freeze
|
||||||
|
s.version = "0.5.0".freeze
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
|
s.require_paths = ["lib".freeze]
|
||||||
|
s.authors = ["Keiju ISHITSUKA".freeze]
|
||||||
|
s.bindir = "exe".freeze
|
||||||
|
s.date = "2018-12-04"
|
||||||
|
s.description = "A module that provides a two-phase lock with a counter.".freeze
|
||||||
|
s.email = ["keiju@ruby-lang.org".freeze]
|
||||||
|
s.homepage = "https://github.com/ruby/sync".freeze
|
||||||
|
s.licenses = ["BSD-2-Clause".freeze]
|
||||||
|
s.rubygems_version = "2.7.6".freeze
|
||||||
|
s.summary = "A module that provides a two-phase lock with a counter.".freeze
|
||||||
|
|
||||||
|
s.installed_by_version = "3.5.16".freeze if s.respond_to? :installed_by_version
|
||||||
|
|
||||||
|
s.specification_version = 4
|
||||||
|
|
||||||
|
s.add_development_dependency(%q<bundler>.freeze, [">= 0".freeze])
|
||||||
|
s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
|
||||||
|
s.add_development_dependency(%q<test-unit>.freeze, [">= 0".freeze])
|
||||||
|
end
|
@ -0,0 +1,16 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
def load_ruby_options_defaults
|
||||||
|
hestiacp_ruby_func_path = "/usr/local/hestia/func_ruby"
|
||||||
|
$LOAD_PATH.unshift hestiacp_ruby_func_path
|
||||||
|
hestiacp_ruby_gem_version = "3.3.0"
|
||||||
|
Gem.paths = {
|
||||||
|
"GEM_HOME" => "#{hestiacp_ruby_func_path}/gems/ruby/#{hestiacp_ruby_gem_version}",
|
||||||
|
"GEM_PATH" => "#{hestiacp_ruby_func_path}/gems/ruby/#{hestiacp_ruby_gem_version}",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_hestia_default_path_from_env
|
||||||
|
return ENV["HESTIA"] if ENV.key? "HESTIA"
|
||||||
|
"/usr/local/hestia"
|
||||||
|
end
|
@ -0,0 +1,230 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
require "date"
|
||||||
|
require "envbash"
|
||||||
|
require "interface"
|
||||||
|
require "json"
|
||||||
|
require "csv"
|
||||||
|
|
||||||
|
date_internal = DateTime.now
|
||||||
|
di = date_internal.strftime("%d%m%Y%H%M")
|
||||||
|
# Internal variables
|
||||||
|
$HOMEDIR = "/home"
|
||||||
|
$BACKUP = "/backup"
|
||||||
|
$BACKUP_GZIP = 9
|
||||||
|
$BACKUP_DISK_LIMIT = 95
|
||||||
|
$BACKUP_LA_LIMIT = %x(cat /proc/cpuinfo | grep processor | wc -l)
|
||||||
|
$RRD_STEP = 300
|
||||||
|
$BIN = "#{$HESTIA}/bin"
|
||||||
|
$HESTIA_INSTALL_DIR = "#{$HESTIA}/install/rpm"
|
||||||
|
$HESTIA_COMMON_DIR = "#{$HESTIA}/install/common"
|
||||||
|
$HESTIA_BACKUP = "/root/hst_backups/#{di}"
|
||||||
|
$HESTIA_PHP = "#{$HESTIA}/php/bin/php"
|
||||||
|
$USER_DATA = "#{$HESTIA}/data/users/#{$user}"
|
||||||
|
$WEBTPL = "#{$HESTIA}/data/templates/web"
|
||||||
|
$MAILTPL = "#{$HESTIA}/data/templates/mail"
|
||||||
|
$DNSTPL = "#{$HESTIA}/data/templates/dns"
|
||||||
|
$RRD = "#{$HESTIA}/web/rrd"
|
||||||
|
$SENDMAIL = "#{$HESTIA}/web/inc/mail-wrapper.php"
|
||||||
|
$HESTIA_GIT_REPO = "https://dev.brepo.ru/bayrepo/hestiacp"
|
||||||
|
$HESTIA_THEMES = "#{$HESTIA}/web/css/themes"
|
||||||
|
$HESTIA_THEMES_CUSTOM = "#{$HESTIA}/web/css/themes/custom"
|
||||||
|
$SCRIPT = File.basename($PROGRAM_NAME)
|
||||||
|
|
||||||
|
# Return codes
|
||||||
|
OK = 0
|
||||||
|
E_ARGS = 1
|
||||||
|
E_INVALID = 2
|
||||||
|
E_NOTEXIST = 3
|
||||||
|
E_EXISTS = 4
|
||||||
|
E_SUSPENDED = 5
|
||||||
|
E_UNSUSPENDED = 6
|
||||||
|
E_INUSE = 7
|
||||||
|
E_LIMIT = 8
|
||||||
|
E_PASSWORD = 9
|
||||||
|
E_FORBIDEN = 10
|
||||||
|
E_DISABLED = 11
|
||||||
|
E_PARSING = 12
|
||||||
|
E_DISK = 13
|
||||||
|
E_LA = 14
|
||||||
|
E_CONNECT = 15
|
||||||
|
E_FTP = 16
|
||||||
|
E_DB = 17
|
||||||
|
E_RRD = 18
|
||||||
|
E_UPDATE = 19
|
||||||
|
E_RESTART = 20
|
||||||
|
E_PERMISSION = 21
|
||||||
|
E_MODULE = 22
|
||||||
|
|
||||||
|
$ARGUMENTS = ""
|
||||||
|
ARGV.each_with_index do |item, index|
|
||||||
|
if !$HIDE.nil? && $HIDE == index
|
||||||
|
$ARGUMENTS = "#{$ARGUMENTS} '******'"
|
||||||
|
else
|
||||||
|
$ARGUMENTS = "#{$ARGUMENTS} #{item}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class File
|
||||||
|
class << self
|
||||||
|
def append(path, content)
|
||||||
|
File.open(path, "a") { |f| f << content }
|
||||||
|
end
|
||||||
|
|
||||||
|
def append!(path, content)
|
||||||
|
File.open(path, "a") { |f| f << (content + "\n") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hestia_print_error_message_to_cli(error_message)
|
||||||
|
puts "Error: #{error_message}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_global_bash_variables(*scripts)
|
||||||
|
local_arr = {}
|
||||||
|
|
||||||
|
scripts.each do |script|
|
||||||
|
EnvBash.load(script, into: local_arr) if File.exist? script
|
||||||
|
end
|
||||||
|
diff_arr = local_arr.reject { |key, _value| ENV.key? key }
|
||||||
|
|
||||||
|
diff_arr.each do |key, val|
|
||||||
|
e_val = val.gsub(/'/) { |_c| "\\'" }
|
||||||
|
str_data = "$#{key}='#{e_val}'"
|
||||||
|
eval str_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_timestamp()
|
||||||
|
date = DateTime.now
|
||||||
|
$time = date.strftime("%T")
|
||||||
|
$date = date.strftime("%d-%m-%Y")
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_event(error_string, args)
|
||||||
|
if $time.nil?
|
||||||
|
date = DateTime.now
|
||||||
|
log_time = date.strftime("%Y-%m-%d %T")
|
||||||
|
log_time = "#{log_time} #{File.basename($PROGRAM_NAME)}"
|
||||||
|
else
|
||||||
|
log_time = "#{$date} #{$time} #{File.basename($PROGRAM_NAME)}"
|
||||||
|
end
|
||||||
|
code_number = error_string.to_i
|
||||||
|
if code_number.zero?
|
||||||
|
File.append! "#{$HESTIA}/log/system.log", "#{log_time} #{args}" unless $HESTIA.nil?
|
||||||
|
else
|
||||||
|
File.append! "#{$HESTIA}/log/error.log", "#{log_time} #{args} [Error #{error_string}]" unless $HESTIA.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_result(error_code:, error_message:, custom_error: -1, silent: false, callback_func: nil)
|
||||||
|
if error_code != OK
|
||||||
|
loc_error = custom_error != -1 ? custom_error : error_code
|
||||||
|
return callback_func(error_code, error_message) if callback_func
|
||||||
|
|
||||||
|
hestia_print_error_message_to_cli error_message unless silent
|
||||||
|
log_event loc_error, $ARGUMENTS
|
||||||
|
exit error_code
|
||||||
|
end
|
||||||
|
OK
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_args(req_params, params, usage)
|
||||||
|
if req_params > params.length
|
||||||
|
puts "Usage #{File.basename($PROGRAM_NAME)} #{usage}"
|
||||||
|
check_result error_code: E_ARGS, error_message: "not enought arguments", silent: true
|
||||||
|
else
|
||||||
|
OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_hestia_demo_mode
|
||||||
|
File.open("/usr/local/hestia/conf/hestia.conf") do |f|
|
||||||
|
until f.eof?
|
||||||
|
item = f.gets.strip
|
||||||
|
conf_data = item.split("=").map(&:strip!)
|
||||||
|
if conf_data.length > 1 && conf_data[0] == "DEMO_MODE" && conf_data[1].downcase == "yes"
|
||||||
|
hestia_print_error_message_to_cli "Unable to perform operation due to security restrictions that are in place."
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hestia_check_privileged_user
|
||||||
|
if Process.uid != 0
|
||||||
|
hestia_print_error_message_to_cli "Script must run under privileged user"
|
||||||
|
log_event E_PERMISSION, $ARGUMENTS
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hestia_format_cli_table(in_array)
|
||||||
|
arr_max_len = {}
|
||||||
|
in_array.each do |elem|
|
||||||
|
elem.each_with_index do |item, index|
|
||||||
|
arr_max_len[index] = item.to_s.length unless arr_max_len.key? index
|
||||||
|
arr_max_len[index] = item.to_s.length if arr_max_len.key?(index) && (arr_max_len[index] < item.to_s.length)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
in_array.each do |elem|
|
||||||
|
elem.each_with_index do |item, index|
|
||||||
|
print " %s " % item.to_s.ljust(arr_max_len[index])
|
||||||
|
end
|
||||||
|
print "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hestia_print_array_of_hashes(in_array = nil, format = "shell", header = nil)
|
||||||
|
return if in_array.nil? && (format == "json" || format == "plain" || format == "csv")
|
||||||
|
case format
|
||||||
|
when "json"
|
||||||
|
puts in_array.to_json
|
||||||
|
when "plain"
|
||||||
|
in_array.each do |item|
|
||||||
|
data_wrapper = []
|
||||||
|
item.each do |key, val|
|
||||||
|
data_wrapper << val.to_s
|
||||||
|
end
|
||||||
|
puts data_wrapper.join("\t")
|
||||||
|
end
|
||||||
|
when "csv"
|
||||||
|
data_wrapper = in_array.map do |row|
|
||||||
|
row.values.to_csv
|
||||||
|
end
|
||||||
|
puts data_wrapper
|
||||||
|
else
|
||||||
|
headers = nil
|
||||||
|
unless header.nil?
|
||||||
|
headers = header.split(",").map(&:strip)
|
||||||
|
end
|
||||||
|
if !in_array.nil? && headers.nil?
|
||||||
|
headers = []
|
||||||
|
in_array.first.each_key do |key|
|
||||||
|
headers << key.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
data_out = []
|
||||||
|
unless headers.nil?
|
||||||
|
data_out << headers
|
||||||
|
data_out << headers.map { |i| "-" * i.to_s.length }
|
||||||
|
end
|
||||||
|
unless in_array.nil?
|
||||||
|
in_array.each do |val|
|
||||||
|
row = []
|
||||||
|
headers.each do |item|
|
||||||
|
row << if val.key? item
|
||||||
|
val[item]
|
||||||
|
elsif val.key? item.to_sym
|
||||||
|
val[item.to_sym]
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
data_out << row
|
||||||
|
end
|
||||||
|
end
|
||||||
|
hestia_format_cli_table(data_out)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,271 @@
|
|||||||
|
#!/opt/brepo/ruby33/bin/ruby
|
||||||
|
|
||||||
|
require "main"
|
||||||
|
require "interface"
|
||||||
|
require "json"
|
||||||
|
require "csv"
|
||||||
|
require "date"
|
||||||
|
|
||||||
|
IPluginInterface = interface do
|
||||||
|
required_methods :info, :key, :enable, :disable, :log, :command
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kernel::PluginConfiguration
|
||||||
|
attr_accessor :key_readed
|
||||||
|
CONF_PATH = "#{$HESTIA}/conf/ext-modules.conf"
|
||||||
|
MODULES_PATH = "#{$HESTIA}/func_ruby/ext-modules"
|
||||||
|
KEY_FILE_PATH = "#{$HESTIA}/func_ruby/ext-modules/api.key"
|
||||||
|
MODULES_DATA_PATH = "#{$HESTIA}/func_ruby/ext-modules/payload"
|
||||||
|
MODULES_CONF_PATH = "#{$HESTIA}/func_ruby/ext-modules/configs"
|
||||||
|
|
||||||
|
@@loaded_plugins = {}
|
||||||
|
|
||||||
|
def get_loaded_plugins
|
||||||
|
@@loaded_plugins
|
||||||
|
end
|
||||||
|
|
||||||
|
def not_implemented
|
||||||
|
raise "Not Implemented"
|
||||||
|
end
|
||||||
|
|
||||||
|
def key_file_create
|
||||||
|
g_key = (0...10).map { ("0".."9").to_a[rand(10)] }.join
|
||||||
|
begin
|
||||||
|
f = File.new(KEY_FILE_PATH, File::CREAT | File::WRONLY, 0o600)
|
||||||
|
f.write(g_key)
|
||||||
|
f.close
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Error with ext-modules key file creation #{e.message} #{e.backtrace.first}"
|
||||||
|
log_event E_PERMISSION, $ARGUMENTS
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
g_key
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_key
|
||||||
|
hestia_check_privileged_user
|
||||||
|
if File.exist?(KEY_FILE_PATH)
|
||||||
|
if (File.stat(KEY_FILE_PATH).mode & 0xFFF).to_s(8) != "600"
|
||||||
|
File.unlink(KEY_FILE_PATH)
|
||||||
|
key_file_create
|
||||||
|
end
|
||||||
|
else
|
||||||
|
key_file_create
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
f = File.open(KEY_FILE_PATH)
|
||||||
|
result = f.gets
|
||||||
|
f.close
|
||||||
|
raise "incorrect length" if result.nil? || result.length != 10
|
||||||
|
result.chomp
|
||||||
|
rescue => e
|
||||||
|
File.unlink(KEY_FILE_PATH) if File.exist?(KEY_FILE_PATH)
|
||||||
|
key_file_create
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@key_readed = generate_key
|
||||||
|
end
|
||||||
|
|
||||||
|
@@loaded_plugins["default"] = :not_implemented
|
||||||
|
end
|
||||||
|
|
||||||
|
class Kernel::ModuleCoreWorker
|
||||||
|
ACTION_OK = ""
|
||||||
|
|
||||||
|
def key
|
||||||
|
begin
|
||||||
|
File.open(Kernel::PluginConfiguration::KEY_FILE_PATH) do |f|
|
||||||
|
result = f.gets.chomp
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_log
|
||||||
|
"#{$HESTIA}/log/#{self.class::MODULE_ID}.log"
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(format, *args)
|
||||||
|
return if $HESTIA.nil?
|
||||||
|
|
||||||
|
log_file = "#{$HESTIA}/log/#{self.class::MODULE_ID}.log"
|
||||||
|
date = DateTime.now
|
||||||
|
log_time = date.strftime("%Y-%m-%d %T")
|
||||||
|
log_time = "#{log_time} #{File.basename($PROGRAM_NAME)}"
|
||||||
|
out_result = format % args
|
||||||
|
|
||||||
|
File.append! log_file, "#{log_time} #{out_result}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def check
|
||||||
|
result = self.info
|
||||||
|
if result[:REQ] == "" || result[:REQ].nil?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
reqs = result[:REQ].split(",")
|
||||||
|
full_result = true
|
||||||
|
reqs.each do |mname|
|
||||||
|
nm = mname.strip
|
||||||
|
if hestia_ext_module_state_in_conf(nm, :get) == "disabled"
|
||||||
|
full_result = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
full_result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable
|
||||||
|
log("#{self.class::MODULE_ID} enabled")
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable
|
||||||
|
log("#{self.class::MODULE_ID} disabled")
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_module_paydata(file_path)
|
||||||
|
"#{Kernel::PluginConfiguration::MODULES_DATA_PATH}/#{self.class::MODULE_ID}/#{file_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_module_conf(file_path)
|
||||||
|
"#{Kernel::PluginConfiguration::MODULES_CONF_PATH}/#{self.class::MODULE_ID}/#{file_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def command(args)
|
||||||
|
log("#{self.class::MODULE_ID} execute commands with args #{args}")
|
||||||
|
ACTION_OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class PluginManager
|
||||||
|
def initialize(default_plugin = "default")
|
||||||
|
@default_plugin = default_plugin
|
||||||
|
@config = PluginConfiguration.new
|
||||||
|
@loaded_modules = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_loaded_plugins
|
||||||
|
@config.get_loaded_plugins
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_key
|
||||||
|
@config.key_readed
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_instance(plugin_name)
|
||||||
|
plugin_handler = case get_loaded_plugins[plugin_name]
|
||||||
|
when Symbol
|
||||||
|
@config.method(get_loaded_plugins[plugin_name])
|
||||||
|
when Proc
|
||||||
|
get_loaded_plugins[plugin_name]
|
||||||
|
else
|
||||||
|
@config.method(get_loaded_plugins[@default_plugin])
|
||||||
|
end
|
||||||
|
plugin_handler.call
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_plugins(filter = nil, list = nil)
|
||||||
|
Dir.glob("#{PluginConfiguration::MODULES_PATH}/*.mod").each do |f|
|
||||||
|
if File.exist?(f) && !File.directory?(f) && File.stat(f).uid.zero? && !@loaded_modules.include?(f)
|
||||||
|
begin
|
||||||
|
process_file = true
|
||||||
|
process_f = File.basename(f, ".mod")
|
||||||
|
if !list.nil? && filter.nil?
|
||||||
|
result = list.split(",").map do |nm|
|
||||||
|
nm1 = nm.strip
|
||||||
|
File.basename(nm1, ".mod")
|
||||||
|
end
|
||||||
|
process_file = result.include? process_f unless result.nil?
|
||||||
|
else
|
||||||
|
process_file = (process_f.match? Regexp.new(filter)) unless filter.nil?
|
||||||
|
end
|
||||||
|
f_name = File.basename(f, ".mod").gsub("-", "_")
|
||||||
|
eval "module PluginsContainer_#{f_name}; end"
|
||||||
|
eval "load f, PluginsContainer_#{f_name} if process_file"
|
||||||
|
@loaded_modules[f] = 1
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "Module loading #{f}: #{e.message} #{e.backtrace.first}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hestia_ext_module_state_in_conf(module_id, action = :get)
|
||||||
|
case action
|
||||||
|
when :get
|
||||||
|
return "disabled" unless File.exist?(Kernel::PluginConfiguration::CONF_PATH)
|
||||||
|
File.open(Kernel::PluginConfiguration::CONF_PATH, File::RDONLY) do |fl|
|
||||||
|
fl.flock(File::LOCK_SH)
|
||||||
|
fl.each do |line|
|
||||||
|
res = line.split("=", 2)
|
||||||
|
if res.length > 1
|
||||||
|
if res[0].strip == module_id.to_s
|
||||||
|
return "enabled" if res[1].strip == "enabled"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return "disabled"
|
||||||
|
when :enable
|
||||||
|
begin
|
||||||
|
File.open(Kernel::PluginConfiguration::CONF_PATH, File::RDWR | File::CREAT, 0o600) do |fl|
|
||||||
|
fl.flock(File::LOCK_EX)
|
||||||
|
strings = []
|
||||||
|
fl.each do |line|
|
||||||
|
res = line.split("=", 2)
|
||||||
|
if res.length > 1
|
||||||
|
unless res[0].strip == module_id.to_s
|
||||||
|
strings << line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
strings << "#{module_id}=enabled"
|
||||||
|
fl.truncate(0)
|
||||||
|
fl.rewind
|
||||||
|
strings.each { |str| fl.puts(str) }
|
||||||
|
end
|
||||||
|
return "enabled"
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "problem with config file #{e.message} #{e.backtrace.first}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
when :disable
|
||||||
|
begin
|
||||||
|
File.open(Kernel::PluginConfiguration::CONF_PATH, File::RDWR | File::CREAT, 0o600) do |fl|
|
||||||
|
fl.flock(File::LOCK_EX)
|
||||||
|
strings = []
|
||||||
|
fl.each do |line|
|
||||||
|
res = line.split("=", 2)
|
||||||
|
if res.length > 1
|
||||||
|
unless res[0].strip == module_id.to_s
|
||||||
|
strings << line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
strings << "#{module_id}=disabled"
|
||||||
|
fl.truncate(0)
|
||||||
|
fl.rewind
|
||||||
|
strings.each { |str| fl.puts(str) }
|
||||||
|
end
|
||||||
|
return "disabled"
|
||||||
|
rescue => e
|
||||||
|
hestia_print_error_message_to_cli "problem with config file #{e.message} #{e.backtrace.first}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
hestia_print_error_message_to_cli "incorrect module state #{module_id} - #{action.to_s}"
|
||||||
|
log_event E_INVALID, $ARGUMENTS
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
if [ -e /opt/brepo/ruby33/bin/bundle ]; then
|
||||||
|
/opt/brepo/ruby33/bin/bundle install
|
||||||
|
else
|
||||||
|
bundle install
|
||||||
|
fi
|
@ -1 +1 @@
|
|||||||
d /run/hestia 710 root wheel
|
d /run/hestia 710 admin admin
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
use function Hestiacp\quoteshellarg\quoteshellarg;
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
include $_SERVER["DOCUMENT_ROOT"] . "/inc/main.php";
|
||||||
|
|
||||||
|
// Check token
|
||||||
|
verify_csrf($_GET);
|
||||||
|
|
||||||
|
// Check user
|
||||||
|
if ($_SESSION["userContext"] != "admin") {
|
||||||
|
header("Location: /list/user");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET["id"])) {
|
||||||
|
$v_name = urldecode($_GET["id"]);
|
||||||
|
$v_action = urldecode($_GET["state"]);
|
||||||
|
if ($v_action == "enable") {
|
||||||
|
exec(HESTIA_CMD . "v-ext-modules enable " . quoteshellarg($v_name), $output, $return_var);
|
||||||
|
} else {
|
||||||
|
exec(HESTIA_CMD . "v-ext-modules disable " . quoteshellarg($v_name), $output, $return_var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_return_code($return_var, $output);
|
||||||
|
unset($output);
|
||||||
|
|
||||||
|
$back = getenv("HTTP_REFERER");
|
||||||
|
if (!empty($back)) {
|
||||||
|
header("Location: " . $back);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: /list/extmodules/");
|
||||||
|
exit();
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
$TAB = "EXTMODULES";
|
||||||
|
|
||||||
|
// Main include
|
||||||
|
include $_SERVER["DOCUMENT_ROOT"] . "/inc/main.php";
|
||||||
|
|
||||||
|
// Check user
|
||||||
|
if ($_SESSION["userContext"] != "admin") {
|
||||||
|
header("Location: /list/user");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data
|
||||||
|
exec(HESTIA_CMD . "v-ext-modules list json", $output, $return_var);
|
||||||
|
$data = json_decode(implode("", $output), true);
|
||||||
|
ksort($data);
|
||||||
|
|
||||||
|
unset($output);
|
||||||
|
|
||||||
|
// Render page
|
||||||
|
render_page($user, $TAB, "extmodules");
|
||||||
|
|
||||||
|
// Back uri
|
||||||
|
$_SESSION["back"] = $_SERVER["REQUEST_URI"];
|
@ -0,0 +1,100 @@
|
|||||||
|
<!-- Begin toolbar -->
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="toolbar-inner">
|
||||||
|
<div class="toolbar-buttons">
|
||||||
|
<a class="button button-secondary button-back js-button-back" href="/list/server/">
|
||||||
|
<i class="fas fa-arrow-left icon-blue"></i><?= _("Back") ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End toolbar -->
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<h1 class="u-text-center u-hide-desktop u-mt20 u-pr30 u-mb20 u-pl30"><?= _("List modules") ?></h1>
|
||||||
|
|
||||||
|
<div class="units-table js-units-container">
|
||||||
|
<div class="units-table-header">
|
||||||
|
<div class="units-table-cell"><?= _("Module ID") ?></div>
|
||||||
|
<div class="units-table-cell"><?= _("Module name") ?></div>
|
||||||
|
<div class="units-table-cell"></div>
|
||||||
|
<div class="units-table-cell u-text-center"><?= _("Module description") ?></div>
|
||||||
|
<div class="units-table-cell u-text-center"><?= _("Module state") ?></div>
|
||||||
|
<div class="units-table-cell u-text-center"><?= _("Requirements") ?></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Begin extmodules list item loop -->
|
||||||
|
<?php
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
++$i;
|
||||||
|
if ($data[$key]['STATE'] == 'disabled') {
|
||||||
|
$status = 'disabled';
|
||||||
|
$module_action = 'enable';
|
||||||
|
$module_action_title = _('Enable module');
|
||||||
|
$module_icon = 'fa-play';
|
||||||
|
$module_icon_class = 'icon-green';
|
||||||
|
$module_confirmation = _('Are you sure you want to enable module %s?') ;
|
||||||
|
} else {
|
||||||
|
$status = 'enabled';
|
||||||
|
$module_action = 'disable';
|
||||||
|
$module_action_title = _('Disable module');
|
||||||
|
$module_icon = 'fa-stop';
|
||||||
|
$module_icon_class = 'icon-red';
|
||||||
|
$module_confirmation = _('Are you sure you want to disable module %s?') ;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="units-table-row <?php if ($status == 'disabled') echo 'disabled'; ?> js-unit">
|
||||||
|
<div class="units-table-cell u-text-bold">
|
||||||
|
<span class="u-hide-desktop"><?= _("Module ID") ?>:</span>
|
||||||
|
<?= $data[$key]["ID"] ?>
|
||||||
|
</div>
|
||||||
|
<div class="units-table-cell units-table-heading-cell u-text-bold">
|
||||||
|
<span class="u-hide-desktop"><?= _("Module name") ?>:</span>
|
||||||
|
<?php
|
||||||
|
$iconClass = $status == "disabled" ? "fa-circle-minus" : "fa-circle-check";
|
||||||
|
$colorClass = $status == "disabled" ? "icon-red" : "icon-green";
|
||||||
|
?>
|
||||||
|
<i class="fas <?= $iconClass ?> u-mr5 <?= $status ? $colorClass : "" ?>"></i> <?= $data[$key]["NAME"] ?>
|
||||||
|
</div>
|
||||||
|
<div class="units-table-cell">
|
||||||
|
<ul class="units-table-row-actions">
|
||||||
|
<li class="units-table-row-action shortcut-s" data-key-action="js">
|
||||||
|
<a
|
||||||
|
class="units-table-row-action-link data-controls js-confirm-action"
|
||||||
|
href="/edit/extmodules/?id=<?= urlencode($data[$key]['NAME']) ?>&state=<?= $module_action ?>&token=<?= $_SESSION["token"] ?>"
|
||||||
|
title="<?= $module_action_title ?>"
|
||||||
|
data-confirm-title="<?= $module_action_title ?>"
|
||||||
|
data-confirm-message="<?= sprintf($module_confirmation, $data[$key]['NAME']) ?>"
|
||||||
|
>
|
||||||
|
<i class="fas <?= $module_icon ?> <?= $module_icon_class ?>"></i>
|
||||||
|
<span class="u-hide-desktop"><?= $module_action_title ?></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="units-table-cell">
|
||||||
|
<span class="u-hide-desktop"><?= _("Module description") ?>:</span>
|
||||||
|
<?= $data[$key]["DESCR"] ?>
|
||||||
|
</div>
|
||||||
|
<div class="units-table-cell u-text-center-desktop">
|
||||||
|
<span class="u-hide-desktop u-text-bold"><?= _("Module state") ?>:</span>
|
||||||
|
<?= $data[$key]["STATE"] ?>
|
||||||
|
</div>
|
||||||
|
<div class="units-table-cell u-text-bold u-text-center-desktop">
|
||||||
|
<span class="u-hide-desktop"><?= _("Requirements") ?>:</span>
|
||||||
|
<?= $data[$key]["REQ"] ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="app-footer">
|
||||||
|
<div class="container app-footer-inner">
|
||||||
|
<p>
|
||||||
|
<?= _("Extended modules list") ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
Loading…
Reference in new issue