1#
2# = un.rb
3#
4# Copyright (c) 2003 WATANABE Hirofumi <eban@ruby-lang.org>
5#
6# This program is free software.
7# You can distribute/modify this program under the same terms of Ruby.
8#
9# == Utilities to replace common UNIX commands in Makefiles etc
10#
11# == SYNOPSIS
12#
13#   ruby -run -e cp -- [OPTION] SOURCE DEST
14#   ruby -run -e ln -- [OPTION] TARGET LINK_NAME
15#   ruby -run -e mv -- [OPTION] SOURCE DEST
16#   ruby -run -e rm -- [OPTION] FILE
17#   ruby -run -e mkdir -- [OPTION] DIRS
18#   ruby -run -e rmdir -- [OPTION] DIRS
19#   ruby -run -e install -- [OPTION] SOURCE DEST
20#   ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
21#   ruby -run -e touch -- [OPTION] FILE
22#   ruby -run -e wait_writable -- [OPTION] FILE
23#   ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
24#   ruby -run -e httpd -- [OPTION] DocumentRoot
25#   ruby -run -e help [COMMAND]
26
27require "fileutils"
28require "optparse"
29
30module FileUtils
31#  @fileutils_label = ""
32  @fileutils_output = $stdout
33end
34
35def setup(options = "", *long_options)
36  opt_hash = {}
37  argv = []
38  OptionParser.new do |o|
39    options.scan(/.:?/) do |s|
40      opt_name = s.delete(":").intern
41      o.on("-" + s.tr(":", " ")) do |val|
42        opt_hash[opt_name] = val
43      end
44    end
45    long_options.each do |s|
46      opt_name, arg_name = s.split(/(?=[\s=])/, 2)
47      opt_name.sub!(/\A--/, '')
48      s = "--#{opt_name.gsub(/([A-Z]+|[a-z])([A-Z])/, '\1-\2').downcase}#{arg_name}"
49      puts "#{opt_name}=>#{s}" if $DEBUG
50      opt_name = opt_name.intern
51      o.on(s) do |val|
52        opt_hash[opt_name] = val
53      end
54    end
55    o.on("-v") do opt_hash[:verbose] = true end
56    o.order!(ARGV) do |x|
57      if /[*?\[{]/ =~ x
58        argv.concat(Dir[x])
59      else
60        argv << x
61      end
62    end
63  end
64  yield argv, opt_hash
65end
66
67##
68# Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY
69#
70#   ruby -run -e cp -- [OPTION] SOURCE DEST
71#
72#   -p          preserve file attributes if possible
73#   -r          copy recursively
74#   -v          verbose
75#
76
77def cp
78  setup("pr") do |argv, options|
79    cmd = "cp"
80    cmd += "_r" if options.delete :r
81    options[:preserve] = true if options.delete :p
82    dest = argv.pop
83    argv = argv[0] if argv.size == 1
84    FileUtils.send cmd, argv, dest, options
85  end
86end
87
88##
89# Create a link to the specified TARGET with LINK_NAME.
90#
91#   ruby -run -e ln -- [OPTION] TARGET LINK_NAME
92#
93#   -s          make symbolic links instead of hard links
94#   -f          remove existing destination files
95#   -v          verbose
96#
97
98def ln
99  setup("sf") do |argv, options|
100    cmd = "ln"
101    cmd += "_s" if options.delete :s
102    options[:force] = true if options.delete :f
103    dest = argv.pop
104    argv = argv[0] if argv.size == 1
105    FileUtils.send cmd, argv, dest, options
106  end
107end
108
109##
110# Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
111#
112#   ruby -run -e mv -- [OPTION] SOURCE DEST
113#
114#   -v          verbose
115#
116
117def mv
118  setup do |argv, options|
119    dest = argv.pop
120    argv = argv[0] if argv.size == 1
121    FileUtils.mv argv, dest, options
122  end
123end
124
125##
126# Remove the FILE
127#
128#   ruby -run -e rm -- [OPTION] FILE
129#
130#   -f          ignore nonexistent files
131#   -r          remove the contents of directories recursively
132#   -v          verbose
133#
134
135def rm
136  setup("fr") do |argv, options|
137    cmd = "rm"
138    cmd += "_r" if options.delete :r
139    options[:force] = true if options.delete :f
140    FileUtils.send cmd, argv, options
141  end
142end
143
144##
145# Create the DIR, if they do not already exist.
146#
147#   ruby -run -e mkdir -- [OPTION] DIR
148#
149#   -p          no error if existing, make parent directories as needed
150#   -v          verbose
151#
152
153def mkdir
154  setup("p") do |argv, options|
155    cmd = "mkdir"
156    cmd += "_p" if options.delete :p
157    FileUtils.send cmd, argv, options
158  end
159end
160
161##
162# Remove the DIR.
163#
164#   ruby -run -e rmdir -- [OPTION] DIR
165#
166#   -p          remove DIRECTORY and its ancestors.
167#   -v          verbose
168#
169
170def rmdir
171  setup("p") do |argv, options|
172    options[:parents] = true if options.delete :p
173    FileUtils.rmdir argv, options
174  end
175end
176
177##
178# Copy SOURCE to DEST.
179#
180#   ruby -run -e install -- [OPTION] SOURCE DEST
181#
182#   -p          apply access/modification times of SOURCE files to
183#               corresponding destination files
184#   -m          set permission mode (as in chmod), instead of 0755
185#   -v          verbose
186#
187
188def install
189  setup("pm:") do |argv, options|
190    options[:mode] = (mode = options.delete :m) ? mode.oct : 0755
191    options[:preserve] = true if options.delete :p
192    dest = argv.pop
193    argv = argv[0] if argv.size == 1
194    FileUtils.install argv, dest, options
195  end
196end
197
198##
199# Change the mode of each FILE to OCTAL-MODE.
200#
201#   ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
202#
203#   -v          verbose
204#
205
206def chmod
207  setup do |argv, options|
208    mode = argv.shift.oct
209    FileUtils.chmod mode, argv, options
210  end
211end
212
213##
214# Update the access and modification times of each FILE to the current time.
215#
216#   ruby -run -e touch -- [OPTION] FILE
217#
218#   -v          verbose
219#
220
221def touch
222  setup do |argv, options|
223    FileUtils.touch argv, options
224  end
225end
226
227##
228# Wait until the file becomes writable.
229#
230#   ruby -run -e wait_writable -- [OPTION] FILE
231#
232#   -n RETRY    count to retry
233#   -w SEC      each wait time in seconds
234#   -v          verbose
235#
236
237def wait_writable
238  setup("n:w:v") do |argv, options|
239    verbose = options[:verbose]
240    n = options[:n] and n = Integer(n)
241    wait = (wait = options[:w]) ? Float(wait) : 0.2
242    argv.each do |file|
243      begin
244        open(file, "r+b")
245      rescue Errno::ENOENT
246        break
247      rescue Errno::EACCES => e
248        raise if n and (n -= 1) <= 0
249        puts e
250        STDOUT.flush
251        sleep wait
252        retry
253      end
254    end
255  end
256end
257
258##
259# Create makefile using mkmf.
260#
261#   ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
262#
263#   -d ARGS     run dir_config
264#   -h ARGS     run have_header
265#   -l ARGS     run have_library
266#   -f ARGS     run have_func
267#   -v ARGS     run have_var
268#   -t ARGS     run have_type
269#   -m ARGS     run have_macro
270#   -c ARGS     run have_const
271#   --vendor    install to vendor_ruby
272#
273
274def mkmf
275  setup("d:h:l:f:v:t:m:c:", "vendor") do |argv, options|
276    require 'mkmf'
277    opt = options[:d] and opt.split(/:/).each {|n| dir_config(*n.split(/,/))}
278    opt = options[:h] and opt.split(/:/).each {|n| have_header(*n.split(/,/))}
279    opt = options[:l] and opt.split(/:/).each {|n| have_library(*n.split(/,/))}
280    opt = options[:f] and opt.split(/:/).each {|n| have_func(*n.split(/,/))}
281    opt = options[:v] and opt.split(/:/).each {|n| have_var(*n.split(/,/))}
282    opt = options[:t] and opt.split(/:/).each {|n| have_type(*n.split(/,/))}
283    opt = options[:m] and opt.split(/:/).each {|n| have_macro(*n.split(/,/))}
284    opt = options[:c] and opt.split(/:/).each {|n| have_const(*n.split(/,/))}
285    $configure_args["--vendor"] = true if options[:vendor]
286    create_makefile(*argv)
287  end
288end
289
290##
291# Run WEBrick HTTP server.
292#
293#   ruby -run -e httpd -- [OPTION] DocumentRoot
294#
295#   --bind-address=ADDR         address to bind
296#   --port=NUM                  listening port number
297#   --max-clients=MAX           max number of simultaneous clients
298#   --temp-dir=DIR              temporary directory
299#   --do-not-reverse-lookup     disable reverse lookup
300#   --request-timeout=SECOND    request timeout in seconds
301#   --http-version=VERSION      HTTP version
302#   -v                          verbose
303#
304
305def httpd
306  setup("", "BindAddress=ADDR", "Port=PORT", "MaxClients=NUM", "TempDir=DIR",
307        "DoNotReverseLookup", "RequestTimeout=SECOND", "HTTPVersion=VERSION") do
308    |argv, options|
309    require 'webrick'
310    opt = options[:RequestTimeout] and options[:RequestTimeout] = opt.to_i
311    [:Port, :MaxClients].each do |name|
312      opt = options[name] and (options[name] = Integer(opt)) rescue nil
313    end
314    unless argv.size == 1
315      raise ArgumentError, "DocumentRoot is mandatory"
316    end
317    options[:DocumentRoot] = argv.shift
318    s = WEBrick::HTTPServer.new(options)
319    shut = proc {s.shutdown}
320    siglist = %w"TERM QUIT"
321    siglist.concat(%w"HUP INT") if STDIN.tty?
322    siglist &= Signal.list.keys
323    siglist.each do |sig|
324      Signal.trap(sig, shut)
325    end
326    s.start
327  end
328end
329
330##
331# Display help message.
332#
333#   ruby -run -e help [COMMAND]
334#
335
336def help
337  setup do |argv,|
338    all = argv.empty?
339    open(__FILE__) do |me|
340      while me.gets("##\n")
341        if help = me.gets("\n\n")
342          if all or argv.delete help[/-e \w+/].sub(/-e /, "")
343            print help.gsub(/^# ?/, "")
344          end
345        end
346      end
347    end
348  end
349end
350