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