Added passenger support. Partialy 1

devel
Alexey Berezhok 4 months ago
parent 92f77aceca
commit d0da95dfc5

@ -14,6 +14,10 @@
user=$1
restart=$2
if [ "$user" == "puppet" ]; then
exit
fi
# Includes
# shellcheck source=/etc/hestiacp/hestia.conf
source /etc/hestiacp/hestia.conf

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

@ -69,8 +69,8 @@ HOSTNAME=$(hostname)
# Check OS/Release
if [ -d '/etc/sysconfig' ]; then
if [ -e '/etc/redhat-release' ]; then
OS='CentOS'
VERSION=$(cat /etc/redhat-release | tr ' ' '\n' | grep [0-9])
OS=$(cat /etc/redhat-release | cut -d' ' -f1)
VERSION=$(cat /etc/redhat-release | tr ' ' '\n' | grep -P "\d+(\.\d+)?")
else
OS="Amazon"
VERSION=$(cat /etc/issue | tr ' ' '\n' | grep [0-9])

@ -118,11 +118,6 @@ upgrade_complete_message() {
echo "============================================================================="
echo
echo "Upgrade complete! If you encounter any issues or find a bug, "
echo "please take a moment to report it to us on GitHub at the URL below: "
echo "https://github.com/hestiacp/hestiacp/issues "
echo
echo "Read the release notes to learn about new fixes and features: "
echo "https://github.com/hestiacp/hestiacp/blob/release/CHANGELOG.md "
echo
echo "We hope that you enjoy using this version of Hestia Control Panel, "
echo "have a wonderful day! "
@ -130,13 +125,6 @@ upgrade_complete_message() {
echo "Sincerely, "
echo "The Hestia Control Panel development team "
echo
echo "Web: https://www.hestiacp.com/ "
echo "Docs: https://docs.hestiacp.com/ "
echo "Forum: https://forum.hestiacp.com/ "
echo "GitHub: https://github.com/hestiacp/hestiacp/ "
echo
echo "Help support the Hestia Control Panel project by donating via PayPal: "
echo "https://www.hestiacp.com/donate "
echo
echo "Made with love & pride by the open-source community around the world. "
echo
@ -148,8 +136,6 @@ upgrade_complete_message_log() {
echo
echo "============================================================================="
echo "UPGRADE COMPLETE. "
echo "Please report any issues on GitHub: "
echo "https://github.com/hestiacp/hestiacp/issues "
echo "============================================================================="
echo
$BIN/v-log-action "system" "Info" "Updates" "Update installed (Version: $new_version)."
@ -219,17 +205,6 @@ upgrade_send_notification_to_email() {
echo "" >> $message_tmp_file
fi
echo "What's new: https://github.com/hestiacp/hestiacp/blob/$RELEASE_BRANCH/CHANGELOG.md" >> $message_tmp_file
echo >> $message_tmp_file
echo "What to do if you run into issues:" >> $message_tmp_file
echo "- Check our forums for possible solutions: https://forum.hestiacp.com" >> $message_tmp_file
echo "- File an issue report on GitHub: https://github.com/hestiacp/hestiacp/issues" >> $message_tmp_file
echo "" >> $message_tmp_file
echo "Help support the Hestia Control Panel project by donating via PayPal: https://www.hestiacp.com/donate" >> $message_tmp_file
echo "===================================================" >> $message_tmp_file
echo "Have a wonderful day," >> $message_tmp_file
echo "The Hestia Control Panel development team" >> $message_tmp_file
# Read back message from file and pass through to sendmail
cat $message_tmp_file | $send_mail -s "Update Installed - v${new_version}" $admin_email
rm -f $message_tmp_file

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

@ -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,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
[![gem](https://img.shields.io/gem/v/envbash.svg?style=plastic)](https://rubygems.org/gems/envbash)
[![travis](https://img.shields.io/travis/scampersand/envbash-ruby/master.svg?style=plastic)](https://travis-ci.org/scampersand/envbash-ruby?branch=master)
[![codecov](https://img.shields.io/codecov/c/github/scampersand/envbash-ruby/master.svg?style=plastic)](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

@ -679,7 +679,8 @@ echo
# Installing Nginx repo
echo "[ * ] NGINX"
dnf config-manager --add-repo https://dev.brepo.ru/bayrepo/hestiacp/raw/branch/master/install/rpm/nginx/nginx.repo
#dnf config-manager --add-repo https://dev.brepo.ru/bayrepo/hestiacp/raw/branch/master/install/rpm/nginx/nginx.repo
#nginx will be installed from hestia.repo
# Installing Remi PHP repo
echo "[ * ] PHP"
@ -705,7 +706,6 @@ dnf config-manager --add-repo https://dev.brepo.ru/bayrepo/hestiacp/raw/branch/m
rpm --import https://repo.brepo.ru/repo/gpgkeys/repo.brepo.ru.pub
check_result $? "rpm import brepo.ru GPG key failed"
mkdir /var/cache/hestia-nginx/
chown admin:admin /var/cache/hestia-nginx/
# Installing PostgreSQL repo
if [ "$postgresql" = 'yes' ]; then
@ -1257,6 +1257,7 @@ $HESTIA/bin/v-change-user-shell admin nologin
$HESTIA/bin/v-change-user-role admin admin
$HESTIA/bin/v-change-user-language admin $lang
$HESTIA/bin/v-change-sys-config-value 'POLICY_SYSTEM_PROTECTED_ADMIN' 'yes'
chown admin:admin /var/cache/hestia-nginx/
locale-gen "en_US.utf8" > /dev/null 2>&1

@ -27,9 +27,17 @@ Requires: zstd
Requires: jq
Requires: util-linux-user
Requires: hestiacp-php-selector
Requires: alt-brepo-ruby33
Requires: alt-brepo-ruby33-libs
Requires: alt-brepo-ruby33-rubygems
Requires: alt-brepo-ruby33-rubygem-rake
Requires: alt-brepo-ruby33-rubygem-bundler
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
Requires: ruby
Requires: puppet
Requires: puppet-stdlib
Provides: hestia = %{version}-%{release}
Conflicts: redhat-release < 8

@ -1 +1 @@
d /run/hestia 710 root wheel
d /run/hestia 710 admin admin

@ -7,9 +7,10 @@ After=hestia-php.service
[Service]
Type=forking
PIDFile=/run/hestia/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/hestia/nginx.pid
ExecStartPre=/usr/local/hestia/nginx/sbin/hestia-nginx -t -c /usr/local/hestia/nginx/conf/nginx.conf
ExecStart=/usr/local/hestia/nginx/sbin/hestia-nginx -c /usr/local/hestia/nginx/conf/nginx.conf
ExecStartPost=/bin/bash -c "[ -e /usr/local/hestia/bin/v-oneshot-service ] && /usr/local/hestia/bin/v-oneshot-service"
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5

@ -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();

@ -183,6 +183,14 @@ exec(HESTIA_CMD . "v-list-web-stats json", $output, $return_var);
$stats = json_decode(implode("", $output), true);
unset($output);
//Check if passenger enabled
exec(HESTIA_CMD . "v-ext-modules state passenger_manager json", $output, $return_var);
$check_passenger_enabled = json_decode(implode("", $output), true);
if (($return_var == 0) && (!empty($check_passenger_enabled)) && ($check_passenger_enabled[0]["STATE"] == "enabled")){
//TODO
}
unset($output);
// Check POST request
if (!empty($_POST["save"])) {
$v_domain = $_POST["v_domain"];

@ -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"];

@ -3304,6 +3304,10 @@ msgstr "Квота размера памяти файловой системы"
msgid "Firewall"
msgstr "Файрвол"
#: ../../web/templates/pages/list_services.php:31
msgid "ExtModules"
msgstr "Доп. модули"
#: ../../web/templates/pages/edit_server_bind9.php:38
#: ../../web/templates/pages/edit_server_dovecot.php:68
#: ../../web/templates/pages/edit_server_httpd.php:37

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

@ -27,6 +27,9 @@
>
<i class="fas fa-arrow-rotate-left icon-red"></i><?= _("Restart") ?>
</a>
<a href="/list/extmodules/" class="button button-secondary">
<i class="fas fa-hashtag icon-lightblue"></i><?= _("ExtModules") ?>
</a>
</div>
<div class="toolbar-right">
<form x-data x-bind="BulkEdit" action="/bulk/service/" method="post">

Loading…
Cancel
Save