# Copyright (c) 2003 WATANABE Hirofumi <eban@ruby-lang.org>
# This program is free software.
# You can distribute/modify this program under the same terms of Ruby.
# == Utilities to replace common UNIX commands in Makefiles etc
# ruby -run -e cp -- [OPTION] SOURCE DEST
# ruby -run -e ln -- [OPTION] TARGET LINK_NAME
# ruby -run -e mv -- [OPTION] SOURCE DEST
# ruby -run -e rm -- [OPTION] FILE
# ruby -run -e mkdir -- [OPTION] DIRS
# ruby -run -e rmdir -- [OPTION] DIRS
# ruby -run -e install -- [OPTION] SOURCE DEST
# ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
# ruby -run -e touch -- [OPTION] FILE
# ruby -run -e wait_writable -- [OPTION] FILE
# ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
# ruby -run -e httpd -- [OPTION] DocumentRoot
# ruby -run -e help [COMMAND]
@fileutils_output = $stdout
def setup(options = "", *long_options)
caller = caller_locations(1, 1)[0].label
options.scan(/.:?/) do |s|
opt_name = s.delete(":").intern
o.on("-" + s.tr(":", " ")) do |val|
opt_name, arg_name = s.split(/(?=[\s=])/, 2)
opt_name.sub!(/\A--/, '')
s = "--#{opt_name.gsub(/([A-Z]+|[a-z])([A-Z])/, '\1-\2').downcase}#{arg_name}"
puts "#{opt_name}=>#{s}" if $DEBUG
opt_name = opt_name.intern
o.on("-v") do opt_hash[:verbose] = true end
# Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY
# ruby -run -e cp -- [OPTION] SOURCE DEST
# -p preserve file attributes if possible
setup("pr") do |argv, options|
cmd += "_r" if options.delete :r
options[:preserve] = true if options.delete :p
argv = argv[0] if argv.size == 1
FileUtils.send cmd, argv, dest, options
# Create a link to the specified TARGET with LINK_NAME.
# ruby -run -e ln -- [OPTION] TARGET LINK_NAME
# -s make symbolic links instead of hard links
# -f remove existing destination files
setup("sf") do |argv, options|
cmd += "_s" if options.delete :s
options[:force] = true if options.delete :f
argv = argv[0] if argv.size == 1
FileUtils.send cmd, argv, dest, options
# Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
# ruby -run -e mv -- [OPTION] SOURCE DEST
argv = argv[0] if argv.size == 1
FileUtils.mv argv, dest, options
# ruby -run -e rm -- [OPTION] FILE
# -f ignore nonexistent files
# -r remove the contents of directories recursively
setup("fr") do |argv, options|
cmd += "_r" if options.delete :r
options[:force] = true if options.delete :f
FileUtils.send cmd, argv, options
# Create the DIR, if they do not already exist.
# ruby -run -e mkdir -- [OPTION] DIR
# -p no error if existing, make parent directories as needed
setup("p") do |argv, options|
cmd += "_p" if options.delete :p
FileUtils.send cmd, argv, options
# ruby -run -e rmdir -- [OPTION] DIR
# -p remove DIRECTORY and its ancestors.
setup("p") do |argv, options|
options[:parents] = true if options.delete :p
FileUtils.rmdir argv, options
# ruby -run -e install -- [OPTION] SOURCE DEST
# -p apply access/modification times of SOURCE files to
# corresponding destination files
# -m set permission mode (as in chmod), instead of 0755
setup("pm:") do |argv, options|
options[:mode] = (mode = options.delete :m) ? mode.oct : 0755
options[:preserve] = true if options.delete :p
argv = argv[0] if argv.size == 1
FileUtils.install argv, dest, options
# Change the mode of each FILE to OCTAL-MODE.
# ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
FileUtils.chmod mode, argv, options
# Update the access and modification times of each FILE to the current time.
# ruby -run -e touch -- [OPTION] FILE
FileUtils.touch argv, options
# Wait until the file becomes writable.
# ruby -run -e wait_writable -- [OPTION] FILE
# -n RETRY count to retry
# -w SEC each wait time in seconds
setup("n:w:v") do |argv, options|
verbose = options[:verbose]
n = options[:n] and n = Integer(n)
wait = (wait = options[:w]) ? Float(wait) : 0.2
rescue Errno::EACCES => e
raise if n and (n -= 1) <= 0
# Create makefile using mkmf.
# ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
# -h ARGS run have_header
# -l ARGS run have_library
# --vendor install to vendor_ruby
setup("d:h:l:f:v:t:m:c:", "vendor") do |argv, options|
opt = options[:d] and opt.split(/:/).each {|n| dir_config(*n.split(/,/))}
opt = options[:h] and opt.split(/:/).each {|n| have_header(*n.split(/,/))}
opt = options[:l] and opt.split(/:/).each {|n| have_library(*n.split(/,/))}
opt = options[:f] and opt.split(/:/).each {|n| have_func(*n.split(/,/))}
opt = options[:v] and opt.split(/:/).each {|n| have_var(*n.split(/,/))}
opt = options[:t] and opt.split(/:/).each {|n| have_type(*n.split(/,/))}
opt = options[:m] and opt.split(/:/).each {|n| have_macro(*n.split(/,/))}
opt = options[:c] and opt.split(/:/).each {|n| have_const(*n.split(/,/))}
$configure_args["--vendor"] = true if options[:vendor]
# Run WEBrick HTTP server.
# ruby -run -e httpd -- [OPTION] DocumentRoot
# --bind-address=ADDR address to bind
# --port=NUM listening port number
# --max-clients=MAX max number of simultaneous clients
# --temp-dir=DIR temporary directory
# --do-not-reverse-lookup disable reverse lookup
# --request-timeout=SECOND request timeout in seconds
# --http-version=VERSION HTTP version
setup("", "BindAddress=ADDR", "Port=PORT", "MaxClients=NUM", "TempDir=DIR",
"DoNotReverseLookup", "RequestTimeout=SECOND", "HTTPVersion=VERSION") do
opt = options[:RequestTimeout] and options[:RequestTimeout] = opt.to_i
[:Port, :MaxClients].each do |name|
opt = options[name] and (options[name] = Integer(opt)) rescue nil
raise ArgumentError, "DocumentRoot is mandatory"
options[:DocumentRoot] = argv.shift
s = WEBrick::HTTPServer.new(options)
siglist.concat(%w"HUP INT") if STDIN.tty?
siglist &= Signal.list.keys
# ruby -run -e help [COMMAND]
def help(argv, output: $stdout)
store = proc {|msg| output << msg}
store = proc {|msg| messages[cmd] = msg}
if help = me.gets("\n\n")
if all or argv.include?(cmd = help[/^#\s*ruby\s.*-e\s+(\w+)/, 1])
store[help.gsub(/^# ?/, "")]
break unless all or argv.size > messages.size
argv.each {|cmd| output << messages[cmd]}