[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / lib / un.rb
blob8fb3c61a93e722bcd08451f92809114f6d13e0dd
1 # frozen_string_literal: false
3 # = un.rb
5 # Copyright (c) 2003 WATANABE Hirofumi <eban@ruby-lang.org>
7 # This program is free software.
8 # You can distribute/modify this program under the same terms of Ruby.
10 # == Utilities to replace common UNIX commands in Makefiles etc
12 # == SYNOPSIS
14 #   ruby -run -e cp -- [OPTION] SOURCE DEST
15 #   ruby -run -e ln -- [OPTION] TARGET LINK_NAME
16 #   ruby -run -e mv -- [OPTION] SOURCE DEST
17 #   ruby -run -e rm -- [OPTION] FILE
18 #   ruby -run -e mkdir -- [OPTION] DIRS
19 #   ruby -run -e rmdir -- [OPTION] DIRS
20 #   ruby -run -e install -- [OPTION] SOURCE DEST
21 #   ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
22 #   ruby -run -e touch -- [OPTION] FILE
23 #   ruby -run -e wait_writable -- [OPTION] FILE
24 #   ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
25 #   ruby -run -e httpd -- [OPTION] [DocumentRoot]
26 #   ruby -run -e colorize -- [FILE]
27 #   ruby -run -e help [COMMAND]
29 require "fileutils"
30 require "optparse"
32 module FileUtils
33   @fileutils_output = $stdout
34 end
36 # :nodoc:
37 def setup(options = "", *long_options)
38   caller = caller_locations(1, 1)[0].label
39   opt_hash = {}
40   argv = []
41   OptionParser.new do |o|
42     options.scan(/.:?/) do |s|
43       opt_name = s.delete(":").intern
44       o.on("-" + s.tr(":", " ")) do |val|
45         opt_hash[opt_name] = val
46       end
47     end
48     long_options.each do |s|
49       opt_name, arg_name = s.split(/(?=[\s=])/, 2)
50       opt_name.delete_prefix!('--')
51       s = "--#{opt_name.gsub(/([A-Z]+|[a-z])([A-Z])/, '\1-\2').downcase}#{arg_name}"
52       puts "#{opt_name}=>#{s}" if $DEBUG
53       opt_name = opt_name.intern
54       o.on(s) do |val|
55         opt_hash[opt_name] = val
56       end
57     end
58     o.on("-v") do opt_hash[:verbose] = true end
59     o.on("--help") do
60       UN.help([caller])
61       exit
62     end
63     o.order!(ARGV) do |x|
64       if /[*?\[{]/ =~ x
65         argv.concat(Dir[x])
66       else
67         argv << x
68       end
69     end
70   end
71   yield argv, opt_hash
72 end
75 # Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY
77 #   ruby -run -e cp -- [OPTION] SOURCE DEST
79 #   -p          preserve file attributes if possible
80 #   -r          copy recursively
81 #   -l          make hard link instead of copying (implies -r)
82 #   -v          verbose
85 def cp
86   setup("prl") do |argv, options|
87     cmd = "cp"
88     cmd += "_r" if options.delete :r
89     cmd = "cp_lr" if options.delete :l
90     options[:preserve] = true if options.delete :p
91     dest = argv.pop
92     argv = argv[0] if argv.size == 1
93     FileUtils.__send__ cmd, argv, dest, **options
94   end
95 end
98 # Create a link to the specified TARGET with LINK_NAME.
100 #   ruby -run -e ln -- [OPTION] TARGET LINK_NAME
102 #   -s          make symbolic links instead of hard links
103 #   -f          remove existing destination files
104 #   -v          verbose
107 def ln
108   setup("sf") do |argv, options|
109     cmd = "ln"
110     cmd += "_s" if options.delete :s
111     options[:force] = true if options.delete :f
112     dest = argv.pop
113     argv = argv[0] if argv.size == 1
114     FileUtils.__send__ cmd, argv, dest, **options
115   end
119 # Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
121 #   ruby -run -e mv -- [OPTION] SOURCE DEST
123 #   -v          verbose
126 def mv
127   setup do |argv, options|
128     dest = argv.pop
129     argv = argv[0] if argv.size == 1
130     FileUtils.mv argv, dest, **options
131   end
135 # Remove the FILE
137 #   ruby -run -e rm -- [OPTION] FILE
139 #   -f          ignore nonexistent files
140 #   -r          remove the contents of directories recursively
141 #   -v          verbose
144 def rm
145   setup("fr") do |argv, options|
146     cmd = "rm"
147     cmd += "_r" if options.delete :r
148     options[:force] = true if options.delete :f
149     FileUtils.__send__ cmd, argv, **options
150   end
154 # Create the DIR, if they do not already exist.
156 #   ruby -run -e mkdir -- [OPTION] DIR
158 #   -p          no error if existing, make parent directories as needed
159 #   -v          verbose
162 def mkdir
163   setup("p") do |argv, options|
164     cmd = "mkdir"
165     cmd += "_p" if options.delete :p
166     FileUtils.__send__ cmd, argv, **options
167   end
171 # Remove the DIR.
173 #   ruby -run -e rmdir -- [OPTION] DIR
175 #   -p          remove DIRECTORY and its ancestors.
176 #   -v          verbose
179 def rmdir
180   setup("p") do |argv, options|
181     options[:parents] = true if options.delete :p
182     FileUtils.rmdir argv, **options
183   end
187 # Copy SOURCE to DEST.
189 #   ruby -run -e install -- [OPTION] SOURCE DEST
191 #   -p          apply access/modification times of SOURCE files to
192 #               corresponding destination files
193 #   -m          set permission mode (as in chmod), instead of 0755
194 #   -o          set owner user id, instead of the current owner
195 #   -g          set owner group id, instead of the current group
196 #   -v          verbose
199 def install
200   setup("pm:o:g:") do |argv, options|
201     (mode = options.delete :m) and options[:mode] = /\A\d/ =~ mode ? mode.oct : mode
202     options[:preserve] = true if options.delete :p
203     (owner = options.delete :o) and options[:owner] = owner
204     (group = options.delete :g) and options[:group] = group
205     dest = argv.pop
206     argv = argv[0] if argv.size == 1
207     FileUtils.install argv, dest, **options
208   end
212 # Change the mode of each FILE to OCTAL-MODE.
214 #   ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
216 #   -v          verbose
219 def chmod
220   setup do |argv, options|
221     mode = argv.shift
222     mode = /\A\d/ =~ mode ? mode.oct : mode
223     FileUtils.chmod mode, argv, **options
224   end
228 # Update the access and modification times of each FILE to the current time.
230 #   ruby -run -e touch -- [OPTION] FILE
232 #   -v          verbose
235 def touch
236   setup do |argv, options|
237     FileUtils.touch argv, **options
238   end
242 # Wait until the file becomes writable.
244 #   ruby -run -e wait_writable -- [OPTION] FILE
246 #   -n RETRY    count to retry
247 #   -w SEC      each wait time in seconds
248 #   -v          verbose
251 def wait_writable
252   setup("n:w:v") do |argv, options|
253     verbose = options[:verbose]
254     n = options[:n] and n = Integer(n)
255     wait = (wait = options[:w]) ? Float(wait) : 0.2
256     argv.each do |file|
257       begin
258         File.open(file, "r+b") {}
259       rescue Errno::ENOENT
260         break
261       rescue Errno::EACCES => e
262         raise if n and (n -= 1) <= 0
263         if verbose
264           puts e
265           STDOUT.flush
266         end
267         sleep wait
268         retry
269       end
270     end
271   end
275 # Create makefile using mkmf.
277 #   ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
279 #   -d ARGS     run dir_config
280 #   -h ARGS     run have_header
281 #   -l ARGS     run have_library
282 #   -f ARGS     run have_func
283 #   -v ARGS     run have_var
284 #   -t ARGS     run have_type
285 #   -m ARGS     run have_macro
286 #   -c ARGS     run have_const
287 #   --vendor    install to vendor_ruby
290 def mkmf
291   setup("d:h:l:f:v:t:m:c:", "vendor") do |argv, options|
292     require 'mkmf'
293     opt = options[:d] and opt.split(/:/).each {|n| dir_config(*n.split(/,/))}
294     opt = options[:h] and opt.split(/:/).each {|n| have_header(*n.split(/,/))}
295     opt = options[:l] and opt.split(/:/).each {|n| have_library(*n.split(/,/))}
296     opt = options[:f] and opt.split(/:/).each {|n| have_func(*n.split(/,/))}
297     opt = options[:v] and opt.split(/:/).each {|n| have_var(*n.split(/,/))}
298     opt = options[:t] and opt.split(/:/).each {|n| have_type(*n.split(/,/))}
299     opt = options[:m] and opt.split(/:/).each {|n| have_macro(*n.split(/,/))}
300     opt = options[:c] and opt.split(/:/).each {|n| have_const(*n.split(/,/))}
301     $configure_args["--vendor"] = true if options[:vendor]
302     create_makefile(*argv)
303   end
307 # Run WEBrick HTTP server.
309 #   ruby -run -e httpd -- [OPTION] [DocumentRoot]
311 #   --bind-address=ADDR         address to bind
312 #   --port=NUM                  listening port number
313 #   --max-clients=MAX           max number of simultaneous clients
314 #   --temp-dir=DIR              temporary directory
315 #   --do-not-reverse-lookup     disable reverse lookup
316 #   --request-timeout=SECOND    request timeout in seconds
317 #   --http-version=VERSION      HTTP version
318 #   --server-name=NAME          name of the server host
319 #   --server-software=NAME      name and version of the server
320 #   --ssl-certificate=CERT      The SSL certificate file for the server
321 #   --ssl-private-key=KEY       The SSL private key file for the server certificate
322 #   -v                          verbose
325 def httpd
326   setup("", "BindAddress=ADDR", "Port=PORT", "MaxClients=NUM", "TempDir=DIR",
327         "DoNotReverseLookup", "RequestTimeout=SECOND", "HTTPVersion=VERSION",
328         "ServerName=NAME", "ServerSoftware=NAME",
329         "SSLCertificate=CERT", "SSLPrivateKey=KEY") do
330     |argv, options|
331     begin
332       require 'webrick'
333     rescue LoadError
334       abort "webrick is not found. You may need to `gem install webrick` to install webrick."
335     end
336     opt = options[:RequestTimeout] and options[:RequestTimeout] = opt.to_i
337     [:Port, :MaxClients].each do |name|
338       opt = options[name] and (options[name] = Integer(opt)) rescue nil
339     end
340     if cert = options[:SSLCertificate]
341       key = options[:SSLPrivateKey] or
342         raise "--ssl-private-key option must also be given"
343       require 'webrick/https'
344       options[:SSLEnable] = true
345       options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(cert))
346       options[:SSLPrivateKey] = OpenSSL::PKey.read(File.read(key))
347       options[:Port] ||= 8443   # HTTPS Alternate
348     end
349     options[:Port] ||= 8080     # HTTP Alternate
350     options[:DocumentRoot] = argv.shift || '.'
351     s = nil
352     options[:StartCallback] = proc {
353       logger = s.logger
354       logger.info("To access this server, open this URL in a browser:")
355       s.listeners.each do |listener|
356         if options[:SSLEnable]
357           addr = listener.addr
358           addr[3] = "127.0.0.1" if addr[3] == "0.0.0.0"
359           addr[3] = "::1" if addr[3] == "::"
360           logger.info("    https://#{Addrinfo.new(addr).inspect_sockaddr}")
361         else
362           logger.info("    http://#{listener.connect_address.inspect_sockaddr}")
363         end
364       end
365     }
366     s = WEBrick::HTTPServer.new(options)
367     shut = proc {s.shutdown}
368     siglist = %w"TERM QUIT"
369     siglist.concat(%w"HUP INT") if STDIN.tty?
370     siglist &= Signal.list.keys
371     siglist.each do |sig|
372       Signal.trap(sig, shut)
373     end
374     s.start
375   end
379 # Colorize ruby code.
381 #   ruby -run -e colorize -- [FILE]
384 def colorize
385   begin
386     require "irb/color"
387   rescue LoadError
388     raise "colorize requires irb 1.1.0 or later"
389   end
390   setup do |argv, |
391     if argv.empty?
392       puts IRB::Color.colorize_code STDIN.read
393       return
394     end
395     argv.each do |file|
396       puts IRB::Color.colorize_code File.read(file)
397     end
398   end
402 # Display help message.
404 #   ruby -run -e help [COMMAND]
407 def help
408   setup do |argv,|
409     UN.help(argv)
410   end
413 module UN # :nodoc:
415   VERSION = "0.3.0"
417   module_function
418   def help(argv, output: $stdout)
419     all = argv.empty?
420     cmd = nil
421     if all
422       store = proc {|msg| output << msg}
423     else
424       messages = {}
425       store = proc {|msg| messages[cmd] = msg}
426     end
427     File.open(__FILE__) do |me|
428       while me.gets("##\n")
429         if help = me.gets("\n\n")
430           if all or argv.include?(cmd = help[/^#\s*ruby\s.*-e\s+(\w+)/, 1])
431             store[help.gsub(/^# ?/, "")]
432             break unless all or argv.size > messages.size
433           end
434         end
435       end
436     end
437     if messages
438       argv.each {|arg| output << messages[arg]}
439     end
440   end