1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
# frozen_string_literal: true
require "securerandom"
require "shellwords"
module TTY
class Command
# Encapsulates the executed command
#
# @api private
class Cmd
# A string command name, or shell program
# @api public
attr_reader :command
# A string arguments
# @api public
attr_reader :argv
# Hash of operations to peform
# @api public
attr_reader :options
# Unique identifier
# @api public
attr_reader :uuid
# Flag that controls whether to print the output only on error or not
attr_reader :only_output_on_error
# Initialize a new Cmd object
#
# @api private
def initialize(env_or_cmd, *args)
opts = args.last.respond_to?(:to_hash) ? args.pop : {}
if env_or_cmd.respond_to?(:to_hash)
@env = env_or_cmd
unless command = args.shift
raise ArgumentError, "Cmd requires command argument"
end
else
command = env_or_cmd
end
if args.empty? && cmd = command.to_s
raise ArgumentError, "No command provided" if cmd.empty?
@command = sanitize(cmd)
@argv = []
else
if command.respond_to?(:to_ary)
@command = sanitize(command[0])
args.unshift(*command[1..-1])
else
@command = sanitize(command)
end
@argv = args.map { |i| Shellwords.escape(i) }
end
@env ||= {}
@options = opts
@uuid = SecureRandom.uuid.split("-")[0]
@only_output_on_error = opts.fetch(:only_output_on_error) { false }
freeze
end
# Extend command options if keys don't already exist
#
# @api public
def update(options)
@options.update(options.merge(@options))
end
# The shell environment variables
#
# @api public
def environment
@env.merge(options.fetch(:env, {}))
end
def environment_string
environment.map do |key, val|
converted_key = key.is_a?(Symbol) ? key.to_s.upcase : key.to_s
escaped_val = val.to_s.gsub(/"/, '\"')
%(#{converted_key}="#{escaped_val}")
end.join(" ")
end
def evars(value, &block)
return (value || block) unless environment.any?
"( export #{environment_string} ; #{value || block.call} )"
end
def umask(value)
return value unless options[:umask]
%(umask #{options[:umask]} && %s) % [value]
end
def chdir(value)
return value unless options[:chdir]
%(cd #{Shellwords.escape(options[:chdir])} && #{value})
end
def user(value)
return value unless options[:user]
vars = environment.any? ? "#{environment_string} " : ""
%(sudo -u #{options[:user]} #{vars}-- sh -c '%s') % [value]
end
def group(value)
return value unless options[:group]
%(sg #{options[:group]} -c \\\"%s\\\") % [value]
end
# Clear environment variables except specified by env
#
# @api public
def with_clean_env
end
# Assemble full command
#
# @api public
def to_command
chdir(umask(evars(user(group(to_s)))))
end
# @api public
def to_s
[command.to_s, *Array(argv)].join(" ")
end
# @api public
def to_hash
{
command: command,
argv: argv,
uuid: uuid
}
end
private
# Coerce to string
#
# @api private
def sanitize(value)
value.to_s.dup
end
end # Cmd
end # Command
end # TTY
|