lib/bio/command.rb in bio-1.0.0 vs lib/bio/command.rb in bio-1.1.0

- old
+ new

@@ -1,53 +1,37 @@ # # = bio/command.rb - general methods for external command execution # -# Copyright:: Copyright (C) 2003-2005 +# Copyright:: Copyright (C) 2003-2006 # Naohisa Goto <ng@bioruby.org>, # Toshiaki Katayama <k@bioruby.org> -# License:: LGPL +# License:: The Ruby License # -# $Id: command.rb,v 1.3 2005/11/04 17:36:00 k Exp $ +# $Id: command.rb,v 1.17 2007/04/05 23:35:39 trevor Exp $ # -#-- -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -#++ -# require 'open3' +require 'uri' +require 'open-uri' +require 'net/http' module Bio -module Command -# = Bio::Command::Tools +# = Bio::Command # -# Bio::Command::Tools is a collection of useful methods for execution -# of external commands or web applications. Any wrapper class for -# applications shall include this class. Note that all methods below -# are private except for some methods. -module Tools +# Bio::Command is a collection of useful methods for execution +# of external commands or web applications. +# Any wrapper class for applications shall use this class. +# +# Library internal use only. Users should not directly use it. +module Command UNSAFE_CHARS_UNIX = /[^A-Za-z0-9\_\-\.\:\,\/\@\x1b\x80-\xfe]/n QUOTE_CHARS_WINDOWS = /[^A-Za-z0-9\_\-\.\:\,\/\@\\]/n UNESCAPABLE_CHARS = /[\x00-\x08\x10-\x1a\x1c-\x1f\x7f\xff]/n - #module_function - private + module_function # Escape special characters in command line string for cmd.exe on Windows. def escape_shell_windows(str) str = str.to_s raise 'cannot escape control characters' if UNESCAPABLE_CHARS =~ str @@ -96,71 +80,258 @@ def make_command_line_unix(ary) ary.collect { |str| escape_shell_unix(str) }.join(" ") end # Executes the program. Automatically select popen for Windows - # environment and open3 for the others. - # - # If block is given, yield the block with input and output IO objects. - # Note that in some platform, inn and out are the same object. - # Please be careful to do inn.close and out.close. - def call_command_local(cmd, query = nil, &block) + # environment and fork for the others. + # A block must be given. An IO object is passed to the block. + def call_command(cmd, &block) case RUBY_PLATFORM when /mswin32|bccwin32/ - call_command_local_popen(cmd, query, &block) + call_command_popen(cmd, &block) else - call_command_local_open3(cmd, query, &block) + call_command_fork(cmd, &block) end end # Executes the program via IO.popen for OS which doesn't support fork. - # If block is given, yield the block with IO objects. - # The two objects are the same because of limitation of IO.popen. - def call_command_local_popen(cmd, query = nil) + # A block must be given. An IO object is passed to the block. + def call_command_popen(cmd) str = make_command_line(cmd) IO.popen(str, "w+") do |io| - if block_given? then - io.sync = true - yield io, io + io.sync = true + yield io + end + end + + # Executes the program via fork (by using IO.popen("-")) and exec. + # A block must be given. An IO object is passed to the block. + # + # From the view point of security, this method is recommended + # rather than call_command_popen. + def call_command_fork(cmd) + IO.popen("-", "r+") do |io| + if io then + # parent + yield io else + # child + begin + Kernel.exec(*cmd) + rescue Errno::ENOENT, Errno::EACCES + Process.exit!(127) + rescue Exception + end + Process.exit!(1) + end + end + end + + # Executes the program via Open3.popen3 + # A block must be given. IO objects are passed to the block. + # + # You would use this method only when you really need to get stderr. + def call_command_open3(cmd) + cmd = cmd.collect { |x| x.to_s } + Open3.popen3(*cmd) do |pin, pout, perr| + yield pin, pout, perr + end + end + + # Executes the program with the query (String) given to the standard input, + # waits the program termination, and returns the output data printed to the + # standard output as a string. + # + # Automatically select popen for Windows environment and fork for the others. + def query_command(cmd, query = nil) + case RUBY_PLATFORM + when /mswin32|bccwin32/ + query_command_popen(cmd, query) + else + query_command_fork(cmd, query) + end + end + + # Executes the program with the query (String) given to the standard input, + # waits the program termination, and returns the output data printed to the + # standard output as a string. + # + # IO.popen is used for OS which doesn't support fork. + def query_command_popen(cmd, query = nil) + str = make_command_line(cmd) + IO.popen(str, "w+") do |io| + io.sync = true + io.print query if query + io.close_write + io.read + end + end + + # Executes the program with the query (String) given to the standard input, + # waits the program termination, and returns the output data printed to the + # standard output as a string. + # + # Fork (by using IO.popen("-")) and exec is used to execute the program. + # + # From the view point of security, this method is recommended + # rather than query_popen. + def query_command_fork(cmd, query = nil) + IO.popen("-", "r+") do |io| + if io then + # parent io.sync = true io.print query if query io.close_write io.read + else + # child + begin + Kernel.exec(*cmd) + rescue Errno::ENOENT, Errno::EACCES + Process.exit!(127) + rescue Exception + end + Process.exit!(1) end end end - # Executes the program via Open3.popen3 - # If block is given, yield the block with input and output IO objects. + # Executes the program via Open3.popen3 with the query (String) given + # to the stain, waits the program termination, and + # returns the data from stdout and stderr as an array of the strings. # # From the view point of security, this method is recommended # rather than exec_local_popen. - def call_command_local_open3(cmd, query = nil) + def query_command_open3(cmd, query = nil) + errorlog = nil cmd = cmd.collect { |x| x.to_s } Open3.popen3(*cmd) do |pin, pout, perr| perr.sync = true - t = Thread.start { @errorlog = perr.read } - if block_given? then - yield pin, pout - else - begin - pin.print query if query - pin.close - output = pout.read - ensure - t.join - end - output + t = Thread.start { errorlog = perr.read } + begin + pin.print query if query + pin.close + output = pout.read + ensure + t.join end + [ output, errorlog ] end end - # Shows the latest stderr of the program execution. - # Note that this method may be thread unsafe. - attr_reader :errorlog - public :errorlog + # Same as OpenURI.open_uri(uri).read. + def read_uri(uri) + OpenURI.open_uri(uri).read + end -end # module Tools + # Same as: + # Net::HTTP.start(address, port) + # and + # it uses proxy if an environment variable (same as OpenURI.open_uri) + # is set. + # + def start_http(address, port = 80, &block) + uri = URI.parse("http://#{address}:#{port}") + # Note: URI#find_proxy is an unofficial method defined in open-uri.rb. + # If the spec of open-uri.rb would be changed, we should change below. + if proxyuri = uri.find_proxy then + raise 'Non-HTTP proxy' if proxyuri.class != URI::HTTP + http = Net::HTTP.Proxy(proxyuri.host, proxyuri.port) + else + http = Net::HTTP + end + http.start(address, port, &block) + end + + # Same as: + # Net::HTTP.new(address, port) + # and + # it uses proxy if an environment variable (same as OpenURI.open_uri) + # is set. + # + def new_http(address, port = 80) + uri = URI.parse("http://#{address}:#{port}") + # Note: URI#find_proxy is an unofficial method defined in open-uri.rb. + # If the spec of open-uri.rb would be changed, we should change below. + if proxyuri = uri.find_proxy then + raise 'Non-HTTP proxy' if proxyuri.class != URI::HTTP + Net::HTTP.new(address, port, proxyuri.host, proxyuri.port) + else + Net::HTTP.new(address, port) + end + end + + # Same as: + # Net::HTTP.post_form(uri, params) + # and + # it uses proxy if an environment variable (same as OpenURI.open_uri) + # is set. + # In addition, +header+ can be set. + # (Note that Content-Type and Content-Length are automatically + # set by default.) + # +uri+ must be a URI object, +params+ must be a hash, and + # +header+ must be a hash. + # + def post_form(uri, params = nil, header = {}) + unless uri.is_a?(URI) + uri = URI.parse(uri) + end + + data = make_cgi_params(params) + + hash = { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => data.length.to_s + } + hash.update(header) + + start_http(uri.host, uri.port) do |http| + http.post(uri.path, data, hash) + end + end + + def make_cgi_params(params) + data = "" + case params + when Hash + data = params.map do |key, val| + make_cgi_params_key_value(key, val) + end.join('&') + when Array + case params.first + when Hash + data = params.map do |hash| + hash.map do |key, val| + make_cgi_params_key_value(key, val) + end + end.join('&') + when Array + data = params.map do |key, val| + make_cgi_params_key_value(key, val) + end.join('&') + when String + data = params.map do |str| + URI.escape(str.strip) + end.join('&') + end + when String + data = URI.escape(params.strip) + end + return data + end + + def make_cgi_params_key_value(key, value) + result = [] + case value + when Array + value.each do |val| + result << [key, val].map {|x| URI.escape(x.to_s) }.join('=') + end + else + result << [key, value].map {|x| URI.escape(x.to_s) }.join('=') + end + return result + end + end # module Command end # module Bio