diff options
author | ser <ser@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-09-22 12:40:45 +0000 |
---|---|---|
committer | ser <ser@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-09-22 12:40:45 +0000 |
commit | 8b53e39a2e6110cf13827c88673a3ef2667991cf (patch) | |
tree | eb1fa37c2a675b75c295285b5c313ba05801c2b8 /lib | |
parent | f36a3f0ea57aa05a72cb58937c7a737455a98a38 (diff) | |
download | ruby-rexml_adds_tests.tar.gz |
Second merge from trunk.rexml_adds_tests
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/rexml_adds_tests@19455 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r-- | lib/cmath.rb | 66 | ||||
-rw-r--r-- | lib/complex.rb | 18 | ||||
-rw-r--r-- | lib/csv.rb | 706 | ||||
-rw-r--r-- | lib/mathn.rb | 14 | ||||
-rw-r--r-- | lib/matrix.rb | 20 | ||||
-rw-r--r-- | lib/open-uri.rb | 12 | ||||
-rw-r--r-- | lib/optparse.rb | 9 | ||||
-rw-r--r-- | lib/pathname.rb | 10 | ||||
-rw-r--r-- | lib/pp.rb | 54 | ||||
-rw-r--r-- | lib/prettyprint.rb | 14 | ||||
-rwxr-xr-x | lib/rake.rb | 642 | ||||
-rw-r--r-- | lib/rake/loaders/makefile.rb | 23 | ||||
-rw-r--r-- | lib/rake/packagetask.rb | 1 | ||||
-rw-r--r-- | lib/rake/rdoctask.rb | 14 | ||||
-rw-r--r-- | lib/rake/tasklib.rb | 11 | ||||
-rw-r--r-- | lib/rake/testtask.rb | 7 | ||||
-rw-r--r-- | lib/resolv.rb | 40 | ||||
-rw-r--r-- | lib/time.rb | 38 | ||||
-rw-r--r-- | lib/tsort.rb | 20 | ||||
-rw-r--r-- | lib/uri/common.rb | 2 |
20 files changed, 1205 insertions, 516 deletions
diff --git a/lib/cmath.rb b/lib/cmath.rb index 55995879e6..d293401804 100644 --- a/lib/cmath.rb +++ b/lib/cmath.rb @@ -25,17 +25,17 @@ module CMath alias atanh! atanh def exp(z) - if z.scalar? + if z.real? exp!(z) else - Complex(exp!(z.real) * cos!(z.image), - exp!(z.real) * sin!(z.image)) + Complex(exp!(z.real) * cos!(z.imag), + exp!(z.real) * sin!(z.imag)) end end def log(*args) z, b = args - if z.scalar? and z >= 0 and (b.nil? or b >= 0) + if z.real? and z >= 0 and (b.nil? or b >= 0) log!(*args) else r, theta = z.polar @@ -48,7 +48,7 @@ module CMath end def log10(z) - if z.scalar? + if z.real? log10!(z) else log(z) / log!(10) @@ -56,14 +56,14 @@ module CMath end def sqrt(z) - if z.scalar? + if z.real? if z >= 0 sqrt!(z) else - Complex(0,sqrt!(-z)) + Complex(0, sqrt!(-z)) end else - if z.image < 0 + if z.imag < 0 sqrt(z.conjugate).conjugate else r = z.abs @@ -74,25 +74,25 @@ module CMath end def sin(z) - if z.scalar? + if z.real? sin!(z) else - Complex(sin!(z.real) * cosh!(z.image), - cos!(z.real) * sinh!(z.image)) + Complex(sin!(z.real) * cosh!(z.imag), + cos!(z.real) * sinh!(z.imag)) end end def cos(z) - if z.scalar? + if z.real? cos!(z) else - Complex(cos!(z.real) * cosh!(z.image), - -sin!(z.real) * sinh!(z.image)) + Complex(cos!(z.real) * cosh!(z.imag), + -sin!(z.real) * sinh!(z.imag)) end end def tan(z) - if z.scalar? + if z.real? tan!(z) else sin(z)/cos(z) @@ -100,25 +100,25 @@ module CMath end def sinh(z) - if z.scalar? + if z.real? sinh!(z) else - Complex(sinh!(z.real) * cos!(z.image), - cosh!(z.real) * sin!(z.image)) + Complex(sinh!(z.real) * cos!(z.imag), + cosh!(z.real) * sin!(z.imag)) end end def cosh(z) - if z.scalar? + if z.real? cosh!(z) else - Complex(cosh!(z.real) * cos!(z.image), - sinh!(z.real) * sin!(z.image)) + Complex(cosh!(z.real) * cos!(z.imag), + sinh!(z.real) * sin!(z.imag)) end end def tanh(z) - if z.scalar? + if z.real? tanh!(z) else sinh(z) / cosh(z) @@ -126,39 +126,39 @@ module CMath end def asin(z) - if z.scalar? and z >= -1 and z <= 1 + if z.real? and z >= -1 and z <= 1 asin!(z) else - -1.0.im * log(1.0.im * z + sqrt(1.0 - z * z)) + Complex(0, -1.0) * log(Complex(0, 1.0) * z + sqrt(1.0 - z * z)) end end def acos(z) - if z.scalar? and z >= -1 and z <= 1 + if z.real? and z >= -1 and z <= 1 acos!(z) else - -1.0.im * log(z + 1.0.im * sqrt(1.0 - z * z)) + Complex(0, -1.0) * log(z + Complex(0, 1.0) * sqrt(1.0 - z * z)) end end def atan(z) - if z.scalar? + if z.real? atan!(z) else - 1.0.im * log((1.0.im + z) / (1.0.im - z)) / 2.0 + Complex(0, 1.0) * log((Complex(0, 1.0) + z) / (Complex(0, 1.0) - z)) / 2.0 end end def atan2(y,x) - if y.scalar? and x.scalar? + if y.real? and x.real? atan2!(y,x) else - -1.0.im * log((x + 1.0.im * y) / sqrt(x * x + y * y)) + Complex(0, -1.0) * log((x + Complex(0, 1.0) * y) / sqrt(x * x + y * y)) end end def acosh(z) - if z.scalar? and z >= 1 + if z.real? and z >= 1 acosh!(z) else log(z + sqrt(z * z - 1.0)) @@ -166,7 +166,7 @@ module CMath end def asinh(z) - if z.scalar? + if z.real? asinh!(z) else log(z + sqrt(1.0 + z * z)) @@ -174,7 +174,7 @@ module CMath end def atanh(z) - if z.scalar? and z >= -1 and z <= 1 + if z.real? and z >= -1 and z <= 1 atanh!(z) else log((1.0 + z) / (1.0 - z)) / 2.0 diff --git a/lib/complex.rb b/lib/complex.rb index 1845f30b1f..301879143f 100644 --- a/lib/complex.rb +++ b/lib/complex.rb @@ -1,10 +1,24 @@ require 'cmath' -Object.instance_eval{remove_const :Math} -Math = CMath +unless defined?(Math.exp!) + Object.instance_eval{remove_const :Math} + Math = CMath +end def Complex.generic? (other) other.kind_of?(Integer) || other.kind_of?(Float) || other.kind_of?(Rational) end + +class Complex + + alias image imag + +end + +class Numeric + + def im() Complex(0, self) end + +end diff --git a/lib/csv.rb b/lib/csv.rb index f60d5b1cb0..dccee6cbe8 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -1,5 +1,5 @@ -#!/usr/local/bin/ruby -w - +#!/usr/bin/env ruby -w +# encoding: UTF-8 # = csv.rb -- CSV Reading and Writing # # Created by James Edward Gray II on 2005-10-31. @@ -37,6 +37,7 @@ # # === CSV Parsing # +# * This parser is m17n aware. See CSV for full details. # * This library has a stricter parser and will throw MalformedCSVErrors on # problematic data. # * This library has a less liberal idea of a line ending than CSV. What you @@ -91,7 +92,6 @@ require "forwardable" require "English" -require "enumerator" require "date" require "stringio" @@ -130,7 +130,7 @@ require "stringio" # # === To a File # -# CSV.open("path/to/file.csv", "w") do |csv| +# CSV.open("path/to/file.csv", "wb") do |csv| # csv << ["row", "of", "CSV", "data"] # csv << ["another", "row"] # # ... @@ -155,9 +155,51 @@ require "stringio" # CSV(csv = "") { |csv_str| csv_str << %w{my data here} } # to a String # CSV($stderr) { |csv_err| csv_err << %w{my data here} } # to $stderr # +# == CSV and Character Encodings (M17n or Multilingualization) +# +# This new CSV parser is m17n savvy. The parser works in the Encoding of the IO +# or String object being read from or written to. Your data is never transcoded +# (unless you ask Ruby to transcode it for you) and will literally be parsed in +# the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the +# Encoding of your data. This is accomplished by transcoding the parser itself +# into your Encoding. +# +# Some transcoding must take place, of course, to accomplish this multiencoding +# support. For example, <tt>:col_sep</tt>, <tt>:row_sep</tt>, and +# <tt>:quote_char</tt> must be transcoded to match your data. Hopefully this +# makes the entire process feel transparent, since CSV's defaults should just +# magically work for you data. However, you can set these values manually in +# the target Encoding to avoid the translation. +# +# It's also important to note that while all of CSV's core parser is now +# Encoding agnostic, some features are not. For example, the built-in +# converters will try to transcode data to UTF-8 before making conversions. +# Again, you can provide custom converters that are aware of your Encodings to +# avoid this translation. It's just too hard for me to support native +# conversions in all of Ruby's Encodings. +# +# Anyway, the practical side of this is simple: make sure IO and String objects +# passed into CSV have the proper Encoding set and everything should just work. +# CSV methods that allow you to open IO objects (CSV::foreach(), CSV::open(), +# CSV::read(), and CSV::readlines()) do allow you to specify the Encoding. +# +# One minor exception comes when generating CSV into a String with an Encoding +# that is not ASCII compatible. There's no existing data for CSV to use to +# prepare itself and thus you will probably need to manually specify the desired +# Encoding for most of those cases. It will try to guess using the fields in a +# row of output though, when using CSV::generate_line() or Array#to_csv(). +# +# I try to point out any other Encoding issues in the documentation of methods +# as they come up. +# +# This has been tested to the best of my ability with all non-"dummy" Encodings +# Ruby ships with. However, it is brave new code and may have some bugs. +# Please feel free to {report}[mailto:james@grayproductions.net] any issues you +# find with it. +# class CSV # The version of the installed library. - VERSION = "2.0.0".freeze + VERSION = "2.4.0".freeze # # A CSV::Row is part Array and part Hash. It retains an order for the fields @@ -188,9 +230,9 @@ class CSV # handle extra headers or fields @row = if headers.size > fields.size - headers.each_with_index.map { |header, i| [header, fields[i]] } + headers.zip(fields) else - fields.each_with_index.map { |field, i| [headers[i], field] } + fields.zip(headers).map { |pair| pair.reverse } end end @@ -444,6 +486,17 @@ class CSV fields.to_csv(options) end alias_method :to_s, :to_csv + + # A summary of fields, by header, in an ASCII-8BIT String. + def inspect + str = ["#<", self.class.to_s] + each do |header, field| + str << " " << (header.is_a?(Symbol) ? header.to_s : header.inspect) << + ":" << field.inspect + end + str << ">" + str.map { |s| s.encode("ASCII-8BIT") }.join + end end # @@ -775,6 +828,11 @@ class CSV end.join end alias_method :to_s, :to_csv + + # Shows the mode and size of this table in a US-ASCII String. + def inspect + "#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>" + end end # The error thrown when the parser encounters illegal CSV formatting. @@ -799,6 +857,10 @@ class CSV DateTimeMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} | \d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} )\z /x + + # The encoding used by all converters. + ConverterEncoding = Encoding.find("UTF-8") + # # This Hash holds the built-in converters of CSV that can be accessed by name. # You can select Converters with CSV.convert() or through the +options+ Hash @@ -813,20 +875,38 @@ class CSV # <b><tt>:all</tt></b>:: All built-in converters. A combination of # <tt>:date_time</tt> and <tt>:numeric</tt>. # + # All built-in converters transcode field data to UTF-8 before attempting a + # conversion. If your data cannot be transcoded to UTF-8 the conversion will + # fail and the field will remain unchanged. + # # This Hash is intentionally left unfrozen and users should feel free to add # values to it that can be accessed by all CSV objects. # # To add a combo field, the value should be an Array of names. Combo fields # can be nested with other combo fields. # - Converters = { :integer => lambda { |f| Integer(f) rescue f }, - :float => lambda { |f| Float(f) rescue f }, + Converters = { :integer => lambda { |f| + Integer(f.encode(ConverterEncoding)) rescue f + }, + :float => lambda { |f| + Float(f.encode(ConverterEncoding)) rescue f + }, :numeric => [:integer, :float], :date => lambda { |f| - f =~ DateMatcher ? (Date.parse(f) rescue f) : f + begin + e = f.encode(ConverterEncoding) + e =~ DateMatcher ? Date.parse(e) : f + rescue # encoding conversion or date parse errors + f + end }, :date_time => lambda { |f| - f =~ DateTimeMatcher ? (DateTime.parse(f) rescue f) : f + begin + e = f.encode(ConverterEncoding) + e =~ DateTimeMatcher ? DateTime.parse(e) : f + rescue # encoding conversion or date parse errors + f + end }, :all => [:date_time, :numeric] } @@ -840,6 +920,10 @@ class CSV # replaced with underscores, non-word characters # are dropped, and finally to_sym() is called. # + # All built-in header converters transcode header data to UTF-8 before + # attempting a conversion. If your data cannot be transcoded to UTF-8 the + # conversion will fail and the header will remain unchanged. + # # This Hash is intetionally left unfrozen and users should feel free to add # values to it that can be accessed by all CSV objects. # @@ -847,9 +931,10 @@ class CSV # can be nested with other combo fields. # HeaderConverters = { - :downcase => lambda { |h| h.downcase }, + :downcase => lambda { |h| h.encode(ConverterEncoding).downcase }, :symbol => lambda { |h| - h.downcase.tr(" ", "_").delete("^a-z0-9_").to_sym + h.encode(ConverterEncoding). + downcase.tr(" ", "_").delete("^a-z0-9_").to_sym } } @@ -859,6 +944,7 @@ class CSV # <b><tt>:col_sep</tt></b>:: <tt>","</tt> # <b><tt>:row_sep</tt></b>:: <tt>:auto</tt> # <b><tt>:quote_char</tt></b>:: <tt>'"'</tt> + # <b><tt>:field_size_limit</tt></b>:: +nil+ # <b><tt>:converters</tt></b>:: +nil+ # <b><tt>:unconverted_fields</tt></b>:: +nil+ # <b><tt>:headers</tt></b>:: +false+ @@ -870,6 +956,7 @@ class CSV DEFAULT_OPTIONS = { :col_sep => ",", :row_sep => :auto, :quote_char => '"', + :field_size_limit => nil, :converters => nil, :unconverted_fields => nil, :headers => false, @@ -879,6 +966,31 @@ class CSV :force_quotes => false }.freeze # + # This method will return a CSV instance, just like CSV::new(), but the + # instance will be cached and returned for all future calls to this method for + # the same +data+ object (tested by Object#object_id()) with the same + # +options+. + # + # If a block is given, the instance is passed to the block and the return + # value becomes the return value of the block. + # + def self.instance(data = $stdout, options = Hash.new) + # create a _signature_ for this method call, data object and options + sig = [data.object_id] + + options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s }) + + # fetch or create the instance for this signature + @@instances ||= Hash.new + instance = (@@instances[sig] ||= new(data, options)) + + if block_given? + yield instance # run block, if given, returning result + else + instance # or return the instance + end + end + + # # This method allows you to serialize an Array of Ruby objects to a String or # File of CSV data. This is not as powerful as Marshal or YAML, but perhaps # useful for spreadsheet and database interaction. @@ -959,6 +1071,53 @@ class CSV end # + # This method is the reading counterpart to CSV::dump(). See that method for + # a detailed description of the process. + # + # You can customize loading by adding a class method called csv_load() which + # will be passed a Hash of meta information, an Array of headers, and an Array + # of fields for the object the method is expected to return. + # + # Remember that all fields will be Strings after this load. If you need + # something else, use +options+ to setup converters or provide a custom + # csv_load() implementation. + # + def self.load(io_or_str, options = Hash.new) + csv = new(io_or_str, options) + + # load meta information + meta = Hash[*csv.shift] + cls = meta["class".encode(csv.encoding)].split("::".encode(csv.encoding)). + inject(Object) do |c, const| + c.const_get(const) + end + + # load headers + headers = csv.shift + + # unserialize each object stored in the file + results = csv.inject(Array.new) do |all, row| + begin + obj = cls.csv_load(meta, headers, row) + rescue NoMethodError + obj = cls.allocate + headers.zip(row) do |name, value| + if name[0] == ?@ + obj.instance_variable_set(name, value) + else + obj.send(name, value) + end + end + end + all << obj + end + + csv.close unless io_or_str.is_a? String + + results + end + + # # :call-seq: # filter( options = Hash.new ) { |row| ... } # filter( input, options = Hash.new ) { |row| ... } @@ -1014,10 +1173,20 @@ class CSV # pass a +path+ and any +options+ you wish to set for the read. Each row of # file will be passed to the provided +block+ in turn. # - # The +options+ parameter can be anything CSV::new() understands. + # The +options+ parameter can be anything CSV::new() understands. This method + # also understands an additional <tt>:encoding</tt> parameter that you can use + # to specify the Encoding of the data in the file to be read. You must provide + # this unless your data is in Encoding::default_external(). CSV will use this + # to deterime how to parse the data. You may provide a second Encoding to + # have the data transcoded as it is read. For example, + # <tt>:encoding => "UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the + # file but transcode it to UTF-8 before CSV parses it. # def self.foreach(path, options = Hash.new, &block) - open(path, options) do |csv| + encoding = options.delete(:encoding) + mode = "rb" + mode << ":#{encoding}" if encoding + open(path, mode, options) do |csv| csv.each(&block) end end @@ -1035,7 +1204,10 @@ class CSV # Note that a passed String *is* modfied by this method. Call dup() before # passing if you need a new String. # - # The +options+ parameter can be anthing CSV::new() understands. + # The +options+ parameter can be anthing CSV::new() understands. This method + # understands an additional <tt>:encoding</tt> parameter when not passed a + # String to set the base Encoding for the output. CSV needs this hint if you + # plan to output non-ASCII compatible data. # def self.generate(*args) # add a default empty String, if none was given @@ -1044,7 +1216,10 @@ class CSV io.seek(0, IO::SEEK_END) args.unshift(io) else - args.unshift("") + encoding = args.last.is_a?(Hash) ? args.last.delete(:encoding) : nil + str = "" + str.encode!(encoding) if encoding + args.unshift(str) end csv = new(*args) # wrap yield csv # yield for appending @@ -1055,97 +1230,40 @@ class CSV # This method is a shortcut for converting a single row (Array) into a CSV # String. # - # The +options+ parameter can be anthing CSV::new() understands. + # The +options+ parameter can be anthing CSV::new() understands. This method + # understands an additional <tt>:encoding</tt> parameter to set the base + # Encoding for the output. This method will try to guess your Encoding from + # the first non-+nil+ field in +row+, if possible, but you may need to use + # this parameter as a backup plan. # # The <tt>:row_sep</tt> +option+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt> # (<tt>$/</tt>) when calling this method. # def self.generate_line(row, options = Hash.new) - options = {:row_sep => $INPUT_RECORD_SEPARATOR}.merge(options) - (new("", options) << row).string - end - - # - # This method will return a CSV instance, just like CSV::new(), but the - # instance will be cached and returned for all future calls to this method for - # the same +data+ object (tested by Object#object_id()) with the same - # +options+. - # - # If a block is given, the instance is passed to the block and the return - # value becomes the return value of the block. - # - def self.instance(data = $stdout, options = Hash.new) - # create a _signature_ for this method call, data object and options - sig = [data.object_id] + - options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s }) - - # fetch or create the instance for this signature - @@instances ||= Hash.new - instance = (@@instances[sig] ||= new(data, options)) - - if block_given? - yield instance # run block, if given, returning result - else - instance # or return the instance - end - end - - # - # This method is the reading counterpart to CSV::dump(). See that method for - # a detailed description of the process. - # - # You can customize loading by adding a class method called csv_load() which - # will be passed a Hash of meta information, an Array of headers, and an Array - # of fields for the object the method is expected to return. - # - # Remember that all fields will be Strings after this load. If you need - # something else, use +options+ to setup converters or provide a custom - # csv_load() implementation. - # - def self.load(io_or_str, options = Hash.new) - csv = new(io_or_str, options) - - # load meta information - meta = Hash[*csv.shift] - cls = meta["class"].split("::").inject(Object) do |c, const| - c.const_get(const) - end - - # load headers - headers = csv.shift - - # unserialize each object stored in the file - results = csv.inject(Array.new) do |all, row| - begin - obj = cls.csv_load(meta, headers, row) - rescue NoMethodError - obj = cls.allocate - headers.zip(row) do |name, value| - if name[0] == ?@ - obj.instance_variable_set(name, value) - else - obj.send(name, value) - end - end - end - all << obj - end - - csv.close unless io_or_str.is_a? String - - results + options = {:row_sep => $INPUT_RECORD_SEPARATOR}.merge(options) + encoding = options.delete(:encoding) + str = "" + if encoding + str.encode!(encoding) + elsif field = row.find { |f| not f.nil? } + str.encode!(String(field).encoding) + end + (new(str, options) << row).string end # # :call-seq: - # open( filename, mode="r", options = Hash.new ) { |csv| ... } - # open( filename, mode="r", options = Hash.new ) + # open( filename, mode = "rb", options = Hash.new ) { |faster_csv| ... } + # open( filename, options = Hash.new ) { |faster_csv| ... } + # open( filename, mode = "rb", options = Hash.new ) + # open( filename, options = Hash.new ) # # This method opens an IO object, and wraps that with CSV. This is intended # as the primary interface for writing a CSV file. # - # You may pass any +args+ Ruby's open() understands followed by an optional - # Hash containing any +options+ CSV::new() understands. + # You must pass a +filename+ and may optionally add a +mode+ for Ruby's + # open(). You may also pass an optional Hash containing any +options+ + # CSV::new() understands as the final argument. # # This method works like Ruby's open() call, in that it will pass a CSV object # to a provided block and close it when the block terminates, or it will @@ -1153,24 +1271,38 @@ class CSV # from the Ruby 1.8 CSV library which passed rows to the block. Use # CSV::foreach() for that behavior.) # - # An opened CSV object will delegate to many IO methods, for convenience. You + # You must provide a +mode+ with an embedded Encoding designator unless your + # data is in Encoding::default_external(). CSV will check the Encoding of the + # underlying IO object (set by the +mode+ you pass) to deterime how to parse + # the data. You may provide a second Encoding to have the data transcoded as + # it is read just as you can with a normal call to IO::open(). For example, + # <tt>"rb:UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the file but + # transcode it to UTF-8 before CSV parses it. + # + # An opened CSV object will delegate to many IO methods for convenience. You # may call: # # * binmode() + # * binmode?() # * close() # * close_read() # * close_write() # * closed?() # * eof() # * eof?() + # * external_encoding() # * fcntl() # * fileno() + # * flock() # * flush() # * fsync() + # * internal_encoding() # * ioctl() # * isatty() + # * path() # * pid() # * pos() + # * pos=() # * reopen() # * seek() # * stat() @@ -1179,11 +1311,14 @@ class CSV # * tell() # * to_i() # * to_io() + # * truncate() # * tty?() # def self.open(*args) # find the +options+ Hash options = if args.last.is_a? Hash then args.pop else Hash.new end + # default to a binary open mode + args << "rb" if args.size == 1 # wrap a File opened with the remaining +args+ csv = new(File.open(*args), options) @@ -1237,10 +1372,20 @@ class CSV # # Use to slurp a CSV file into an Array of Arrays. Pass the +path+ to the - # file and any +options+ CSV::new() understands. + # file and any +options+ CSV::new() understands. This method also understands + # an additional <tt>:encoding</tt> parameter that you can use to specify the + # Encoding of the data in the file to be read. You must provide this unless + # your data is in Encoding::default_external(). CSV will use this to deterime + # how to parse the data. You may provide a second Encoding to have the data + # transcoded as it is read. For example, + # <tt>:encoding => "UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the + # file but transcode it to UTF-8 before CSV parses it. # def self.read(path, options = Hash.new) - open(path, options) { |csv| csv.read } + encoding = options.delete(:encoding) + mode = "rb" + mode << ":#{encoding}" if encoding + open(path, mode, options) { |csv| csv.read } end # Alias for CSV::read(). @@ -1276,6 +1421,8 @@ class CSV # Available options are: # # <b><tt>:col_sep</tt></b>:: The String placed between each field. + # This String will be transcoded into + # the data's Encoding before parsing. # <b><tt>:row_sep</tt></b>:: The String appended to the end of each # row. This can be set to the special # <tt>:auto</tt> setting, which requests @@ -1295,7 +1442,16 @@ class CSV # <tt>$INPUT_RECORD_SEPARATOR</tt> # (<tt>$/</tt>) is used. Obviously, # discovery takes a little time. Set - # manually if speed is important. + # manually if speed is important. Also + # note that IO objects should be opened + # in binary mode on Windows if this + # feature will be used as the + # line-ending translation can cause + # problems with resetting the document + # position to where it was before the + # read ahead. This String will be + # transcoded into the data's Encoding + # before parsing. # <b><tt>:quote_char</tt></b>:: The character used to quote fields. # This has to be a single character # String. This is useful for @@ -1304,11 +1460,31 @@ class CSV # instead of the correct <tt>"</tt>. # CSV will always consider a double # sequence this character to be an - # escaped quote. + # escaped quote. This String will be + # transcoded into the data's Encoding + # before parsing. + # <b><tt>:field_size_limit</tt></b>:: This is a maximum size CSV will read + # ahead looking for the closing quote + # for a field. (In truth, it reads to + # the first line ending beyond this + # size.) If a quote cannot be found + # within the limit CSV will raise a + # MalformedCSVError, assuming the data + # is faulty. You can use this limit to + # prevent what are effectively DoS + # attacks on the parser. However, this + # limit can cause a legitimate parse to + # fail and thus is set to +nil+, or off, + # by default. # <b><tt>:converters</tt></b>:: An Array of names from the Converters # Hash and/or lambdas that handle custom # conversion. A single converter - # doesn't have to be in an Array. + # doesn't have to be in an Array. All + # built-in converters try to transcode + # fields to UTF-8 before converting. + # The conversion will fail if the data + # cannot be transcoded, leaving the + # field unchanged. # <b><tt>:unconverted_fields</tt></b>:: If set to +true+, an # unconverted_fields() method will be # added to all returned rows (Array or @@ -1324,11 +1500,14 @@ class CSV # headers. If set to an Array, the # contents will be used as the headers. # If set to a String, the String is run - # through a call of CSV::parse_line() to - # produce an Array of headers. This - # setting causes CSV.shift() to return + # through a call of CSV::parse_line() + # with the same <tt>:col_sep</tt>, + # <tt>:row_sep</tt>, and + # <tt>:quote_char</tt> as this instance + # to produce an Array of headers. This + # setting causes CSV#shift() to return # rows as CSV::Row objects instead of - # Arrays and CSV.read() to return + # Arrays and CSV#read() to return # CSV::Table objects instead of an Array # of Arrays. # <b><tt>:return_headers</tt></b>:: When +false+, header rows are silently @@ -1337,10 +1516,17 @@ class CSV # with identical headers and # fields (save that the fields do not go # through the converters). + # <b><tt>:write_headers</tt></b>:: When +true+ and <tt>:headers</tt> is + # set, a header row will be added to the + # output. # <b><tt>:header_converters</tt></b>:: Identical in functionality to # <tt>:converters</tt> save that the # conversions are only made to header - # rows. + # rows. All built-in converters try to + # transcode headers to UTF-8 before + # converting. The conversion will fail + # if the data cannot be transcoded, + # leaving the header unchanged. # <b><tt>:skip_blanks</tt></b>:: When set to a +true+ value, CSV will # skip over any rows with no content. # <b><tt>:force_quotes</tt></b>:: When set to a +true+ value, CSV will @@ -1356,8 +1542,24 @@ class CSV options = DEFAULT_OPTIONS.merge(options) # create the IO object we will read from - @io = if data.is_a? String then StringIO.new(data) else data end - + @io = if data.is_a? String then StringIO.new(data) else data end + # honor the IO encoding if we can, otherwise default to ASCII-8BIT + @encoding = if @io.respond_to? :internal_encoding + @io.internal_encoding || @io.external_encoding + elsif @io.is_a? StringIO + @io.string.encoding + end + @encoding ||= Encoding.default_external + # + # prepare for build safe regular expressions in the target encoding, + # if we can transcode the needed characters + # + @re_esc = "\\".encode(@encoding) rescue "" + @re_chars = %w[ \\ . [ ] - ^ $ ? + * + { } ( ) | # + \ \r \n \t \f \v ]. + map { |s| s.encode(@encoding) rescue nil }.compact + init_separators(options) init_parsers(options) init_converters(options) @@ -1372,6 +1574,79 @@ class CSV end # + # The encoded <tt>:col_sep</tt> used in parsing and writing. See CSV::new + # for details. + # + attr_reader :col_sep + # + # The encoded <tt>:row_sep</tt> used in parsing and writing. See CSV::new + # for details. + # + attr_reader :row_sep + # + # The encoded <tt>:quote_char</tt> used in parsing and writing. See CSV::new + # for details. + # + attr_reader :quote_char + # The limit for field size, if any. See CSV::new for details. + attr_reader :field_size_limit + # + # Returns the current list of converters in effect. See CSV::new for details. + # Built-in converters will be returned by name, while others will be returned + # as is. + # + def converters + @converters.map do |converter| + name = Converters.rassoc(converter) + name ? name.first : converter + end + end + # + # Returns +true+ if unconverted_fields() to parsed results. See CSV::new + # for details. + # + def unconverted_fields?() @unconverted_fields end + # + # Returns +nil+ if headers will not be used, +true+ if they will but have not + # yet been read, or the actual headers after they have been read. See + # CSV::new for details. + # + def headers + @headers || true if @use_headers + end + # + # Returns +true+ if headers will be returned as a row of results. + # See CSV::new for details. + # + def return_headers?() @return_headers end + # Returns +true+ if headers are written in output. See CSV::new for details. + def write_headers?() @write_headers end + # + # Returns the current list of converters in effect for headers. See CSV::new + # for details. Built-in converters will be returned by name, while others + # will be returned as is. + # + def header_converters + @header_converters.map do |converter| + name = HeaderConverters.rassoc(converter) + name ? name.first : converter + end + end + # + # Returns +true+ blank lines are skipped by the parser. See CSV::new + # for details. + # + def skip_blanks?() @skip_blanks end + # Returns +true+ if all output fields are quoted. See CSV::new for details. + def force_quotes?() @force_quotes end + + # + # The Encoding CSV is parsing or writing in. This will be the Encoding you + # receive parsed data in and/or the Encoding data will be written in. + # + attr_reader :encoding + + # # The line number of the last row read from this file. Fields with nested # line-end characters will not affect this count. # @@ -1380,10 +1655,12 @@ class CSV ### IO and StringIO Delegation ### extend Forwardable - def_delegators :@io, :binmode, :close, :close_read, :close_write, :closed?, - :eof, :eof?, :fcntl, :fileno, :flush, :fsync, :ioctl, - :isatty, :pid, :pos, :reopen, :seek, :stat, :string, - :sync, :sync=, :tell, :to_i, :to_io, :tty? + def_delegators :@io, :binmode, :binmode?, :close, :close_read, :close_write, + :closed?, :eof, :eof?, :external_encoding, :fcntl, + :fileno, :flock, :flush, :fsync, :internal_encoding, + :ioctl, :isatty, :path, :pid, :pos, :pos=, :reopen, + :seek, :stat, :string, :sync, :sync=, :tell, :to_i, + :to_io, :truncate, :tty? # Rewinds the underlying IO object and resets CSV's lineno() counter. def rewind @@ -1403,12 +1680,18 @@ class CSV # The data source must be open for writing. # def <<(row) + # make sure headers have been assigned + if header_row? and [Array, String].include? @use_headers.class + parse_headers # won't read data for Array or String + self << @headers if @write_headers + end + # handle CSV::Row objects and Hashes row = case row - when self.class::Row then row.fields - when Hash then @headers.map { |header| row[header] } - else row - end + when self.class::Row then row.fields + when Hash then @headers.map { |header| row[header] } + else row + end @headers = row if header_row? @lineno += 1 @@ -1431,7 +1714,7 @@ class CSV # # If you provide a block that takes one argument, it will be passed the field # and is expected to return the converted value or the field itself. If your - # block takes two arguments, it will also be passed a FieldInfo Struct, + # block takes two arguments, it will also be passed a CSV::FieldInfo Struct, # containing details about the field. Again, the block should return a # converted field or the field itself. # @@ -1445,7 +1728,7 @@ class CSV # header_convert { |field| ... } # header_convert { |field, field_info| ... } # - # Identical to CSV.convert(), but for header rows. + # Identical to CSV#convert(), but for header rows. # # Note that this method must be called before header rows are read to have any # effect. @@ -1526,7 +1809,7 @@ class CSV # add another read to the line (line += @io.gets(@row_sep)) rescue return nil # copy the line so we can chop it up in parsing - parse = line.dup + parse = line.dup parse.sub!(@parsers[:line_end], "") # @@ -1566,7 +1849,7 @@ class CSV nil # for Ruby 1.8 CSV compatibility else # I decided to take a strict approach to CSV parsing... - if $2.count("\r\n").zero? # verify correctness of field... + if $2.count(@parsers[:return_newline]).zero? # verify correctness $2 else # or throw an Exception @@ -1603,6 +1886,10 @@ class CSV # if we're not empty?() but at eof?(), a quoted field wasn't closed... if @io.eof? raise MalformedCSVError, "Unclosed quoted field on line #{lineno + 1}." + elsif parse =~ @parsers[:bad_field] + raise MalformedCSVError, "Illegal quoting on line #{lineno + 1}." + elsif @field_size_limit and parse.length >= @field_size_limit + raise MalformedCSVError, "Field size exceeded on line #{lineno + 1}." end # otherwise, we need to loop and pull some more data to complete the row end @@ -1610,6 +1897,38 @@ class CSV alias_method :gets, :shift alias_method :readline, :shift + # + # Returns a simplified description of the key FasterCSV attributes in an + # ASCII-8BIT String. + # + def inspect + str = ["<#", self.class.to_s, " io_type:"] + # show type of wrapped IO + if @io == $stdout then str << "$stdout" + elsif @io == $stdin then str << "$stdin" + elsif @io == $stderr then str << "$stderr" + else str << @io.class.to_s + end + # show IO.path(), if available + if @io.respond_to?(:path) and (p = @io.path) + str << " io_path:" << p.inspect + end + # show encoding + str << " encoding:" << @encoding.name + # show other attributes + %w[ lineno col_sep row_sep + quote_char skip_blanks ].each do |attr_name| + if a = instance_variable_get("@#{attr_name}") + str << " " << attr_name << ":" << a.inspect + end + end + if @use_headers + str << " headers:" << headers.inspect + end + str << ">" + str.map { |s| s.encode("ASCII-8BIT") }.join + end + private # @@ -1624,15 +1943,18 @@ class CSV # def init_separators(options) # store the selected separators - @col_sep = options.delete(:col_sep) - @row_sep = options.delete(:row_sep) - @quote_char = options.delete(:quote_char) + @col_sep = options.delete(:col_sep).to_s.encode(@encoding) + @row_sep = options.delete(:row_sep) # encode after resolving :auto + @quote_char = options.delete(:quote_char).to_s.encode(@encoding) if @quote_char.length != 1 raise ArgumentError, ":quote_char has to be a single character String" end + # # automatically discover row separator when requested + # (not fully encoding safe) + # if @row_sep == :auto if [ARGF, STDIN, STDOUT, STDERR].include?(@io) or (defined?(Zlib) and @io.class == Zlib::GzipWriter) @@ -1651,11 +1973,12 @@ class CSV end # read ahead a bit - sample = @io.read(1024) - sample += @io.read(1) if sample[-1..-1] == "\r" and not @io.eof? + sample = read_to_char(1024) + sample += read_to_char(1) if sample[-1..-1] == encode_str("\r") and + not @io.eof? # try to find a standard separator - if sample =~ /\r\n?|\n/ + if sample =~ encode_re("\r\n?|\n") @row_sep = $& break end @@ -1673,14 +1996,17 @@ class CSV end end end + @row_sep = @row_sep.to_s.encode(@encoding) # establish quoting rules - do_quote = lambda do |field| + @force_quotes = options.delete(:force_quotes) + do_quote = lambda do |field| @quote_char + String(field).gsub(@quote_char, @quote_char * 2) + @quote_char end - @quote = if options.delete(:force_quotes) + quotable_chars = encode_str("\r\n", @col_sep, @quote_char) + @quote = if @force_quotes do_quote else lambda do |field| @@ -1690,7 +2016,7 @@ class CSV field = String(field) # Stringify fields # represent empty fields as empty quoted fields if field.empty? or - field.count("\r\n#{@col_sep}#{@quote_char}").nonzero? + field.count(quotable_chars).nonzero? do_quote.call(field) else field # unquoted field @@ -1703,27 +2029,45 @@ class CSV # Pre-compiles parsers and stores them by name for access during reads. def init_parsers(options) # store the parser behaviors - @skip_blanks = options.delete(:skip_blanks) + @skip_blanks = options.delete(:skip_blanks) + @field_size_limit = options.delete(:field_size_limit) # prebuild Regexps for faster parsing - esc_col_sep = Regexp.escape(@col_sep) - esc_row_sep = Regexp.escape(@row_sep) - esc_quote = Regexp.escape(@quote_char) + esc_col_sep = escape_re(@col_sep) + esc_row_sep = escape_re(@row_sep) + esc_quote = escape_re(@quote_char) @parsers = { - :leading_fields => - /\A(?:#{esc_col_sep})+/, # for empty leading fields - :csv_row => - ### The Primary Parser ### - / \G(?:^|#{esc_col_sep}) # anchor the match - (?: #{esc_quote}( (?>[^#{esc_quote}]*) # find quoted fields - (?> #{esc_quote*2} - [^#{esc_quote}]* )* )#{esc_quote} - | # ... or ... - ([^#{esc_quote}#{esc_col_sep}]*) # unquoted fields - )/x, - ### End Primary Parser ### - :line_end => - /#{esc_row_sep}\z/ # safer than chomp!() + # for empty leading fields + :leading_fields => encode_re("\\A(?:", esc_col_sep, ")+"), + # The Primary Parser + :csv_row => encode_re( + "\\G(?:\\A|", esc_col_sep, ")", # anchor the match + "(?:", esc_quote, # find quoted fields + "((?>[^", esc_quote, "]*)", # "unrolling the loop" + "(?>", esc_quote * 2, # double for escaping + "[^", esc_quote, "]*)*)", + esc_quote, + "|", # ... or ... + "([^", esc_quote, esc_col_sep, "]*))", # unquoted fields + "(?=", esc_col_sep, "|\\z)" # ensure field is ended + ), + # a test for unescaped quotes + :bad_field => encode_re( + "\\A", esc_col_sep, "?", # an optional comma + "(?:", esc_quote, # a quoted field + "(?>[^", esc_quote, "]*)", # "unrolling the loop" + "(?>", esc_quote * 2, # double for escaping + "[^", esc_quote, "]*)*", + esc_quote, # the closing quote + "[^", esc_quote, "]", # an extra character + "|", # ... or ... + "[^", esc_quote, esc_col_sep, "]+", # an unquoted field + esc_quote, ")" # an extra quote + ), + # safer than chomp!() + :line_end => encode_re(esc_row_sep, "\\z"), + # illegal unquoted characters + :return_newline => encode_str("\r\n") } end @@ -1770,6 +2114,7 @@ class CSV def init_headers(options) @use_headers = options.delete(:headers) @return_headers = options.delete(:return_headers) + @write_headers = options.delete(:write_headers) # headers must be delayed until shift(), in case they need a row of content @headers = nil @@ -1812,7 +2157,7 @@ class CSV # see if we are converting headers or fields converters = headers ? @header_converters : @converters - fields.each_with_index.map do |field, index| # map_with_index + fields.map.with_index do |field, index| converters.each do |converter| field = if converter.arity == 1 # straight field converter converter[field] @@ -1839,10 +2184,17 @@ class CSV def parse_headers(row = nil) if @headers.nil? # header row @headers = case @use_headers # save headers - when Array then @use_headers # Array of headers - when String then self.class.parse_line(@use_headers) # CSV header String - else row # first row headers - end + # Array of headers + when Array then @use_headers + # CSV header String + when String + self.class.parse_line( @use_headers, + :col_sep => @col_sep, + :row_sep => @row_sep, + :quote_char => @quote_char ) + # first row is headers + else row + end # prepare converted and unconverted copies row = @headers if row.nil? @@ -1870,6 +2222,56 @@ class CSV row.instance_eval { @unconverted_fields = fields } row end + + # + # This method is an encoding safe version of Regexp::escape(). I will escape + # any characters that would change the meaning of a regular expression in the + # encoding of +str+. Regular expression characters that cannot be transcoded + # to the target encodign will be skipped and no escaping will be performed if + # a backslash cannot be transcoded. + # + def escape_re(str) + str.chars.map { |c| @re_chars.include?(c) ? @re_esc + c : c }.join + end + + # + # Builds a regular expression in <tt>@encoding</tt>. All +chunks+ will be + # transcoded to that encoding. + # + def encode_re(*chunks) + Regexp.new(encode_str(*chunks)) + end + + # + # Builds a String in <tt>@encoding</tt>. All +chunks+ will be transcoded to + # that encoding. + # + def encode_str(*chunks) + chunks.map { |chunk| chunk.encode(@encoding.name) }.join + end + + # + # Reads at least +bytes+ from <tt>@io</tt>, but will read on until the data + # read is valid in the ecoding of that data. This should ensure that it is + # safe to use regular expressions on the read data. The read data will be + # returned in <tt>@encoding</tt>. + # + def read_to_char(bytes) + return "" if @io.eof? + data = @io.read(bytes) + begin + encoded = encode_str(data) + raise unless encoded.valid_encoding? + return encoded + rescue # encoding error or my invalid data raise + if @io.eof? + return data + else + data += @io.read(1) until data.valid_encoding? or @io.eof? + retry + end + end + end end # Another name for CSV::instance(). diff --git a/lib/mathn.rb b/lib/mathn.rb index 2af2b83da3..e918608b0d 100644 --- a/lib/mathn.rb +++ b/lib/mathn.rb @@ -9,11 +9,15 @@ # # -require "complex.rb" -require "rational.rb" +require "cmath.rb" require "matrix.rb" require "prime.rb" +unless defined?(Math.exp!) + Object.instance_eval{remove_const :Math} + Math = CMath +end + class Fixnum remove_method :/ alias / quo @@ -33,7 +37,7 @@ class Rational if other.kind_of?(Rational) other2 = other if self < 0 - return Complex.__send__(:new!, self, 0) ** other + return Complex(self, 0.0) ** other elsif other == 0 return Rational(1,1) elsif self == 0 @@ -95,7 +99,7 @@ module Math remove_method(:sqrt) def sqrt(a) if a.kind_of?(Complex) - abs = sqrt(a.real*a.real + a.image*a.image) + abs = sqrt(a.real*a.real + a.imag*a.imag) # if not abs.kind_of?(Rational) # return a**Rational(1,2) # end @@ -104,7 +108,7 @@ module Math # if !(x.kind_of?(Rational) and y.kind_of?(Rational)) # return a**Rational(1,2) # end - if a.image >= 0 + if a.imag >= 0 Complex(x, y) else Complex(x, -y) diff --git a/lib/matrix.rb b/lib/matrix.rb index d31fcc5464..c672ee5198 100644 --- a/lib/matrix.rb +++ b/lib/matrix.rb @@ -410,17 +410,21 @@ class Matrix other.compare_by_row_vectors(@rows) end - alias eql? == + def eql?(other) + return false unless Matrix === other + + other.compare_by_row_vectors(@rows, :eql?) + end # # Not really intended for general consumption. # - def compare_by_row_vectors(rows) + def compare_by_row_vectors(rows, comparison = :==) return false unless @rows.size == rows.size 0.upto(@rows.size - 1) do |i| - return false unless @rows[i] == rows[i] + return false unless @rows[i].send(comparison, rows[i]) end true end @@ -1200,13 +1204,17 @@ class Vector other.compare_by(@elements) end - alias eqn? == + def eql?(other) + return false unless Vector === other + + other.compare_by(@elements, :eql?) + end # # For internal use. # - def compare_by(elements) - @elements == elements + def compare_by(elements, comparison = :==) + @elements.send(comparison, elements) end # diff --git a/lib/open-uri.rb b/lib/open-uri.rb index 238d759d5f..c68aee6e97 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -542,7 +542,7 @@ module OpenURI # :proxy => true # :proxy => false # :proxy => nil - # + # # If :proxy option is specified, the value should be String, URI, # boolean or nil. # When String or URI is given, it is treated as proxy URI. @@ -556,7 +556,7 @@ module OpenURI # Synopsis: # :proxy_http_basic_authentication => ["http://proxy.foo.com:8000/", "proxy-user", "proxy-password"] # :proxy_http_basic_authentication => [URI.parse("http://proxy.foo.com:8000/"), "proxy-user", "proxy-password"] - # + # # If :proxy option is specified, the value should be an Array with 3 elements. # It should contain a proxy URI, a proxy user name and a proxy password. # The proxy URI should be a String, an URI or nil. @@ -564,7 +564,7 @@ module OpenURI # # If nil is given for the proxy URI, this option is just ignored. # - # If :proxy and :proxy_http_basic_authentication is specified, + # If :proxy and :proxy_http_basic_authentication is specified, # ArgumentError is raised. # # [:http_basic_authentication] @@ -579,14 +579,14 @@ module OpenURI # [:content_length_proc] # Synopsis: # :content_length_proc => lambda {|content_length| ... } - # + # # If :content_length_proc option is specified, the option value procedure # is called before actual transfer is started. # It takes one argument which is expected content length in bytes. - # + # # If two or more transfer is done by HTTP redirection, the procedure # is called only one for a last transfer. - # + # # When expected content length is unknown, the procedure is called with # nil. # It is happen when HTTP response has no Content-Length header. diff --git a/lib/optparse.rb b/lib/optparse.rb index 86681235a0..f457b072a9 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -632,7 +632,7 @@ class OptionParser list.each do |opt| if opt.respond_to?(:summarize) # perhaps OptionParser::Switch opt.summarize(*args, &block) - elsif !opt + elsif !opt or opt.empty? yield("") elsif opt.respond_to?(:each_line) opt.each_line(&block) @@ -1605,6 +1605,13 @@ class OptionParser argv end + def set_backtrace(array) + unless $DEBUG + array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + end + super(array) + end + def set_option(opt, eq) if eq @args[0] = opt diff --git a/lib/pathname.rb b/lib/pathname.rb index 7bee22b58f..45974545a6 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -43,7 +43,7 @@ # base = p.basename # Pathname:ruby # dir, base = p.split # [Pathname:/usr/bin, Pathname:ruby] # data = p.read -# p.open { |f| _ } +# p.open { |f| _ } # p.each_line { |line| _ } # # === Example 2: Using standard Ruby @@ -55,7 +55,7 @@ # base = File.basename(p) # "ruby" # dir, base = File.split(p) # ["/usr/bin", "ruby"] # data = File.read(p) -# File.open(p) { |f| _ } +# File.open(p) { |f| _ } # File.foreach(p) { |line| _ } # # === Example 3: Special features @@ -71,7 +71,7 @@ # p5.cleanpath # Pathname:articles # p5.realpath # Pathname:/home/gavin/articles # p5.children # [Pathname:/home/gavin/articles/linux, ...] -# +# # == Breakdown of functionality # # === Core methods @@ -452,7 +452,7 @@ class Pathname # Returns a real (absolute) pathname of +self+ in the actual filesystem. # The real pathname doesn't contain symlinks or useless dots. # - # No arguments should be given; the old behaviour is *obsoleted*. + # No arguments should be given; the old behaviour is *obsoleted*. # def realpath path = @path @@ -587,7 +587,7 @@ class Pathname # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby # p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd # - # This method doesn't access the file system; it is pure string manipulation. + # This method doesn't access the file system; it is pure string manipulation. # def +(other) other = Pathname.new(other) unless Pathname === other @@ -1,10 +1,10 @@ # == Pretty-printer for Ruby objects. -# +# # = Which seems better? -# +# # non-pretty-printed output by #p is: # #<PP:0x81fedf0 @genspace=#<Proc:0x81feda0>, @group_queue=#<PrettyPrint::GroupQueue:0x81fed3c @queue=[[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], []]>, @buffer=[], @newline="\n", @group_stack=[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], @buffer_width=0, @indent=0, @maxwidth=79, @output_width=2, @output=#<IO:0x8114ee4>> -# +# # pretty-printed output by #pp is: # #<PP:0x81fedf0 # @buffer=[], @@ -22,17 +22,17 @@ # @newline="\n", # @output=#<IO:0x8114ee4>, # @output_width=2> -# +# # I like the latter. If you do too, this library is for you. -# +# # = Usage -# +# # pp(obj) # # output +obj+ to +$>+ in pretty printed format. -# +# # It returns +nil+. -# +# # = Output Customization # To define your customized pretty printing function for your classes, # redefine a method #pretty_print(+pp+) in the class. @@ -67,10 +67,10 @@ end class PP < PrettyPrint # Outputs +obj+ to +out+ in pretty printed format of # +width+ columns in width. - # + # # If +out+ is omitted, +$>+ is assumed. # If +width+ is omitted, 79 is assumed. - # + # # PP.pp returns +out+. def PP.pp(obj, out=$>, width=79) q = PP.new(out, width) @@ -82,7 +82,7 @@ class PP < PrettyPrint # Outputs +obj+ to +out+ like PP.pp but with no indent and # newline. - # + # # PP.singleline_pp returns +out+. def PP.singleline_pp(obj, out=$>) q = SingleLine.new(out) @@ -138,12 +138,12 @@ class PP < PrettyPrint # Adds +obj+ to the pretty printing buffer # using Object#pretty_print or Object#pretty_print_cycle. - # + # # Object#pretty_print_cycle is used when +obj+ is already # printed, a.k.a the object reference chain has a cycle. def pp(obj) id = obj.object_id - + if check_inspect_key(id) group {obj.pretty_print_cycle self} return @@ -158,7 +158,7 @@ class PP < PrettyPrint end # A convenience method which is same as follows: - # + # # group(1, '#<' + obj.class.name, '>') { ... } def object_group(obj, &block) # :yield: group(1, '#<' + obj.class.name, '>', &block) @@ -185,7 +185,7 @@ class PP < PrettyPrint end # A convenience method which is same as follows: - # + # # text ',' # breakable def comma_breakable @@ -195,23 +195,23 @@ class PP < PrettyPrint # Adds a separated list. # The list is separated by comma with breakable space, by default. - # + # # #seplist iterates the +list+ using +iter_method+. # It yields each object to the block given for #seplist. # The procedure +separator_proc+ is called between each yields. - # + # # If the iteration is zero times, +separator_proc+ is not called at all. - # + # # If +separator_proc+ is nil or not given, # +lambda { comma_breakable }+ is used. # If +iter_method+ is not given, :each is used. - # + # # For example, following 3 code fragments has similar effect. - # + # # q.seplist([1,2,3]) {|v| xxx v } - # + # # q.seplist([1,2,3], lambda { q.comma_breakable }, :each) {|v| xxx v } - # + # # xxx 1 # q.comma_breakable # xxx 2 @@ -275,11 +275,11 @@ class PP < PrettyPrint # A default pretty printing method for general objects. # It calls #pretty_print_instance_variables to list instance variables. - # + # # If +self+ has a customized (redefined) #inspect method, # the result of self.inspect is used but it obviously has no # line break hints. - # + # # This module provides predefined #pretty_print methods for some of # the most commonly used built-in classes for convenience. def pretty_print(q) @@ -302,7 +302,7 @@ class PP < PrettyPrint end # Returns a sorted array of instance variable names. - # + # # This method should return an array of names of instance variables as symbols or strings as: # +[:@a, :@b]+. def pretty_print_instance_variables @@ -311,7 +311,7 @@ class PP < PrettyPrint # Is #inspect implementation using #pretty_print. # If you implement #pretty_print, it can be used as follows. - # + # # alias inspect pretty_print_inspect # # However, doing this requires that every class that #inspect is called on @@ -629,7 +629,7 @@ if __FILE__ == $0 result = PP.pp(a, '') assert_equal("#{a.inspect}\n", result) end - + def test_to_s_without_iv a = Object.new def a.to_s() "aaa" end diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb index 315c422e9e..48f2ebf1e4 100644 --- a/lib/prettyprint.rb +++ b/lib/prettyprint.rb @@ -2,7 +2,7 @@ # This class implements a pretty printing algorithm. It finds line breaks and # nice indentations for grouped structure. -# +# # By default, the class assumes that primitive elements are strings and each # byte in the strings have single column in width. But it can be used for # other situations by giving suitable arguments for some methods: @@ -18,28 +18,28 @@ # == Bugs # * Box based formatting? # * Other (better) model/algorithm? -# +# # == References # Christian Lindig, Strictly Pretty, March 2000, # http://www.st.cs.uni-sb.de/~lindig/papers/#pretty -# +# # Philip Wadler, A prettier printer, March 1998, # http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier -# +# # == Author # Tanaka Akira <akr@m17n.org> -# +# class PrettyPrint # This is a convenience method which is same as follows: - # + # # begin # q = PrettyPrint.new(output, maxwidth, newline, &genspace) # ... # q.flush # output # end - # + # def PrettyPrint.format(output='', maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n}) q = PrettyPrint.new(output, maxwidth, newline, &genspace) yield q diff --git a/lib/rake.rb b/lib/rake.rb index 0b21eb7350..caafa5caf0 100755 --- a/lib/rake.rb +++ b/lib/rake.rb @@ -29,13 +29,13 @@ # as a library via a require statement, but it can be distributed # independently as an application. -RAKEVERSION = '0.8.0' +RAKEVERSION = '0.8.2' require 'rbconfig' -require 'getoptlong' require 'fileutils' require 'singleton' -require 'thread' +require 'monitor' +require 'optparse' require 'ostruct' ###################################################################### @@ -239,6 +239,33 @@ end # class String ############################################################################## module Rake + # Errors ----------------------------------------------------------- + + # Error indicating an ill-formed task declaration. + class TaskArgumentError < ArgumentError + end + + # Error indicating a recursion overflow error in task selection. + class RuleRecursionOverflowError < StandardError + def initialize(*args) + super + @targets = [] + end + + def add_target(target) + @targets << target + end + + def message + super + ": [" + @targets.reverse.join(' => ') + "]" + end + end + + # Error indicating a problem in locating the home directory on a + # Win32 system. + class Win32HomeError < RuntimeError + end + # -------------------------------------------------------------------------- # Rake module singleton methods. # @@ -266,16 +293,22 @@ module Rake module Cloneable # Clone an object by making a new object and setting all the instance # variables to the same values. - def clone + def dup sibling = self.class.new instance_variables.each do |ivar| value = self.instance_variable_get(ivar) new_value = value.clone rescue value sibling.instance_variable_set(ivar, new_value) end + sibling.taint if tainted? + sibling + end + + def clone + sibling = dup + sibling.freeze if frozen? sibling end - alias dup clone end #################################################################### @@ -286,12 +319,15 @@ module Rake attr_reader :names + # Create a TaskArgument object with a list of named arguments + # (given by :names) and a set of associated values (given by + # :values). :parent is the parent argument object. def initialize(names, values, parent=nil) @names = names @parent = parent @hash = {} names.each_with_index { |name, i| - @hash[name.to_sym] = values[i] + @hash[name.to_sym] = values[i] unless values[i].nil? } end @@ -307,6 +343,13 @@ module Rake lookup(index.to_sym) end + # Specify a hash of default values for task arguments. Use the + # defaults only if there is no specific value for the given + # argument. + def with_defaults(defaults) + @hash = defaults.merge(@hash) + end + def each(&block) @hash.each(&block) end @@ -342,6 +385,8 @@ module Rake end end + EMPTY_TASK_ARGS = TaskArguments.new([], []) + #################################################################### # InvocationChain tracks the chain of task invocations to detect # circular dependencies. @@ -409,6 +454,9 @@ module Rake # List of prerequisites for a task. attr_reader :prerequisites + # List of actions attached to a task. + attr_reader :actions + # Application owning this task. attr_accessor :application @@ -446,12 +494,12 @@ module Rake # +enhance+ to add actions and prerequisites. def initialize(task_name, app) @name = task_name.to_s - @prerequisites = FileList[] + @prerequisites = [] @actions = [] @already_invoked = false @full_comment = nil @comment = nil - @lock = Mutex.new + @lock = Monitor.new @application = app @scope = app.current_scope @arg_names = nil @@ -488,6 +536,31 @@ module Rake @arg_names || [] end + # Reenable the task, allowing its tasks to be executed if the task + # is invoked again. + def reenable + @already_invoked = false + end + + # Clear the existing prerequisites and actions of a rake task. + def clear + clear_prerequisites + clear_actions + self + end + + # Clear the existing prerequisites of a rake task. + def clear_prerequisites + prerequisites.clear + self + end + + # Clear the existing actions on a rake task. + def clear_actions + actions.clear + self + end + # Invoke the task if it is needed. Prerequites are invoked first. def invoke(*args) task_args = TaskArguments.new(arg_names, args) @@ -496,7 +569,7 @@ module Rake # Same as invoke, but explicitly pass a call chain to detect # circular dependencies. - def invoke_with_call_chain(task_args, invocation_chain) + def invoke_with_call_chain(task_args, invocation_chain) # :nodoc: new_chain = InvocationChain.append(self, invocation_chain) @lock.synchronize do if application.options.trace @@ -511,7 +584,7 @@ module Rake protected :invoke_with_call_chain # Invoke all the prerequisites of a task. - def invoke_prerequisites(task_args, invocation_chain) + def invoke_prerequisites(task_args, invocation_chain) # :nodoc: @prerequisites.each { |n| prereq = application[n, @scope] prereq_args = task_args.new_scope(prereq.arg_names) @@ -529,7 +602,8 @@ module Rake private :format_trace_flags # Execute the actions associated with this task. - def execute(args) + def execute(args=nil) + args ||= EMPTY_TASK_ARGS if application.options.dryrun puts "** Execute (dry run) #{name}" return @@ -772,8 +846,8 @@ end # end # end # -def file(args, &block) - Rake::FileTask.define_task(args, &block) +def file(*args, &block) + Rake::FileTask.define_task(*args, &block) end # Declare a file creation task. @@ -899,14 +973,38 @@ module FileUtils ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]" } end + if RakeFileUtils.verbose_flag == :default + options[:verbose] = false + else + options[:verbose] ||= RakeFileUtils.verbose_flag + end + options[:noop] ||= RakeFileUtils.nowrite_flag rake_check_options options, :noop, :verbose rake_output_message cmd.join(" ") if options[:verbose] unless options[:noop] - res = system(*cmd) + res = rake_system(*cmd) block.call(res, $?) end end + def rake_system(*cmd) + if Rake.application.windows? + rake_win32_system(*cmd) + else + system(*cmd) + end + end + private :rake_system + + def rake_win32_system(*cmd) + if cmd.size == 1 + system("call #{cmd}") + else + system(*cmd) + end + end + private :rake_win32_system + # Run a Ruby interpreter with the given arguments. # # Example: @@ -917,7 +1015,7 @@ module FileUtils if args.length > 1 then sh(*([RUBY] + args + [options]), &block) else - sh("#{RUBY.sub(/.*\s.*/m, '"\&"')} #{args.first}", options, &block) + sh("#{RUBY} #{args.first}", options, &block) end end @@ -961,7 +1059,7 @@ module RakeFileUtils class << self attr_accessor :verbose_flag, :nowrite_flag end - RakeFileUtils.verbose_flag = true + RakeFileUtils.verbose_flag = :default RakeFileUtils.nowrite_flag = false $fileutils_verbose = true @@ -969,10 +1067,10 @@ module RakeFileUtils FileUtils::OPT_TABLE.each do |name, opts| default_options = [] - if opts.include?('verbose') + if opts.include?(:verbose) || opts.include?("verbose") default_options << ':verbose => RakeFileUtils.verbose_flag' end - if opts.include?('noop') + if opts.include?(:noop) || opts.include?("noop") default_options << ':noop => RakeFileUtils.nowrite_flag' end @@ -1029,7 +1127,7 @@ module RakeFileUtils oldvalue end - # Use this function to prevent potentially destructive ruby code from + # Use this function to prevent protentially destructive ruby code from # running when the :nowrite flag is set. # # Example: @@ -1094,21 +1192,6 @@ private(*RakeFileUtils.instance_methods(false)) ###################################################################### module Rake - class RuleRecursionOverflowError < StandardError - def initialize(*args) - super - @targets = [] - end - - def add_target(target) - @targets << target - end - - def message - super + ": [" + @targets.reverse.join(' => ') + "]" - end - end - # ######################################################################### # A FileList is essentially an array with a few helper methods defined to # make file manipulation a bit easier. @@ -1352,7 +1435,7 @@ module Rake private :resolve_exclude # Return a new FileList with the results of running +sub+ against each - # element of the original list. + # element of the oringal list. # # Example: # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o'] @@ -1503,7 +1586,7 @@ module Rake class << self # Yield each file or directory component. - def each_dir_parent(dir) + def each_dir_parent(dir) # :nodoc: old_length = nil while dir != '.' && dir.length != old_length yield(dir) @@ -1642,23 +1725,65 @@ module Rake # Resolve the arguments for a task/rule. Returns a triplet of # [task_name, arg_name_list, prerequisites]. def resolve_args(args) + if args.last.is_a?(Hash) + deps = args.pop + resolve_args_with_dependencies(args, deps) + else + resolve_args_without_dependencies(args) + end + end + + # Resolve task arguments for a task or rule when there are no + # dependencies declared. + # + # The patterns recognized by this argument resolving function are: + # + # task :t + # task :t, [:a] + # task :t, :a (deprecated) + # + def resolve_args_without_dependencies(args) task_name = args.shift - arg_names = args #.map { |a| a.to_sym } - needs = [] - if task_name.is_a?(Hash) - hash = task_name - task_name = hash.keys[0] - needs = hash[task_name] + if args.size == 1 && args.first.respond_to?(:to_ary) + arg_names = args.first.to_ary + else + arg_names = args end - if arg_names.last.is_a?(Hash) - hash = arg_names.pop - needs = hash[:needs] - fail "Unrecognized keys in task hash: #{hash.keys.inspect}" if hash.size > 1 + [task_name, arg_names, []] + end + private :resolve_args_without_dependencies + + # Resolve task arguments for a task or rule when there are + # dependencies declared. + # + # The patterns recognized by this argument resolving function are: + # + # task :t => [:d] + # task :t, [a] => [:d] + # task :t, :needs => [:d] (deprecated) + # task :t, :a, :needs => [:d] (deprecated) + # + def resolve_args_with_dependencies(args, hash) # :nodoc: + fail "Task Argument Error" if hash.size != 1 + key, value = hash.map { |k, v| [k,v] }.first + if args.empty? + task_name = key + arg_names = [] + deps = value + elsif key == :needs + task_name = args.shift + arg_names = args + deps = value + else + task_name = args.shift + arg_names = key + deps = value end - needs = [needs] unless needs.respond_to?(:to_ary) - [task_name, arg_names, needs] + deps = [deps] unless deps.respond_to?(:to_ary) + [task_name, arg_names, deps] end - + private :resolve_args_with_dependencies + # If a rule can be found that matches the task name, enhance the # task with the prerequisites and actions from the rule. Set the # source attribute of the task appropriately for the rule. Return @@ -1749,15 +1874,23 @@ module Rake "_anon_#{@seed}" end + def trace_rule(level, message) + puts "#{" "*level}#{message}" if Rake.application.options.trace_rules + end + # Attempt to create a rule given the list of prerequisites. def attempt_rule(task_name, extensions, block, level) sources = make_sources(task_name, extensions) prereqs = sources.collect { |source| + trace_rule level, "Attempting Rule #{task_name} => #{source}" if File.exist?(source) || Rake::Task.task_defined?(source) + trace_rule level, "(#{task_name} => #{source} ... EXIST)" source - elsif parent = enhance_with_matching_rule(sources.first, level+1) + elsif parent = enhance_with_matching_rule(source, level+1) + trace_rule level, "(#{task_name} => #{source} ... ENHANCE)" parent.name else + trace_rule level, "(#{task_name} => #{source} ... FAIL)" return nil end } @@ -1814,41 +1947,6 @@ module Rake DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze - OPTIONS = [ # :nodoc: - ['--classic-namespace', '-C', GetoptLong::NO_ARGUMENT, - "Put Task and FileTask in the top level namespace"], - ['--describe', '-D', GetoptLong::OPTIONAL_ARGUMENT, - "Describe the tasks (matching optional PATTERN), then exit."], - ['--rakefile', '-f', GetoptLong::OPTIONAL_ARGUMENT, - "Use FILE as the rakefile."], - ['--help', '-h', '-H', GetoptLong::NO_ARGUMENT, - "Display this help message."], - ['--libdir', '-I', GetoptLong::REQUIRED_ARGUMENT, - "Include LIBDIR in the search path for required modules."], - ['--dry-run', '-n', GetoptLong::NO_ARGUMENT, - "Do a dry run without executing actions."], - ['--nosearch', '-N', GetoptLong::NO_ARGUMENT, - "Do not search parent directories for the Rakefile."], - ['--prereqs', '-P', GetoptLong::NO_ARGUMENT, - "Display the tasks and dependencies, then exit."], - ['--quiet', '-q', GetoptLong::NO_ARGUMENT, - "Do not log messages to standard output."], - ['--require', '-r', GetoptLong::REQUIRED_ARGUMENT, - "Require MODULE before executing rakefile."], - ['--rakelibdir', '-R', GetoptLong::REQUIRED_ARGUMENT, - "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')"], - ['--silent', '-s', GetoptLong::NO_ARGUMENT, - "Like --quiet, but also suppresses the 'in directory' announcement."], - ['--tasks', '-T', GetoptLong::OPTIONAL_ARGUMENT, - "Display the tasks (matching optional PATTERN) with descriptions, then exit."], - ['--trace', '-t', GetoptLong::NO_ARGUMENT, - "Turn on invoke/execute tracing, enable full backtrace."], - ['--verbose', '-v', GetoptLong::NO_ARGUMENT, - "Log message to standard output (default)."], - ['--version', '-V', GetoptLong::NO_ARGUMENT, - "Display the program version."], - ] - # Initialize a Rake::Application object. def initialize super @@ -1861,8 +1959,10 @@ module Rake @default_loader = Rake::DefaultLoader.new @original_dir = Dir.pwd @top_level_tasks = [] + add_loader('rb', DefaultLoader.new) add_loader('rf', DefaultLoader.new) add_loader('rake', DefaultLoader.new) + @tty_output = STDOUT.tty? end # Run the Rake application. The run method performs the following three steps: @@ -1886,8 +1986,7 @@ module Rake def init(app_name='rake') standard_exception_handling do @name = app_name - handle_options - collect_tasks + collect_tasks handle_options end end @@ -1942,16 +2041,16 @@ module Rake [name, args] end - # Provide standard exception handling for the given block. + # Provide standard execption handling for the given block. def standard_exception_handling begin yield rescue SystemExit => ex # Exit silently with current status - exit(ex.status) - rescue SystemExit, GetoptLong::InvalidOption => ex + raise + rescue OptionParser::InvalidOption => ex # Exit silently - exit(1) + exit(false) rescue Exception => ex # Exit with error message $stderr.puts "rake aborted!" @@ -1962,7 +2061,7 @@ module Rake $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || "" $stderr.puts "(See full trace by running task with --trace)" end - exit(1) + exit(false) end end @@ -1971,33 +2070,26 @@ module Rake def have_rakefile @rakefiles.each do |fn| if File.exist?(fn) || fn == '' - @rakefile = fn - return true + return fn end end - return false + return nil end - # Display the rake command line help. - def help - puts "rake [-f rakefile] {options} targets..." - puts - puts "Options are ..." - puts - OPTIONS.sort.each do |long, *short, mode, desc| - case mode - when GetoptLong::REQUIRED_ARGUMENT - if desc =~ /\b([A-Z]{2,})\b/ - long = long + "=#{$1}" - end - when GetoptLong::OPTIONAL_ARGUMENT - if desc =~ /\b([A-Z]{2,})\b/ - long = long + "[=#{$1}]" - end - end - printf " %-20s (%s)\n", long, short.join(", ") - printf " %s\n", desc - end + # True if we are outputting to TTY, false otherwise + def tty_output? + @tty_output + end + + # Override the detected TTY output state (mostly for testing) + def tty_output=( tty_output_state ) + @tty_output = tty_output_state + end + + # We will truncate output if we are outputting to a TTY or if we've been + # given an explicit column width to honor + def truncate_output? + tty_output? || ENV['RAKE_COLUMNS'] end # Display the tasks and dependencies. @@ -2015,19 +2107,51 @@ module Rake end else width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10 - max_column = 80 - name.size - width - 7 + max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil displayable_tasks.each do |t| printf "#{name} %-#{width}s # %s\n", - t.name_with_args, truncate(t.comment, max_column) + t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment end end end + def terminal_width + if ENV['RAKE_COLUMNS'] + result = ENV['RAKE_COLUMNS'].to_i + else + result = unix? ? dynamic_width : 80 + end + (result < 10) ? 80 : result + rescue + 80 + end + + # Calculate the dynamic width of the + def dynamic_width + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty + %x{stty size 2>/dev/null}.split[1].to_i + end + + def dynamic_width_tput + %x{tput cols 2>/dev/null}.to_i + end + + def unix? + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + end + + def windows? + Config::CONFIG['host_os'] =~ /mswin/ + end + def truncate(string, width) if string.length <= width string else - string[0, width-3] + "..." + ( string[0, width-3] || "" ) + "..." end end @@ -2039,77 +2163,141 @@ module Rake end end - # Return a list of the command line options supported by the - # program. - def command_line_options - OPTIONS.collect { |lst| lst[0..-2] } - end - - # Do the option defined by +opt+ and +value+. - def do_option(opt, value) - case opt - when '--describe' - options.show_tasks = true - options.show_task_pattern = Regexp.new(value || '.') - options.full_description = true - when '--dry-run' - verbose(true) - nowrite(true) - options.dryrun = true - options.trace = true - when '--help' - help - exit - when '--libdir' - $:.push(value) - when '--nosearch' - options.nosearch = true - when '--prereqs' - options.show_prereqs = true - when '--quiet' - verbose(false) - when '--rakefile' - @rakefiles.clear - @rakefiles << value - when '--rakelibdir' - options.rakelib = value.split(':') - when '--require' - begin - require value - rescue LoadError => ex - begin - rake_require value - rescue LoadError => ex2 - raise ex - end - end - when '--silent' - verbose(false) - options.silent = true - when '--tasks' - options.show_tasks = true - options.show_task_pattern = Regexp.new(value || '.') - options.full_description = false - when '--trace' - options.trace = true - verbose(true) - when '--verbose' - verbose(true) - when '--version' - puts "rake, version #{RAKEVERSION}" - exit - when '--classic-namespace' - require 'rake/classic_namespace' - options.classic_namespace = true - end + # A list of all the standard options used in rake, suitable for + # passing to OptionParser. + def standard_rake_options + [ + ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace", + lambda { |value| + require 'rake/classic_namespace' + options.classic_namespace = true + } + ], + ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + options.show_tasks = true + options.full_description = true + options.show_task_pattern = Regexp.new(value || '') + } + ], + ['--dry-run', '-n', "Do a dry run without executing actions.", + lambda { |value| + verbose(true) + nowrite(true) + options.dryrun = true + options.trace = true + } + ], + ['--execute', '-e CODE', "Execute some Ruby code and exit.", + lambda { |value| + eval(value) + exit + } + ], + ['--execute-print', '-p CODE', "Execute some Ruby code, print the result, then exit.", + lambda { |value| + puts eval(value) + exit + } + ], + ['--execute-continue', '-E CODE', + "Execute some Ruby code, then continue with normal task processing.", + lambda { |value| eval(value) } + ], + ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.", + lambda { |value| $:.push(value) } + ], + ['--prereqs', '-P', "Display the tasks and dependencies, then exit.", + lambda { |value| options.show_prereqs = true } + ], + ['--quiet', '-q', "Do not log messages to standard output.", + lambda { |value| verbose(false) } + ], + ['--rakefile', '-f [FILE]', "Use FILE as the rakefile.", + lambda { |value| + value ||= '' + @rakefiles.clear + @rakefiles << value + } + ], + ['--rakelibdir', '--rakelib', '-R RAKELIBDIR', + "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')", + lambda { |value| options.rakelib = value.split(':') } + ], + ['--require', '-r MODULE', "Require MODULE before executing rakefile.", + lambda { |value| + begin + require value + rescue LoadError => ex + begin + rake_require value + rescue LoadError => ex2 + raise ex + end + end + } + ], + ['--rules', "Trace the rules resolution.", + lambda { |value| options.trace_rules = true } + ], + ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.", + lambda { |value| options.nosearch = true } + ], + ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.", + lambda { |value| + verbose(false) + options.silent = true + } + ], + ['--system', '-g', + "Using system wide (global) rakefiles (usually '~/.rake/*.rake').", + lambda { |value| options.load_system = true } + ], + ['--no-system', '--nosystem', '-G', + "Use standard project Rakefile search paths, ignore system wide rakefiles.", + lambda { |value| options.ignore_system = true } + ], + ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.", + lambda { |value| + options.show_tasks = true + options.show_task_pattern = Regexp.new(value || '') + options.full_description = false + } + ], + ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.", + lambda { |value| + options.trace = true + verbose(true) + } + ], + ['--verbose', '-v', "Log message to standard output (default).", + lambda { |value| verbose(true) } + ], + ['--version', '-V', "Display the program version.", + lambda { |value| + puts "rake, version #{RAKEVERSION}" + exit + } + ] + ] end # Read and handle the command line options. def handle_options options.rakelib = ['rakelib'] - opts = GetoptLong.new(*command_line_options) - opts.each { |opt, value| do_option(opt, value) } + opts = OptionParser.new + opts.banner = "rake [-f rakefile] {options} targets..." + opts.separator "" + opts.separator "Options are ..." + + opts.on_tail("-h", "--help", "-H", "Display this help message.") do + puts opts + exit + end + + standard_rake_options.each { |args| opts.on(*args) } + parsed_argv = opts.parse(ARGV) # If class namespaces are requested, set the global options # according to the values in the options structure. @@ -2120,12 +2308,11 @@ module Rake $dryrun = options.dryrun $silent = options.silent end - rescue NoMethodError => ex - raise GetoptLong::InvalidOption, "While parsing options, error = #{ex.class}:#{ex.message}" + parsed_argv end # Similar to the regular Ruby +require+ command, but will check - # for .rake files in addition to .rb files. + # for *.rake files in addition to *.rb files. def rake_require(file_name, paths=$LOAD_PATH, loaded=$") return false if loaded.include?(file_name) paths.each do |path| @@ -2140,34 +2327,95 @@ module Rake fail LoadError, "Can't find #{file_name}" end - def raw_load_rakefile # :nodoc: + def find_rakefile_location here = Dir.pwd - while ! have_rakefile + while ! (fn = have_rakefile) Dir.chdir("..") if Dir.pwd == here || options.nosearch - fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" + return nil end here = Dir.pwd end - puts "(in #{Dir.pwd})" unless options.silent - $rakefile = @rakefile - load File.expand_path(@rakefile) if @rakefile != '' - options.rakelib.each do |rlib| - Dir["#{rlib}/*.rake"].each do |name| add_import name end + [fn, here] + ensure + Dir.chdir(Rake.original_dir) + end + + def raw_load_rakefile # :nodoc: + rakefile, location = find_rakefile_location + if (! options.ignore_system) && + (options.load_system || rakefile.nil?) && + directory?(system_dir) + puts "(in #{Dir.pwd})" unless options.silent + glob("#{system_dir}/*.rake") do |name| + add_import name + end + else + fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if + rakefile.nil? + @rakefile = rakefile + Dir.chdir(location) + puts "(in #{Dir.pwd})" unless options.silent + $rakefile = @rakefile if options.classic_namespace + load File.expand_path(@rakefile) if @rakefile && @rakefile != '' + options.rakelib.each do |rlib| + glob("#{rlib}/*.rake") do |name| + add_import name + end + end end load_imports end + def glob(path, &block) + Dir[path.gsub("\\", '/')].each(&block) + end + private :glob + + # The directory path containing the system wide rakefiles. + def system_dir + if ENV['RAKE_SYSTEM'] + ENV['RAKE_SYSTEM'] + elsif windows? + win32_system_dir + else + standard_system_dir + end + end + + # The standard directory containing system wide rake files. + def standard_system_dir #:nodoc: + File.join(File.expand_path('~'), '.rake') + end + private :standard_system_dir + + # The standard directory containing system wide rake files on Win + # 32 systems. + def win32_system_dir #:nodoc: + win32home = File.join(ENV['APPDATA'], 'Rake') + unless directory?(win32home) + raise Win32HomeError, "Unable to determine home path environment variable." + else + win32home + end + end + private :win32_system_dir + + def directory?(path) + File.directory?(path) + end + private :directory? + # Collect the list of tasks on the command line. If no tasks are # given, return a list containing only the default task. # Environmental assignments are processed at this time as well. - def collect_tasks + def collect_tasks(argv) @top_level_tasks = [] - ARGV.each do |arg| + argv.each do |arg| if arg =~ /^(\w+)=(.*)$/ ENV[$1] = $2 else - @top_level_tasks << arg + @top_level_tasks << arg unless arg =~ /^-/ end end @top_level_tasks.push("default") if @top_level_tasks.size == 0 diff --git a/lib/rake/loaders/makefile.rb b/lib/rake/loaders/makefile.rb index f66eb3b35f..9ade098a1b 100644 --- a/lib/rake/loaders/makefile.rb +++ b/lib/rake/loaders/makefile.rb @@ -7,31 +7,26 @@ module Rake # Load the makefile dependencies in +fn+. def load(fn) - buffer = '' open(fn) do |mf| - mf.each do |line| - next if line =~ /^\s*#/ - buffer << line - if buffer =~ /\\$/ - buffer.sub!(/\\\n/, ' ') - state = :append - else - process_line(buffer) - buffer = '' - end + lines = mf.read + lines.gsub!(/#[^\n]*\n/m, "") + lines.gsub!(/\\\n/, ' ') + lines.split("\n").each do |line| + process_line(line) end end - process_line(buffer) if buffer != '' end private # Process one logical line of makefile data. def process_line(line) - file_task, args = line.split(':') + file_tasks, args = line.split(':') return if args.nil? dependents = args.split - file file_task => dependents + file_tasks.strip.split.each do |file_task| + file file_task => dependents + end end end diff --git a/lib/rake/packagetask.rb b/lib/rake/packagetask.rb index 71b66a6481..4b0775d09c 100644 --- a/lib/rake/packagetask.rb +++ b/lib/rake/packagetask.rb @@ -122,6 +122,7 @@ module Rake task :package => ["#{package_dir}/#{file}"] file "#{package_dir}/#{file}" => [package_dir_path] + package_files do chdir(package_dir) do + sh %{env} sh %{#{@tar_command} #{flag}cvf #{file} #{package_name}} end end diff --git a/lib/rake/rdoctask.rb b/lib/rake/rdoctask.rb index 54adc6feb5..6cfbda1d6a 100644 --- a/lib/rake/rdoctask.rb +++ b/lib/rake/rdoctask.rb @@ -55,7 +55,7 @@ module Rake # RDoc. (default is none) attr_accessor :main - # Name of template to be used by rdoc. (default is 'html') + # Name of template to be used by rdoc. (defaults to rdoc's default) attr_accessor :template # List of files to be included in the rdoc generation. (default is []) @@ -74,7 +74,7 @@ module Rake @rdoc_dir = 'html' @main = nil @title = nil - @template = 'html' + @template = nil @external = false @options = [] yield self if block_given? @@ -91,18 +91,18 @@ module Rake task name desc "Force a rebuild of the RDOC files" - task paste("re", name) => [paste("clobber_", name), name] + task "re#{name}" => ["clobber_#{name}", name] desc "Remove rdoc products" - task paste("clobber_", name) do + task "clobber_#{name}" do rm_r rdoc_dir rescue nil end - - task :clobber => [paste("clobber_", name)] + + task :clobber => ["clobber_#{name}"] directory @rdoc_dir task name => [rdoc_target] - file rdoc_target => @rdoc_files + [$rakefile] do + file rdoc_target => @rdoc_files + [Rake.application.rakefile] do rm_r @rdoc_dir rescue nil args = option_list + @rdoc_files if @external diff --git a/lib/rake/tasklib.rb b/lib/rake/tasklib.rb index 465a58a0c7..c7fd98133c 100644 --- a/lib/rake/tasklib.rb +++ b/lib/rake/tasklib.rb @@ -6,11 +6,16 @@ module Rake # Base class for Task Libraries. class TaskLib - include Cloneable - # Make a symbol by pasting two strings together. - def paste(a,b) + # Make a symbol by pasting two strings together. + # + # NOTE: DEPRECATED! This method is kinda stupid. I don't know why + # I didn't just use string interpolation. But now other task + # libraries depend on this so I can't remove it without breaking + # other people's code. So for now it stays for backwards + # compatibility. BUT DON'T USE IT. + def paste(a,b) # :nodoc: (a.to_s + b.to_s).intern end end diff --git a/lib/rake/testtask.rb b/lib/rake/testtask.rb index f5b77e5957..79154e422b 100644 --- a/lib/rake/testtask.rb +++ b/lib/rake/testtask.rb @@ -136,7 +136,12 @@ module Rake end def fix # :nodoc: - '' + case RUBY_VERSION + when '1.8.2' + find_file 'rake/ruby182_test_unit_fix' + else + nil + end || '' end def rake_loader # :nodoc: diff --git a/lib/resolv.rb b/lib/resolv.rb index d1494b46c9..fc3c78215b 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -13,24 +13,24 @@ end # interpreter. # # See also resolv-replace.rb to replace the libc resolver with # Resolv. -# +# # Resolv can look up various DNS resources using the DNS module directly. -# +# # Examples: -# +# # p Resolv.getaddress "www.ruby-lang.org" # p Resolv.getname "210.251.121.214" -# +# # Resolv::DNS.open do |dns| # ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A # p ress.map { |r| r.address } # ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX # p ress.map { |r| [r.exchange.to_s, r.preference] } # end -# -# +# +# # == Bugs -# +# # * NIS is not supported. # * /etc/nsswitch.conf is not supported. @@ -38,14 +38,14 @@ class Resolv ## # Looks up the first IP address for +name+. - + def self.getaddress(name) DefaultResolver.getaddress(name) end ## # Looks up all IP address for +name+. - + def self.getaddresses(name) DefaultResolver.getaddresses(name) end @@ -87,7 +87,7 @@ class Resolv ## # Looks up the first IP address for +name+. - + def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("no address for #{name}") @@ -95,7 +95,7 @@ class Resolv ## # Looks up all IP address for +name+. - + def getaddresses(name) ret = [] each_address(name) {|address| ret << address} @@ -309,7 +309,7 @@ class Resolv # Creates a new DNS resolver. # # +config_info+ can be: - # + # # nil:: Uses /etc/resolv.conf. # String:: Path to a file using /etc/resolv.conf's format. # Hash:: Must contain :nameserver, :search and :ndots keys. @@ -457,7 +457,7 @@ class Resolv ## # Looks up all +typeclass+ DNS resources for +name+. See #getresource for # argument details. - + def getresources(name, typeclass) ret = [] each_resource(name, typeclass) {|resource| ret << resource} @@ -467,7 +467,7 @@ class Resolv ## # Iterates over all +typeclass+ DNS resources for +name+. See # #getresource for argument details. - + def each_resource(name, typeclass, &proc) lazy_initialize requester = make_requester @@ -569,7 +569,7 @@ class Resolv h = (RequestID[[host, port]] ||= {}) begin id = rangerand(0x0000..0xffff) - end while h[id] + end while h[id] h[id] = true } id @@ -1037,7 +1037,7 @@ class Resolv # A representation of a DNS name. class Name - + ## # Creates a new DNS name from +arg+. +arg+ can be: # @@ -1460,11 +1460,11 @@ class Resolv class Query def encode_rdata(msg) # :nodoc: - raise EncodeError.new("#{self.class} is query.") + raise EncodeError.new("#{self.class} is query.") end def self.decode_rdata(msg) # :nodoc: - raise DecodeError.new("#{self.class} is query.") + raise DecodeError.new("#{self.class} is query.") end end @@ -1939,7 +1939,7 @@ class Resolv def initialize(address) @address = IPv6.create(address) end - + ## # The Resolv::IPv6 address for this AAAA. @@ -1956,7 +1956,7 @@ class Resolv ## # SRV resource record defined in RFC 2782 - # + # # These records identify the hostname and port that a service is # available at. diff --git a/lib/time.rb b/lib/time.rb index 96f3945320..85c715b80b 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -1,37 +1,37 @@ # # == Introduction -# +# # This library extends the Time class: # * conversion between date string and time object. # * date-time defined by RFC 2822 # * HTTP-date defined by RFC 2616 # * dateTime defined by XML Schema Part 2: Datatypes (ISO 8601) # * various formats handled by Date._parse (string to time only) -# +# # == Design Issues -# +# # === Specialized interface -# +# # This library provides methods dedicated to special purposes: # * RFC 2822, RFC 2616 and XML Schema. # * They makes usual life easier. -# +# # === Doesn't depend on strftime -# +# # This library doesn't use +strftime+. Especially #rfc2822 doesn't depend # on +strftime+ because: -# +# # * %a and %b are locale sensitive -# +# # Since they are locale sensitive, they may be replaced to # invalid weekday/month name in some locales. # Since ruby-1.6 doesn't invoke setlocale by default, # the problem doesn't arise until some external library invokes setlocale. # Ruby/GTK is the example of such library. -# +# # * %z is not portable -# +# # %z is required to generate zone in date-time of RFC 2822 # but it is not portable. # @@ -61,9 +61,9 @@ class Time 'PST' => -8, 'PDT' => -7, # Following definition of military zones is original one. # See RFC 1123 and RFC 2822 for the error in RFC 822. - 'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4, 'E' => +5, 'F' => +6, + 'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4, 'E' => +5, 'F' => +6, 'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10, 'L' => +11, 'M' => +12, - 'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4, 'R' => -5, 'S' => -6, + 'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4, 'R' => -5, 'S' => -6, 'T' => -7, 'U' => -8, 'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12, } def zone_offset(zone, year=self.now.year) @@ -436,8 +436,8 @@ class Time # # Returns a string which represents the time as rfc1123-date of HTTP-date - # defined by RFC 2616: - # + # defined by RFC 2616: + # # day-of-week, DD month-name CCYY hh:mm:ss GMT # # Note that the result is always UTC (GMT). @@ -768,21 +768,21 @@ if __FILE__ == $0 def test_rfc2822_leap_second t = Time.utc(1998,12,31,23,59,59) assert_equal(t, Time.rfc2822("Thu, 31 Dec 1998 23:59:59 UTC")) - assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 23:59:59 -0000"));t.localtime + assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 23:59:59 -0000"));t.localtime assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 08:59:59 +0900")) assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 00:59:59 +0100")) assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 23:59:59 +0000")) - assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 22:59:59 -0100"));t.utc + assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 22:59:59 -0100"));t.utc t += 1 assert_equal(t, Time.rfc2822("Thu, 31 Dec 1998 23:59:60 UTC")) - assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 23:59:60 -0000"));t.localtime + assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 23:59:60 -0000"));t.localtime assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 08:59:60 +0900")) assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 00:59:60 +0100")) assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 23:59:60 +0000")) - assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 22:59:60 -0100"));t.utc + assert_equal(t, Time.rfc2822("Fri, 31 Dec 1998 22:59:60 -0100"));t.utc t += 1 if t.sec == 60 assert_equal(t, Time.rfc2822("Thu, 1 Jan 1999 00:00:00 UTC")) - assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 00:00:00 -0000"));t.localtime + assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 00:00:00 -0000"));t.localtime assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 09:00:00 +0900")) assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 01:00:00 +0100")) assert_equal(t, Time.rfc2822("Fri, 1 Jan 1999 00:00:00 +0000")) diff --git a/lib/tsort.rb b/lib/tsort.rb index a014e7f6c2..9fc4feadcd 100644 --- a/lib/tsort.rb +++ b/lib/tsort.rb @@ -32,7 +32,7 @@ # array using the user-supplied block. # # require 'tsort' -# +# # class Hash # include TSort # alias tsort_each_node each_key @@ -40,10 +40,10 @@ # fetch(node).each(&block) # end # end -# +# # {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort # #=> [3, 2, 1, 4] -# +# # {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components # #=> [[4], [2, 3], [1]] # @@ -52,19 +52,19 @@ # A very simple `make' like tool can be implemented as follows: # # require 'tsort' -# +# # class Make # def initialize # @dep = {} # @dep.default = [] # end -# +# # def rule(outputs, inputs=[], &block) # triple = [outputs, inputs, block] # outputs.each {|f| @dep[f] = [triple]} # @dep[triple] = inputs # end -# +# # def build(target) # each_strongly_connected_component_from(target) {|ns| # if ns.length != 1 @@ -88,18 +88,18 @@ # end # } # end -# +# # def tsort_each_child(node, &block) # @dep[node].each(&block) # end # include TSort # end -# +# # def command(arg) # print arg, "\n" # system arg # end -# +# # m = Make.new # m.rule(%w[t1]) { command 'date > t1' } # m.rule(%w[t2]) { command 'date > t2' } @@ -189,7 +189,7 @@ module TSort end # - # Iterates over strongly connected component in the subgraph reachable from + # Iterates over strongly connected component in the subgraph reachable from # _node_. # # Return value is unspecified. diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 98dda2a350..9a6a06894a 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -227,7 +227,7 @@ module URI end def unescape(str, escaped = @regexp[:ESCAPED]) - str.gsub(escaped) { [$&.hex].pack('U') } + str.gsub(escaped) { [$&[1, 2].hex].pack('U') } end @@to_s = Kernel.instance_method(:to_s) |