Index: gserver.rb =================================================================== --- gserver.rb (revision 2900) +++ gserver.rb (working copy) @@ -12,21 +12,23 @@ require "thread" # -# +GServer+ implements a generic server, featuring thread pool management, simple logging, and -# multi-server management. See xmlrpc/httpserver.rb in the Ruby standard library for -# an example of +GServer+ in action. +# GServer implements a generic server, featuring thread pool management, +# simple logging, and multi-server management. See HttpServer in +# xmlrpc/httpserver.rb in the Ruby standard library for an example of +# GServer in action. # -# Any kind of application-level server can be implemented using this class. It accepts -# multiple simultaneous connections from clients, up to an optional maximum number. Several -# _services_ (i.e. one service per TCP port) can be run simultaneously, and stopped at any time -# through the class method GServer.stop(port). All the threading issues are handled, -# saving you the effort. All events are optionally logged, but you can provide your own event -# handlers if you wish. +# Any kind of application-level server can be implemented using this class. +# It accepts multiple simultaneous connections from clients, up to an optional +# maximum number. Several _services_ (i.e. one service per TCP port) can be +# run simultaneously, and stopped at any time through the class method +# GServer.stop(port). All the threading issues are handled, saving +# you the effort. All events are optionally logged, but you can provide your +# own event handlers if you wish. # # === Example # -# Using +GServer+ is simple. Below we implement a simple time server, run it, query it, and -# shut it down. Try this code in +irb+: +# Using GServer is simple. Below we implement a simple time server, run it, +# query it, and shut it down. Try this code in +irb+: # # require 'gserver' # @@ -60,14 +62,16 @@ # GServer.stop(10001) # # or, of course, "server.stop". # -# All the business of accepting connections and exception handling is taken care of. All we -# have to do is implement the method that actually serves the client. +# All the business of accepting connections and exception handling is taken +# care of. All we have to do is implement the method that actually serves the +# client. # # === Advanced # -# As the example above shows, the way to use +GServer+ is to subclass it to create a specific -# server, overriding the +serve+ method. You can override other methods as well if you wish, -# perhaps to collect statistics, or emit more detailed logging. +# As the example above shows, the way to use GServer is to subclass it to +# create a specific server, overriding the +serve+ method. You can override +# other methods as well if you wish, perhaps to collect statistics, or emit +# more detailed logging. # # connecting # disconnecting @@ -76,8 +80,8 @@ # # The above methods are only called if auditing is enabled. # -# You can also override +log+ and +error+ if, for example, you wish to use a more sophisticated -# logging system. +# You can also override +log+ and +error+ if, for example, you wish to use a +# more sophisticated logging system. # class GServer Index: mutex_m.rb =================================================================== --- mutex_m.rb (revision 2900) +++ mutex_m.rb (working copy) @@ -1,4 +1,4 @@ -# +#-- # mutex_m.rb - # $Release Version: 3.0$ # $Revision$ @@ -7,22 +7,26 @@ # by Keiju ISHITSUKA(keiju@ishitsuka.com) # modified by matz # patched by akira yamada +#++ # -# -- -# Usage: -# require "mutex_m.rb" -# obj = Object.new -# obj.extend Mutex_m -# ... -# extended object can be handled like Mutex -# or -# class Foo -# include Mutex_m -# ... -# end -# obj = Foo.new -# this obj can be handled like Mutex +# == Usage # +# Extend an object and use it like a Mutex object: +# +# require "mutex_m.rb" +# obj = Object.new +# obj.extend Mutex_m +# # ... +# +# Or, include Mutex_m in a class to have its instances behave like a Mutex +# object: +# +# class Foo +# include Mutex_m +# # ... +# end +# +# obj = Foo.new module Mutex_m def Mutex_m.define_aliases(cl) Index: mailread.rb =================================================================== --- mailread.rb (revision 2900) +++ mailread.rb (working copy) @@ -1,5 +1,14 @@ +# The Mail class represents an internet mail message (as per RFC822, RFC2822) +# with headers and a body. class Mail + # Create a new Mail where +f+ is either a stream which responds to gets(), + # or a path to a file. If +f+ is a path it will be opened. + # + # The whole message is read so it can be made available through the #header, + # #[] and #body methods. + # + # The "From " line is ignored if the mail is in mbox format. def initialize(f) unless defined? f.gets f = open(f, "r") @@ -34,14 +43,19 @@ end end + # Return the headers as a Hash. def header return @header end + # Return the message body as an Array of lines def body return @body end + # Return the header corresponding to +field+. + # + # Matching is case-insensitive. def [](field) @header[field.capitalize] end Index: open3.rb =================================================================== --- open3.rb (revision 2900) +++ open3.rb (working copy) @@ -1,17 +1,48 @@ -# open3.rb: Spawn a program like popen, but with stderr, too. You might also -# want to use this if you want to bypass the shell. (By passing multiple args, -# which IO#popen does not allow) # -# Usage: -# require "open3" +# = open3.rb: Popen, but with stderr, too # -# stdin, stdout, stderr = Open3.popen3('nroff -man') -# or -# include Open3 -# stdin, stdout, stderr = popen3('nroff -man') +# Author:: Yukihiro Matsumoto +# Documentation:: Konrad Meyer +# +# Open3 gives you access to stdin, stdout, and stderr when running other +# programs. +# +# +# Open3 grants you access to stdin, stdout, and stderr when running another +# program. Example: +# +# require "open3" +# include Open3 +# +# stdin, stdout, stderr = popen3('nroff -man') +# +# Open3.popen3 can also take a block which will receive stdin, stdout and +# stderr as parameters. This ensures stdin, stdout and stderr are closed +# once the block exits. Example: +# +# require "open3" +# +# Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... } +# + module Open3 - #[stdin, stdout, stderr] = popen3(command); + # + # Open stdin, stdout, and stderr streams and start external executable. + # Non-block form: + # + # require 'open3' + # + # [stdin, stdout, stderr] = Open3.popen3(cmd) + # + # Block form: + # + # require 'open3' + # + # Open3.popen3(cmd) { |stdin, stdout, stderr| ... } + # + # The parameter +cmd+ is passed directly to Kernel#exec. + # def popen3(*cmd) pw = IO::pipe # pipe[0] for read, pipe[1] for write pr = IO::pipe Index: jcode.rb =================================================================== --- jcode.rb (revision 2900) +++ jcode.rb (working copy) @@ -77,7 +77,7 @@ def succ! reg = end_regexp - if self =~ reg + if $KCODE != 'NONE' && self =~ reg succ_table = SUCC[$KCODE[0,1].downcase] begin self[-1] += succ_table[self[-1]] @@ -175,7 +175,7 @@ def tr_s!(from, to) return self.delete!(from) if to.length == 0 - pattern = SqueezePatternCache[from] ||= /([#{_regex_quote(from)}])\1+/ + pattern = SqueezePatternCache[from] ||= /([#{_regex_quote(from)}])\1*/ if from[0] == ?^ last = /.$/.match(to)[0] self.gsub!(pattern, last) Index: getopts.rb =================================================================== --- getopts.rb (revision 2900) +++ getopts.rb (working copy) @@ -19,6 +19,7 @@ $RCS_ID=%q$Header$ +# getopts is obsolete. Use GetoptLong. def getopts(single_options, *options) boolopts = {} Index: forwardable.rb =================================================================== --- forwardable.rb (revision 2900) +++ forwardable.rb (working copy) @@ -92,7 +92,7 @@ # array, like this: # # class RecordCollection -# extends Forwardable +# extend Forwardable # def_delegator :@records, :[], :record_number # end # @@ -100,7 +100,7 @@ # all of which delegate to @records, this is how you can do it: # # class RecordCollection -# # extends Forwardable, but we did that above +# # extend Forwardable, but we did that above # def_delegators :@records, :size, :<<, :map # end # Index: .document =================================================================== --- .document (revision 0) +++ .document (revision 0) @@ -0,0 +1,103 @@ +# We only run RDoc on the top-level files in here: we skip +# all the helper stuff in sub-directories + +# Eventually, we hope to see... +# *.rb + +# But for now + +English.rb +Env.rb +abbrev.rb +base64.rb +benchmark.rb +cgi +cgi.rb +cgi-lib.rb +complex.rb +csv.rb +date +date.rb +date2.rb +debug.rb +delegate.rb +drb +drb.rb +erb.rb +eregex.rb +fileutils.rb +finalize.rb +find.rb +forwardable.rb +ftools.rb +generator.rb +getoptlong.rb +getopts.rb +gserver.rb +importenv.rb +ipaddr.rb +irb.rb +jcode.rb +logger.rb +mailread.rb +mathn.rb +matrix.rb +mkmf.rb +monitor.rb +mutex_m.rb +net +observer.rb +open-uri.rb +open3.rb +optparse +optparse.rb +ostruct.rb +parsearg.rb +parsedate.rb +pathname.rb +ping.rb +pp.rb +prettyprint.rb +profile.rb +profiler.rb +pstore.rb +racc +rational.rb +rdoc +readbytes.rb +resolv-replace.rb +resolv.rb +rexml +rinda +rss +rss.rb +rubyunit.rb +runit +scanf.rb +set.rb +shell +shell.rb +shellwords.rb +singleton.rb +soap +sync.rb +tempfile.rb +test +thread.rb +thwait.rb +time.rb +timeout.rb +tmpdir.rb +tracer.rb +tsort.rb +un.rb +uri +uri.rb +weakref.rb +webrick +webrick.rb +wsdl +xmlrpc +xsd +yaml +yaml.rb Index: finalize.rb =================================================================== --- finalize.rb (revision 2900) +++ finalize.rb (working copy) @@ -1,44 +1,51 @@ -# +#-- # finalizer.rb - # $Release Version: 0.3$ # $Revision$ # $Date$ # by Keiju ISHITSUKA +#++ # -# -- +# Usage: # -# Usage: -# +# add dependency R_method(obj, dependant) # add(obj, dependant, method = :finalize, *opt) # add_dependency(obj, dependant, method = :finalize, *opt) -# add dependency R_method(obj, dependant) # +# delete dependency R_method(obj, dependant) # delete(obj_or_id, dependant, method = :finalize) # delete_dependency(obj_or_id, dependant, method = :finalize) -# delete dependency R_method(obj, dependant) +# +# delete dependency R_*(obj, dependant) # delete_all_dependency(obj_or_id, dependant) -# delete dependency R_*(obj, dependant) +# +# delete dependency R_method(*, dependant) # delete_by_dependant(dependant, method = :finalize) -# delete dependency R_method(*, dependant) +# +# delete dependency R_*(*, dependant) # delete_all_by_dependant(dependant) -# delete dependency R_*(*, dependant) +# +# delete all dependency R_*(*, *) # delete_all -# delete all dependency R_*(*, *) # +# finalize the dependant connected by dependency R_method(obj, dependtant). # finalize(obj_or_id, dependant, method = :finalize) # finalize_dependency(obj_or_id, dependant, method = :finalize) -# finalize the dependant connected by dependency R_method(obj, dependtant). +# +# finalize all dependants connected by dependency R_*(obj, dependtant). # finalize_all_dependency(obj_or_id, dependant) -# finalize all dependants connected by dependency R_*(obj, dependtant). +# +# finalize the dependant connected by dependency R_method(*, dependtant). # finalize_by_dependant(dependant, method = :finalize) -# finalize the dependant connected by dependency R_method(*, dependtant). +# +# finalize all dependants connected by dependency R_*(*, dependant). # finalize_all_by_dependant(dependant) -# finalize all dependants connected by dependency R_*(*, dependant). +# +# finalize all dependency registered to the Finalizer. # finalize_all -# finalize all dependency registered to the Finalizer. # +# stop invoking Finalizer on GC. # safe{..} -# stop invoking Finalizer on GC. # module Finalizer Index: xmlrpc/create.rb =================================================================== --- xmlrpc/create.rb (revision 2900) +++ xmlrpc/create.rb (working copy) @@ -241,12 +241,7 @@ @writer.ele("data", *a) ) - when Date - t = param - @writer.tag("dateTime.iso8601", - format("%.4d%02d%02dT00:00:00", t.year, t.month, t.day)) - - when Time + when Time, Date @writer.tag("dateTime.iso8601", param.strftime("%Y%m%dT%H:%M:%S")) when XMLRPC::DateTime Index: xmlrpc/README.txt =================================================================== --- xmlrpc/README.txt (revision 0) +++ xmlrpc/README.txt (revision 0) @@ -0,0 +1,31 @@ += XMLRPC for Ruby, Standard Library Documentation + +== Overview + +XMLRPC is a lightweight protocol that enables remote procedure calls over +HTTP. It is defined at http://www.xmlrpc.com. + +XMLRPC allows you to create simple distributed computing solutions that span +computer languages. Its distinctive feature is its simplicity compared to +other approaches like SOAP and CORBA. + +The Ruby standard library package 'xmlrpc' enables you to create a server that +implements remote procedures and a client that calls them. Very little code +is required to achieve either of these. + +== Example + +Try the following code. It calls a standard demonstration remote procedure. + + require 'xmlrpc/client' + require 'pp' + + server = XMLRPC::Client.new2("http://xmlrpc-c.sourceforge.net/api/sample.php") + result = server.call("sample.sumAndDifference", 5, 3) + pp result + +== Documentation + +See http://www.ntecs.de/projects/xmlrpc4r. There is plenty of detail there to +use the client and implement a server. + Index: prettyprint.rb =================================================================== --- prettyprint.rb (revision 2900) +++ prettyprint.rb (working copy) @@ -1,131 +1,45 @@ # $Id$ -=begin -= PrettyPrint -The class implements 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: -newline object and space generation block for (({PrettyPrint.new})), -optional width argument for (({PrettyPrint#text})), -(({PrettyPrint#breakable})), etc. -There are several candidates to use them: -text formatting using proportional fonts, -multibyte characters which has columns different to number of bytes, -non-string formatting, etc. - -== class methods ---- PrettyPrint.new([output[, maxwidth[, newline]]]) [{|width| ...}] - creates a buffer for pretty printing. - - ((|output|)) is an output target. - If it is not specified, (({''})) is assumed. - It should have a (({<<})) method which accepts - the first argument ((|obj|)) of (({PrettyPrint#text})), - the first argument ((|sep|)) of (({PrettyPrint#breakable})), - the first argument ((|newline|)) of (({PrettyPrint.new})), - and - the result of a given block for (({PrettyPrint.new})). - - ((|maxwidth|)) specifies maximum line length. - If it is not specified, 79 is assumed. - However actual outputs may overflow ((|maxwidth|)) if - long non-breakable texts are provided. - - ((|newline|)) is used for line breaks. - (({"\n"})) is used if it is not specified. - - The block is used to generate spaces. - (({{|width| ' ' * width}})) is used if it is not given. - ---- PrettyPrint.format([output[, maxwidth[, newline[, genspace]]]]) {|q| ...} - is a convenience method which is same as follows: - - begin - q = PrettyPrint.new(output, maxwidth, newline, &genspace) - ... - q.flush - output - end - ---- PrettyPrint.singleline_format([output[, maxwidth[, newline[, genspace]]]]) {|q| ...} - is similar to (({PrettyPrint.format})) but the result has no breaks. - - ((|maxwidth|)), ((|newline|)) and ((|genspace|)) are ignored. - The invocation of (({breakable})) in the block doesn't break a line and - treated as just an invocation of (({text})). - -== methods ---- text(obj[, width]) - adds ((|obj|)) as a text of ((|width|)) columns in width. - - If ((|width|)) is not specified, (({((|obj|)).length})) is used. - ---- breakable([sep[, width]]) - tells "you can break a line here if necessary", and a - ((|width|))-column text ((|sep|)) is inserted if a line is not - broken at the point. - - If ((|sep|)) is not specified, (({" "})) is used. - - If ((|width|)) is not specified, (({((|sep|)).length})) is used. - You will have to specify this when ((|sep|)) is a multibyte - character, for example. - ---- nest(indent) {...} - increases left margin after newline with ((|indent|)) for line breaks added - in the block. - ---- group([indent[, open_obj[, close_obj[, open_width[, close_width]]]]]) {...} - groups line break hints added in the block. - The line break hints are all to be breaked or not. - - If ((|indent|)) is specified, the method call is regarded as nested by - (({nest(((|indent|))) { ... }})). - - If ((|open_obj|)) is specified, (({text open_obj, open_width})) is called - at first. - If ((|close_obj|)) is specified, (({text close_obj, close_width})) is - called at last. - ---- flush - outputs buffered data. - ---- first? - first? is obsoleted at 1.8.2. - - first? is a predicate to test the call is a first call to (({first?})) with - current group. - It is useful to format comma separated values as: - - q.group(1, '[', ']') { - xxx.each {|yyy| - unless q.first? - q.text ',' - q.breakable - end - ... pretty printing yyy ... - } - } - -== Bugs -* Box based formatting? Other (better) model/algorithm? - -== References -Christian Lindig, Strictly Pretty, March 2000, -(()) - -Philip Wadler, A prettier printer, March 1998, -(()) - -== AUTHOR -Tanaka Akira -=end - +# 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: +# * newline object and space generation block for PrettyPrint.new +# * optional width argument for PrettyPrint#text +# * PrettyPrint#breakable +# +# There are several candidate uses: +# * text formatting using proportional fonts +# * multibyte characters which has columns different to number of bytes +# * non-string formatting +# +# == 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 +# 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 @@ -133,12 +47,36 @@ output end + # This is similar to PrettyPrint::format but the result has no breaks. + # + # +maxwidth+, +newline+ and +genspace+ are ignored. + # + # The invocation of +breakable+ in the block doesn't break a line and is + # treated as just an invocation of +text+. + # def PrettyPrint.singleline_format(output='', maxwidth=nil, newline=nil, genspace=nil) q = SingleLine.new(output) yield q output end + # Creates a buffer for pretty printing. + # + # +output+ is an output target. If it is not specified, '' is assumed. It + # should have a << method which accepts the first argument +obj+ of + # PrettyPrint#text, the first argument +sep+ of PrettyPrint#breakable, the + # first argument +newline+ of PrettyPrint.new, and the result of a given + # block for PrettyPrint.new. + # + # +maxwidth+ specifies maximum line length. If it is not specified, 79 is + # assumed. However actual outputs may overflow +maxwidth+ if long + # non-breakable texts are provided. + # + # +newline+ is used for line breaks. "\n" is used if it is not specified. + # + # The block is used to generate spaces. {|width| ' ' * width} is used if it + # is not given. + # def initialize(output='', maxwidth=79, newline="\n", &genspace) @output = output @maxwidth = maxwidth @@ -161,6 +99,23 @@ @group_stack.last end + # first? is a predicate to test the call is a first call to first? with + # current group. + # + # It is useful to format comma separated values as: + # + # q.group(1, '[', ']') { + # xxx.each {|yyy| + # unless q.first? + # q.text ',' + # q.breakable + # end + # ... pretty printing yyy ... + # } + # } + # + # first? is obsoleted in 1.8.2. + # def first? warn "PrettyPrint#first? is obsoleted at 1.8.2." current_group.first? @@ -182,6 +137,10 @@ end end + # This adds +obj+ as a text of +width+ columns in width. + # + # If +width+ is not specified, obj.length is used. + # def text(obj, width=obj.length) if @buffer.empty? @output << obj @@ -202,6 +161,14 @@ group { breakable sep, width } end + # This tells "you can break a line here if necessary", and a +width+\-column + # text +sep+ is inserted if a line is not broken at the point. + # + # If +sep+ is not specified, " " is used. + # + # If +width+ is not specified, +sep.length+ is used. You will have to + # specify this when +sep+ is a multibyte character, for example. + # def breakable(sep=' ', width=sep.length) group = @group_stack.last if group.break? @@ -217,6 +184,16 @@ end end + # Groups line break hints added in the block. The line break hints are all + # to be used or not. + # + # If +indent+ is specified, the method call is regarded as nested by + # nest(indent) { ... }. + # + # If +open_obj+ is specified, text open_obj, open_width is called + # before grouping. If +close_obj+ is specified, text close_obj, + # close_width is called after grouping. + # def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length) text open_obj, open_width group_sub { @@ -241,6 +218,9 @@ end end + # Increases left margin after newline with +indent+ for line breaks added in + # the block. + # def nest(indent) @indent += indent begin @@ -250,6 +230,8 @@ end end + # outputs buffered data. + # def flush @buffer.each {|data| @output_width = data.output(@output, @output_width) Index: ftools.rb =================================================================== --- ftools.rb (revision 2900) +++ ftools.rb (working copy) @@ -11,25 +11,22 @@ # # == Description # -# +ftools+ adds several (class, not instance) methods to the File class, for copying, moving, -# deleting, installing, and comparing files, as well as creating a directory path. See the -# File class for details. +# ftools adds several (class, not instance) methods to the File class, for +# copying, moving, deleting, installing, and comparing files, as well as +# creating a directory path. See the File class for details. # -# +fileutils+ contains all or nearly all the same functionality and more, and is a recommended -# option over +ftools+. +# FileUtils contains all or nearly all the same functionality and more, and +# is a recommended option over ftools # - - -# # When you # # require 'ftools' # -# then the File class aquires some utility methods for copying, moving, and deleting files, and -# more. +# then the File class aquires some utility methods for copying, moving, and +# deleting files, and more. # -# See the method descriptions below, and consider using +fileutils+ as it is more -# comprehensive. +# See the method descriptions below, and consider using FileUtils as it is +# more comprehensive. # class File end @@ -96,8 +93,8 @@ # # Moves a file +from+ to +to+ using #syscopy. If +to+ is a directory, - # copies from +from+ to to/from. If +verbose+ is true, from -> to - # is printed. + # copies from +from+ to to/from. If +verbose+ is true, from -> + # to is printed. # def move(from, to, verbose = false) to = catname(from, to) @@ -127,7 +124,7 @@ alias mv move # - # Returns +true+ iff the contents of files +from+ and +to+ are + # Returns +true+ if and only if the contents of files +from+ and +to+ are # identical. If +verbose+ is +true+, from <=> to is printed. # def compare(from, to, verbose = false) Index: rational.rb =================================================================== --- rational.rb (revision 2900) +++ rational.rb (working copy) @@ -435,39 +435,17 @@ # # The result is positive, no matter the sign of the arguments. # - def gcd(n) - m = self.abs - n = n.abs - - return n if m == 0 - return m if n == 0 - - b = 0 - while n[0] == 0 && m[0] == 0 - b += 1; n >>= 1; m >>= 1 + def gcd(other) + min = self.abs + max = other.abs + while min > 0 + tmp = min + min = max % min + max = tmp end - m >>= 1 while m[0] == 0 - n >>= 1 while n[0] == 0 - while m != n - m, n = n, m if n > m - m -= n; m >>= 1 while m[0] == 0 - end - m << b + max end - def gcd2(int) - a = self.abs - b = int.abs - - a, b = b, a if a < b - - while b != 0 - void, a = a.divmod(b) - a, b = b, a - end - return a - end - # # Returns the lowest common multiple (LCM) of the two arguments # (+self+ and +other+). Index: resolv.rb =================================================================== --- resolv.rb (revision 2900) +++ resolv.rb (working copy) @@ -621,7 +621,7 @@ super() @host = host @port = port - @sock = UDPSocket.new + @sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET) @sock.connect(host, port) @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD @id = -1 Index: tsort.rb =================================================================== --- tsort.rb (revision 2900) +++ tsort.rb (working copy) @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby #-- # tsort.rb - provides a module for topological sorting and strongly connected components. #++ @@ -7,11 +8,12 @@ # TSort implements topological sorting using Tarjan's algorithm for # strongly connected components. # -# TSort is designed to be able to be used with any object which can be interpreted -# as a directed graph. -# TSort requires two methods to interpret an object as a graph: -# tsort_each_node and tsort_each_child: +# TSort is designed to be able to be used with any object which can be +# interpreted as a directed graph. # +# TSort requires two methods to interpret an object as a graph, +# tsort_each_node and tsort_each_child. +# # * tsort_each_node is used to iterate for all nodes over a graph. # * tsort_each_child is used to iterate for child nodes of a given node. # @@ -29,83 +31,83 @@ # method, which fetches the array of child nodes and then iterates over that # array using the user-supplied block. # -# require 'tsort' +# require 'tsort' +# +# class Hash +# include TSort +# alias tsort_each_node each_key +# def tsort_each_child(node, &block) +# 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]] # -# class Hash -# include TSort -# alias tsort_each_node each_key -# def tsort_each_child(node, &block) -# 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]] -# # == A More Realistic Example # # A very simple `make' like tool can be implemented as follows: # -# require 'tsort' +# 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 +# fs = ns.delete_if {|n| Array === n} +# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# end +# n = ns.first +# if Array === n +# outputs, inputs, block = n +# inputs_time = inputs.map {|f| File.mtime f}.max +# begin +# outputs_time = outputs.map {|f| File.mtime f}.min +# rescue Errno::ENOENT +# outputs_time = nil +# end +# if outputs_time == nil || +# inputs_time != nil && outputs_time <= inputs_time +# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i +# block.call +# end +# 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' } +# m.rule(%w[t3]) { command 'date > t3' } +# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } +# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } +# m.build('t5') # -# 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 -# fs = ns.delete_if {|n| Array === n} -# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") -# end -# n = ns.first -# if Array === n -# outputs, inputs, block = n -# inputs_time = inputs.map {|f| File.mtime f}.max -# begin -# outputs_time = outputs.map {|f| File.mtime f}.min -# rescue Errno::ENOENT -# outputs_time = nil -# end -# if outputs_time == nil || -# inputs_time != nil && outputs_time <= inputs_time -# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i -# block.call -# end -# 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' } -# m.rule(%w[t3]) { command 'date > t3' } -# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } -# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } -# m.build('t5') -# # == Bugs # # * 'tsort.rb' is wrong name because this library uses Index: pp.rb =================================================================== --- pp.rb (revision 2900) +++ pp.rb (working copy) @@ -46,8 +46,16 @@ require 'prettyprint' module Kernel + # returns a pretty printed object as a string. + def pretty_inspect + PP.pp(self, '') + end + private - def pp(*objs) + # prints arguments in pretty form. + # + # pp returns nil. + def pp(*objs) # :doc: objs.each {|obj| PP.pp(obj) } @@ -83,6 +91,12 @@ out end + # :stopdoc: + def PP.mcall(obj, mod, meth, *args, &block) + mod.instance_method(meth).bind(obj).call(*args, &block) + end + # :startdoc: + @sharing_detection = false class << self # Returns the sharing detection flag as a boolean value. @@ -276,7 +290,7 @@ # implement #pretty_print, or a RuntimeError will be raised. def pretty_print_inspect if /\(PP::ObjectMixin\)#/ =~ method(:pretty_print).inspect - raise "pretty_print is not overridden." + raise "pretty_print is not overridden for #{self.class}" end PP.singleline_pp(self, '') end @@ -315,8 +329,8 @@ class Struct def pretty_print(q) - q.group(1, '#') { - q.seplist(self.members, lambda { q.text "," }) {|member| + q.group(1, '#') { + q.seplist(PP.mcall(self, Struct, :members), lambda { q.text "," }) {|member| q.breakable q.text member.to_s q.text '=' @@ -329,7 +343,7 @@ end def pretty_print_cycle(q) - q.text sprintf("#", self.class.name) + q.text sprintf("#", PP.mcall(self, Kernel, :class).name) end end @@ -468,6 +482,12 @@ def test_list0123_11 assert_equal("[0,\n 1,\n 2,\n 3]\n", PP.pp([0,1,2,3], '', 11)) end + + OverriddenStruct = Struct.new("OverriddenStruct", :members, :class) + def test_struct_override_members # [ruby-core:7865] + a = OverriddenStruct.new(1,2) + assert_equal("#\n", PP.pp(a, '')) + end end class HasInspect Index: weakref.rb =================================================================== --- weakref.rb (revision 2900) +++ weakref.rb (working copy) @@ -1,6 +1,13 @@ -# Weak Reference class that does not bother GCing. +require "delegate" + +# WeakRef is a class to represent a reference to an object that is not seen by +# the tracing phase of the garbage collector. This allows the referenced +# object to be garbage collected as if nothing is referring to it. Because +# WeakRef delegates method calls to the referenced object, it may be used in +# place of that object, i.e. it is of the same duck type. # # Usage: +# # foo = Object.new # foo = Object.new # p foo.to_s # original's class @@ -8,11 +15,9 @@ # p foo.to_s # should be same class # ObjectSpace.garbage_collect # p foo.to_s # should raise exception (recycled) - -require "delegate" - class WeakRef 0 host, port = @addr[2], @addr[1] else Index: webrick/httpserver.rb =================================================================== --- webrick/httpserver.rb (revision 2900) +++ webrick/httpserver.rb (working copy) @@ -120,7 +120,7 @@ end def unmount(dir) - @logger.debug(sprintf("unmount %s.", inspect, dir)) + @logger.debug(sprintf("unmount %s.", dir)) @mount_tab.delete(dir) end alias umount unmount Index: webrick/cookie.rb =================================================================== --- webrick/cookie.rb (revision 2900) +++ webrick/cookie.rb (working copy) @@ -100,5 +100,11 @@ } return cookie end + + def self.parse_set_cookies(str) + return str.split(/,(?=[^;,]*=)|,$/).collect{|c| + parse_set_cookie(c) + } + end end end Index: rexml/parsers/xpathparser.rb =================================================================== --- rexml/parsers/xpathparser.rb (revision 2900) +++ rexml/parsers/xpathparser.rb (working copy) @@ -596,11 +596,17 @@ parsed << :function parsed << fname path = FunctionCall(path, parsed) - when LITERAL, NUMBER + when NUMBER #puts "LITERAL or NUMBER: #$1" varname = $1.nil? ? $2 : $1 path = $' parsed << :literal + parsed << (varname.include?('.') ? varname.to_f : varname.to_i) + when LITERAL + #puts "LITERAL or NUMBER: #$1" + varname = $1.nil? ? $2 : $1 + path = $' + parsed << :literal parsed << varname when /^\(/ #/ path, contents = get_group(path) Index: rexml/parsers/baseparser.rb =================================================================== --- rexml/parsers/baseparser.rb (revision 2900) +++ rexml/parsers/baseparser.rb (working copy) @@ -42,15 +42,15 @@ CDATA_END = /^\s*\]\s*>/um CDATA_PATTERN = //um XMLDECL_START = /\A<\?xml\s/u; - XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um + XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>/um INSTRUCTION_START = /\A<\?/u INSTRUCTION_PATTERN = /<\?(.*?)(\s+.*?)?\?>/um TAG_MATCH = /^<((?>#{NAME_STR}))\s*((?>\s+#{NAME_STR}\s*=\s*(["']).*?\3)*)\s*(\/)?>/um CLOSE_MATCH = /^\s*<\/(#{NAME_STR})\s*>/um VERSION = /\bversion\s*=\s*["'](.*?)['"]/um - ENCODING = /\bencoding=["'](.*?)['"]/um - STANDALONE = /\bstandalone=["'](.*?)['"]/um + ENCODING = /\bencoding\s*=\s*["'](.*?)['"]/um + STANDALONE = /\bstandalone\s*=\s["'](.*?)['"]/um ENTITY_START = /^\s*/um NOTATIONDECL_START = /^\s*/um - SYSTEM = /^\s*/um + PUBLIC = /^\s*/um + SYSTEM = /^\s*/um TEXT_PATTERN = /\A([^<]*)/um @@ -96,6 +96,13 @@ "apos" => [/'/, "'", "'", /'/] } + + ###################################################################### + # These are patterns to identify common markup errors, to make the + # error messages more informative. + ###################################################################### + MISSING_ATTRIBUTE_QUOTES = /^<#{NAME_STR}\s+#{NAME_STR}\s*=\s*[^"']/um + def initialize( source ) self.stream = source end @@ -120,18 +127,7 @@ attr_reader :source def stream=( source ) - if source.kind_of? String - @source = Source.new(source) - elsif source.kind_of? IO - @source = IOSource.new(source) - elsif source.kind_of? Source - @source = source - elsif defined? StringIO and source.kind_of? StringIO - @source = IOSource.new(source) - else - raise "#{source.class} is not a valid input stream. It must be \n"+ - "either a String, IO, StringIO or Source." - end + @source = SourceFactory.create_from( source ) @closed = nil @document_status = nil @tags = [] @@ -139,10 +135,17 @@ @entities = [] end + def position + if @source.respond_to? :position + @source.position + else + # FIXME + 0 + end + end + # Returns true if there are no more events def empty? - #puts "@source.empty? = #{@source.empty?}" - #puts "@stack.empty? = #{@stack.empty?}" return (@source.empty? and @stack.empty?) end @@ -186,14 +189,17 @@ return [ :end_document ] if empty? return @stack.shift if @stack.size > 0 @source.read if @source.buffer.size<2 + #STDERR.puts "BUFFER = #{@source.buffer.inspect}" if @document_status == nil - @source.consume( /^\s*/um ) - word = @source.match( /(<[^>]*)>/um ) + #@source.consume( /^\s*/um ) + word = @source.match( /^((?:\s+)|(?:<[^>]*>))/um ) word = word[1] unless word.nil? + #STDERR.puts "WORD = #{word.inspect}" case word when COMMENT_START return [ :comment, @source.match( COMMENT_PATTERN, true )[1] ] when XMLDECL_START + #STDERR.puts "XMLDECL" results = @source.match( XMLDECL_PATTERN, true )[1] version = VERSION.match( results ) version = version[1] unless version.nil? @@ -202,7 +208,7 @@ @source.encoding = encoding standalone = STANDALONE.match(results) standalone = standalone[1] unless standalone.nil? - return [ :xmldecl, version, encoding, standalone] + return [ :xmldecl, version, encoding, standalone ] when INSTRUCTION_START return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ] when DOCTYPE_START @@ -225,6 +231,7 @@ @document_status = :in_doctype end return args + when /^\s+/ else @document_status = :after_doctype @source.read if @source.buffer.size<2 @@ -288,12 +295,14 @@ md = nil if @source.match( PUBLIC ) md = @source.match( PUBLIC, true ) + vals = [md[1],md[2],md[4],md[6]] elsif @source.match( SYSTEM ) md = @source.match( SYSTEM, true ) + vals = [md[1],md[2],nil,md[4]] else raise REXML::ParseException.new( "error parsing notation: no matching pattern", @source ) end - return [ :notationdecl, md[1], md[2], md[3] ] + return [ :notationdecl, *vals ] when CDATA_END @document_status = :after_doctype @source.match( CDATA_END, true ) @@ -312,7 +321,7 @@ return [ :end_element, last_tag ] elsif @source.buffer[1] == ?! md = @source.match(/\A(\s*[^>]*>)/um) - #puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}" + #STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}" raise REXML::ParseException.new("Malformed node", @source) unless md if md[0][2] == ?- md = @source.match( COMMENT_PATTERN, true ) @@ -331,7 +340,11 @@ else # Get the next tag md = @source.match(TAG_MATCH, true) - raise REXML::ParseException.new("malformed XML: missing tag start", @source) unless md + unless md + # Check for missing attribute quotes + raise REXML::ParseException.new("missing attribute quote", @source) if @source.match(MISSING_ATTRIBUTE_QUOTES ) + raise REXML::ParseException.new("malformed XML: missing tag start", @source) + end attrs = [] if md[2].size > 0 attrs = md[2].scan( ATTRIBUTE_PATTERN ) @@ -350,10 +363,9 @@ else md = @source.match( TEXT_PATTERN, true ) if md[0].length == 0 - #puts "EMPTY = #{empty?}" - #puts "BUFFER = \"#{@source.buffer}\"" @source.match( /(\s+)/, true ) end + #STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0 #return [ :text, "" ] if md[0].length == 0 # unnormalized = Text::unnormalize( md[1], self ) # return PullEvent.new( :text, md[1], unnormalized ) Index: rexml/parsers/streamparser.rb =================================================================== --- rexml/parsers/streamparser.rb (revision 2900) +++ rexml/parsers/streamparser.rb (working copy) @@ -1,42 +1,46 @@ module REXML - module Parsers - class StreamParser - def initialize source, listener - @listener = listener - @parser = BaseParser.new( source ) - end - + module Parsers + class StreamParser + def initialize source, listener + @listener = listener + @parser = BaseParser.new( source ) + end + def add_listener( listener ) @parser.add_listener( listener ) end - - def parse - # entity string - while true - event = @parser.pull - case event[0] - when :end_document - return - when :start_element - attrs = event[2].each do |n, v| - event[2][n] = @parser.unnormalize( v ) - end - @listener.tag_start( event[1], attrs ) - when :end_element - @listener.tag_end( event[1] ) - when :text - normalized = @parser.unnormalize( event[1] ) - @listener.text( normalized ) - when :processing_instruction - @listener.instruction( *event[1,2] ) + + def parse + # entity string + while true + event = @parser.pull + case event[0] + when :end_document + return + when :start_element + attrs = event[2].each do |n, v| + event[2][n] = @parser.unnormalize( v ) + end + @listener.tag_start( event[1], attrs ) + when :end_element + @listener.tag_end( event[1] ) + when :text + normalized = @parser.unnormalize( event[1] ) + @listener.text( normalized ) + when :processing_instruction + @listener.instruction( *event[1,2] ) when :start_doctype @listener.doctype( *event[1..-1] ) - when :comment, :attlistdecl, :notationdecl, :elementdecl, - :entitydecl, :cdata, :xmldecl, :attlistdecl - @listener.send( event[0].to_s, *event[1..-1] ) - end - end - end - end - end + when :end_doctype + # FIXME: remove this condition for milestone:3.2 + @listener.doctype_end if @listener.respond_to? :doctype_end + when :comment, :attlistdecl, :cdata, :xmldecl, :elementdecl + @listener.send( event[0].to_s, *event[1..-1] ) + when :entitydecl, :notationdecl + @listener.send( event[0].to_s, event[1..-1] ) + end + end + end + end + end end Index: rexml/parsers/pullparser.rb =================================================================== --- rexml/parsers/pullparser.rb (revision 2900) +++ rexml/parsers/pullparser.rb (working copy) @@ -32,6 +32,7 @@ def_delegators( :@parser, :has_next? ) def_delegators( :@parser, :entity ) def_delegators( :@parser, :empty? ) + def_delegators( :@parser, :source ) def initialize stream @entities = {} Index: rexml/parsers/sax2parser.rb =================================================================== --- rexml/parsers/sax2parser.rb (revision 2900) +++ rexml/parsers/sax2parser.rb (working copy) @@ -16,6 +16,10 @@ @tag_stack = [] @entities = {} end + + def source + @parser.source + end def add_listener( listener ) @parser.add_listener( listener ) @@ -167,7 +171,7 @@ :elementdecl, :cdata, :notationdecl, :xmldecl handle( *event ) end - handle( :progress, @parser.source.position ) + handle( :progress, @parser.position ) end end Index: rexml/parsers/treeparser.rb =================================================================== --- rexml/parsers/treeparser.rb (revision 2900) +++ rexml/parsers/treeparser.rb (working copy) @@ -19,8 +19,13 @@ begin while true event = @parser.pull + #STDERR.puts "TREEPARSER GOT #{event.inspect}" case event[0] when :end_document + unless tag_stack.empty? + #raise ParseException.new("No close tag for #{tag_stack.inspect}") + raise ParseException.new("No close tag for #{@build_context.xpath}") + end return when :start_element tag_stack.push(event[1]) @@ -35,10 +40,10 @@ @build_context[-1] << event[1] else @build_context.add( - Text.new( event[1], @build_context.whitespace, nil, true ) + Text.new(event[1], @build_context.whitespace, nil, true) ) unless ( - event[1].strip.size==0 and - @build_context.ignore_whitespace_nodes + @build_context.ignore_whitespace_nodes and + event[1].strip.size==0 ) end end Index: rexml/document.rb =================================================================== --- rexml/document.rb (revision 2900) +++ rexml/document.rb (working copy) @@ -70,11 +70,23 @@ if child.kind_of? XMLDecl @children.unshift child elsif child.kind_of? DocType - if @children[0].kind_of? XMLDecl - @children[1,0] = child - else - @children.unshift child - end + # Find first Element or DocType node and insert the decl right + # before it. If there is no such node, just insert the child at the + # end. If there is a child and it is an DocType, then replace it. + insert_before_index = 0 + @children.find { |x| + insert_before_index += 1 + x.kind_of?(Element) || x.kind_of?(DocType) + } + if @children[ insert_before_index ] # Not null = not end of list + if @children[ insert_before_index ].kind_of DocType + @children[ insert_before_index ] = child + else + @children[ index_before_index-1, 0 ] = child + end + else # Insert at end of list + @children[insert_before_index] = child + end child.parent = self else rv = super Index: rexml/xpath.rb =================================================================== --- rexml/xpath.rb (revision 2900) +++ rexml/xpath.rb (working copy) @@ -19,9 +19,9 @@ # XPath.first( node ) # XPath.first( doc, "//b"} ) # XPath.first( node, "a/x:b", { "x"=>"http://doofus" } ) - def XPath::first element, path=nil, namespaces={}, variables={} - raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.kind_of? Hash - raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of? Hash + def XPath::first element, path=nil, namespaces=nil, variables={} + raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash) + raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash) parser = XPathParser.new parser.namespaces = namespaces parser.variables = variables @@ -42,9 +42,9 @@ # XPath.each( node ) { |el| ... } # XPath.each( node, '/*[@attr='v']' ) { |el| ... } # XPath.each( node, 'ancestor::x' ) { |el| ... } - def XPath::each element, path=nil, namespaces={}, variables={}, &block - raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.kind_of? Hash - raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of? Hash + def XPath::each element, path=nil, namespaces=nil, variables={}, &block + raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash) + raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash) parser = XPathParser.new parser.namespaces = namespaces parser.variables = variables @@ -54,7 +54,7 @@ end # Returns an array of nodes matching a given XPath. - def XPath::match element, path=nil, namespaces={}, variables={} + def XPath::match element, path=nil, namespaces=nil, variables={} parser = XPathParser.new parser.namespaces = namespaces parser.variables = variables Index: rexml/xmldecl.rb =================================================================== --- rexml/xmldecl.rb (revision 2900) +++ rexml/xmldecl.rb (working copy) @@ -80,6 +80,11 @@ self.dowrite end + # Only use this if you do not want the XML declaration to be written; + # this object is ignored by the XML writer. Otherwise, instantiate your + # own XMLDecl and add it to the document. + # + # Note that XML 1.1 documents *must* include an XML declaration def XMLDecl.default rv = XMLDecl.new( "1.0" ) rv.nowrite Index: rexml/parent.rb =================================================================== --- rexml/parent.rb (revision 2900) +++ rexml/parent.rb (working copy) @@ -1,165 +1,166 @@ require "rexml/child" module REXML - # A parent has children, and has methods for accessing them. The Parent + # A parent has children, and has methods for accessing them. The Parent # class is never encountered except as the superclass for some other # object. - class Parent < Child - include Enumerable - - # Constructor - # @param parent if supplied, will be set as the parent of this object - def initialize parent=nil - super(parent) - @children = [] - end - - def add( object ) - #puts "PARENT GOTS #{size} CHILDREN" - object.parent = self - @children << object - #puts "PARENT NOW GOTS #{size} CHILDREN" - object - end - - alias :push :add - alias :<< :push - - def unshift( object ) - object.parent = self - @children.unshift object - end - - def delete( object ) - return unless @children.include? object - @children.delete object - object.parent = nil - end - - def each(&block) - @children.each(&block) - end - - def delete_if( &block ) - @children.delete_if(&block) - end - - def delete_at( index ) - @children.delete_at index - end - - def each_index( &block ) - @children.each_index(&block) - end - - # Fetches a child at a given index - # @param index the Integer index of the child to fetch - def []( index ) - @children[index] - end - - alias :each_child :each - - - - # Set an index entry. See Array.[]= - # @param index the index of the element to set - # @param opt either the object to set, or an Integer length - # @param child if opt is an Integer, this is the child to set - # @return the parent (self) - def []=( *args ) - args[-1].parent = self - @children[*args[0..-2]] = args[-1] - end - - # Inserts an child before another child - # @param child1 this is either an xpath or an Element. If an Element, - # child2 will be inserted before child1 in the child list of the parent. - # If an xpath, child2 will be inserted before the first child to match - # the xpath. - # @param child2 the child to insert - # @return the parent (self) - def insert_before( child1, child2 ) - if child1.kind_of? String - child1 = XPath.first( self, child1 ) - child1.parent.insert_before child1, child2 - else - ind = index(child1) - child2.parent.delete(child2) if child2.parent - @children[ind,0] = child2 - child2.parent = self - end - self - end - - # Inserts an child after another child - # @param child1 this is either an xpath or an Element. If an Element, - # child2 will be inserted after child1 in the child list of the parent. - # If an xpath, child2 will be inserted after the first child to match - # the xpath. - # @param child2 the child to insert - # @return the parent (self) - def insert_after( child1, child2 ) - if child1.kind_of? String - child1 = XPath.first( self, child1 ) - child1.parent.insert_after child1, child2 - else - ind = index(child1)+1 - child2.parent.delete(child2) if child2.parent - @children[ind,0] = child2 - child2.parent = self - end - self - end - - def to_a - @children.dup - end - - # Fetches the index of a given child - # @param child the child to get the index of - # @return the index of the child, or nil if the object is not a child - # of this parent. - def index( child ) - count = -1 - @children.find { |i| count += 1 ; i.hash == child.hash } - count - end - - # @return the number of children of this parent - def size - @children.size - end - - # Replaces one child with another, making sure the nodelist is correct - # @param to_replace the child to replace (must be a Child) - # @param replacement the child to insert into the nodelist (must be a - # Child) - def replace_child( to_replace, replacement ) - ind = @children.index( to_replace ) - to_replace.parent = nil - @children[ind,0] = replacement - replacement.parent = self - end - - # Deeply clones this object. This creates a complete duplicate of this - # Parent, including all descendants. - def deep_clone - cl = clone() - each do |child| - if child.kind_of? Parent - cl << child.deep_clone - else - cl << child.clone - end - end - cl - end - - alias :children :to_a - - def parent? - true - end - end + class Parent < Child + include Enumerable + + # Constructor + # @param parent if supplied, will be set as the parent of this object + def initialize parent=nil + super(parent) + @children = [] + end + + def add( object ) + #puts "PARENT GOTS #{size} CHILDREN" + object.parent = self + @children << object + #puts "PARENT NOW GOTS #{size} CHILDREN" + object + end + + alias :push :add + alias :<< :push + + def unshift( object ) + object.parent = self + @children.unshift object + end + + def delete( object ) + found = false + @children.delete_if {|c| c.equal?(object) and found = true } + object.parent = nil if found + end + + def each(&block) + @children.each(&block) + end + + def delete_if( &block ) + @children.delete_if(&block) + end + + def delete_at( index ) + @children.delete_at index + end + + def each_index( &block ) + @children.each_index(&block) + end + + # Fetches a child at a given index + # @param index the Integer index of the child to fetch + def []( index ) + @children[index] + end + + alias :each_child :each + + + + # Set an index entry. See Array.[]= + # @param index the index of the element to set + # @param opt either the object to set, or an Integer length + # @param child if opt is an Integer, this is the child to set + # @return the parent (self) + def []=( *args ) + args[-1].parent = self + @children[*args[0..-2]] = args[-1] + end + + # Inserts an child before another child + # @param child1 this is either an xpath or an Element. If an Element, + # child2 will be inserted before child1 in the child list of the parent. + # If an xpath, child2 will be inserted before the first child to match + # the xpath. + # @param child2 the child to insert + # @return the parent (self) + def insert_before( child1, child2 ) + if child1.kind_of? String + child1 = XPath.first( self, child1 ) + child1.parent.insert_before child1, child2 + else + ind = index(child1) + child2.parent.delete(child2) if child2.parent + @children[ind,0] = child2 + child2.parent = self + end + self + end + + # Inserts an child after another child + # @param child1 this is either an xpath or an Element. If an Element, + # child2 will be inserted after child1 in the child list of the parent. + # If an xpath, child2 will be inserted after the first child to match + # the xpath. + # @param child2 the child to insert + # @return the parent (self) + def insert_after( child1, child2 ) + if child1.kind_of? String + child1 = XPath.first( self, child1 ) + child1.parent.insert_after child1, child2 + else + ind = index(child1)+1 + child2.parent.delete(child2) if child2.parent + @children[ind,0] = child2 + child2.parent = self + end + self + end + + def to_a + @children.dup + end + + # Fetches the index of a given child + # @param child the child to get the index of + # @return the index of the child, or nil if the object is not a child + # of this parent. + def index( child ) + count = -1 + @children.find { |i| count += 1 ; i.hash == child.hash } + count + end + + # @return the number of children of this parent + def size + @children.size + end + + alias :length :size + + # Replaces one child with another, making sure the nodelist is correct + # @param to_replace the child to replace (must be a Child) + # @param replacement the child to insert into the nodelist (must be a + # Child) + def replace_child( to_replace, replacement ) + @children.map! {|c| c.equal?( to_replace ) ? replacement : c } + to_replace.parent = nil + replacement.parent = self + end + + # Deeply clones this object. This creates a complete duplicate of this + # Parent, including all descendants. + def deep_clone + cl = clone() + each do |child| + if child.kind_of? Parent + cl << child.deep_clone + else + cl << child.clone + end + end + cl + end + + alias :children :to_a + + def parent? + true + end + end end Index: rexml/element.rb =================================================================== --- rexml/element.rb (revision 2900) +++ rexml/element.rb (working copy) @@ -94,7 +94,7 @@ # new_a = d.root.clone # puts new_a # => "" def clone - Element.new self + self.class.new self end # Evaluates to the root node of the document that this element @@ -200,9 +200,9 @@ end def namespaces - namespaces = [] + namespaces = {} namespaces = parent.namespaces if parent - namespaces |= attributes.namespaces + namespaces = namespaces.merge( attributes.namespaces ) return namespaces end @@ -494,13 +494,12 @@ # doc.root.add_element 'c' #-> 'Elliott' # doc.root.text = 'Russell' #-> 'Russell' # doc.root.text = nil #-> '' - def text=( text ) + def text=( text ) if text.kind_of? String text = Text.new( text, whitespace(), nil, raw() ) elsif text and !text.kind_of? Text text = Text.new( text.to_s, whitespace(), nil, raw() ) end - old_text = get_text if text.nil? old_text.remove unless old_text.nil? @@ -557,13 +556,9 @@ ################################################# def attribute( name, namespace=nil ) - prefix = '' - if namespace - prefix = attributes.prefixes.each { |prefix| - return "#{prefix}:" if namespace( prefix ) == namespace - } || '' - end - attributes.get_attribute( "#{prefix}#{name}" ) + prefix = nil + prefix = namespaces.index(namespace) if namespace + attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" ) end # Evaluates to +true+ if this element has any attributes set, false @@ -713,7 +708,7 @@ private def __to_xpath_helper node - rv = node.expanded_name + rv = node.expanded_name.clone if node.parent results = node.parent.find_all {|n| n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name @@ -938,6 +933,29 @@ def each( xpath=nil, &block) XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element } end + + def collect( xpath=nil, &block ) + collection = [] + XPath::each( @element, xpath ) {|e| + collection << yield(e) if e.kind_of?(Element) + } + collection + end + + def inject( xpath=nil, initial=nil, &block ) + first = true + XPath::each( @element, xpath ) {|e| + if (e.kind_of? Element) + if (first and initial == nil) + initial = e + first = false + else + initial = yield( initial, e ) if e.kind_of? Element + end + end + } + initial + end # Returns the number of +Element+ children of the parent object. # doc = Document.new 'seanelliottrussell' @@ -1149,16 +1167,16 @@ end def namespaces - namespaces = [] + namespaces = {} each_attribute do |attribute| - namespaces << attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns' + namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns' end if @element.document and @element.document.doctype expn = @element.expanded_name expn = @element.document.doctype.name if expn.size == 0 @element.document.doctype.attributes_of(expn).each { |attribute| - namespaces << attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns' + namespaces[attribute.name] = attribute.value if attribute.prefix == 'xmlns' or attribute.name == 'xmlns' } end namespaces @@ -1224,5 +1242,20 @@ rv.each{ |attr| attr.remove } return rv end + + # The +get_attribute_ns+ method retrieves a method by its namespace + # and name. Thus it is possible to reliably identify an attribute + # even if an XML processor has changed the prefix. + # + # Method contributed by Henrik Martensson + def get_attribute_ns(namespace, name) + each_attribute() { |attribute| + if name == attribute.name && + namespace == attribute.namespace() + return attribute + end + } + nil + end end end Index: rexml/source.rb =================================================================== --- rexml/source.rb (revision 2900) +++ rexml/source.rb (working copy) @@ -6,13 +6,20 @@ # Generates a Source object # @param arg Either a String, or an IO # @return a Source, or nil if a bad argument was given - def SourceFactory::create_from arg#, slurp=true - if arg.kind_of? String - source = Source.new(arg) - elsif arg.kind_of? IO - source = IOSource.new(arg) - end - source + def SourceFactory::create_from(arg) + if arg.kind_of? String + Source.new(arg) + elsif arg.respond_to? :read and + arg.respond_to? :readline and + arg.respond_to? :nil? and + arg.respond_to? :eof? + IOSource.new(arg) + elsif arg.kind_of? Source + arg + else + raise "#{source.class} is not a valid input stream. It must walk \n"+ + "like either a String, IO, or Source." + end end end @@ -28,16 +35,23 @@ # Constructor # @param arg must be a String, and should be a valid XML document - def initialize(arg) + # @param encoding if non-null, sets the encoding of the source to this + # value, overriding all encoding detection + def initialize(arg, encoding=nil) @orig = @buffer = arg - self.encoding = check_encoding( @buffer ) + if encoding + self.encoding = encoding + else + self.encoding = check_encoding( @buffer ) + end @line = 0 end + # Inherited from Encoding # Overridden to support optimized en/decoding def encoding=(enc) - super + return unless super @line_break = encode( '>' ) if enc != UTF_8 @buffer = decode(@buffer) @@ -117,7 +131,7 @@ #attr_reader :block_size # block_size has been deprecated - def initialize(arg, block_size=500) + def initialize(arg, block_size=500, encoding=nil) @er_source = @source = arg @to_utf = false # Determining the encoding is a deceptively difficult issue to resolve. @@ -127,15 +141,17 @@ # if there is one. If there isn't one, the file MUST be UTF-8, as per # the XML spec. If there is one, we can determine the encoding from # it. + @buffer = "" str = @source.read( 2 ) - if (str[0] == 254 && str[1] == 255) || (str[0] == 255 && str[1] == 254) - @encoding = check_encoding( str ) - @line_break = encode( '>' ) + if encoding + self.encoding = encoding + elsif /\A(?:\xfe\xff|\xff\xfe)/n =~ str + self.encoding = check_encoding( str ) else @line_break = '>' end super str+@source.readline( @line_break ) - end + end def scan(pattern, cons=false) rv = super @@ -152,6 +168,8 @@ str = @source.readline(@line_break) str = decode(str) if @to_utf and str @buffer << str + rescue Iconv::IllegalSequence + raise rescue @source = nil end @@ -199,7 +217,7 @@ end def position - @er_source.pos + @er_source.stat.pipe? ? 0 : @er_source.pos end # @return the current line in the source Index: rexml/streamlistener.rb =================================================================== --- rexml/streamlistener.rb (revision 2900) +++ rexml/streamlistener.rb (working copy) @@ -39,6 +39,9 @@ # @p uri the uri of the doctype, or nil. EG, "bar" def doctype name, pub_sys, long_name, uri end + # Called when the doctype is done + def doctype_end + end # If a doctype includes an ATTLIST declaration, it will cause this # method to be called. The content is the declaration itself, unparsed. # EG, will come to this method as "el Index: rexml/comment.rb =================================================================== --- rexml/comment.rb (revision 2900) +++ rexml/comment.rb (working copy) @@ -2,14 +2,16 @@ module REXML ## - # Represents an XML comment; that is, text between + # Represents an XML comment; that is, text between \ class Comment < Child include Comparable START = "" - attr_accessor :string # The content text + # The content text + attr_accessor :string + ## # Constructor. The first argument can be one of three types: # @param first If String, the contents of this comment are set to the @@ -33,31 +35,19 @@ end # output:: - # Where to write the string + # Where to write the string # indent:: - # An integer. If -1, no indenting will be used; otherwise, the - # indentation will be this number of spaces, and children will be - # indented an additional amount. + # An integer. If -1, no indenting will be used; otherwise, the + # indentation will be this number of spaces, and children will be + # indented an additional amount. # transitive:: - # If transitive is true and indent is >= 0, then the output will be - # pretty-printed in such a way that the added whitespace does not affect - # the absolute *value* of the document -- that is, it leaves the value - # and number of Text nodes in the document unchanged. + # Ignored by this class. The contents of comments are never modified. # ie_hack:: - # Internet Explorer is the worst piece of crap to have ever been - # written, with the possible exception of Windows itself. Since IE is - # unable to parse proper XML, we have to provide a hack to generate XML - # that IE's limited abilities can handle. This hack inserts a space - # before the /> on empty tags. - # + # Needed for conformity to the child API, but not used by this class. def write( output, indent=-1, transitive=false, ie_hack=false ) indent( output, indent ) output << START output << @string - if indent>-1 - output << "\n" - indent( output, indent ) - end output << STOP end Index: rexml/doctype.rb =================================================================== --- rexml/doctype.rb (revision 2900) +++ rexml/doctype.rb (working copy) @@ -6,55 +6,55 @@ require 'rexml/xmltokens' module REXML - # Represents an XML DOCTYPE declaration; that is, the contents of . DOCTYPES can be used to declare the DTD of a document, as well as - # being used to declare entities used in the document. - class DocType < Parent - include XMLTokens - START = "" - SYSTEM = "SYSTEM" - PUBLIC = "PUBLIC" - DEFAULT_ENTITIES = { - 'gt'=>EntityConst::GT, - 'lt'=>EntityConst::LT, - 'quot'=>EntityConst::QUOT, - "apos"=>EntityConst::APOS - } + # Represents an XML DOCTYPE declaration; that is, the contents of . DOCTYPES can be used to declare the DTD of a document, as well as + # being used to declare entities used in the document. + class DocType < Parent + include XMLTokens + START = "" + SYSTEM = "SYSTEM" + PUBLIC = "PUBLIC" + DEFAULT_ENTITIES = { + 'gt'=>EntityConst::GT, + 'lt'=>EntityConst::LT, + 'quot'=>EntityConst::QUOT, + "apos"=>EntityConst::APOS + } - # name is the name of the doctype - # external_id is the referenced DTD, if given - attr_reader :name, :external_id, :entities, :namespaces + # name is the name of the doctype + # external_id is the referenced DTD, if given + attr_reader :name, :external_id, :entities, :namespaces - # Constructor - # - # dt = DocType.new( 'foo', '-//I/Hate/External/IDs' ) - # # - # dt = DocType.new( doctype_to_clone ) - # # Incomplete. Shallow clone of doctype + # Constructor # + # dt = DocType.new( 'foo', '-//I/Hate/External/IDs' ) + # # + # dt = DocType.new( doctype_to_clone ) + # # Incomplete. Shallow clone of doctype + # # +Note+ that the constructor: # # Doctype.new( Source.new( "" ) ) # # is _deprecated_. Do not use it. It will probably disappear. - def initialize( first, parent=nil ) - @entities = DEFAULT_ENTITIES - @long_name = @uri = nil - if first.kind_of? String - super() - @name = first - @external_id = parent - elsif first.kind_of? DocType - super( parent ) - @name = first.name - @external_id = first.external_id - elsif first.kind_of? Array - super( parent ) - @name = first[0] - @external_id = first[1] - @long_name = first[2] - @uri = first[3] + def initialize( first, parent=nil ) + @entities = DEFAULT_ENTITIES + @long_name = @uri = nil + if first.kind_of? String + super() + @name = first + @external_id = parent + elsif first.kind_of? DocType + super( parent ) + @name = first.name + @external_id = first.external_id + elsif first.kind_of? Array + super( parent ) + @name = first[0] + @external_id = first[1] + @long_name = first[2] + @uri = first[3] elsif first.kind_of? Source super( parent ) parser = Parsers::BaseParser.new( first ) @@ -64,150 +64,215 @@ end else super() - end - end + end + end - def node_type - :doctype - end + def node_type + :doctype + end - def attributes_of element - rv = [] - each do |child| - child.each do |key,val| - rv << Attribute.new(key,val) - end if child.kind_of? AttlistDecl and child.element_name == element - end - rv - end + def attributes_of element + rv = [] + each do |child| + child.each do |key,val| + rv << Attribute.new(key,val) + end if child.kind_of? AttlistDecl and child.element_name == element + end + rv + end - def attribute_of element, attribute - att_decl = find do |child| - child.kind_of? AttlistDecl and - child.element_name == element and - child.include? attribute - end - return nil unless att_decl - att_decl[attribute] - end + def attribute_of element, attribute + att_decl = find do |child| + child.kind_of? AttlistDecl and + child.element_name == element and + child.include? attribute + end + return nil unless att_decl + att_decl[attribute] + end - def clone - DocType.new self - end + def clone + DocType.new self + end - # output:: - # Where to write the string - # indent:: - # An integer. If -1, no indenting will be used; otherwise, the - # indentation will be this number of spaces, and children will be - # indented an additional amount. - # transitive:: - # If transitive is true and indent is >= 0, then the output will be - # pretty-printed in such a way that the added whitespace does not affect - # the absolute *value* of the document -- that is, it leaves the value - # and number of Text nodes in the document unchanged. - # ie_hack:: - # Internet Explorer is the worst piece of crap to have ever been - # written, with the possible exception of Windows itself. Since IE is - # unable to parse proper XML, we have to provide a hack to generate XML - # that IE's limited abilities can handle. This hack inserts a space - # before the /> on empty tags. - # - def write( output, indent=0, transitive=false, ie_hack=false ) - indent( output, indent ) - output << START - output << ' ' - output << @name - output << " #@external_id" if @external_id - output << " #@long_name" if @long_name - output << " #@uri" if @uri - unless @children.empty? - next_indent = indent + 1 - output << ' [' - child = nil # speed - @children.each { |child| - output << "\n" - child.write( output, next_indent ) - } - output << "\n" - #output << ' '*next_indent - output << "]" - end - output << STOP - end + # output:: + # Where to write the string + # indent:: + # An integer. If -1, no indenting will be used; otherwise, the + # indentation will be this number of spaces, and children will be + # indented an additional amount. + # transitive:: + # If transitive is true and indent is >= 0, then the output will be + # pretty-printed in such a way that the added whitespace does not affect + # the absolute *value* of the document -- that is, it leaves the value + # and number of Text nodes in the document unchanged. + # ie_hack:: + # Internet Explorer is the worst piece of crap to have ever been + # written, with the possible exception of Windows itself. Since IE is + # unable to parse proper XML, we have to provide a hack to generate XML + # that IE's limited abilities can handle. This hack inserts a space + # before the /> on empty tags. + # + def write( output, indent=0, transitive=false, ie_hack=false ) + indent( output, indent ) + output << START + output << ' ' + output << @name + output << " #@external_id" if @external_id + output << " #@long_name" if @long_name + output << " #@uri" if @uri + unless @children.empty? + next_indent = indent + 1 + output << ' [' + child = nil # speed + @children.each { |child| + output << "\n" + child.write( output, next_indent ) + } + #output << ' '*next_indent + output << "\n]" + end + output << STOP + end def context @parent.context end - def entity( name ) - @entities[name].unnormalized if @entities[name] - end + def entity( name ) + @entities[name].unnormalized if @entities[name] + end - def add child - super(child) - @entities = DEFAULT_ENTITIES.clone if @entities == DEFAULT_ENTITIES - @entities[ child.name ] = child if child.kind_of? Entity - end - end + def add child + super(child) + @entities = DEFAULT_ENTITIES.clone if @entities == DEFAULT_ENTITIES + @entities[ child.name ] = child if child.kind_of? Entity + end + + # This method retrieves the public identifier identifying the document's + # DTD. + # + # Method contributed by Henrik Martensson + def public + case @external_id + when "SYSTEM" + nil + when "PUBLIC" + strip_quotes(@long_name) + end + end + + # This method retrieves the system identifier identifying the document's DTD + # + # Method contributed by Henrik Martensson + def system + case @external_id + when "SYSTEM" + strip_quotes(@long_name) + when "PUBLIC" + @uri.kind_of?(String) ? strip_quotes(@uri) : nil + end + end + + # This method returns a list of notations that have been declared in the + # _internal_ DTD subset. Notations in the external DTD subset are not + # listed. + # + # Method contributed by Henrik Martensson + def notations + children().select {|node| node.kind_of?(REXML::NotationDecl)} + end + + # Retrieves a named notation. Only notations declared in the internal + # DTD subset can be retrieved. + # + # Method contributed by Henrik Martensson + def notation(name) + notations.find { |notation_decl| + notation_decl.name == name + } + end + + private + + # Method contributed by Henrik Martensson + def strip_quotes(quoted_string) + quoted_string =~ /^[\'\"].*[\´\"]$/ ? + quoted_string[1, quoted_string.length-2] : + quoted_string + end + end - # We don't really handle any of these since we're not a validating - # parser, so we can be pretty dumb about them. All we need to be able - # to do is spew them back out on a write() + # We don't really handle any of these since we're not a validating + # parser, so we can be pretty dumb about them. All we need to be able + # to do is spew them back out on a write() - # This is an abstract class. You never use this directly; it serves as a - # parent class for the specific declarations. - class Declaration < Child - def initialize src - super() - @string = src - end + # This is an abstract class. You never use this directly; it serves as a + # parent class for the specific declarations. + class Declaration < Child + def initialize src + super() + @string = src + end - def to_s - @string+'>' - end + def to_s + @string+'>' + end - def write( output, indent ) - output << (' '*indent) if indent > 0 - output << to_s - end - end - - public - class ElementDecl < Declaration - def initialize( src ) - super - end - end + def write( output, indent ) + output << (' '*indent) if indent > 0 + output << to_s + end + end + + public + class ElementDecl < Declaration + def initialize( src ) + super + end + end - class ExternalEntity < Child - def initialize( src ) - super() - @entity = src - end - def to_s - @entity - end - def write( output, indent ) - output << @entity - output << "\n" - end - end + class ExternalEntity < Child + def initialize( src ) + super() + @entity = src + end + def to_s + @entity + end + def write( output, indent ) + output << @entity + end + end - class NotationDecl < Child - def initialize name, middle, rest - @name = name - @middle = middle - @rest = rest - end + class NotationDecl < Child + attr_accessor :public, :system + def initialize name, middle, pub, sys + super(nil) + @name = name + @middle = middle + @public = pub + @system = sys + end - def to_s - "" - end + def to_s + "" + end - def write( output, indent=-1 ) - output << (' '*indent) if indent > 0 - output << to_s - end - end + def write( output, indent=-1 ) + output << (' '*indent) if indent > 0 + output << to_s + end + + # This method retrieves the name of the notation. + # + # Method contributed by Henrik Martensson + def name + @name + end + end end Index: rexml/encodings/UTF-16.rb =================================================================== --- rexml/encodings/UTF-16.rb (revision 2900) +++ rexml/encodings/UTF-16.rb (working copy) @@ -16,9 +16,10 @@ end def decode_utf16(str) + str = str[2..-1] if /^\376\377/ =~ str array_enc=str.unpack('C*') array_utf8 = [] - 2.step(array_enc.size-1, 2){|i| + 0.step(array_enc.size-1, 2){|i| array_utf8 << (array_enc.at(i+1) + array_enc.at(i)*0x100) } array_utf8.pack('U*') Index: rexml/encodings/UNILE.rb =================================================================== --- rexml/encodings/UNILE.rb (revision 2900) +++ rexml/encodings/UNILE.rb (working copy) @@ -18,7 +18,7 @@ def decode_unile(str) array_enc=str.unpack('C*') array_utf8 = [] - 2.step(array_enc.size-1, 2){|i| + 0.step(array_enc.size-1, 2){|i| array_utf8 << (array_enc.at(i) + array_enc.at(i+1)*0x100) } array_utf8.pack('U*') Index: rexml/functions.rb =================================================================== --- rexml/functions.rb (revision 2900) +++ rexml/functions.rb (working copy) @@ -67,11 +67,10 @@ if node_set == nil yield @@context[:node] if defined? @@context[:node].namespace else - if node_set.namespace + if node_set.respond_to? :each + node_set.each { |node| yield node if defined? node.namespace } + elsif node_set.respond_to? :namespace yield node_set - else - return unless node_set.kind_of? Enumerable - node_set.each { |node| yield node if defined? node.namespace } end end end @@ -118,16 +117,30 @@ elsif defined? object.node_type if object.node_type == :attribute object.value - elsif object.node_type == :element - object.text + elsif object.node_type == :element || object.node_type == :document + string_value(object) else object.to_s end + elsif object.nil? + return "" else object.to_s end end + def Functions::string_value( o ) + rv = "" + o.children.each { |e| + if e.node_type == :text + rv << e.to_s + elsif e.node_type == :element + rv << string_value( e ) + end + } + rv + end + # UNTESTED def Functions::concat( *objects ) objects.join @@ -140,7 +153,7 @@ # Fixed by Mike Stok def Functions::contains( string, test ) - string(string).include? string(test) + string(string).include?(string(test)) end # Kouhei fixed this @@ -157,12 +170,9 @@ # Kouhei fixed this too def Functions::substring_after( string, test ) ruby_string = string(string) - ruby_index = ruby_string.index(string(test)) - if ruby_index.nil? - "" - else - ruby_string[ ruby_index+1..-1 ] - end + test_string = string(test) + return $1 if ruby_string =~ /#{test}(.*)/ + "" end # Take equal portions of Mike Stok and Sean Russell; mix @@ -330,8 +340,10 @@ else str = string( object ) #puts "STRING OF #{object.inspect} = #{str}" - if str =~ /^\d+/ - object.to_s.to_f + # If XPath ever gets scientific notation... + #if str =~ /^\s*-?(\d*\.?\d+|\d+\.)([Ee]\d*)?\s*$/ + if str =~ /^\s*-?(\d*\.?\d+|\d+\.)\s*$/ + str.to_f else (0.0 / 0.0) end Index: rexml/cdata.rb =================================================================== --- rexml/cdata.rb (revision 2900) +++ rexml/cdata.rb (working copy) @@ -35,6 +35,10 @@ @string end + def value + @string + end + # Generates XML output of this object # # output:: Index: rexml/sax2listener.rb =================================================================== --- rexml/sax2listener.rb (revision 2900) +++ rexml/sax2listener.rb (working copy) @@ -70,7 +70,7 @@ # ["open-hatch", "PUBLIC", "\"-//Textuality//TEXT Standard open-hatch boilerplate//EN\"", "\"http://www.textuality.com/boilerplate/OpenHatch.xml\""] # # ["hatch-pic", "SYSTEM", "\"../grafix/OpenHatch.gif\"", "\n\t\t\t\t\t\t\tNDATA gif", "gif"] - def entitydecl content + def entitydecl name, decl end # def notationdecl content @@ -84,6 +84,7 @@ # @p version the version attribute value. EG, "1.0" # @p encoding the encoding attribute value, or nil. EG, "utf" # @p standalone the standalone attribute value, or nil. EG, nil + # @p spaced the declaration is followed by a line break def xmldecl version, encoding, standalone end # Called when a comment is encountered. Index: rexml/text.rb =================================================================== --- rexml/text.rb (revision 2900) +++ rexml/text.rb (working copy) @@ -39,8 +39,11 @@ # text. If this value is nil (the default), then the raw value of the # parent will be used as the raw value for this node. If there is no raw # value for the parent, and no value is supplied, the default is false. + # Use this field if you have entities defined for some text, and you don't + # want REXML to escape that text in output. # Text.new( "<&", false, nil, false ) #-> "<&" - # Text.new( "<&", false, nil, true ) #-> IllegalArgumentException + # Text.new( "<&", false, nil, false ) #-> "<&" + # Text.new( "<&", false, nil, true ) #-> Parse exception # Text.new( "<&", false, nil, true ) #-> "<&" # # Assume that the entity "s" is defined to be "sean" # # and that the entity "r" is defined to be "russell" @@ -156,11 +159,11 @@ # # Assume that the entity "s" is defined to be "sean", and that the # # entity "r" is defined to be "russell" # t = Text.new( "< & sean russell", false, nil, false, ['s'] ) - # t.string #-> "< & sean russell" + # t.value #-> "< & sean russell" # t = Text.new( "< & &s; russell", false, nil, false ) - # t.string #-> "< & sean russell" + # t.value #-> "< & sean russell" # u = Text.new( "sean russell", false, nil, true ) - # u.string #-> "sean russell" + # u.value #-> "sean russell" def value @unnormalized if @unnormalized doctype = nil @@ -170,17 +173,6 @@ end @unnormalized = Text::unnormalize( @string, doctype ) end - - def wrap(string, width, addnewline=false) - # Recursivly wrap string at width. - return string if string.length <= width - place = string.rindex(' ', width) # Position in string with last ' ' before cutoff - if addnewline then - return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width) - else - return string[0,place] + "\n" + wrap(string[place+1..-1], width) - end - end # Sets the contents of this text node. This expects the text to be # unnormalized. It returns self. @@ -196,17 +188,28 @@ @raw = false end - def indent_text(string, level=1, style="\t", indentfirstline=true) + def wrap(string, width, addnewline=false) + # Recursivly wrap string at width. + return string if string.length <= width + place = string.rindex(' ', width) # Position in string with last ' ' before cutoff + if addnewline then + return "\n" + string[0,place] + "\n" + wrap(string[place+1..-1], width) + else + return string[0,place] + "\n" + wrap(string[place+1..-1], width) + end + end + + def indent_text(string, level=1, style="\t", indentfirstline=true) return string if level < 0 - new_string = '' - string.each { |line| - indent_string = style * level - new_line = (indent_string + line).sub(/[\s]+$/,'') - new_string << new_line - } - new_string.strip! unless indentfirstline - return new_string - end + new_string = '' + string.each { |line| + indent_string = style * level + new_line = (indent_string + line).sub(/[\s]+$/,'') + new_string << new_line + } + new_string.strip! unless indentfirstline + return new_string + end def write( writer, indent=-1, transitive=false, ie_hack=false ) s = to_s() @@ -282,17 +285,19 @@ EREFERENCE = /&(?!#{Entity::NAME};)/ # Escapes all possible entities def Text::normalize( input, doctype=nil, entity_filter=nil ) - copy = input.clone + copy = input # Doing it like this rather than in a loop improves the speed + #copy = copy.gsub( EREFERENCE, '&' ) + copy = copy.gsub( "&", "&" ) if doctype - copy = copy.gsub( EREFERENCE, '&' ) + # Replace all ampersands that aren't part of an entity doctype.entities.each_value do |entity| copy = copy.gsub( entity.value, "{entity.name};" ) if entity.value and not( entity_filter and entity_filter.include?(entity) ) end else - copy = copy.gsub( EREFERENCE, '&' ) + # Replace all ampersands that aren't part of an entity DocType::DEFAULT_ENTITIES.each_value do |entity| copy = copy.gsub(entity.value, "{entity.name};" ) end Index: rexml/node.rb =================================================================== --- rexml/node.rb (revision 2900) +++ rexml/node.rb (working copy) @@ -55,10 +55,8 @@ return nil end - # Returns the index that +self+ has in its parent's elements array, so that - # the following equation holds true: - # - # node == node.parent.elements[node.index_in_parent] + # Returns the position that +self+ holds in its parent's array, indexed + # from 1. def index_in_parent parent.index(self)+1 end Index: rexml/encoding.rb =================================================================== --- rexml/encoding.rb (revision 2900) +++ rexml/encoding.rb (working copy) @@ -1,58 +1,66 @@ # -*- mode: ruby; ruby-indent-level: 2; indent-tabs-mode: t; tab-width: 2 -*- vim: sw=2 ts=2 module REXML - module Encoding - @encoding_methods = {} - def self.register(enc, &block) - @encoding_methods[enc] = block - end - def self.apply(obj, enc) - @encoding_methods[enc][obj] - end - def self.encoding_method(enc) - @encoding_methods[enc] - end + module Encoding + @encoding_methods = {} + def self.register(enc, &block) + @encoding_methods[enc] = block + end + def self.apply(obj, enc) + @encoding_methods[enc][obj] + end + def self.encoding_method(enc) + @encoding_methods[enc] + end - # Native, default format is UTF-8, so it is declared here rather than in - # an encodings/ definition. - UTF_8 = 'UTF-8' - UTF_16 = 'UTF-16' - UNILE = 'UNILE' + # Native, default format is UTF-8, so it is declared here rather than in + # an encodings/ definition. + UTF_8 = 'UTF-8' + UTF_16 = 'UTF-16' + UNILE = 'UNILE' - # ID ---> Encoding name - attr_reader :encoding - def encoding=( enc ) - old_verbosity = $VERBOSE - begin - $VERBOSE = false - return if defined? @encoding and enc == @encoding - if enc - raise ArgumentError, "Bad encoding name #{enc}" unless /\A[\w-]+\z/n =~ enc - @encoding = enc.upcase.untaint - else - @encoding = UTF_8 - end - err = nil - [@encoding, "ICONV"].each do |enc| - begin - require File.join("rexml", "encodings", "#{enc}.rb") - return Encoding.apply(self, enc) - rescue LoadError, Exception => err - end - end - puts err.message - raise ArgumentError, "No decoder found for encoding #@encoding. Please install iconv." - ensure - $VERBOSE = old_verbosity - end - end + # ID ---> Encoding name + attr_reader :encoding + def encoding=( enc ) + old_verbosity = $VERBOSE + begin + $VERBOSE = false + enc = enc.nil? ? nil : enc.upcase + return false if defined? @encoding and enc == @encoding + if enc and enc != UTF_8 + @encoding = enc + raise ArgumentError, "Bad encoding name #@encoding" unless @encoding =~ /^[\w-]+$/ + @encoding.untaint + begin + require 'rexml/encodings/ICONV.rb' + Encoding.apply(self, "ICONV") + rescue LoadError, Exception + begin + enc_file = File.join( "rexml", "encodings", "#@encoding.rb" ) + require enc_file + Encoding.apply(self, @encoding) + rescue LoadError => err + puts err.message + raise ArgumentError, "No decoder found for encoding #@encoding. Please install iconv." + end + end + else + @encoding = UTF_8 + require 'rexml/encodings/UTF-8.rb' + Encoding.apply(self, @encoding) + end + ensure + $VERBOSE = old_verbosity + end + true + end - def check_encoding str - # We have to recognize UTF-16, LSB UTF-16, and UTF-8 - return UTF_16 if str[0] == 254 && str[1] == 255 - return UNILE if str[0] == 255 && str[1] == 254 - str =~ /^\s* -# Version:: 3.1.3 -# Date:: 2005/224 +# Version:: 3.1.6 +# Date:: 2006/335 # # This API documentation can be downloaded from the REXML home page, or can # be accessed online[http://www.germane-software.com/software/rexml_doc] @@ -20,7 +20,10 @@ # or can be accessed # online[http://www.germane-software.com/software/rexml/docs/tutorial.html] module REXML - Copyright = "Copyright © 2001, 2002, 2003, 2004 Sean Russell " - Date = "2005/224" - Version = "3.1.3" + COPYRIGHT = "Copyright © 2001-2006 Sean Russell " + DATE = "2006/335" + VERSION = "3.1.6" + + Copyright = COPYRIGHT + Version = VERSION end Index: rexml/xpath_parser.rb =================================================================== --- rexml/xpath_parser.rb (revision 2900) +++ rexml/xpath_parser.rb (working copy) @@ -10,10 +10,14 @@ end end class Symbol - def dclone - self - end + def dclone ; self ; end end +class Fixnum + def dclone ; self ; end +end +class Float + def dclone ; self ; end +end class Array def dclone klone = self.clone @@ -34,7 +38,7 @@ def initialize( ) @parser = REXML::Parsers::XPathParser.new - @namespaces = {} + @namespaces = nil @variables = {} end @@ -130,6 +134,21 @@ private + # Returns a String namespace for a node, given a prefix + # The rules are: + # + # 1. Use the supplied namespace mapping first. + # 2. If no mapping was supplied, use the context node to look up the namespace + def get_namespace( node, prefix ) + if @namespaces + return @namespaces[prefix] || '' + else + return node.namespace( prefix ) if node.node_type == :element + return '' + end + end + + # Expr takes a stack of path elements and a set of nodes (either a Parent # or an Array and returns an Array of matching nodes ALL = [ :attribute, :element, :text, :processing_instruction, :comment ] @@ -143,6 +162,10 @@ while path_stack.length > 0 #puts "Path stack = #{path_stack.inspect}" #puts "Nodeset is #{nodeset.inspect}" + if nodeset.length == 0 + path_stack.clear + return [] + end case (op = path_stack.shift) when :document nodeset = [ nodeset[0].root_node ] @@ -152,11 +175,9 @@ #puts "IN QNAME" prefix = path_stack.shift name = path_stack.shift - ns = @namespaces[prefix] - ns = ns ? ns : '' nodeset.delete_if do |node| # FIXME: This DOUBLES the time XPath searches take - ns = node.namespace( prefix ) if node.node_type == :element and ns == '' + ns = get_namespace( node, prefix ) #puts "NS = #{ns.inspect}" #puts "node.node_type == :element => #{node.node_type == :element}" if node.node_type == :element @@ -208,11 +229,7 @@ node_types = ELEMENTS when :literal - literal = path_stack.shift - if literal =~ /^\d+(\.\d+)?$/ - return ($1 ? literal.to_f : literal.to_i) - end - return literal + return path_stack.shift when :attribute new_nodeset = [] @@ -222,9 +239,11 @@ name = path_stack.shift for element in nodeset if element.node_type == :element - #puts element.name - attr = element.attribute( name, @namespaces[prefix] ) - new_nodeset << attr if attr + #puts "Element name = #{element.name}" + #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}" + attrib = element.attribute( name, get_namespace(element, prefix) ) + #puts "attrib = #{attrib.inspect}" + new_nodeset << attrib if attrib end end when :any @@ -286,8 +305,10 @@ #puts "Adding node #{node.inspect}" if result == (index+1) new_nodeset << node if result == (index+1) elsif result.instance_of? Array - #puts "Adding node #{node.inspect}" if result.size > 0 - new_nodeset << node if result.size > 0 + if result.size > 0 and result.inject(false) {|k,s| s or k} + #puts "Adding node #{node.inspect}" if result.size > 0 + new_nodeset << node if result.size > 0 + end else #puts "Adding node #{node.inspect}" if result new_nodeset << node if result @@ -347,7 +368,7 @@ preceding_siblings = all_siblings[ 0 .. current_index-1 ].reverse #results += expr( path_stack.dclone, preceding_siblings ) end - nodeset = preceding_siblings + nodeset = preceding_siblings || [] node_types = ELEMENTS when :preceding @@ -368,9 +389,19 @@ node_types = ELEMENTS when :namespace - new_set = [] + new_nodeset = [] + prefix = path_stack.shift for node in nodeset - new_nodeset << node.namespace if node.node_type == :element or node.node_type == :attribute + if (node.node_type == :element or node.node_type == :attribute) + if (node.node_type == :element) + namespaces = node.namespaces + else + namespaces = node.element.namesapces + end + if (node.namespace == namespaces[prefix]) + new_nodeset << node + end + end end nodeset = new_nodeset @@ -379,15 +410,30 @@ return @variables[ var_name ] # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq + # TODO: Special case for :or and :and -- not evaluate the right + # operand if the left alone determines result (i.e. is true for + # :or and false for :and). when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or - left = expr( path_stack.shift, nodeset, context ) + left = expr( path_stack.shift, nodeset.dup, context ) #puts "LEFT => #{left.inspect} (#{left.class.name})" - right = expr( path_stack.shift, nodeset, context ) + right = expr( path_stack.shift, nodeset.dup, context ) #puts "RIGHT => #{right.inspect} (#{right.class.name})" res = equality_relational_compare( left, op, right ) #puts "RES => #{res.inspect}" return res + when :and + left = expr( path_stack.shift, nodeset.dup, context ) + #puts "LEFT => #{left.inspect} (#{left.class.name})" + if left == false || left.nil? || !left.inject(false) {|a,b| a | b} + return [] + end + right = expr( path_stack.shift, nodeset.dup, context ) + #puts "RIGHT => #{right.inspect} (#{right.class.name})" + res = equality_relational_compare( left, op, right ) + #puts "RES => #{res.inspect}" + return res + when :div left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f @@ -461,13 +507,16 @@ # The next two methods are BAD MOJO! # This is my achilles heel. If anybody thinks of a better # way of doing this, be my guest. This really sucks, but - # it took me three days to get it to work at all. + # it is a wonder it works at all. # ######################################################## def descendant_or_self( path_stack, nodeset ) rs = [] + #puts "#"*80 + #puts "PATH_STACK = #{path_stack.inspect}" + #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}" d_o_s( path_stack, nodeset, rs ) - #puts "RS = #{rs.collect{|n|n.to_s}.inspect}" + #puts "RS = #{rs.collect{|n|n.inspect}.inspect}" document_order(rs.flatten.compact) #rs.flatten.compact end Index: monitor.rb =================================================================== --- monitor.rb (revision 2900) +++ monitor.rb (working copy) @@ -86,6 +86,10 @@ class ConditionVariable class Timeout < Exception; end + # Create a new timer with the argument timeout, and add the + # current thread to the list of waiters. Then the thread is + # stopped. It will be resumed when a corresponding #signal + # occurs. def wait(timeout = nil) @monitor.instance_eval {mon_check_owner()} timer = create_timer(timeout) @@ -112,18 +116,22 @@ end end + + # call #wait while the supplied block returns +true+. def wait_while while yield wait end end + # call #wait until the supplied block returns +true+. def wait_until until yield wait end end + # Wake up and run the next waiter def signal @monitor.instance_eval {mon_check_owner()} Thread.critical = true @@ -133,6 +141,7 @@ Thread.pass end + # Wake up all the waiters. def broadcast @monitor.instance_eval {mon_check_owner()} Thread.critical = true @@ -235,6 +244,9 @@ # # FIXME: This isn't documented in Nutshell. + # + # Create a new condition variable for this monitor. + # This facilitates control of the monitor with #signal and #wait. # def new_cond return ConditionVariable.new(self) @@ -247,6 +259,7 @@ mon_initialize end + # called by initialize method to set defaults for instance variables. def mon_initialize @mon_owner = nil @mon_count = 0 @@ -254,6 +267,8 @@ @mon_waiting_queue = [] end + # Throw a ThreadError exception if the current thread + # does't own the monitor def mon_check_owner if @mon_owner != Thread.current raise ThreadError, "current thread not owner" @@ -289,6 +304,17 @@ end end +# Monitors provide means of mutual exclusion for Thread programming. +# A critical region is created by means of the synchronize method, +# which takes a block. +# The condition variables (created with #new_cond) may be used +# to control the execution of a monitor with #signal and #wait. +# +# the Monitor class wraps MonitorMixin, and provides aliases +# alias try_enter try_mon_enter +# alias enter mon_enter +# alias exit mon_exit +# to access its methods more concisely. class Monitor include MonitorMixin alias try_enter try_mon_enter Index: pathname.rb =================================================================== --- pathname.rb (revision 2900) +++ pathname.rb (working copy) @@ -15,8 +15,12 @@ # == Pathname # # Pathname represents a pathname which locates a file in a filesystem. -# It supports only Unix style pathnames. It does not represent the file -# itself. A Pathname can be relative or absolute. It's not until you try to +# The pathname depends on OS: Unix, Windows, etc. +# Pathname library works with pathnames of local OS. +# However non-Unix pathnames are supported experimentally. +# +# It does not represent the file itself. +# A Pathname can be relative or absolute. It's not until you try to # reference the file that it even matters whether the file exists or not. # # Pathname is immutable. It has no method for destructive update. @@ -103,6 +107,7 @@ # - #owned? # - #pipe? # - #readable? +# - #world_readable? # - #readable_real? # - #setgid? # - #setuid? @@ -112,6 +117,7 @@ # - #sticky? # - #symlink? # - #writable? +# - #world_writable? # - #writable_real? # - #zero? # @@ -180,12 +186,22 @@ # information. In some cases, a brief description will follow. # class Pathname + + # :stopdoc: + if RUBY_VERSION < "1.9" + TO_PATH = :to_str + else + # to_path is implemented so Pathname objects are usable with File.open, etc. + TO_PATH = :to_path + end + # :startdoc: + # # Create a Pathname object from the given String (or String-like object). # If +path+ contains a NUL character (\0), an ArgumentError is raised. # def initialize(path) - path = path.to_str if path.respond_to? :to_str + path = path.__send__(TO_PATH) if path.respond_to? TO_PATH @path = path.dup if /\0/ =~ @path @@ -226,14 +242,59 @@ @path.dup end - # to_str is implemented so Pathname objects are usable with File.open, etc. - alias to_str to_s + # to_path is implemented so Pathname objects are usable with File.open, etc. + alias_method TO_PATH, :to_s def inspect # :nodoc: "#<#{self.class}:#{@path}>" end - # + # Return a pathname which is substituted by String#sub. + def sub(pattern, *rest, &block) + self.class.new(@path.sub(pattern, *rest, &block)) + end + + if File::ALT_SEPARATOR + SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/ + else + SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/ + end + + # chop_basename(path) -> [pre-basename, basename] or nil + def chop_basename(path) + base = File.basename(path) + if /\A#{SEPARATOR_PAT}?\z/ =~ base + return nil + else + return path[0, path.rindex(base)], base + end + end + private :chop_basename + + # split_names(path) -> prefix, [name, ...] + def split_names(path) + names = [] + while r = chop_basename(path) + path, basename = r + names.unshift basename + end + return path, names + end + private :split_names + + def prepend_prefix(prefix, relpath) + if relpath.empty? + File.dirname(prefix) + elsif /#{SEPARATOR_PAT}/ =~ prefix + prefix = File.dirname(prefix) + prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a' + prefix + relpath + else + prefix + relpath + end + end + private :prepend_prefix + # Returns clean pathname of +self+ with consecutive slashes and useless dots # removed. The filesystem is not accessed. # @@ -255,118 +316,142 @@ # Nothing more, nothing less. # def cleanpath_aggressive - # cleanpath_aggressive assumes: - # * no symlink - # * all pathname prefix contained in the pathname is existing directory - return Pathname.new('') if @path == '' - absolute = absolute? + path = @path names = [] - @path.scan(%r{[^/]+}) {|name| - next if name == '.' - if name == '..' - if names.empty? - next if absolute + pre = path + while r = chop_basename(pre) + pre, base = r + case base + when '.' + when '..' + names.unshift base + else + if names[0] == '..' + names.shift else - if names.last != '..' - names.pop - next - end + names.unshift base end end - names << name - } - return Pathname.new(absolute ? '/' : '.') if names.empty? - path = absolute ? '/' : '' - path << names.join('/') - Pathname.new(path) + end + if /#{SEPARATOR_PAT}/o =~ File.basename(pre) + names.shift while names[0] == '..' + end + self.class.new(prepend_prefix(pre, File.join(*names))) end private :cleanpath_aggressive - def cleanpath_conservative - return Pathname.new('') if @path == '' - names = @path.scan(%r{[^/]+}) - last_dot = names.last == '.' - names.delete('.') - names.shift while names.first == '..' if absolute? - return Pathname.new(absolute? ? '/' : '.') if names.empty? - path = absolute? ? '/' : '' - path << names.join('/') - if names.last != '..' - if last_dot - path << '/.' - elsif %r{/\z} =~ @path - path << '/' - end + # has_trailing_separator?(path) -> bool + def has_trailing_separator?(path) + if r = chop_basename(path) + pre, basename = r + pre.length + basename.length < path.length + else + false end - Pathname.new(path) end - private :cleanpath_conservative + private :has_trailing_separator? - # - # 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*. - # - def realpath(*args) - unless args.empty? - warn "The argument for Pathname#realpath is obsoleted." + # add_trailing_separator(path) -> path + def add_trailing_separator(path) + if File.basename(path + 'a') == 'a' + path + else + File.join(path, "") # xxx: Is File.join is appropriate to add separator? end - force_absolute = args.fetch(0, true) + end + private :add_trailing_separator - if %r{\A/} =~ @path - top = '/' - unresolved = @path.scan(%r{[^/]+}) - elsif force_absolute - # Although POSIX getcwd returns a pathname which contains no symlink, - # 4.4BSD-Lite2 derived getcwd may return the environment variable $PWD - # which may contain a symlink. - # So the return value of Dir.pwd should be examined. - top = '/' - unresolved = Dir.pwd.scan(%r{[^/]+}) + @path.scan(%r{[^/]+}) + def del_trailing_separator(path) + if r = chop_basename(path) + pre, basename = r + pre + basename + elsif /#{SEPARATOR_PAT}+\z/o =~ path + $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o] else - top = '' - unresolved = @path.scan(%r{[^/]+}) + path end - resolved = [] + end + private :del_trailing_separator + def cleanpath_conservative + path = @path + names = [] + pre = path + while r = chop_basename(pre) + pre, base = r + names.unshift base if base != '.' + end + if /#{SEPARATOR_PAT}/o =~ File.basename(pre) + names.shift while names[0] == '..' + end + if names.empty? + self.class.new(File.dirname(pre)) + else + if names.last != '..' && File.basename(path) == '.' + names << '.' + end + result = prepend_prefix(pre, File.join(*names)) + if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path) + self.class.new(add_trailing_separator(result)) + else + self.class.new(result) + end + end + end + private :cleanpath_conservative + + def realpath_rec(prefix, unresolved, h) + resolved = [] until unresolved.empty? - case unresolved.last - when '.' - unresolved.pop - when '..' - resolved.unshift unresolved.pop + n = unresolved.shift + if n == '.' + next + elsif n == '..' + resolved.pop else - loop_check = {} - while (stat = File.lstat(path = top + unresolved.join('/'))).symlink? - symlink_id = "#{stat.dev}:#{stat.ino}" - raise Errno::ELOOP.new(path) if loop_check[symlink_id] - loop_check[symlink_id] = true - if %r{\A/} =~ (link = File.readlink(path)) - top = '/' - unresolved = link.scan(%r{[^/]+}) + path = prepend_prefix(prefix, File.join(*(resolved + [n]))) + if h.include? path + if h[path] == :resolving + raise Errno::ELOOP.new(path) else - unresolved[-1,1] = link.scan(%r{[^/]+}) + prefix, *resolved = h[path] end - end - next if (filename = unresolved.pop) == '.' - if filename != '..' && resolved.first == '..' - resolved.shift else - resolved.unshift filename + s = File.lstat(path) + if s.symlink? + h[path] = :resolving + link_prefix, link_names = split_names(File.readlink(path)) + if link_prefix == '' + prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h) + else + prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h) + end + else + resolved << n + h[path] = [prefix, *resolved] + end end end end + return prefix, *resolved + end + private :realpath_rec - if top == '/' - resolved.shift while resolved[0] == '..' + # + # 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*. + # + def realpath + path = @path + prefix, names = split_names(path) + if prefix == '' + prefix, names2 = split_names(Dir.pwd) + names = names2 + names end - - if resolved.empty? - Pathname.new(top.empty? ? '.' : '/') - else - Pathname.new(top + resolved.join('/')) - end + prefix, *names = realpath_rec(prefix, names, {}) + self.class.new(prepend_prefix(prefix, File.join(*names))) end # #parent returns the parent directory. @@ -396,18 +481,22 @@ # pathnames which points to roots such as /usr/... # def root? - %r{\A/+\z} =~ @path ? true : false + !!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path) end # Predicate method for testing whether a path is absolute. # It returns +true+ if the pathname begins with a slash. def absolute? - %r{\A/} =~ @path ? true : false + !relative? end # The opposite of #absolute? def relative? - !absolute? + path = @path + while r = chop_basename(path) + path, basename = r + end + path == '' end # @@ -416,11 +505,70 @@ # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... } # # yields "usr", "bin", and "ruby". # - def each_filename # :yield: s - @path.scan(%r{[^/]+}) { yield $& } + def each_filename # :yield: filename + prefix, names = split_names(@path) + names.each {|filename| yield filename } + nil end + # Iterates over and yields a new Pathname object + # for each element in the given path in descending order. # + # Pathname.new('/path/to/some/file.rb').descend {|v| p v} + # # + # # + # # + # # + # # + # + # Pathname.new('path/to/some/file.rb').descend {|v| p v} + # # + # # + # # + # # + # + # It doesn't access actual filesystem. + # + # This method is available since 1.8.5. + # + def descend + vs = [] + ascend {|v| vs << v } + vs.reverse_each {|v| yield v } + nil + end + + # Iterates over and yields a new Pathname object + # for each element in the given path in ascending order. + # + # Pathname.new('/path/to/some/file.rb').ascend {|v| p v} + # # + # # + # # + # # + # # + # + # Pathname.new('path/to/some/file.rb').ascend {|v| p v} + # # + # # + # # + # # + # + # It doesn't access actual filesystem. + # + # This method is available since 1.8.5. + # + def ascend + path = @path + yield self + while r = chop_basename(path) + path, name = r + break if path.empty? + yield self.class.new(del_trailing_separator(path)) + end + end + + # # Pathname#+ appends a pathname fragment to this one to produce a new Pathname # object. # @@ -432,32 +580,50 @@ # def +(other) other = Pathname.new(other) unless Pathname === other + Pathname.new(plus(@path, other.to_s)) + end - return other if other.absolute? - - path1 = @path - path2 = other.to_s - while m2 = %r{\A\.\.(?:/+|\z)}.match(path2) and - m1 = %r{(\A|/+)([^/]+)\z}.match(path1) and - %r{\A(?:\.|\.\.)\z} !~ m1[2] - path1 = m1[1].empty? ? '.' : '/' if (path1 = m1.pre_match).empty? - path2 = '.' if (path2 = m2.post_match).empty? + def plus(path1, path2) # -> path + prefix2 = path2 + index_list2 = [] + basename_list2 = [] + while r2 = chop_basename(prefix2) + prefix2, basename2 = r2 + index_list2.unshift prefix2.length + basename_list2.unshift basename2 end - if %r{\A/+\z} =~ path1 - while m2 = %r{\A\.\.(?:/+|\z)}.match(path2) - path2 = '.' if (path2 = m2.post_match).empty? + return path2 if prefix2 != '' + prefix1 = path1 + while true + while !basename_list2.empty? && basename_list2.first == '.' + index_list2.shift + basename_list2.shift end + break unless r1 = chop_basename(prefix1) + prefix1, basename1 = r1 + next if basename1 == '.' + if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..' + prefix1 = prefix1 + basename1 + break + end + index_list2.shift + basename_list2.shift end - - return Pathname.new(path2) if path1 == '.' - return Pathname.new(path1) if path2 == '.' - - if %r{/\z} =~ path1 - Pathname.new(path1 + path2) + r1 = chop_basename(prefix1) + if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1) + while !basename_list2.empty? && basename_list2.first == '..' + index_list2.shift + basename_list2.shift + end + end + if !basename_list2.empty? + suffix2 = path2[index_list2.first..-1] + r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2 else - Pathname.new(path1 + '/' + path2) + r1 ? prefix1 : File.dirname(prefix1) end end + private :plus # # Pathname#join joins pathnames. @@ -505,9 +671,9 @@ Dir.foreach(@path) {|e| next if e == '.' || e == '..' if with_directory - result << Pathname.new(File.join(@path, e)) + result << self.class.new(File.join(@path, e)) else - result << Pathname.new(e) + result << self.class.new(e) end } result @@ -525,44 +691,42 @@ # This method has existed since 1.8.1. # def relative_path_from(base_directory) - if self.absolute? != base_directory.absolute? - raise ArgumentError, - "relative path between absolute and relative path: #{self.inspect}, #{base_directory.inspect}" + dest_directory = self.cleanpath.to_s + base_directory = base_directory.cleanpath.to_s + dest_prefix = dest_directory + dest_names = [] + while r = chop_basename(dest_prefix) + dest_prefix, basename = r + dest_names.unshift basename if basename != '.' end - - dest = [] - self.cleanpath.each_filename {|f| - next if f == '.' - dest << f - } - - base = [] - base_directory.cleanpath.each_filename {|f| - next if f == '.' - base << f - } - - while !base.empty? && !dest.empty? && base[0] == dest[0] - base.shift - dest.shift + base_prefix = base_directory + base_names = [] + while r = chop_basename(base_prefix) + base_prefix, basename = r + base_names.unshift basename if basename != '.' end - - if base.include? '..' + if dest_prefix != base_prefix + raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}" + end + while !dest_names.empty? && + !base_names.empty? && + dest_names.first == base_names.first + dest_names.shift + base_names.shift + end + if base_names.include? '..' raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" end - - base.fill '..' - relpath = base + dest - if relpath.empty? - Pathname.new(".") + base_names.fill('..') + relpath_names = base_names + dest_names + if relpath_names.empty? + Pathname.new('.') else - Pathname.new(relpath.join('/')) + Pathname.new(File.join(*relpath_names)) end end - end - class Pathname # * IO * # # #each_line iterates over the line in the file. It yields a String object @@ -635,7 +799,7 @@ end # See File.readlink. Read symbolic link. - def readlink() Pathname.new(File.readlink(@path)) end + def readlink() self.class.new(File.readlink(@path)) end # See File.rename. Rename the file. def rename(to) File.rename(@path, to) end @@ -656,20 +820,20 @@ def utime(atime, mtime) File.utime(atime, mtime, @path) end # See File.basename. Returns the last component of the path. - def basename(*args) Pathname.new(File.basename(@path, *args)) end + def basename(*args) self.class.new(File.basename(@path, *args)) end # See File.dirname. Returns all but the last component of the path. - def dirname() Pathname.new(File.dirname(@path)) end + def dirname() self.class.new(File.dirname(@path)) end # See File.extname. Returns the file's extension. def extname() File.extname(@path) end # See File.expand_path. - def expand_path(*args) Pathname.new(File.expand_path(@path, *args)) end + def expand_path(*args) self.class.new(File.expand_path(@path, *args)) end # See File.split. Returns the #dirname and the #basename in an # Array. - def split() File.split(@path).map {|f| Pathname.new(f) } end + def split() File.split(@path).map {|f| self.class.new(f) } end # Pathname#link is confusing and *obsoleted* because the receiver/argument # order is inverted to corresponding system call. @@ -725,6 +889,9 @@ # See FileTest.readable?. def readable?() FileTest.readable?(@path) end + # See FileTest.world_readable?. + def world_readable?() FileTest.world_readable?(@path) end + # See FileTest.readable_real?. def readable_real?() FileTest.readable_real?(@path) end @@ -749,6 +916,9 @@ # See FileTest.writable?. def writable?() FileTest.writable?(@path) end + # See FileTest.world_writable?. + def world_writable?() FileTest.world_writable?(@path) end + # See FileTest.writable_real?. def writable_real?() FileTest.writable_real?(@path) end @@ -761,14 +931,14 @@ # See Dir.glob. Returns or yields Pathname objects. def Pathname.glob(*args) # :yield: p if block_given? - Dir.glob(*args) {|f| yield Pathname.new(f) } + Dir.glob(*args) {|f| yield self.new(f) } else - Dir.glob(*args).map {|f| Pathname.new(f) } + Dir.glob(*args).map {|f| self.new(f) } end end # See Dir.getwd. Returns the current working directory as a Pathname. - def Pathname.getwd() Pathname.new(Dir.getwd) end + def Pathname.getwd() self.new(Dir.getwd) end class << self; alias pwd getwd end # Pathname#chdir is *obsoleted* at 1.8.1. @@ -785,14 +955,14 @@ # Return the entries (files and subdirectories) in the directory, each as a # Pathname object. - def entries() Dir.entries(@path).map {|f| Pathname.new(f) } end + def entries() Dir.entries(@path).map {|f| self.class.new(f) } end # Iterates over the entries (files and subdirectories) in the directory. It # yields a Pathname object for each entry. # # This method has existed since 1.8.1. def each_entry(&block) # :yield: p - Dir.foreach(@path) {|f| yield Pathname.new(f) } + Dir.foreach(@path) {|f| yield self.class.new(f) } end # Pathname#dir_foreach is *obsoleted* at 1.8.1. @@ -828,9 +998,9 @@ def find(&block) # :yield: p require 'find' if @path == '.' - Find.find(@path) {|f| yield Pathname.new(f.sub(%r{\A\./}, '')) } + Find.find(@path) {|f| yield self.class.new(f.sub(%r{\A\./}, '')) } else - Find.find(@path) {|f| yield Pathname.new(f) } + Find.find(@path) {|f| yield self.class.new(f) } end end end @@ -881,315 +1051,12 @@ end end -if $0 == __FILE__ - require 'test/unit' - - class PathnameTest < Test::Unit::TestCase # :nodoc: - def test_initialize - p1 = Pathname.new('a') - assert_equal('a', p1.to_s) - p2 = Pathname.new(p1) - assert_equal(p1, p2) - end - - class AnotherStringLike # :nodoc: - def initialize(s) @s = s end - def to_str() @s end - def ==(other) @s == other end - end - - def test_equality - obj = Pathname.new("a") - str = "a" - sym = :a - ano = AnotherStringLike.new("a") - assert_equal(false, obj == str) - assert_equal(false, str == obj) - assert_equal(false, obj == ano) - assert_equal(false, ano == obj) - assert_equal(false, obj == sym) - assert_equal(false, sym == obj) - - obj2 = Pathname.new("a") - assert_equal(true, obj == obj2) - assert_equal(true, obj === obj2) - assert_equal(true, obj.eql?(obj2)) - end - - def test_hashkey - h = {} - h[Pathname.new("a")] = 1 - h[Pathname.new("a")] = 2 - assert_equal(1, h.size) - end - - def assert_pathname_cmp(e, s1, s2) - p1 = Pathname.new(s1) - p2 = Pathname.new(s2) - r = p1 <=> p2 - assert(e == r, - "#{p1.inspect} <=> #{p2.inspect}: <#{e}> expected but was <#{r}>") - end - def test_comparison - assert_pathname_cmp( 0, "a", "a") - assert_pathname_cmp( 1, "b", "a") - assert_pathname_cmp(-1, "a", "b") - ss = %w( - a - a/ - a/b - a. - a0 - ) - s1 = ss.shift - ss.each {|s2| - assert_pathname_cmp(-1, s1, s2) - s1 = s2 - } - end - - def test_comparison_string - assert_equal(nil, Pathname.new("a") <=> "a") - assert_equal(nil, "a" <=> Pathname.new("a")) - end - - def test_syntactical - assert_equal(true, Pathname.new("/").root?) - assert_equal(true, Pathname.new("//").root?) - assert_equal(true, Pathname.new("///").root?) - assert_equal(false, Pathname.new("").root?) - assert_equal(false, Pathname.new("a").root?) - end - - def test_cleanpath - assert_equal('/', Pathname.new('/').cleanpath(true).to_s) - assert_equal('/', Pathname.new('//').cleanpath(true).to_s) - assert_equal('', Pathname.new('').cleanpath(true).to_s) - - assert_equal('.', Pathname.new('.').cleanpath(true).to_s) - assert_equal('..', Pathname.new('..').cleanpath(true).to_s) - assert_equal('a', Pathname.new('a').cleanpath(true).to_s) - assert_equal('/', Pathname.new('/.').cleanpath(true).to_s) - assert_equal('/', Pathname.new('/..').cleanpath(true).to_s) - assert_equal('/a', Pathname.new('/a').cleanpath(true).to_s) - assert_equal('.', Pathname.new('./').cleanpath(true).to_s) - assert_equal('..', Pathname.new('../').cleanpath(true).to_s) - assert_equal('a/', Pathname.new('a/').cleanpath(true).to_s) - - assert_equal('a/b', Pathname.new('a//b').cleanpath(true).to_s) - assert_equal('a/.', Pathname.new('a/.').cleanpath(true).to_s) - assert_equal('a/.', Pathname.new('a/./').cleanpath(true).to_s) - assert_equal('a/..', Pathname.new('a/../').cleanpath(true).to_s) - assert_equal('/a/.', Pathname.new('/a/.').cleanpath(true).to_s) - assert_equal('..', Pathname.new('./..').cleanpath(true).to_s) - assert_equal('..', Pathname.new('../.').cleanpath(true).to_s) - assert_equal('..', Pathname.new('./../').cleanpath(true).to_s) - assert_equal('..', Pathname.new('.././').cleanpath(true).to_s) - assert_equal('/', Pathname.new('/./..').cleanpath(true).to_s) - assert_equal('/', Pathname.new('/../.').cleanpath(true).to_s) - assert_equal('/', Pathname.new('/./../').cleanpath(true).to_s) - assert_equal('/', Pathname.new('/.././').cleanpath(true).to_s) - - assert_equal('a/b/c', Pathname.new('a/b/c').cleanpath(true).to_s) - assert_equal('b/c', Pathname.new('./b/c').cleanpath(true).to_s) - assert_equal('a/c', Pathname.new('a/./c').cleanpath(true).to_s) - assert_equal('a/b/.', Pathname.new('a/b/.').cleanpath(true).to_s) - assert_equal('a/..', Pathname.new('a/../.').cleanpath(true).to_s) - - assert_equal('/a', Pathname.new('/../.././../a').cleanpath(true).to_s) - assert_equal('a/b/../../../../c/../d', - Pathname.new('a/b/../../../../c/../d').cleanpath(true).to_s) - end - - def test_cleanpath_no_symlink - assert_equal('/', Pathname.new('/').cleanpath.to_s) - assert_equal('/', Pathname.new('//').cleanpath.to_s) - assert_equal('', Pathname.new('').cleanpath.to_s) - - assert_equal('.', Pathname.new('.').cleanpath.to_s) - assert_equal('..', Pathname.new('..').cleanpath.to_s) - assert_equal('a', Pathname.new('a').cleanpath.to_s) - assert_equal('/', Pathname.new('/.').cleanpath.to_s) - assert_equal('/', Pathname.new('/..').cleanpath.to_s) - assert_equal('/a', Pathname.new('/a').cleanpath.to_s) - assert_equal('.', Pathname.new('./').cleanpath.to_s) - assert_equal('..', Pathname.new('../').cleanpath.to_s) - assert_equal('a', Pathname.new('a/').cleanpath.to_s) - - assert_equal('a/b', Pathname.new('a//b').cleanpath.to_s) - assert_equal('a', Pathname.new('a/.').cleanpath.to_s) - assert_equal('a', Pathname.new('a/./').cleanpath.to_s) - assert_equal('.', Pathname.new('a/../').cleanpath.to_s) - assert_equal('/a', Pathname.new('/a/.').cleanpath.to_s) - assert_equal('..', Pathname.new('./..').cleanpath.to_s) - assert_equal('..', Pathname.new('../.').cleanpath.to_s) - assert_equal('..', Pathname.new('./../').cleanpath.to_s) - assert_equal('..', Pathname.new('.././').cleanpath.to_s) - assert_equal('/', Pathname.new('/./..').cleanpath.to_s) - assert_equal('/', Pathname.new('/../.').cleanpath.to_s) - assert_equal('/', Pathname.new('/./../').cleanpath.to_s) - assert_equal('/', Pathname.new('/.././').cleanpath.to_s) - - assert_equal('a/b/c', Pathname.new('a/b/c').cleanpath.to_s) - assert_equal('b/c', Pathname.new('./b/c').cleanpath.to_s) - assert_equal('a/c', Pathname.new('a/./c').cleanpath.to_s) - assert_equal('a/b', Pathname.new('a/b/.').cleanpath.to_s) - assert_equal('.', Pathname.new('a/../.').cleanpath.to_s) - - assert_equal('/a', Pathname.new('/../.././../a').cleanpath.to_s) - assert_equal('../../d', Pathname.new('a/b/../../../../c/../d').cleanpath.to_s) - end - - def test_destructive_update - path = Pathname.new("a") - path.to_s.replace "b" - assert_equal(Pathname.new("a"), path) - end - - def test_null_character - assert_raise(ArgumentError) { Pathname.new("\0") } - end - - def assert_relpath(result, dest, base) - assert_equal(Pathname.new(result), - Pathname.new(dest).relative_path_from(Pathname.new(base))) - end - - def assert_relpath_err(dest, base) - assert_raise(ArgumentError) { - Pathname.new(dest).relative_path_from(Pathname.new(base)) - } - end - - def test_relative_path_from - assert_relpath("../a", "a", "b") - assert_relpath("../a", "a", "b/") - assert_relpath("../a", "a/", "b") - assert_relpath("../a", "a/", "b/") - assert_relpath("../a", "/a", "/b") - assert_relpath("../a", "/a", "/b/") - assert_relpath("../a", "/a/", "/b") - assert_relpath("../a", "/a/", "/b/") - - assert_relpath("../b", "a/b", "a/c") - assert_relpath("../a", "../a", "../b") - - assert_relpath("a", "a", ".") - assert_relpath("..", ".", "a") - - assert_relpath(".", ".", ".") - assert_relpath(".", "..", "..") - assert_relpath("..", "..", ".") - - assert_relpath("c/d", "/a/b/c/d", "/a/b") - assert_relpath("../..", "/a/b", "/a/b/c/d") - assert_relpath("../../../../e", "/e", "/a/b/c/d") - assert_relpath("../b/c", "a/b/c", "a/d") - - assert_relpath("../a", "/../a", "/b") - assert_relpath("../../a", "../a", "b") - assert_relpath(".", "/a/../../b", "/b") - assert_relpath("..", "a/..", "a") - assert_relpath(".", "a/../b", "b") - - assert_relpath("a", "a", "b/..") - assert_relpath("b/c", "b/c", "b/..") - - assert_relpath_err("/", ".") - assert_relpath_err(".", "/") - assert_relpath_err("a", "..") - assert_relpath_err(".", "..") - end - - def assert_pathname_plus(a, b, c) - a = Pathname.new(a) - b = Pathname.new(b) - c = Pathname.new(c) - d = b + c - assert(a == d, - "#{b.inspect} + #{c.inspect}: #{a.inspect} expected but was #{d.inspect}") - end - - def test_plus - assert_pathname_plus('a/b', 'a', 'b') - assert_pathname_plus('a', 'a', '.') - assert_pathname_plus('b', '.', 'b') - assert_pathname_plus('.', '.', '.') - assert_pathname_plus('/b', 'a', '/b') - - assert_pathname_plus('/', '/', '..') - assert_pathname_plus('.', 'a', '..') - assert_pathname_plus('a', 'a/b', '..') - assert_pathname_plus('../..', '..', '..') - assert_pathname_plus('/c', '/', '../c') - assert_pathname_plus('c', 'a', '../c') - assert_pathname_plus('a/c', 'a/b', '../c') - assert_pathname_plus('../../c', '..', '../c') - end - - def test_taint - obj = Pathname.new("a"); assert_same(obj, obj.taint) - obj = Pathname.new("a"); assert_same(obj, obj.untaint) - - assert_equal(false, Pathname.new("a" ) .tainted?) - assert_equal(false, Pathname.new("a" ) .to_s.tainted?) - assert_equal(true, Pathname.new("a" ).taint .tainted?) - assert_equal(true, Pathname.new("a" ).taint.to_s.tainted?) - assert_equal(true, Pathname.new("a".taint) .tainted?) - assert_equal(true, Pathname.new("a".taint) .to_s.tainted?) - assert_equal(true, Pathname.new("a".taint).taint .tainted?) - assert_equal(true, Pathname.new("a".taint).taint.to_s.tainted?) - - str = "a" - path = Pathname.new(str) - str.taint - assert_equal(false, path .tainted?) - assert_equal(false, path.to_s.tainted?) - end - - def test_untaint - obj = Pathname.new("a"); assert_same(obj, obj.untaint) - - assert_equal(false, Pathname.new("a").taint.untaint .tainted?) - assert_equal(false, Pathname.new("a").taint.untaint.to_s.tainted?) - - str = "a".taint - path = Pathname.new(str) - str.untaint - assert_equal(true, path .tainted?) - assert_equal(true, path.to_s.tainted?) - end - - def test_freeze - obj = Pathname.new("a"); assert_same(obj, obj.freeze) - - assert_equal(false, Pathname.new("a" ) .frozen?) - assert_equal(false, Pathname.new("a".freeze) .frozen?) - assert_equal(true, Pathname.new("a" ).freeze .frozen?) - assert_equal(true, Pathname.new("a".freeze).freeze .frozen?) - assert_equal(false, Pathname.new("a" ) .to_s.frozen?) - assert_equal(false, Pathname.new("a".freeze) .to_s.frozen?) - assert_equal(false, Pathname.new("a" ).freeze.to_s.frozen?) - assert_equal(false, Pathname.new("a".freeze).freeze.to_s.frozen?) - end - - def test_to_s - str = "a" - obj = Pathname.new(str) - assert_equal(str, obj.to_s) - assert_not_same(str, obj.to_s) - assert_not_same(obj.to_s, obj.to_s) - end - - def test_kernel_open - count = 0 - result = Kernel.open(Pathname.new(__FILE__)) {|f| - assert(File.identical?(__FILE__, f)) - count += 1 - 2 - } - assert_equal(1, count) - assert_equal(2, result) - end +module Kernel + # create a pathname object. + # + # This method is available since 1.8.5. + def Pathname(path) # :doc: + Pathname.new(path) end + private :Pathname end Index: ping.rb =================================================================== --- ping.rb (revision 2900) +++ ping.rb (working copy) @@ -1,46 +1,46 @@ # -# ping.rb -- check a host for upness +# = ping.rb: Check a host for upness # -#= SYNOPSIS +# Author:: Yukihiro Matsumoto +# Documentation:: Konrad Meyer +# +# Performs the function of the basic network testing tool, ping. +# See: Ping. # -# require 'ping' -# print "'jimmy' is alive and kicking\n" if Ping.pingecho('jimmy', 10) ; + +require 'timeout' +require "socket" + +# +# Ping contains routines to test for the reachability of remote hosts. +# Currently the only routine implemented is pingecho(). # -#= DESCRIPTION -# -# This module contains routines to test for the reachability of remote hosts. -# Currently the only routine implemented is pingecho(). -# -# pingecho() uses a TCP echo (I an ICMP one) to determine if the +# Ping.pingecho uses a TCP echo (not an ICMP echo) to determine if the # remote host is reachable. This is usually adequate to tell that a remote -# host is available to rsh(1), ftp(1), or telnet(1) onto. +# host is available to telnet, ftp, or ssh to. # -#== Parameters +# Warning: Ping.pingecho may block for a long time if DNS resolution is +# slow. Requiring 'resolv-replace' allows non-blocking name resolution. # -# : hostname +# Usage: +# +# require 'ping' # -# The remote host to check, specified either as a hostname or as an -# IP address. +# puts "'jimmy' is alive and kicking" if Ping.pingecho('jimmy', 10) # -# : timeout -# -# The timeout in seconds. If not specified it will default to 5 seconds. -# -# : service -# -# The service port to connect. The default is "echo". -# -#= WARNING -# -# pingecho() uses user-level thread to implement the timeout, so it may block -# for long period if named does not respond for some reason. -# -#=end +module Ping -require 'timeout' -require "socket" - -module Ping + # + # Return true if we can open a connection to the hostname or IP address + # +host+ on port +service+ (which defaults to the "echo" port) waiting up + # to +timeout+ seconds. + # + # Example: + # + # require 'ping' + # + # Ping.pingecho "google.com", 10, 80 + # def pingecho(host, timeout=5, service="echo") begin timeout(timeout) do Index: open-uri.rb =================================================================== --- open-uri.rb (revision 2900) +++ open-uri.rb (working copy) @@ -1,59 +1,3 @@ -#= open-uri.rb -# -#open-uri.rb is easy-to-use wrapper for net/http, net/https and net/ftp. -# -#== Example -# -#It is possible to open http/https/ftp URL as usual a file: -# -# open("http://www.ruby-lang.org/") {|f| -# f.each_line {|line| p line} -# } -# -#The opened file has several methods for meta information as follows since -#it is extended by OpenURI::Meta. -# -# open("http://www.ruby-lang.org/en") {|f| -# f.each_line {|line| p line} -# p f.base_uri # -# p f.content_type # "text/html" -# p f.charset # "iso-8859-1" -# p f.content_encoding # [] -# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002 -# } -# -#Additional header fields can be specified by an optional hash argument. -# -# open("http://www.ruby-lang.org/en/", -# "User-Agent" => "Ruby/#{RUBY_VERSION}", -# "From" => "foo@bar.invalid", -# "Referer" => "http://www.ruby-lang.org/") {|f| -# ... -# } -# -#The environment variables such as http_proxy, https_proxy and ftp_proxy -#are in effect by default. :proxy => nil disables proxy. -# -# open("http://www.ruby-lang.org/en/raa.html", -# :proxy => nil) {|f| -# ... -# } -# -#URI objects can be opened in similar way. -# -# uri = URI.parse("http://www.ruby-lang.org/en/") -# uri.open {|f| -# ... -# } -# -#URI objects can be read directly. -#The returned string is also extended by OpenURI::Meta. -# -# str = uri.read -# p str.base_uri -# -#Author:: Tanaka Akira - require 'uri' require 'stringio' require 'time' @@ -91,6 +35,59 @@ module_function :open end +# OpenURI is an easy-to-use wrapper for net/http, net/https and net/ftp. +# +#== Example +# +# It is possible to open http/https/ftp URL as usual like opening a file: +# +# open("http://www.ruby-lang.org/") {|f| +# f.each_line {|line| p line} +# } +# +# The opened file has several methods for meta information as follows since +# it is extended by OpenURI::Meta. +# +# open("http://www.ruby-lang.org/en") {|f| +# f.each_line {|line| p line} +# p f.base_uri # +# p f.content_type # "text/html" +# p f.charset # "iso-8859-1" +# p f.content_encoding # [] +# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002 +# } +# +# Additional header fields can be specified by an optional hash argument. +# +# open("http://www.ruby-lang.org/en/", +# "User-Agent" => "Ruby/#{RUBY_VERSION}", +# "From" => "foo@bar.invalid", +# "Referer" => "http://www.ruby-lang.org/") {|f| +# # ... +# } +# +# The environment variables such as http_proxy, https_proxy and ftp_proxy +# are in effect by default. :proxy => nil disables proxy. +# +# open("http://www.ruby-lang.org/en/raa.html", :proxy => nil) {|f| +# # ... +# } +# +# URI objects can be opened in a similar way. +# +# uri = URI.parse("http://www.ruby-lang.org/en/") +# uri.open {|f| +# # ... +# } +# +# URI objects can be read directly. The returned string is also extended by +# OpenURI::Meta. +# +# str = uri.read +# p str.base_uri +# +# Author:: Tanaka Akira + module OpenURI Options = { :proxy => true, Index: fileutils.rb =================================================================== --- fileutils.rb (revision 2900) +++ fileutils.rb (working copy) @@ -1,7 +1,7 @@ # # = fileutils.rb # -# Copyright (c) 2000-2005 Minero Aoki +# Copyright (c) 2000-2006 Minero Aoki # # This program is free software. # You can distribute/modify this program under the same terms of ruby. @@ -116,7 +116,7 @@ # FileUtils.cd('/', :verbose => true) # chdir and report it # def cd(dir, options = {}, &block) # :yield: dir - fu_check_options options, :verbose + fu_check_options options, OPT_TABLE['cd'] fu_output_message "cd #{dir}" if options[:verbose] Dir.chdir(dir, &block) fu_output_message 'cd -' if options[:verbose] and block @@ -127,7 +127,7 @@ module_function :chdir OPT_TABLE['cd'] = - OPT_TABLE['chdir'] = %w( verbose ) + OPT_TABLE['chdir'] = [:verbose] # # Options: (none) @@ -163,7 +163,7 @@ # FileUtils.mkdir 'tmp', :mode => 0700 # def mkdir(list, options = {}) - fu_check_options options, :mode, :noop, :verbose + fu_check_options options, OPT_TABLE['mkdir'] list = fu_list(list) fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] return if options[:noop] @@ -174,7 +174,7 @@ end module_function :mkdir - OPT_TABLE['mkdir'] = %w( noop verbose mode ) + OPT_TABLE['mkdir'] = [:mode, :noop, :verbose] # # Options: mode noop verbose @@ -193,7 +193,7 @@ # You can pass several directories at a time in a list. # def mkdir_p(list, options = {}) - fu_check_options options, :mode, :noop, :verbose + fu_check_options options, OPT_TABLE['mkdir_p'] list = fu_list(list) fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] return *list if options[:noop] @@ -232,7 +232,7 @@ OPT_TABLE['mkdir_p'] = OPT_TABLE['mkpath'] = - OPT_TABLE['makedirs'] = %w( noop verbose ) + OPT_TABLE['makedirs'] = [:mode, :noop, :verbose] def fu_mkdir(path, mode) #:nodoc: path = path.sub(%r\z>, '') @@ -256,7 +256,7 @@ # FileUtils.rmdir 'somedir', :verbose => true, :noop => true # def rmdir(list, options = {}) - fu_check_options options, :noop, :verbose + fu_check_options options, OPT_TABLE['rmdir'] list = fu_list(list) fu_output_message "rmdir #{list.join ' '}" if options[:verbose] return if options[:noop] @@ -266,7 +266,7 @@ end module_function :rmdir - OPT_TABLE['rmdir'] = %w( noop verbose ) + OPT_TABLE['rmdir'] = [:noop, :verbose] # # Options: force noop verbose @@ -291,7 +291,7 @@ # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. # def ln(src, dest, options = {}) - fu_check_options options, :force, :noop, :verbose + fu_check_options options, OPT_TABLE['ln'] fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] fu_each_src_dest0(src, dest) do |s,d| @@ -305,7 +305,7 @@ module_function :link OPT_TABLE['ln'] = - OPT_TABLE['link'] = %w( noop verbose force ) + OPT_TABLE['link'] = [:force, :noop, :verbose] # # Options: force noop verbose @@ -330,7 +330,7 @@ # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin' # def ln_s(src, dest, options = {}) - fu_check_options options, :force, :noop, :verbose + fu_check_options options, OPT_TABLE['ln_s'] fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] fu_each_src_dest0(src, dest) do |s,d| @@ -344,7 +344,7 @@ module_function :symlink OPT_TABLE['ln_s'] = - OPT_TABLE['symlink'] = %w( noop verbose force ) + OPT_TABLE['symlink'] = [:force, :noop, :verbose] # # Options: noop verbose @@ -353,14 +353,14 @@ # #ln_s(src, dest, :force) # def ln_sf(src, dest, options = {}) - fu_check_options options, :noop, :verbose + fu_check_options options, OPT_TABLE['ln_sf'] options = options.dup options[:force] = true ln_s src, dest, options end module_function :ln_sf - OPT_TABLE['ln_sf'] = %w( noop verbose ) + OPT_TABLE['ln_sf'] = [:noop, :verbose] # # Options: preserve noop verbose @@ -376,7 +376,7 @@ # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink # def cp(src, dest, options = {}) - fu_check_options options, :preserve, :noop, :verbose + fu_check_options options, OPT_TABLE['cp'] fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] fu_each_src_dest(src, dest) do |s, d| @@ -389,10 +389,10 @@ module_function :copy OPT_TABLE['cp'] = - OPT_TABLE['copy'] = %w( noop verbose preserve ) + OPT_TABLE['copy'] = [:preserve, :noop, :verbose] # - # Options: preserve noop verbose dereference_root + # Options: preserve noop verbose dereference_root remove_destination # # Copies +src+ to +dest+. If +src+ is a directory, this method copies # all its contents recursively. If +dest+ is a directory, copies @@ -415,17 +415,18 @@ # # but this doesn't. # def cp_r(src, dest, options = {}) - fu_check_options options, :preserve, :noop, :verbose, :dereference_root - fu_output_message "cp -r#{options[:preserve] ? 'p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] + fu_check_options options, OPT_TABLE['cp_r'] + fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] options[:dereference_root] = true unless options.key?(:dereference_root) fu_each_src_dest(src, dest) do |s, d| - copy_entry s, d, options[:preserve], options[:dereference_root] + copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination] end end module_function :cp_r - OPT_TABLE['cp_r'] = %w( noop verbose preserve dereference_root ) + OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose, + :dereference_root, :remove_destination] # # Copies a file system entry +src+ to +dest+. @@ -441,9 +442,12 @@ # # If +dereference_root+ is true, this method dereference tree root. # - def copy_entry(src, dest, preserve = false, dereference_root = false) + # If +remove_destination+ is true, this method removes each destination file before copy. + # + def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) Entry_.new(src, nil, dereference_root).traverse do |ent| destent = Entry_.new(dest, ent.rel, false) + File.unlink destent.path if remove_destination && File.file?(destent.path) ent.copy destent.path ent.copy_metadata destent.path if preserve end @@ -484,7 +488,7 @@ # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true # def mv(src, dest, options = {}) - fu_check_options options, :force, :noop, :verbose + fu_check_options options, OPT_TABLE['mv'] fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] fu_each_src_dest(src, dest) do |s, d| @@ -513,7 +517,7 @@ module_function :move OPT_TABLE['mv'] = - OPT_TABLE['move'] = %w( noop verbose force ) + OPT_TABLE['move'] = [:force, :noop, :verbose] def rename_cannot_overwrite_file? #:nodoc: /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM @@ -531,7 +535,7 @@ # FileUtils.rm 'NotExistFile', :force => true # never raises exception # def rm(list, options = {}) - fu_check_options options, :force, :noop, :verbose + fu_check_options options, OPT_TABLE['rm'] list = fu_list(list) fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] return if options[:noop] @@ -546,7 +550,7 @@ module_function :remove OPT_TABLE['rm'] = - OPT_TABLE['remove'] = %w( noop verbose force ) + OPT_TABLE['remove'] = [:force, :noop, :verbose] # # Options: noop verbose @@ -556,7 +560,7 @@ # #rm(list, :force => true) # def rm_f(list, options = {}) - fu_check_options options, :noop, :verbose + fu_check_options options, OPT_TABLE['rm_f'] options = options.dup options[:force] = true rm list, options @@ -567,7 +571,7 @@ module_function :safe_unlink OPT_TABLE['rm_f'] = - OPT_TABLE['safe_unlink'] = %w( noop verbose ) + OPT_TABLE['safe_unlink'] = [:noop, :verbose] # # Options: force noop verbose secure @@ -591,7 +595,7 @@ # See also #remove_entry_secure. # def rm_r(list, options = {}) - fu_check_options options, :force, :noop, :verbose, :secure + fu_check_options options, OPT_TABLE['rm_r'] # options[:secure] = true unless options.key?(:secure) list = fu_list(list) fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] @@ -606,7 +610,7 @@ end module_function :rm_r - OPT_TABLE['rm_r'] = %w( noop verbose force secure ) + OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure] # # Options: noop verbose secure @@ -619,7 +623,7 @@ # Read the documentation of #rm_r first. # def rm_rf(list, options = {}) - fu_check_options options, :noop, :verbose, :secure + fu_check_options options, OPT_TABLE['rm_rf'] options = options.dup options[:force] = true rm_r list, options @@ -630,7 +634,7 @@ module_function :rmtree OPT_TABLE['rm_rf'] = - OPT_TABLE['rmtree'] = %w( noop verbose secure ) + OPT_TABLE['rmtree'] = [:noop, :verbose, :secure] # # This method removes a file system entry +path+. +path+ shall be a @@ -814,16 +818,17 @@ module_function :compare_stream # - # Options: mode noop verbose + # Options: mode preserve noop verbose # # If +src+ is not same as +dest+, copies it and changes the permission # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. + # This method removes destination before copy. # # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true # def install(src, dest, options = {}) - fu_check_options options, :mode, :preserve, :noop, :verbose + fu_check_options options, OPT_TABLE['install'] fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] return if options[:noop] fu_each_src_dest(src, dest) do |s, d| @@ -838,7 +843,7 @@ end module_function :install - OPT_TABLE['install'] = %w( noop verbose preserve mode ) + OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose] # # Options: noop verbose @@ -851,7 +856,7 @@ # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true # def chmod(mode, list, options = {}) - fu_check_options options, :noop, :verbose + fu_check_options options, OPT_TABLE['chmod'] list = fu_list(list) fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose] return if options[:noop] @@ -861,7 +866,7 @@ end module_function :chmod - OPT_TABLE['chmod'] = %w( noop verbose ) + OPT_TABLE['chmod'] = [:noop, :verbose] # # Options: noop verbose force @@ -872,7 +877,7 @@ # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" # def chmod_R(mode, list, options = {}) - fu_check_options options, :noop, :verbose, :force + fu_check_options options, OPT_TABLE['chmod_R'] list = fu_list(list) fu_output_message sprintf('chmod -R%s %o %s', (options[:force] ? 'f' : ''), @@ -890,7 +895,7 @@ end module_function :chmod_R - OPT_TABLE['chmod_R'] = %w( noop verbose ) + OPT_TABLE['chmod_R'] = [:noop, :verbose, :force] # # Options: noop verbose @@ -905,7 +910,7 @@ # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true # def chown(user, group, list, options = {}) - fu_check_options options, :noop, :verbose + fu_check_options options, OPT_TABLE['chown'] list = fu_list(list) fu_output_message sprintf('chown %s%s', [user,group].compact.join(':') + ' ', @@ -919,7 +924,7 @@ end module_function :chown - OPT_TABLE['chown'] = %w( noop verbose ) + OPT_TABLE['chown'] = [:noop, :verbose] # # Options: noop verbose force @@ -934,7 +939,7 @@ # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true # def chown_R(user, group, list, options = {}) - fu_check_options options, :noop, :verbose, :force + fu_check_options options, OPT_TABLE['chown_R'] list = fu_list(list) fu_output_message sprintf('chown -R%s %s%s', (options[:force] ? 'f' : ''), @@ -956,7 +961,7 @@ end module_function :chown_R - OPT_TABLE['chown_R'] = %w( noop verbose ) + OPT_TABLE['chown_R'] = [:noop, :verbose, :force] begin require 'etc' @@ -1004,7 +1009,7 @@ # FileUtils.touch Dir.glob('*.c'); system 'make' # def touch(list, options = {}) - fu_check_options options, :noop, :verbose + fu_check_options options, OPT_TABLE['touch'] list = fu_list(list) fu_output_message "touch #{list.join ' '}" if options[:verbose] return if options[:noop] @@ -1021,7 +1026,7 @@ end module_function :touch - OPT_TABLE['touch'] = %w( noop verbose ) + OPT_TABLE['touch'] = [:noop, :verbose] private @@ -1414,10 +1419,10 @@ end private_module_function :fu_have_st_ino? - def fu_check_options(options, *optdecl) #:nodoc: + def fu_check_options(options, optdecl) #:nodoc: h = options.dup - optdecl.each do |name| - h.delete name + optdecl.each do |opt| + h.delete opt end raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? end @@ -1443,8 +1448,6 @@ end private_module_function :fu_output_message - METHODS = singleton_methods() - ['private_module_function'] - # # Returns an Array of method names which have any options. # @@ -1460,7 +1463,7 @@ # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] # def FileUtils.options - OPT_TABLE.values.flatten.uniq + OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } end # @@ -1472,7 +1475,7 @@ # def FileUtils.have_option?(mid, opt) li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" - li.include?(opt.to_s) + li.include?(opt) end # @@ -1481,7 +1484,7 @@ # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] # def FileUtils.options_of(mid) - OPT_TABLE[mid.to_s] + OPT_TABLE[mid.to_s].map {|sym| sym.to_s } end # @@ -1490,9 +1493,12 @@ # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] # def FileUtils.collect_method(opt) - OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt.to_s) } + OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } end + METHODS = singleton_methods() - %w( private_module_function + commands options have_option? options_of collect_method ) + # # This module has all methods of FileUtils module, but it outputs messages # before acting. This equates to passing the :verbose flag to @@ -1502,7 +1508,7 @@ include FileUtils @fileutils_output = $stderr @fileutils_label = '' - ::FileUtils.collect_method('verbose').each do |name| + ::FileUtils.collect_method(:verbose).each do |name| module_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{name}(*args) super(*fu_update_option(args, :verbose => true)) @@ -1527,7 +1533,7 @@ include FileUtils @fileutils_output = $stderr @fileutils_label = '' - ::FileUtils.collect_method('noop').each do |name| + ::FileUtils.collect_method(:noop).each do |name| module_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{name}(*args) super(*fu_update_option(args, :noop => true)) @@ -1553,7 +1559,7 @@ include FileUtils @fileutils_output = $stderr @fileutils_label = '' - ::FileUtils.collect_method('noop').each do |name| + ::FileUtils.collect_method(:noop).each do |name| module_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{name}(*args) super(*fu_update_option(args, :noop => true, :verbose => true)) Index: cgi.rb =================================================================== --- cgi.rb (revision 2900) +++ cgi.rb (working copy) @@ -708,13 +708,13 @@ require "nkf" case options["charset"] when /iso-2022-jp/ni - content = NKF::nkf('-j', content) + content = NKF::nkf('-m0 -x -j', content) options["language"] = "ja" unless options.has_key?("language") when /euc-jp/ni - content = NKF::nkf('-e', content) + content = NKF::nkf('-m0 -x -e', content) options["language"] = "ja" unless options.has_key?("language") when /shift_jis/ni - content = NKF::nkf('-s', content) + content = NKF::nkf('-m0 -x -s', content) options["language"] = "ja" unless options.has_key?("language") end end @@ -967,8 +967,10 @@ def read_multipart(boundary, content_length) params = Hash.new([]) boundary = "--" + boundary + quoted_boundary = Regexp.quote(boundary, "n") buf = "" bufsize = 10 * 1024 + boundary_end="" # start multipart/form-data stdinput.binmode if defined? stdinput.binmode @@ -997,7 +999,7 @@ end body.binmode if defined? body.binmode - until head and /#{boundary}(?:#{EOL}|--)/n.match(buf) + until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf) if (not head) and /#{EOL}#{EOL}/n.match(buf) buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do @@ -1017,18 +1019,19 @@ else stdinput.read(content_length) end - if c.nil? + if c.nil? || c.empty? raise EOFError, "bad content body" end buf.concat(c) content_length -= c.size end - buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{boundary}([\r\n]{1,2}|--)/n) do + buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do body.print $1 if "--" == $2 content_length = -1 end + boundary_end = $2.dup "" end @@ -1060,8 +1063,9 @@ params[name] = [body] end break if buf.size == 0 - break if content_length === -1 + break if content_length == -1 end + raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/ params end # read_multipart Index: ipaddr.rb =================================================================== --- ipaddr.rb (revision 2900) +++ ipaddr.rb (working copy) @@ -7,29 +7,9 @@ # You can redistribute and/or modify it under the same terms as Ruby. # # $Id$ -#-- +# # TODO: # - scope_id support -#++ -# -#== Example -# -# require 'ipaddr' -# -# ipaddr1 = IPAddr.new "3ffe:505:2::1" -# -# p ipaddr1 #=> # -# -# p ipaddr1.to_s #=> "3ffe:505:2::1" -# -# ipaddr2 = ipaddr1.mask(48) #=> # -# -# p ipaddr2.to_s #=> "3ffe:505:2::" -# -# ipaddr3 = IPAddr.new "192.168.2.0/24" -# -# p ipaddr3 #=> # - require 'socket' unless Socket.const_defined? "AF_INET6" @@ -75,8 +55,27 @@ end end -# IPAddr provides a set of methods to manipulate an IP address. Both -# IPv4 and IPv6 are supported. +# IPAddr provides a set of methods to manipulate an IP address. Both IPv4 and +# IPv6 are supported. +# +# == Example +# +# require 'ipaddr' +# +# ipaddr1 = IPAddr.new "3ffe:505:2::1" +# +# p ipaddr1 #=> # +# +# p ipaddr1.to_s #=> "3ffe:505:2::1" +# +# ipaddr2 = ipaddr1.mask(48) #=> # +# +# p ipaddr2.to_s #=> "3ffe:505:2::" +# +# ipaddr3 = IPAddr.new "192.168.2.0/24" +# +# p ipaddr3 #=> # + class IPAddr IN4MASK = 0xffffffff Index: rss/xmlparser.rb =================================================================== --- rss/xmlparser.rb (revision 2900) +++ rss/xmlparser.rb (working copy) @@ -59,11 +59,13 @@ class XMLParserParser < BaseParser + class << self + def listener + XMLParserListener + end + end + private - def listener - XMLParserListener - end - def _parse begin parser = REXMLLikeXMLParser.new Index: rss/taxonomy.rb =================================================================== --- rss/taxonomy.rb (revision 2900) +++ rss/taxonomy.rb (working copy) @@ -1,32 +1,145 @@ -# Experimental - require "rss/1.0" +require "rss/dublincore" module RSS TAXO_PREFIX = "taxo" - TAXO_NS = "http://purl.org/rss/1.0/modules/taxonomy/" + TAXO_URI = "http://purl.org/rss/1.0/modules/taxonomy/" - Element.install_ns(TAXO_PREFIX, TAXO_NS) + RDF.install_ns(TAXO_PREFIX, TAXO_URI) TAXO_ELEMENTS = [] %w(link).each do |name| full_name = "#{TAXO_PREFIX}_#{name}" - BaseListener.install_get_text_element(TAXO_NS, name, "#{full_name}=") + BaseListener.install_get_text_element(TAXO_URI, name, "#{full_name}=") TAXO_ELEMENTS << "#{TAXO_PREFIX}_#{name}" end + + %w(topic topics).each do |name| + class_name = Utils.to_class_name(name) + BaseListener.install_class_name(TAXO_URI, name, "Taxonomy#{class_name}") + TAXO_ELEMENTS << "#{TAXO_PREFIX}_#{name}" + end + + module TaxonomyTopicsModel + extend BaseModel - module TaxonomyModel - attr_writer(*%w(title description creator subject publisher - contributor date format identifier source - language relation coverage rights - ).collect{|name| "#{TAXO_PREFIX}_#{name}"}) + def self.append_features(klass) + super + + klass.install_must_call_validator(TAXO_PREFIX, TAXO_URI) + %w(topics).each do |name| + klass.install_have_child_element(name, TAXO_URI, "?", + "#{TAXO_PREFIX}_#{name}") + end + end + + class TaxonomyTopics < Element + include RSS10 + + Bag = ::RSS::RDF::Bag + + class << self + def required_prefix + TAXO_PREFIX + end + + def required_uri + TAXO_URI + end + end + + @tag_name = "topics" + + install_have_child_element("Bag", RDF::URI, nil) + install_must_call_validator('rdf', RDF::URI) + + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.Bag = args[0] + end + self.Bag ||= Bag.new + end + + def full_name + tag_name_with_prefix(TAXO_PREFIX) + end + + def maker_target(target) + target.taxo_topics + end + + def resources + if @Bag + @Bag.lis.collect do |li| + li.resource + end + else + [] + end + end + end end - class Channel; extend TaxonomyModel; end - class Item; extend TaxonomyModel; end - class Image; extend TaxonomyModel; end - class TextInput; extend TaxonomyModel; end - + module TaxonomyTopicModel + extend BaseModel + + def self.append_features(klass) + super + var_name = "#{TAXO_PREFIX}_topic" + klass.install_have_children_element("topic", TAXO_URI, "*", var_name) + end + + class TaxonomyTopic < Element + include RSS10 + + include DublinCoreModel + include TaxonomyTopicsModel + + class << self + def required_prefix + TAXO_PREFIX + end + + def required_uri + TAXO_URI + end + end + + @tag_name = "topic" + + install_get_attribute("about", ::RSS::RDF::URI, true, nil, nil, + "#{RDF::PREFIX}:about") + install_text_element("link", TAXO_URI, "?", "#{TAXO_PREFIX}_link") + + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] + end + end + + def full_name + tag_name_with_prefix(TAXO_PREFIX) + end + + def maker_target(target) + target.new_taxo_topic + end + end + end + + class RDF + include TaxonomyTopicModel + class Channel + include TaxonomyTopicsModel + end + class Item; include TaxonomyTopicsModel; end + end end Index: rss/image.rb =================================================================== --- rss/image.rb (revision 2900) +++ rss/image.rb (working copy) @@ -8,10 +8,20 @@ RDF.install_ns(IMAGE_PREFIX, IMAGE_URI) + IMAGE_ELEMENTS = [] + + %w(item favicon).each do |name| + class_name = Utils.to_class_name(name) + BaseListener.install_class_name(IMAGE_URI, name, "Image#{class_name}") + IMAGE_ELEMENTS << "#{IMAGE_PREFIX}_#{name}" + end + module ImageModelUtils - def validate_one_tag_name(name, tags) - invalid = tags.find {|tag| tag != name} - raise UnknownTagError.new(invalid, IMAGE_URI) if invalid + def validate_one_tag_name(ignore_unknown_element, name, tags) + if !ignore_unknown_element + invalid = tags.find {|tag| tag != name} + raise UnknownTagError.new(invalid, IMAGE_URI) if invalid + end raise TooMuchTagError.new(name, tag_name) if tags.size > 1 end end @@ -23,17 +33,17 @@ def self.append_features(klass) super - klass.install_have_child_element("#{IMAGE_PREFIX}_item") + klass.install_have_child_element("item", IMAGE_URI, "?", + "#{IMAGE_PREFIX}_item") + klass.install_must_call_validator(IMAGE_PREFIX, IMAGE_URI) end - def image_validate(tags) - validate_one_tag_name("item", tags) - end - - class Item < Element + class ImageItem < Element include RSS10 include DublinCoreModel + @tag_name = "item" + class << self def required_prefix IMAGE_PREFIX @@ -43,80 +53,45 @@ IMAGE_URI end end - + + install_must_call_validator(IMAGE_PREFIX, IMAGE_URI) + [ ["about", ::RSS::RDF::URI, true], ["resource", ::RSS::RDF::URI, false], ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{::RSS::RDF::PREFIX}:#{name}") end %w(width height).each do |tag| full_name = "#{IMAGE_PREFIX}_#{tag}" - install_text_element(full_name) + disp_name = "#{IMAGE_PREFIX}:#{tag}" + install_text_element(tag, IMAGE_URI, "?", + full_name, :integer, disp_name) BaseListener.install_get_text_element(IMAGE_URI, tag, "#{full_name}=") end - def initialize(about=nil, resource=nil) - super() - @about = about - @resource = resource - end - - def full_name - tag_name_with_prefix(IMAGE_PREFIX) - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - - alias _image_width= image_width= - def image_width=(new_value) - if @do_validate - self._image_width = Integer(new_value) - else - self._image_width = new_value.to_i - end - end - - alias _image_height= image_height= - def image_height=(new_value) - if @do_validate - self._image_height = Integer(new_value) - else - self._image_height = new_value.to_i - end - end - alias width= image_width= alias width image_width alias height= image_height= alias height image_height - private - def _tags - [ - [IMAGE_URI, 'width'], - [IMAGE_URI, 'height'], - ].delete_if do |uri, name| - send(name).nil? + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] + self.resource = args[1] end end - - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:about", true, "about"], - ["#{::RSS::RDF::PREFIX}:resource", false, "resource"], - ] + + def full_name + tag_name_with_prefix(IMAGE_PREFIX) end + private def maker_target(target) target.image_item end @@ -136,18 +111,18 @@ super unless klass.class == Module - klass.install_have_child_element("#{IMAGE_PREFIX}_favicon") + klass.install_have_child_element("favicon", IMAGE_URI, "?", + "#{IMAGE_PREFIX}_favicon") + klass.install_must_call_validator(IMAGE_PREFIX, IMAGE_URI) end end - def image_validate(tags) - validate_one_tag_name("favicon", tags) - end - - class Favicon < Element + class ImageFavicon < Element include RSS10 include DublinCoreModel + @tag_name = "favicon" + class << self def required_prefix IMAGE_PREFIX @@ -157,45 +132,47 @@ IMAGE_URI end end - + [ - ["about", ::RSS::RDF::URI, true], - ["size", IMAGE_URI, true], - ].each do |name, uri, required| - install_get_attribute(name, uri, required) + ["about", ::RSS::RDF::URI, true, ::RSS::RDF::PREFIX], + ["size", IMAGE_URI, true, IMAGE_PREFIX], + ].each do |name, uri, required, prefix| + install_get_attribute(name, uri, required, nil, nil, + "#{prefix}:#{name}") end + AVAILABLE_SIZES = %w(small medium large) + alias_method :_size=, :size= + private :_size= + def size=(new_value) + if @do_validate and !new_value.nil? + new_value = new_value.strip + unless AVAILABLE_SIZES.include?(new_value) + attr_name = "#{IMAGE_PREFIX}:size" + raise NotAvailableValueError.new(full_name, new_value, attr_name) + end + end + __send__(:_size=, new_value) + end + alias image_size= size= alias image_size size - def initialize(about=nil, size=nil) - super() - @about = about - @size = size + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] + self.size = args[1] + end end def full_name tag_name_with_prefix(IMAGE_PREFIX) end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end private - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:about", true, "about"], - ["#{IMAGE_PREFIX}:size", true, "size"], - ] - end - def maker_target(target) target.image_favicon end Index: rss/syndication.rb =================================================================== --- rss/syndication.rb (revision 2900) +++ rss/syndication.rb (working copy) @@ -15,14 +15,21 @@ def self.append_features(klass) super - - klass.module_eval(<<-EOC, *get_file_and_line_from_caller(1)) - %w(updatePeriod updateFrequency).each do |name| - install_text_element("\#{SY_PREFIX}_\#{name}") + + klass.install_must_call_validator(SY_PREFIX, SY_URI) + klass.module_eval do + [ + ["updatePeriod"], + ["updateFrequency", :positive_integer] + ].each do |name, type| + install_text_element(name, SY_URI, "?", + "#{SY_PREFIX}_#{name}", type, + "#{SY_PREFIX}:#{name}") end %w(updateBase).each do |name| - install_date_element("\#{SY_PREFIX}_\#{name}", 'w3cdtf', name) + install_date_element(name, SY_URI, "?", + "#{SY_PREFIX}_#{name}", 'w3cdtf', name) end alias_method(:_sy_updatePeriod=, :sy_updatePeriod=) @@ -31,27 +38,7 @@ validate_sy_updatePeriod(new_value) if @do_validate self._sy_updatePeriod = new_value end - - alias_method(:_sy_updateFrequency=, :sy_updateFrequency=) - def sy_updateFrequency=(new_value) - validate_sy_updateFrequency(new_value) if @do_validate - self._sy_updateFrequency = new_value.to_i - end - EOC - end - - def sy_validate(tags) - counter = {} - ELEMENTS.each do |name| - counter[name] = 0 end - - tags.each do |tag| - key = "#{SY_PREFIX}_#{tag}" - raise UnknownTagError.new(tag, SY_URI) unless counter.has_key?(key) - counter[key] += 1 - raise TooMuchTagError.new(tag, tag_name) if counter[key] > 1 - end end private @@ -61,15 +48,6 @@ raise NotAvailableValueError.new("updatePeriod", value) end end - - SY_UPDATEFREQUENCY_AVAILABLE_RE = /\A\s*\+?\d+\s*\z/ - def validate_sy_updateFrequency(value) - value = value.to_s.strip - if SY_UPDATEFREQUENCY_AVAILABLE_RE !~ value - raise NotAvailableValueError.new("updateFrequency", value) - end - end - end class RDF Index: rss/trackback.rb =================================================================== --- rss/trackback.rb (revision 2900) +++ rss/trackback.rb (working copy) @@ -11,39 +11,30 @@ module TrackBackUtils private - def trackback_validate(tags) - counter = {} - %w(ping about).each do |name| - counter["#{TRACKBACK_PREFIX}_#{name}"] = 0 - end - - tags.each do |tag| - key = "#{TRACKBACK_PREFIX}_#{tag}" - raise UnknownTagError.new(tag, TRACKBACK_URI) unless counter.has_key?(key) - counter[key] += 1 - if tag != "about" and counter[key] > 1 - raise TooMuchTagError.new(tag, tag_name) - end - end - - if counter["#{TRACKBACK_PREFIX}_ping"].zero? and - counter["#{TRACKBACK_PREFIX}_about"].nonzero? + def trackback_validate(ignore_unknown_element, tags, uri) + return if tags.nil? + if tags.find {|tag| tag == "about"} and + !tags.find {|tag| tag == "ping"} raise MissingTagError.new("#{TRACKBACK_PREFIX}:ping", tag_name) end end end - + module BaseTrackBackModel + + ELEMENTS = %w(ping about) + def append_features(klass) super unless klass.class == Module klass.module_eval {include TrackBackUtils} + klass.install_must_call_validator(TRACKBACK_PREFIX, TRACKBACK_URI) %w(ping).each do |name| var_name = "#{TRACKBACK_PREFIX}_#{name}" - klass_name = name.capitalize - klass.install_have_child_element(var_name) + klass_name = "TrackBack#{Utils.to_class_name(name)}" + klass.install_have_child_element(name, TRACKBACK_URI, "?", var_name) klass.module_eval(<<-EOC, __FILE__, __LINE__) remove_method :#{var_name} def #{var_name} @@ -59,15 +50,16 @@ [%w(about s)].each do |name, postfix| var_name = "#{TRACKBACK_PREFIX}_#{name}" - klass_name = name.capitalize - klass.install_have_children_element(var_name) + klass_name = "TrackBack#{Utils.to_class_name(name)}" + klass.install_have_children_element(name, TRACKBACK_URI, "*", + var_name) klass.module_eval(<<-EOC, __FILE__, __LINE__) remove_method :#{var_name} def #{var_name}(*args) if args.empty? @#{var_name}.first and @#{var_name}.first.value else - ret = @#{var_name}.send("[]", *args) + ret = @#{var_name}.__send__("[]", *args) if ret.is_a?(Array) ret.collect {|x| x.value} else @@ -91,7 +83,7 @@ else new_val = Utils.new_with_value_if_need(#{klass_name}, new_val) end - @#{var_name}.send("[]=", *(args[0..-2] + [new_val])) + @#{var_name}.__send__("[]=", *(args[0..-2] + [new_val])) end end alias set_#{var_name} #{var_name}= @@ -105,7 +97,7 @@ extend BaseModel extend BaseTrackBackModel - class Ping < Element + class TrackBackPing < Element include RSS10 class << self @@ -119,41 +111,33 @@ end end - + + @tag_name = "ping" + [ ["resource", ::RSS::RDF::URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{::RSS::RDF::PREFIX}:#{name}") end alias_method(:value, :resource) alias_method(:value=, :resource=) - - def initialize(resource=nil) - super() - @resource = resource + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end end def full_name tag_name_with_prefix(TRACKBACK_PREFIX) end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:resource", true, "resource"], - ] - end - end - class About < Element + class TrackBackAbout < Element include RSS10 class << self @@ -168,37 +152,32 @@ end + @tag_name = "about" + [ ["resource", ::RSS::RDF::URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{::RSS::RDF::PREFIX}:#{name}") end alias_method(:value, :resource) alias_method(:value=, :resource=) - def initialize(resource=nil) - super() - @resource = resource + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end end def full_name tag_name_with_prefix(TRACKBACK_PREFIX) end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end private - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:resource", true, "resource"], - ] - end - def maker_target(abouts) abouts.new_about end @@ -214,9 +193,11 @@ extend BaseModel extend BaseTrackBackModel - class Ping < Element + class TrackBackPing < Element include RSS09 + @tag_name = "ping" + content_setup class << self @@ -234,9 +215,13 @@ alias_method(:value, :content) alias_method(:value=, :content=) - def initialize(content=nil) - super() - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end def full_name @@ -245,9 +230,11 @@ end - class About < Element + class TrackBackAbout < Element include RSS09 + @tag_name = "about" + content_setup class << self @@ -265,9 +252,13 @@ alias_method(:value, :content) alias_method(:value=, :content=) - def initialize(content=nil) - super() - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end def full_name @@ -287,4 +278,11 @@ end end + BaseTrackBackModel::ELEMENTS.each do |name| + class_name = Utils.to_class_name(name) + BaseListener.install_class_name(TRACKBACK_URI, name, + "TrackBack#{class_name}") + end + + BaseTrackBackModel::ELEMENTS.collect! {|name| "#{TRACKBACK_PREFIX}_#{name}"} end Index: rss/dublincore.rb =================================================================== --- rss/dublincore.rb (revision 2900) +++ rss/dublincore.rb (working copy) @@ -17,10 +17,10 @@ full_name = "#{DC_PREFIX}_#{name}" full_plural_name = "#{DC_PREFIX}_#{plural}" klass_name = "DublinCore#{Utils.to_class_name(name)}" + klass.install_must_call_validator(DC_PREFIX, DC_URI) + klass.install_have_children_element(name, DC_URI, "*", + full_name, full_plural_name) klass.module_eval(<<-EOC, *get_file_and_line_from_caller(0)) - install_have_children_element(#{full_name.dump}, - #{full_plural_name.dump}) - remove_method :#{full_name} remove_method :#{full_name}= remove_method :set_#{full_name} @@ -97,9 +97,13 @@ alias_method(:value, :content) alias_method(:value=, :content=) - def initialize(content=nil) - super() - self.content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end def full_name @@ -129,16 +133,6 @@ end EOC end - - def dc_validate(tags) - tags.each do |tag| - key = "#{DC_PREFIX}_#{tag}" - unless DublinCoreModel::ELEMENTS.include?(key) - raise UnknownTagError.new(tag, DC_URI) - end - end - end - end # For backward compatibility Index: rss/xml-stylesheet.rb =================================================================== --- rss/xml-stylesheet.rb (revision 2900) +++ rss/xml-stylesheet.rb (working copy) @@ -37,13 +37,13 @@ def initialize(*attrs) @do_validate = true ATTRIBUTES.each do |attr| - self.send("#{attr}=", nil) + __send__("#{attr}=", nil) end vars = ATTRIBUTES.dup vars.unshift(:do_validate) attrs.each do |name, value| if vars.include?(name.to_s) - self.send("#{name}=", value) + __send__("#{name}=", value) end end end @@ -53,8 +53,8 @@ if @href rv << %Q[] Index: rss/parser.rb =================================================================== --- rss/parser.rb (revision 2900) +++ rss/parser.rb (working copy) @@ -7,6 +7,10 @@ class NotWellFormedError < Error attr_reader :line, :element + + # Create a new NotWellFormedError for an error at +line+ + # in +element+. If a block is given the return value of + # the block ends up in the error message. def initialize(line=nil, element=nil) message = "This is not well formed XML" if element or line @@ -21,15 +25,16 @@ class XMLParserNotFound < Error def initialize - super("available XML parser does not found in " << + super("available XML parser was not found in " << "#{AVAILABLE_PARSER_LIBRARIES.inspect}.") end end class NotValidXMLParser < Error def initialize(parser) - super("#{parser} is not available XML parser. " << - "available XML parser is " << + super("#{parser} is not an available XML parser. " << + "Available XML parser"<< + (AVAILABLE_PARSERS.size > 1 ? "s are ": " is ") << "#{AVAILABLE_PARSERS.inspect}.") end end @@ -55,6 +60,8 @@ @@default_parser || AVAILABLE_PARSERS.first end + # Set @@default_parser to new_value if it is one of the + # available parsers. Else raise NotValidXMLParser error. def default_parser=(new_value) if AVAILABLE_PARSERS.include?(new_value) @@default_parser = new_value @@ -63,13 +70,13 @@ end end - def parse(rss, do_validate=true, ignore_unknown_element=true, parser_class=default_parser) + def parse(rss, do_validate=true, ignore_unknown_element=true, + parser_class=default_parser) parser = new(rss, parser_class) parser.do_validate = do_validate parser.ignore_unknown_element = ignore_unknown_element parser.parse end - end def_delegators(:@parser, :parse, :rss, @@ -82,6 +89,10 @@ end private + + # Try to get the XML associated with +rss+. + # Return +rss+ if it already looks like XML, or treat it as a URI, + # or a file to get the XML, def normalize_rss(rss) return rss if maybe_xml?(rss) @@ -96,10 +107,13 @@ end end + # maybe_xml? tests if source is a string that looks like XML. def maybe_xml?(source) source.is_a?(String) and / =~ source end + # Attempt to convert rss to a URI, but just return it if + # there's a ::URI::Error def to_uri(rss) return rss if rss.is_a?(::URI::Generic) @@ -113,8 +127,14 @@ class BaseParser + class << self + def raise_for_undefined_entity? + listener.raise_for_undefined_entity? + end + end + def initialize(rss) - @listener = listener.new + @listener = self.class.listener.new @rss = rss end @@ -157,11 +177,7 @@ @@registered_uris = {} @@class_names = {} - def install_setter(uri, tag_name, setter) - @@setters[uri] ||= {} - @@setters[uri][tag_name] = setter - end - + # return the setter for the uri, tag_name pair, or nil. def setter(uri, tag_name) begin @@setters[uri][tag_name] @@ -170,6 +186,8 @@ end end + + # return the tag_names for setters associated with uri def available_tags(uri) begin @@setters[uri].keys @@ -178,20 +196,25 @@ end end + # register uri against this name. def register_uri(uri, name) @@registered_uris[name] ||= {} @@registered_uris[name][uri] = nil end + # test if this uri is registered against this name def uri_registered?(uri, name) @@registered_uris[name].has_key?(uri) end + # record class_name for the supplied uri and tag_name def install_class_name(uri, tag_name, class_name) @@class_names[uri] ||= {} @@class_names[uri][tag_name] = class_name end + # retrieve class_name for the supplied uri and tag_name + # If it doesn't exist, capitalize the tag_name def class_name(uri, tag_name) begin @@class_names[uri][tag_name] @@ -205,28 +228,31 @@ def_get_text_element(uri, name, *get_file_and_line_from_caller(1)) end + def raise_for_undefined_entity? + true + end + private + # set the setter for the uri, tag_name pair + def install_setter(uri, tag_name, setter) + @@setters[uri] ||= {} + @@setters[uri][tag_name] = setter + end def def_get_text_element(uri, name, file, line) register_uri(uri, name) unless private_instance_methods(false).include?("start_#{name}") module_eval(<<-EOT, file, line) def start_#{name}(name, prefix, attrs, ns) - uri = ns[prefix] + uri = _ns(ns, prefix) if self.class.uri_registered?(uri, #{name.inspect}) - if @do_validate - tags = self.class.available_tags(uri) - unless tags.include?(name) - raise UnknownTagError.new(name, uri) - end - end start_get_text_element(name, prefix, ns, uri) else start_else_element(name, prefix, attrs, ns) end end EOT - send("private", "start_#{name}") + __send__("private", "start_#{name}") end end @@ -254,6 +280,7 @@ @xml_stylesheets = [] end + # set instance vars for version, encoding, standalone def xmldecl(version, encoding, standalone) @version, @encoding, @standalone = version, encoding, standalone end @@ -282,10 +309,10 @@ @ns_stack.push(ns) prefix, local = split_name(name) - @tag_stack.last.push([ns[prefix], local]) + @tag_stack.last.push([_ns(ns, prefix), local]) @tag_stack.push([]) if respond_to?("start_#{local}", true) - send("start_#{local}", local, prefix, attrs, ns.dup) + __send__("start_#{local}", local, prefix, attrs, ns.dup) else start_else_element(local, prefix, attrs, ns.dup) end @@ -308,8 +335,14 @@ end private + def _ns(ns, prefix) + ns.fetch(prefix, "") + end CONTENT_PATTERN = /\s*([^=]+)=(["'])([^\2]+?)\2/ + # Extract the first name="value" pair from content. + # Works with single quotes according to the constant + # CONTENT_PATTERN. Return a Hash. def parse_pi_content(content) params = {} content.scan(CONTENT_PATTERN) do |name, quote, value| @@ -319,20 +352,20 @@ end def start_else_element(local, prefix, attrs, ns) - class_name = self.class.class_name(ns[prefix], local) + class_name = self.class.class_name(_ns(ns, prefix), local) current_class = @last_element.class if current_class.constants.include?(class_name) next_class = current_class.const_get(class_name) start_have_something_element(local, prefix, attrs, ns, next_class) else - if @ignore_unknown_element + if !@do_validate or @ignore_unknown_element @proc_stack.push(nil) else parent = "ROOT ELEMENT???" if current_class.tag_name parent = current_class.tag_name end - raise NotExceptedTagError.new(local, parent) + raise NotExpectedTagError.new(local, _ns(ns, prefix), parent) end end end @@ -345,7 +378,7 @@ def check_ns(tag_name, prefix, ns, require_uri) if @do_validate - if ns[prefix] == require_uri + if _ns(ns, prefix) == require_uri #ns.delete(prefix) else raise NSError.new(tag_name, prefix, require_uri) @@ -356,12 +389,12 @@ def start_get_text_element(tag_name, prefix, ns, required_uri) @proc_stack.push Proc.new {|text, tags| setter = self.class.setter(required_uri, tag_name) - setter ||= "#{tag_name}=" if @last_element.respond_to?(setter) - @last_element.send(setter, text.to_s) + @last_element.__send__(setter, text.to_s) else - if @do_validate and not @ignore_unknown_element - raise NotExceptedTagError.new(tag_name, @last_element.tag_name) + if @do_validate and !@ignore_unknown_element + raise NotExpectedTagError.new(tag_name, _ns(ns, prefix), + @last_element.tag_name) end end } @@ -371,14 +404,13 @@ check_ns(tag_name, prefix, ns, klass.required_uri) - args = [] - - klass.get_attributes.each do |a_name, a_uri, required| + attributes = {} + klass.get_attributes.each do |a_name, a_uri, required, element_name| if a_uri.is_a?(String) or !a_uri.respond_to?(:include?) a_uri = [a_uri] end - unless a_uri == [nil] + unless a_uri == [""] for prefix, uri in ns if a_uri.include?(uri) val = attrs["#{prefix}:#{a_name}"] @@ -386,12 +418,12 @@ end end end - if val.nil? and a_uri.include?(nil) + if val.nil? and a_uri.include?("") val = attrs[a_name] end if @do_validate and required and val.nil? - unless a_uri.include?(nil) + unless a_uri.include?("") for prefix, uri in ns if a_uri.include?(uri) a_name = "#{prefix}:#{a_name}" @@ -401,20 +433,19 @@ raise MissingAttributeError.new(tag_name, a_name) end - args << val + attributes[a_name] = val end previous = @last_element - next_element = klass.send(:new, *args) - next_element.do_validate = @do_validate - prefix = "" - prefix << "#{klass.required_prefix}_" if klass.required_prefix - previous.instance_eval {set_next_element(prefix, tag_name, next_element)} + next_element = klass.new(@do_validate, attributes) + previous.instance_eval {set_next_element(tag_name, next_element)} @last_element = next_element @proc_stack.push Proc.new { |text, tags| p(@last_element.class) if DEBUG @last_element.content = text if klass.have_content? - @last_element.validate_for_stream(tags) if @do_validate + if @do_validate + @last_element.validate_for_stream(tags, @ignore_unknown_element) + end @last_element = previous } end Index: rss/maker/taxonomy.rb =================================================================== --- rss/maker/taxonomy.rb (revision 0) +++ rss/maker/taxonomy.rb (revision 0) @@ -0,0 +1,182 @@ +require 'rss/taxonomy' +require 'rss/maker/1.0' +require 'rss/maker/dublincore' + +module RSS + module Maker + module TaxonomyTopicsModel + def self.append_features(klass) + super + + klass.add_need_initialize_variable("taxo_topics", "make_taxo_topics") + klass.add_other_element("taxo_topics") + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader :taxo_topics + def make_taxo_topics + self.class::TaxonomyTopics.new(@maker) + end + + def setup_taxo_topics(rss, current) + @taxo_topics.to_rss(rss, current) + end +EOC + end + + def self.install_taxo_topics(klass) + klass.module_eval(<<-EOC, *Utils.get_file_and_line_from_caller(1)) + class TaxonomyTopics < TaxonomyTopicsBase + def to_rss(rss, current) + if current.respond_to?(:taxo_topics) + topics = current.class::TaxonomyTopics.new + bag = topics.Bag + @resources.each do |resource| + bag.lis << RDF::Bag::Li.new(resource) + end + current.taxo_topics = topics + end + end + end +EOC + end + + class TaxonomyTopicsBase + include Base + + attr_reader :resources + def_array_element("resources") + end + end + + module TaxonomyTopicModel + def self.append_features(klass) + super + + klass.add_need_initialize_variable("taxo_topics", "make_taxo_topics") + klass.add_other_element("taxo_topics") + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader :taxo_topics + def make_taxo_topics + self.class::TaxonomyTopics.new(@maker) + end + + def setup_taxo_topics(rss, current) + @taxo_topics.to_rss(rss, current) + end + + def taxo_topic + @taxo_topics[0] and @taxo_topics[0].value + end + + def taxo_topic=(new_value) + @taxo_topic[0] = self.class::TaxonomyTopic.new(self) + @taxo_topic[0].value = new_value + end +EOC + end + + def self.install_taxo_topic(klass) + klass.module_eval(<<-EOC, *Utils.get_file_and_line_from_caller(1)) + class TaxonomyTopics < TaxonomyTopicsBase + class TaxonomyTopic < TaxonomyTopicBase + DublinCoreModel.install_dublin_core(self) + TaxonomyTopicsModel.install_taxo_topics(self) + + def to_rss(rss, current) + if current.respond_to?(:taxo_topics) + topic = current.class::TaxonomyTopic.new(value) + topic.taxo_link = value + taxo_topics.to_rss(rss, topic) if taxo_topics + current.taxo_topics << topic + setup_other_elements(rss) + end + end + + def current_element(rss) + super.taxo_topics.last + end + end + end +EOC + end + + class TaxonomyTopicsBase + include Base + + def_array_element("taxo_topics") + + def new_taxo_topic + taxo_topic = self.class::TaxonomyTopic.new(self) + @taxo_topics << taxo_topic + if block_given? + yield taxo_topic + else + taxo_topic + end + end + + def to_rss(rss, current) + @taxo_topics.each do |taxo_topic| + taxo_topic.to_rss(rss, current) + end + end + + class TaxonomyTopicBase + include Base + include DublinCoreModel + include TaxonomyTopicsModel + + attr_accessor :value + add_need_initialize_variable("value") + alias_method(:taxo_link, :value) + alias_method(:taxo_link=, :value=) + + def have_required_values? + @value + end + end + end + end + + class RSSBase + include TaxonomyTopicModel + end + + class ChannelBase + include TaxonomyTopicsModel + end + + class ItemsBase + class ItemBase + include TaxonomyTopicsModel + end + end + + class RSS10 + TaxonomyTopicModel.install_taxo_topic(self) + + class Channel + TaxonomyTopicsModel.install_taxo_topics(self) + end + + class Items + class Item + TaxonomyTopicsModel.install_taxo_topics(self) + end + end + end + + class RSS09 + TaxonomyTopicModel.install_taxo_topic(self) + + class Channel + TaxonomyTopicsModel.install_taxo_topics(self) + end + + class Items + class Item + TaxonomyTopicsModel.install_taxo_topics(self) + end + end + end + end +end Index: rss/maker/image.rb =================================================================== --- rss/maker/image.rb (revision 2900) +++ rss/maker/image.rb (working copy) @@ -95,7 +95,7 @@ DublinCoreModel.install_dublin_core(self) def to_rss(rss, current) if @about - item = ::RSS::ImageItemModel::Item.new(@about, @resource) + item = ::RSS::ImageItemModel::ImageItem.new(@about, @resource) setup_values(item) setup_other_elements(item) current.image_item = item @@ -111,7 +111,7 @@ def to_rss(rss, current) if @about and @image_size args = [@about, @image_size] - favicon = ::RSS::ImageFaviconModel::Favicon.new(*args) + favicon = ::RSS::ImageFaviconModel::ImageFavicon.new(*args) setup_values(favicon) setup_other_elements(favicon) current.image_favicon = favicon Index: rss/maker/trackback.rb =================================================================== --- rss/maker/trackback.rb (revision 2900) +++ rss/maker/trackback.rb (working copy) @@ -41,9 +41,13 @@ def_array_element("abouts") def new_about - about = self.class::About.new(@maker) - @abouts << about - about + about = self.class::TrackBackAbout.new(@maker) + @abouts << about + if block_given? + yield about + else + about + end end def to_rss(rss, current) @@ -52,7 +56,7 @@ end end - class AboutBase + class TrackBackAboutBase include Base attr_accessor :value @@ -79,10 +83,10 @@ class Items class Item class TrackBackAbouts < TrackBackAboutsBase - class About < AboutBase + class TrackBackAbout < TrackBackAboutBase def to_rss(rss, current) if resource - about = ::RSS::TrackBackModel10::About.new(resource) + about = ::RSS::TrackBackModel10::TrackBackAbout.new(resource) current.trackback_abouts << about end end @@ -98,7 +102,7 @@ class TrackBackAbouts < TrackBackAboutsBase def to_rss(*args) end - class About < AboutBase + class TrackBackAbout < TrackBackAboutBase end end end @@ -109,10 +113,10 @@ class Items class Item class TrackBackAbouts < TrackBackAboutsBase - class About < AboutBase + class TrackBackAbout < TrackBackAboutBase def to_rss(rss, current) if content - about = ::RSS::TrackBackModel20::About.new(content) + about = ::RSS::TrackBackModel20::TrackBackAbout.new(content) current.trackback_abouts << about end end Index: rss/maker/dublincore.rb =================================================================== --- rss/maker/dublincore.rb (revision 2900) +++ rss/maker/dublincore.rb (working copy) @@ -53,7 +53,11 @@ def new_#{name} #{name} = self.class::#{klass_name}.new(self) @#{plural_name} << #{name} - #{name} + if block_given? + yield #{name} + else + #{name} + end end def to_rss(rss, current) Index: rss/maker/base.rb =================================================================== --- rss/maker/base.rb (revision 2900) +++ rss/maker/base.rb (working copy) @@ -91,7 +91,7 @@ variables.each do |var| setter = "#{var}=" if target.respond_to?(setter) - value = self.__send__(var) + value = __send__(var) if value target.__send__(setter, value) set = true @@ -207,7 +207,11 @@ def new_xml_stylesheet xss = XMLStyleSheet.new(@maker) @xml_stylesheets << xss - xss + if block_given? + yield xss + else + xss + end end class XMLStyleSheet @@ -281,8 +285,12 @@ def new_day day = self.class::Day.new(@maker) - @days << day - day + @days << day + if block_given? + yield day + else + day + end end def current_element(rss) @@ -311,8 +319,12 @@ def new_hour hour = self.class::Hour.new(@maker) - @hours << hour - hour + @hours << hour + if block_given? + yield hour + else + hour + end end def current_element(rss) @@ -356,7 +368,11 @@ def new_category category = self.class::Category.new(@maker) @categories << category - category + if block_given? + yield category + else + category + end end class CategoryBase @@ -414,8 +430,12 @@ def new_item item = self.class::Item.new(@maker) - @items << item - item + @items << item + if block_given? + yield item + else + item + end end private Index: rss/1.0.rb =================================================================== --- rss/1.0.rb (revision 2900) +++ rss/1.0.rb (working copy) @@ -38,18 +38,13 @@ [ ["channel", nil], ["image", "?"], - ["item", "+"], + ["item", "+", :children], ["textinput", "?"], - ].each do |tag, occurs| - install_model(tag, occurs) + ].each do |tag, occurs, type| + type ||= :child + __send__("install_have_#{type}_element", tag, ::RSS::URI, occurs) end - %w(channel image textinput).each do |name| - install_have_child_element(name) - end - - install_have_children_element("item") - attr_accessor :rss_version, :version, :encoding, :standalone def initialize(version=nil, encoding=nil, standalone=nil) @@ -59,71 +54,60 @@ def full_name tag_name_with_prefix(PREFIX) end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent, ns_declarations) do |next_indent| - [ - channel_element(false, next_indent), - image_element(false, next_indent), - item_elements(false, next_indent), - textinput_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - private - def rdf_validate(tags) - _validate(tags, []) - end + class Li < Element - def children - [@channel, @image, @textinput, *@item] - end + include RSS10 - def _tags - rv = [ - [::RSS::URI, "channel"], - [::RSS::URI, "image"], - ].delete_if {|uri, name| send(name).nil?} - @item.each do |item| - rv << [::RSS::URI, "item"] + class << self + def required_uri + URI + end end - rv << [::RSS::URI, "textinput"] if @textinput - rv + + [ + ["resource", [URI, ""], true] + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end + end + + def full_name + tag_name_with_prefix(PREFIX) + end end class Seq < Element include RSS10 + Li = ::RSS::RDF::Li + class << self - def required_uri URI end - end @tag_name = 'Seq' - install_have_children_element("li") - + install_have_children_element("li", URI, "*") install_must_call_validator('rdf', ::RSS::RDF::URI) - def initialize(li=[]) - super() - @li = li - end - - def to_s(need_convert=true, indent=calc_indent) - tag(indent) do |next_indent| - [ - li_elements(need_convert, next_indent), - other_element(need_convert, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + @li = args[0] if args[0] end end @@ -131,65 +115,48 @@ tag_name_with_prefix(PREFIX) end - private - def children - @li - end - - def rdf_validate(tags) - _validate(tags, [["li", '*']]) - end - - def _tags - rv = [] - @li.each do |li| - rv << [URI, "li"] + def setup_maker(target) + lis.each do |li| + target << li.resource end - rv end - end - class Li < Element + class Bag < Element include RSS10 + Li = ::RSS::RDF::Li + class << self - def required_uri URI end - end + + @tag_name = 'Bag' - [ - ["resource", [URI, nil], true] - ].each do |name, uri, required| - install_get_attribute(name, uri, required) - end + install_have_children_element("li", URI, "*") + install_must_call_validator('rdf', URI) - def initialize(resource=nil) - super() - @resource = resource + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + @li = args[0] if args[0] + end end def full_name tag_name_with_prefix(PREFIX) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv + def setup_maker(target) + lis.each do |li| + target << li.resource + end end - - private - def _attrs - [ - ["resource", true] - ] - end - end class Channel < Element @@ -207,73 +174,31 @@ [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - %w(title link description).each do |name| - install_text_element(name) - end - - %w(image items textinput).each do |name| - install_have_child_element(name) - end - [ - ['title', nil], - ['link', nil], - ['description', nil], - ['image', '?'], - ['items', nil], - ['textinput', '?'], - ].each do |tag, occurs| - install_model(tag, occurs) + ['title', nil, :text], + ['link', nil, :text], + ['description', nil, :text], + ['image', '?', :have_child], + ['items', nil, :have_child], + ['textinput', '?', :have_child], + ].each do |tag, occurs, type| + __send__("install_#{type}_element", tag, ::RSS::URI, occurs) end - - def initialize(about=nil) - super() - @about = about - end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - image_element(false, next_indent), - items_element(false, next_indent), - textinput_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end - rv = convert(rv) if need_convert - rv end private - def children - [@image, @items, @textinput] - end - - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'link'], - [::RSS::URI, 'description'], - [::RSS::URI, 'image'], - [::RSS::URI, 'items'], - [::RSS::URI, 'textinput'], - ].delete_if do |uri, name| - send(name).nil? - end - end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - def maker_target(maker) maker.channel end @@ -297,26 +222,18 @@ [ ["resource", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - def initialize(resource=nil) - super() - @resource = resource + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["#{PREFIX}:resource", true, "resource"] - ] - end end class Textinput < Element @@ -334,26 +251,18 @@ [ ["resource", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - def initialize(resource=nil) - super() - @resource = resource + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["#{PREFIX}:resource", true, "resource"] - ] - end end class Items < Element @@ -361,11 +270,6 @@ include RSS10 Seq = ::RSS::RDF::Seq - class Seq - unless const_defined?(:Li) - Li = ::RSS::RDF::Li - end - end class << self @@ -375,42 +279,29 @@ end - install_have_child_element("Seq") + install_have_child_element("Seq", URI, nil) + install_must_call_validator('rdf', URI) - install_must_call_validator('rdf', ::RSS::RDF::URI) - - def initialize(seq=Seq.new) - super() - @Seq = seq - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - Seq_element(need_convert, next_indent), - other_element(need_convert, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.Seq = args[0] end + self.Seq ||= Seq.new end - private - def children - [@Seq] + def resources + if @Seq + @Seq.lis.collect do |li| + li.resource + end + else + [] + end end - - private - def _tags - rv = [] - rv << [URI, 'Seq'] unless @Seq.nil? - rv - end - - def rdf_validate(tags) - _validate(tags, [["Seq", nil]]) - end - end - end class Image < Element @@ -424,60 +315,28 @@ end end - + [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end %w(title url link).each do |name| - install_text_element(name) + install_text_element(name, ::RSS::URI, nil) end - - [ - ['title', nil], - ['url', nil], - ['link', nil], - ].each do |tag, occurs| - install_model(tag, occurs) - end - def initialize(about=nil) - super() - @about = about - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - url_element(false, next_indent), - link_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end - rv = convert(rv) if need_convert - rv end private - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'url'], - [::RSS::URI, 'link'], - ].delete_if do |uri, name| - send(name).nil? - end - end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - def maker_target(maker) maker.image end @@ -495,62 +354,39 @@ end + [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - %w(title link description).each do |name| - install_text_element(name) - end - [ ["title", nil], ["link", nil], ["description", "?"], ].each do |tag, occurs| - install_model(tag, occurs) + install_text_element(tag, ::RSS::URI, occurs) end - def initialize(about=nil) - super() - @about = about - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end - rv = convert(rv) if need_convert - rv end - + private - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'link'], - [::RSS::URI, 'description'], - ].delete_if do |uri, name| - send(name).nil? + def maker_target(items) + if items.respond_to?("items") + # For backward compatibility + items = items.items end + items.new_item end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - - def maker_target(maker) - maker.items.new_item - end end class Textinput < Element @@ -568,59 +404,24 @@ [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end %w(title description name link).each do |name| - install_text_element(name) + install_text_element(name, ::RSS::URI, nil) end - - [ - ["title", nil], - ["description", nil], - ["name", nil], - ["link", nil], - ].each do |tag, occurs| - install_model(tag, occurs) - end - def initialize(about=nil) - super() - @about = about - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - description_element(false, next_indent), - name_element(false, next_indent), - link_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end - rv = convert(rv) if need_convert - rv end private - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'description'], - [::RSS::URI, 'name'], - [::RSS::URI, 'link'], - ].delete_if do |uri, name| - send(name).nil? - end - end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - def maker_target(maker) maker.textinput end @@ -642,7 +443,7 @@ @rss.xml_stylesheets = @xml_stylesheets @last_element = @rss @proc_stack.push Proc.new { |text, tags| - @rss.validate_for_stream(tags) if @do_validate + @rss.validate_for_stream(tags, @ignore_unknown_element) if @do_validate } end end Index: rss/2.0.rb =================================================================== --- rss/2.0.rb (revision 2900) +++ rss/2.0.rb (working copy) @@ -6,60 +6,26 @@ class Channel - %w(generator ttl).each do |name| - install_text_element(name) - install_model(name, '?') + [ + ["generator"], + ["ttl", :integer], + ].each do |name, type| + install_text_element(name, "", "?", name, type) end - remove_method :ttl= - def ttl=(value) - @ttl = value.to_i - end - [ %w(category categories), ].each do |name, plural_name| - install_have_children_element(name, plural_name) - install_model(name, '*') + install_have_children_element(name, "", "*", name, plural_name) end - + [ ["image", "?"], ["language", "?"], ].each do |name, occurs| - install_model(name, occurs) + install_model(name, "", occurs) end - def other_element(need_convert, indent) - rv = <<-EOT -#{category_elements(need_convert, indent)} -#{generator_element(need_convert, indent)} -#{ttl_element(need_convert, indent)} -EOT - rv << super - end - - private - alias children09 children - def children - children09 + @category.compact - end - - alias _tags09 _tags - def _tags - rv = %w(generator ttl).delete_if do |name| - send(name).nil? - end.collect do |elem| - [nil, elem] - end + _tags09 - - @category.each do - rv << [nil, "category"] - end - - rv - end - Category = Item::Category class Item @@ -68,15 +34,13 @@ ["comments", "?"], ["author", "?"], ].each do |name, occurs| - install_text_element(name) - install_model(name, occurs) + install_text_element(name, "", occurs) end [ ["pubDate", '?'], ].each do |name, occurs| - install_date_element(name, 'rfc822') - install_model(name, occurs) + install_date_element(name, "", occurs, name, 'rfc822') end alias date pubDate alias date= pubDate= @@ -84,37 +48,10 @@ [ ["guid", '?'], ].each do |name, occurs| - install_have_child_element(name) - install_model(name, occurs) + install_have_child_element(name, "", occurs) end - - def other_element(need_convert, indent) - rv = [ - super, - *%w(author comments pubDate guid).collect do |name| - __send__("#{name}_element", false, indent) - end - ].reject do |value| - /\A\s*\z/.match(value) - end - rv.join("\n") - end private - alias children09 children - def children - children09 + [@guid].compact - end - - alias _tags09 _tags - def _tags - %w(comments author pubDate guid).delete_if do |name| - send(name).nil? - end.collect do |elem| - [nil, elem] - end + _tags09 - end - alias _setup_maker_element setup_maker_element def setup_maker_element(item) _setup_maker_element(item) @@ -126,26 +63,31 @@ include RSS09 [ - ["isPermaLink", nil, false] - ].each do |name, uri, required| - install_get_attribute(name, uri, required) + ["isPermaLink", "", false, :boolean] + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) end content_setup - def initialize(isPermaLink=nil, content=nil) - super() - @isPermaLink = isPermaLink - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.isPermaLink = args[0] + self.content = args[1] + end end - private - def _attrs - [ - ["isPermaLink", false] - ] + alias_method :_PermaLink?, :PermaLink? + private :_PermaLink? + def PermaLink? + perma = _PermaLink? + perma or perma.nil? end + private def maker_target(item) item.guid end @@ -163,7 +105,7 @@ end RSS09::ELEMENTS.each do |name| - BaseListener.install_get_text_element(nil, name, "#{name}=") + BaseListener.install_get_text_element("", name, "#{name}=") end end Index: rss/maker.rb =================================================================== --- rss/maker.rb (revision 2900) +++ rss/maker.rb (working copy) @@ -32,5 +32,6 @@ require "rss/maker/content" require "rss/maker/dublincore" require "rss/maker/syndication" +require "rss/maker/taxonomy" require "rss/maker/trackback" require "rss/maker/image" Index: rss/utils.rb =================================================================== --- rss/utils.rb (revision 2900) +++ rss/utils.rb (working copy) @@ -1,8 +1,8 @@ module RSS - module Utils + module_function - module_function + # Convert a name_with_underscores to CamelCase. def to_class_name(name) name.split(/_/).collect do |part| "#{part[0, 1].upcase}#{part[1..-1]}" @@ -14,11 +14,14 @@ [file, line.to_i] end + # escape '&', '"', '<' and '>' for use in HTML. def html_escape(s) s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/, "<") end alias h html_escape + # If +value+ is an instance of class +klass+, return it, else + # create a new instance of +klass+ with value +value+. def new_with_value_if_need(klass, value) if value.is_a?(klass) value @@ -26,6 +29,9 @@ klass.new(value) end end + + def element_initialize_arguments?(args) + [true, false].include?(args[0]) and args[1].is_a?(Hash) + end end - end Index: rss/rexmlparser.rb =================================================================== --- rss/rexmlparser.rb (revision 2900) +++ rss/rexmlparser.rb (working copy) @@ -10,12 +10,13 @@ class REXMLParser < BaseParser + class << self + def listener + REXMLListener + end + end + private - - def listener - REXMLListener - end - def _parse begin REXML::Document.parse_stream(@rss, @listener) @@ -35,6 +36,12 @@ include REXML::StreamListener include ListenerMixin + class << self + def raise_for_undefined_entity? + false + end + end + def xmldecl(version, encoding, standalone) super(version, encoding, standalone == "yes") # Encoding is converted to UTF-8 when REXML parse XML. Index: rss/0.9.rb =================================================================== --- rss/0.9.rb (revision 2900) +++ rss/0.9.rb (working copy) @@ -9,7 +9,7 @@ def self.append_features(klass) super - klass.install_must_call_validator('', nil) + klass.install_must_call_validator('', "") end end @@ -17,16 +17,9 @@ include RSS09 include RootElementMixin - include XMLStyleSheetMixin - [ - ["channel", nil], - ].each do |tag, occurs| - install_model(tag, occurs) - end - %w(channel).each do |name| - install_have_child_element(name) + install_have_child_element(name, "", nil) end attr_accessor :rss_version, :version, :encoding, :standalone @@ -58,31 +51,15 @@ nil end end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent, ns_declarations) do |next_indent| - [ - channel_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - private - def children - [@channel] - end - - def _tags - [ - [nil, 'channel'], - ].delete_if do |uri, name| - send(name).nil? + def setup_maker_elements(maker) + super + items.each do |item| + item.setup_maker(maker.items) end end + private def _attrs [ ["version", true, "rss_version"], @@ -94,119 +71,30 @@ include RSS09 [ - ["title", nil], - ["link", nil], - ["description", nil], - ["language", nil], - ["copyright", "?"], - ["managingEditor", "?"], - ["webMaster", "?"], - ["rating", "?"], - ["docs", "?"], - ].each do |name, occurs| - install_text_element(name) - install_model(name, occurs) + ["title", nil, :text], + ["link", nil, :text], + ["description", nil, :text], + ["language", nil, :text], + ["copyright", "?", :text], + ["managingEditor", "?", :text], + ["webMaster", "?", :text], + ["rating", "?", :text], + ["pubDate", "?", :date, :rfc822], + ["lastBuildDate", "?", :date, :rfc822], + ["docs", "?", :text], + ["cloud", "?", :have_attribute], + ["skipDays", "?", :have_child], + ["skipHours", "?", :have_child], + ["image", nil, :have_child], + ["item", "*", :have_children], + ["textInput", "?", :have_child], + ].each do |name, occurs, type, *args| + __send__("install_#{type}_element", name, "", occurs, name, *args) end - - [ - ["pubDate", "?"], - ["lastBuildDate", "?"], - ].each do |name, occurs| - install_date_element(name, 'rfc822') - install_model(name, occurs) - end alias date pubDate alias date= pubDate= - [ - ["skipDays", "?"], - ["skipHours", "?"], - ["image", nil], - ["textInput", "?"], - ].each do |name, occurs| - install_have_child_element(name) - install_model(name, occurs) - end - - [ - ["cloud", "?"] - ].each do |name, occurs| - install_have_attribute_element(name) - install_model(name, occurs) - end - - [ - ["item", "*"] - ].each do |name, occurs| - install_have_children_element(name) - install_model(name, occurs) - end - - def initialize() - super() - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - language_element(false, next_indent), - copyright_element(false, next_indent), - managingEditor_element(false, next_indent), - webMaster_element(false, next_indent), - rating_element(false, next_indent), - pubDate_element(false, next_indent), - lastBuildDate_element(false, next_indent), - docs_element(false, next_indent), - cloud_element(false, next_indent), - skipDays_element(false, next_indent), - skipHours_element(false, next_indent), - image_element(false, next_indent), - item_elements(false, next_indent), - textInput_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - private - def children - [@skipDays, @skipHours, @image, @textInput, @cloud, *@item] - end - - def _tags - rv = [ - "title", - "link", - "description", - "language", - "copyright", - "managingEditor", - "webMaster", - "rating", - "docs", - "skipDays", - "skipHours", - "image", - "textInput", - "cloud", - ].delete_if do |name| - send(name).nil? - end.collect do |elem| - [nil, elem] - end - - @item.each do - rv << [nil, "item"] - end - - rv - end - def maker_target(maker) maker.channel end @@ -237,39 +125,21 @@ [ ["day", "*"] ].each do |name, occurs| - install_have_children_element(name) - install_model(name, occurs) + install_have_children_element(name, "", occurs) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - day_elements(false, next_indent) - ] - end - rv = convert(rv) if need_convert - rv - end - - private - def children - @day - end - - def _tags - @day.compact.collect do - [nil, "day"] - end - end - class Day < Element include RSS09 content_setup - def initialize(content=nil) - super() - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end end @@ -282,46 +152,22 @@ [ ["hour", "*"] ].each do |name, occurs| - install_have_children_element(name) - install_model(name, occurs) + install_have_children_element(name, "", occurs) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - hour_elements(false, next_indent) - ] - end - rv = convert(rv) if need_convert - rv - end - - private - def children - @hour - end - - def _tags - @hour.compact.collect do - [nil, "hour"] - end - end - class Hour < Element include RSS09 - content_setup + content_setup(:integer) - def initialize(content=nil) - super() - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end - - remove_method :content= - def content=(value) - @content = value.to_i - end - end end @@ -331,39 +177,31 @@ include RSS09 %w(url title link).each do |name| - install_text_element(name) - install_model(name, nil) + install_text_element(name, "", nil) end - %w(width height description).each do |name| - install_text_element(name) - install_model(name, "?") + [ + ["width", :integer], + ["height", :integer], + ["description"], + ].each do |name, type| + install_text_element(name, "", "?", name, type) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - url_element(false, next_indent), - title_element(false, next_indent), - link_element(false, next_indent), - width_element(false, next_indent), - height_element(false, next_indent), - description_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.url = args[0] + self.title = args[1] + self.link = args[2] + self.width = args[3] + self.height = args[4] + self.description = args[5] end - rv = convert(rv) if need_convert - rv end private - def _tags - %w(url title link width height description).delete_if do |name| - send(name).nil? - end.collect do |elem| - [nil, elem] - end - end - def maker_target(maker) maker.image end @@ -372,110 +210,55 @@ class Cloud < Element include RSS09 - + [ - ["domain", nil, true], - ["port", nil, true], - ["path", nil, true], - ["registerProcedure", nil, true], - ["protocol", nil ,true], - ].each do |name, uri, required| - install_get_attribute(name, uri, required) + ["domain", "", true], + ["port", "", true, :integer], + ["path", "", true], + ["registerProcedure", "", true], + ["protocol", "", true], + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) end - def initialize(domain=nil, port=nil, path=nil, rp=nil, protocol=nil) - super() - @domain = domain - @port = port - @path = path - @registerProcedure = rp - @protocol = protocol - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - %w(domain port path registerProcedure protocol).collect do |attr| - [attr, true] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.domain = args[0] + self.port = args[1] + self.path = args[2] + self.registerProcedure = args[3] + self.protocol = args[4] end end - end class Item < Element include RSS09 - %w(title link description).each do |name| - install_text_element(name) - end - - %w(source enclosure).each do |name| - install_have_child_element(name) - end - [ - %w(category categories), - ].each do |name, plural_name| - install_have_children_element(name, plural_name) + ["title", '?', :text], + ["link", '?', :text], + ["description", '?', :text], + ["category", '*', :have_children, "categories"], + ["source", '?', :have_child], + ["enclosure", '?', :have_child], + ].each do |tag, occurs, type, *args| + __send__("install_#{type}_element", tag, "", occurs, tag, *args) end - - [ - ["title", '?'], - ["link", '?'], - ["description", '?'], - ["category", '*'], - ["source", '?'], - ["enclosure", '?'], - ].each do |tag, occurs| - install_model(tag, occurs) - end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - category_elements(false, next_indent), - source_element(false, next_indent), - enclosure_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - private - def children - [@source, @enclosure, *@category].compact - end - - def _tags - rv = %w(title link description author comments - source enclosure).delete_if do |name| - send(name).nil? - end.collect do |name| - [nil, name] + def maker_target(items) + if items.respond_to?("items") + # For backward compatibility + items = items.items end - - @category.each do - rv << [nil, "category"] - end - - rv + items.new_item end - def maker_target(maker) - maker.items.new_item - end - def setup_maker_element(item) super @enclosure.setup_maker(item) if @enclosure @@ -487,31 +270,24 @@ include RSS09 [ - ["url", nil, true] + ["url", "", true] ].each do |name, uri, required| install_get_attribute(name, uri, required) end content_setup - def initialize(url=nil, content=nil) - super() - @url = url - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.url = args[0] + self.content = args[1] + end end private - def _tags - [] - end - - def _attrs - [ - ["url", true] - ] - end - - def maker_target(item) item.source end @@ -527,35 +303,25 @@ include RSS09 [ - ["url", nil, true], - ["length", nil, true], - ["type", nil, true], - ].each do |name, uri, required| - install_get_attribute(name, uri, required) + ["url", "", true], + ["length", "", true, :integer], + ["type", "", true], + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) end - def initialize(url=nil, length=nil, type=nil) - super() - @url = url - @length = length - @type = type + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.url = args[0] + self.length = args[1] + self.type = args[2] + end end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - private - def _attrs - [ - ["url", true], - ["length", true], - ["type", true], - ] - end - def maker_target(item) item.enclosure end @@ -572,26 +338,24 @@ include RSS09 [ - ["domain", nil, false] + ["domain", "", false] ].each do |name, uri, required| install_get_attribute(name, uri, required) end content_setup - def initialize(domain=nil, content=nil) - super() - @domain = domain - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.domain = args[0] + self.content = args[1] + end end private - def _attrs - [ - ["domain", false] - ] - end - def maker_target(item) item.new_category end @@ -610,33 +374,22 @@ include RSS09 %w(title description name link).each do |name| - install_text_element(name) - install_model(name, nil) + install_text_element(name, "", nil) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - description_element(false, next_indent), - name_element(false, next_indent), - link_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.title = args[0] + self.description = args[1] + self.name = args[2] + self.link = args[3] end - rv = convert(rv) if need_convert - rv end private - def _tags - %w(title description name link).each do |name| - send(name).nil? - end.collect do |elem| - [nil, elem] - end - end - def maker_target(maker) maker.textinput end @@ -647,20 +400,20 @@ end RSS09::ELEMENTS.each do |name| - BaseListener.install_get_text_element(nil, name, "#{name}=") + BaseListener.install_get_text_element("", name, "#{name}=") end module ListenerMixin private def start_rss(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, nil) + check_ns(tag_name, prefix, ns, "") @rss = Rss.new(attrs['version'], @version, @encoding, @standalone) @rss.do_validate = @do_validate @rss.xml_stylesheets = @xml_stylesheets @last_element = @rss @proc_stack.push Proc.new { |text, tags| - @rss.validate_for_stream(tags) if @do_validate + @rss.validate_for_stream(tags, @ignore_unknown_element) if @do_validate } end Index: rss/rss.rb =================================================================== --- rss/rss.rb (revision 2900) +++ rss/rss.rb (working copy) @@ -30,28 +30,6 @@ end end -module Enumerable - unless instance_methods.include?("sort_by") - def sort_by - collect do |x| - [yield(x), x] - end.sort do |x, y| - x[0] <=> y[0] - end.collect! do |x| - x[1] - end - end - end -end - -class Hash - unless instance_methods.include?("merge") - def merge(other) - dup.update(other) - end - end -end - require "English" require "rss/utils" require "rss/converter" @@ -59,7 +37,7 @@ module RSS - VERSION = "0.1.5" + VERSION = "0.1.6" URI = "http://purl.org/rss/1.0/" @@ -108,19 +86,24 @@ end end - class NotExceptedTagError < InvalidRSSError - attr_reader :tag, :parent - def initialize(tag, parent) - @tag, @parent = tag, parent - super("tag <#{tag}> is not expected in tag <#{parent}>") + class NotExpectedTagError < InvalidRSSError + attr_reader :tag, :uri, :parent + def initialize(tag, uri, parent) + @tag, @uri, @parent = tag, uri, parent + super("tag <{#{uri}}#{tag}> is not expected in tag <#{parent}>") end end + # For backward compatibility :X + NotExceptedTagError = NotExpectedTagError class NotAvailableValueError < InvalidRSSError - attr_reader :tag, :value - def initialize(tag, value) - @tag, @value = tag, value - super("value <#{value}> of tag <#{tag}> is not available.") + attr_reader :tag, :value, :attribute + def initialize(tag, value, attribute=nil) + @tag, @value, @attribute = tag, value, attribute + message = "value <#{value}> of " + message << "attribute <#{attribute}> of " if attribute + message << "tag <#{tag}> is not available." + super(message) end end @@ -158,8 +141,10 @@ include Utils - def install_have_child_element(name) + def install_have_child_element(tag_name, uri, occurs, name=nil) + name ||= tag_name add_need_initialize_variable(name) + install_model(tag_name, uri, occurs, name) attr_accessor name install_element(name) do |n, elem_name| @@ -174,11 +159,13 @@ end alias_method(:install_have_attribute_element, :install_have_child_element) - def install_have_children_element(name, plural_name=nil) + def install_have_children_element(tag_name, uri, occurs, name=nil, plural_name=nil) + name ||= tag_name plural_name ||= "#{name}s" add_have_children_element(name, plural_name) add_plural_form(name, plural_name) - + install_model(tag_name, uri, occurs, plural_name) + def_children_accessor(name, plural_name) install_element(name, "s") do |n, elem_name| <<-EOC @@ -192,11 +179,14 @@ end end - def install_text_element(name) + def install_text_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil) + name ||= tag_name + disp_name ||= name self::ELEMENTS << name add_need_initialize_variable(name) + install_model(tag_name, uri, occurs, name) - attr_writer name + def_corresponded_attr_writer name, type, disp_name convert_attr_reader name install_element(name) do |n, elem_name| <<-EOC @@ -217,9 +207,13 @@ end end - def install_date_element(name, type, disp_name=name) + def install_date_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil) + name ||= tag_name + type ||= :w3cdtf + disp_name ||= name self::ELEMENTS << name add_need_initialize_variable(name) + install_model(tag_name, uri, occurs, name) # accessor convert_attr_reader name @@ -248,11 +242,13 @@ private def install_element(name, postfix="") elem_name = name.sub('_', ':') + method_name = "#{name}_element#{postfix}" + add_to_element_method(method_name) module_eval(<<-EOC, *get_file_and_line_from_caller(2)) - def #{name}_element#{postfix}(need_convert=true, indent='') + def #{method_name}(need_convert=true, indent='') #{yield(name, elem_name)} end - private :#{name}_element#{postfix} + private :#{method_name} EOC end @@ -279,7 +275,7 @@ else if @do_validate begin - @#{name} = Time.send('#{type}', new_value) + @#{name} = Time.__send__('#{type}', new_value) rescue ArgumentError raise NotAvailableValueError.new('#{disp_name}', new_value) end @@ -306,6 +302,68 @@ EOC end + def integer_writer(name, disp_name=name) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{name}=(new_value) + if new_value.nil? + @#{name} = new_value + else + if @do_validate + begin + @#{name} = Integer(new_value) + rescue ArgumentError + raise NotAvailableValueError.new('#{disp_name}', new_value) + end + else + @#{name} = new_value.to_i + end + end + end +EOC + end + + def positive_integer_writer(name, disp_name=name) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{name}=(new_value) + if new_value.nil? + @#{name} = new_value + else + if @do_validate + begin + tmp = Integer(new_value) + raise ArgumentError if tmp <= 0 + @#{name} = tmp + rescue ArgumentError + raise NotAvailableValueError.new('#{disp_name}', new_value) + end + else + @#{name} = new_value.to_i + end + end + end +EOC + end + + def boolean_writer(name, disp_name=name) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{name}=(new_value) + if new_value.nil? + @#{name} = new_value + else + if @do_validate and + ![true, false, "true", "false"].include?(new_value) + raise NotAvailableValueError.new('#{disp_name}', new_value) + end + if [true, false].include?(new_value) + @#{name} = new_value + else + @#{name} = new_value == "true" + end + end + end +EOC + end + def def_children_accessor(accessor_name, plural_name) module_eval(<<-EOC, *get_file_and_line_from_caller(2)) def #{plural_name} @@ -316,7 +374,7 @@ if args.empty? @#{accessor_name}.first else - @#{accessor_name}.send("[]", *args) + @#{accessor_name}[*args] end end @@ -328,29 +386,12 @@ if args.size == 1 @#{accessor_name}.push(args[0]) else - @#{accessor_name}.send("[]=", *args) + @#{accessor_name}.__send__("[]=", *args) end end alias_method(:set_#{accessor_name}, :#{accessor_name}=) EOC end - - def def_content_only_to_s - module_eval(<<-EOC, *get_file_and_line_from_caller(2)) - def to_s(need_convert=true, indent=calc_indent) - if @content - rv = tag(indent) do |next_indent| - h(@content) - end - rv = convert(rv) if need_convert - rv - else - "" - end - end -EOC - end - end class Element @@ -361,9 +402,10 @@ INDENT = " " MUST_CALL_VALIDATORS = {} - MODEL = [] + MODELS = [] GET_ATTRIBUTES = [] HAVE_CHILDREN_ELEMENTS = [] + TO_ELEMENT_METHODS = [] NEED_INITIALIZE_VARIABLES = [] PLURAL_FORMS = {} @@ -372,8 +414,8 @@ def must_call_validators MUST_CALL_VALIDATORS end - def model - MODEL + def models + MODELS end def get_attributes GET_ATTRIBUTES @@ -381,6 +423,9 @@ def have_children_elements HAVE_CHILDREN_ELEMENTS end + def to_element_methods + TO_ELEMENT_METHODS + end def need_initialize_variables NEED_INITIALIZE_VARIABLES end @@ -391,9 +436,10 @@ def inherited(klass) klass.const_set("MUST_CALL_VALIDATORS", {}) - klass.const_set("MODEL", []) + klass.const_set("MODELS", []) klass.const_set("GET_ATTRIBUTES", []) klass.const_set("HAVE_CHILDREN_ELEMENTS", []) + klass.const_set("TO_ELEMENT_METHODS", []) klass.const_set("NEED_INITIALIZE_VARIABLES", []) klass.const_set("PLURAL_FORMS", {}) @@ -402,14 +448,13 @@ @tag_name = name.split(/::/).last @tag_name[0,1] = @tag_name[0,1].downcase - @indent_size = name.split(/::/).size - 2 @have_content = false def self.must_call_validators super.merge(MUST_CALL_VALIDATORS) end - def self.model - MODEL + super + def self.models + MODELS + super end def self.get_attributes GET_ATTRIBUTES + super @@ -417,6 +462,9 @@ def self.have_children_elements HAVE_CHILDREN_ELEMENTS + super end + def self.to_element_methods + TO_ELEMENT_METHODS + super + end def self.need_initialize_variables NEED_INITIALIZE_VARIABLES + super end @@ -429,24 +477,47 @@ MUST_CALL_VALIDATORS[uri] = prefix end - def self.install_model(tag, occurs=nil) - if m = MODEL.find {|t, o| t == tag} - m[1] = occurs + def self.install_model(tag, uri, occurs=nil, getter=nil) + getter ||= tag + if m = MODELS.find {|t, u, o, g| t == tag and u == uri} + m[2] = occurs else - MODEL << [tag, occurs] + MODELS << [tag, uri, occurs, getter] end end - def self.install_get_attribute(name, uri, required=true) - attr_writer name + def self.install_get_attribute(name, uri, required=true, + type=nil, disp_name=nil, + element_name=nil) + disp_name ||= name + element_name ||= name + def_corresponded_attr_writer name, type, disp_name convert_attr_reader name - GET_ATTRIBUTES << [name, uri, required] + if type == :boolean and /^is/ =~ name + alias_method "\#{$POSTMATCH}?", name + end + GET_ATTRIBUTES << [name, uri, required, element_name] + add_need_initialize_variable(disp_name) end - def self.content_setup - attr_writer :content + def self.def_corresponded_attr_writer(name, type=nil, disp_name=name) + case type + when :integer + integer_writer name, disp_name + when :positive_integer + positive_integer_writer name, disp_name + when :boolean + boolean_writer name, disp_name + when :w3cdtf, :rfc822, :rfc2822 + date_writer name, type, disp_name + else + attr_writer name + end + end + + def self.content_setup(type=nil) + def_corresponded_attr_writer "content", type convert_attr_reader :content - def_content_only_to_s @have_content = true end @@ -458,6 +529,10 @@ HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name] end + def self.add_to_element_method(method_name) + TO_ELEMENT_METHODS << method_name + end + def self.add_need_initialize_variable(variable_name) NEED_INITIALIZE_VARIABLES << variable_name end @@ -474,7 +549,7 @@ end def required_uri - nil + "" end def install_ns(prefix, uri) @@ -487,19 +562,14 @@ def tag_name @tag_name end - - def indent_size - @indent_size - end - end attr_accessor :do_validate - def initialize(do_validate=true) + def initialize(do_validate=true, attrs={}) @converter = nil @do_validate = do_validate - initialize_variables + initialize_variables(attrs) end def tag_name @@ -510,10 +580,6 @@ tag_name end - def indent_size - self.class.indent_size - end - def converter=(converter) @converter = converter targets = children.dup @@ -533,14 +599,14 @@ end end - def validate + def validate(ignore_unknown_element=true) validate_attribute - __validate + __validate(ignore_unknown_element) end - def validate_for_stream(tags) + def validate_for_stream(tags, ignore_unknown_element=true) validate_attribute - __validate(tags, false) + __validate(ignore_unknown_element, tags, false) end def setup_maker(maker) @@ -551,11 +617,37 @@ setup_maker_elements(target) end end - + + def to_s(need_convert=true, indent='') + if self.class.have_content? + return "" unless @content + rv = tag(indent) do |next_indent| + h(@content) + end + else + rv = tag(indent) do |next_indent| + self.class.to_element_methods.collect do |method_name| + __send__(method_name, false, next_indent) + end + end + end + rv = convert(rv) if need_convert + rv + end + private - def initialize_variables + def initialize_variables(attrs) + normalized_attrs = {} + attrs.each do |key, value| + normalized_attrs[key.to_s] = value + end self.class.need_initialize_variables.each do |variable_name| - instance_eval("@#{variable_name} = nil") + value = normalized_attrs[variable_name.to_s] + if value + __send__("#{variable_name}=", value) + else + instance_eval("@#{variable_name} = nil") + end end initialize_have_children_elements @content = "" if self.class.have_content? @@ -567,13 +659,13 @@ end end - def tag(indent, additional_attrs=[], &block) + def tag(indent, additional_attrs={}, &block) next_indent = indent + INDENT attrs = collect_attrs return "" if attrs.nil? - attrs += additional_attrs + attrs.update(additional_attrs) start_tag = make_start_tag(indent, next_indent, attrs) if block @@ -610,21 +702,24 @@ end def collect_attrs - _attrs.collect do |name, required, alias_name| + attrs = {} + _attrs.each do |name, required, alias_name| value = __send__(alias_name || name) return nil if required and value.nil? - [name, value] - end.reject do |name, value| - value.nil? + next if value.nil? + return nil if attrs.has_key?(name) + attrs[name] = value end + attrs end def tag_name_with_prefix(prefix) "#{prefix}:#{tag_name}" end - + + # For backward compatibility def calc_indent - INDENT * (self.class.indent_size) + '' end def maker_target(maker) @@ -655,7 +750,6 @@ def setup_maker_elements(parent) self.class.have_children_elements.each do |name, plural_name| - real_name = name.sub(/^[^_]+_/, '') if parent.respond_to?(plural_name) target = parent.__send__(plural_name) __send__(plural_name).each do |elem| @@ -665,7 +759,7 @@ end end - def set_next_element(prefix, tag_name, next_element) + def set_next_element(tag_name, next_element) klass = next_element.class prefix = "" prefix << "#{klass.required_prefix}_" if klass.required_prefix @@ -677,22 +771,41 @@ __send__("#{prefix}#{tag_name}=", next_element) end end - - # not String class children. + def children - [] + rv = [] + self.class.models.each do |name, uri, occurs, getter| + value = __send__(getter) + next if value.nil? + value = [value] unless value.is_a?(Array) + value.each do |v| + rv << v if v.is_a?(Element) + end + end + rv end - # default #validate() argument. def _tags - [] + rv = [] + self.class.models.each do |name, uri, occurs, getter| + value = __send__(getter) + next if value.nil? + if value.is_a?(Array) + rv.concat([[uri, name]] * value.size) + else + rv << [uri, name] + end + end + rv end def _attrs - [] + self.class.get_attributes.collect do |name, uri, required, element_name| + [element_name, required, name] + end end - def __validate(tags=_tags, recursive=true) + def __validate(ignore_unknown_element, tags=_tags, recursive=true) if recursive children.compact.each do |child| child.validate @@ -701,15 +814,13 @@ must_call_validators = self.class.must_call_validators tags = tag_filter(tags.dup) p tags if DEBUG - self.class::NSPOOL.each do |prefix, uri| - if tags.has_key?(uri) and !must_call_validators.has_key?(uri) - meth = "#{prefix}_validate" - send(meth, tags[uri]) if respond_to?(meth, true) + must_call_validators.each do |uri, prefix| + _validate(ignore_unknown_element, tags[uri], uri) + meth = "#{prefix}_validate" + if respond_to?(meth, true) + __send__(meth, ignore_unknown_element, tags[uri], uri) end end - must_call_validators.each do |uri, prefix| - send("#{prefix}_validate", tags[uri]) - end end def validate_attribute @@ -720,35 +831,25 @@ end end - def other_element(need_convert, indent='') - rv = [] - private_methods.each do |meth| - if /\A([^_]+)_[^_]+_elements?\z/ =~ meth and - self.class::NSPOOL.has_key?($1) - res = __send__(meth, need_convert) - rv << "#{indent}#{res}" if /\A\s*\z/ !~ res - end - end - rv.join("\n") - end - - def _validate(tags, model=self.class.model) + def _validate(ignore_unknown_element, tags, uri, models=self.class.models) count = 1 do_redo = false not_shift = false tag = nil - element_names = model.collect {|elem| elem[0]} + models = models.find_all {|model| model[1] == uri} + element_names = models.collect {|model| model[0]} if tags tags_size = tags.size tags = tags.sort_by {|x| element_names.index(x) || tags_size} end - model.each_with_index do |elem, i| + models.each_with_index do |model, i| + name, model_uri, occurs, getter = model if DEBUG p "before" p tags - p elem + p model end if not_shift @@ -762,41 +863,41 @@ p count end - case elem[1] + case occurs when '?' if count > 2 - raise TooMuchTagError.new(elem[0], tag_name) + raise TooMuchTagError.new(name, tag_name) else - if elem[0] == tag + if name == tag do_redo = true else not_shift = true end end when '*' - if elem[0] == tag + if name == tag do_redo = true else not_shift = true end when '+' - if elem[0] == tag + if name == tag do_redo = true else if count > 1 not_shift = true else - raise MissingTagError.new(elem[0], tag_name) + raise MissingTagError.new(name, tag_name) end end else - if elem[0] == tag - if model[i+1] and model[i+1][0] != elem[0] and - tags and tags.first == elem[0] - raise TooMuchTagError.new(elem[0], tag_name) + if name == tag + if models[i+1] and models[i+1][0] != name and + tags and tags.first == name + raise TooMuchTagError.new(name, tag_name) end else - raise MissingTagError.new(elem[0], tag_name) + raise MissingTagError.new(name, tag_name) end end @@ -817,8 +918,8 @@ end - if !tags.nil? and !tags.empty? - raise NotExceptedTagError.new(tag, tag_name) + if !ignore_unknown_element and !tags.nil? and !tags.empty? + raise NotExpectedTagError.new(tags.first, uri, tag_name) end end @@ -865,11 +966,22 @@ setup_maker_elements(maker) end - + + def to_xml(version=nil, &block) + if version.nil? or version == @rss_version + to_s + else + RSS::Maker.make(version) do |maker| + setup_maker(maker) + block.call(maker) if block + end.to_s + end + end + private - def tag(indent, attrs, &block) + def tag(indent, attrs={}, &block) rv = xmldecl + xml_stylesheet_pi - rv << super(indent, attrs, &block) + rv << super(indent, ns_declarations.merge(attrs), &block) rv end @@ -884,19 +996,19 @@ end def ns_declarations + decls = {} self.class::NSPOOL.collect do |prefix, uri| prefix = ":#{prefix}" unless prefix.empty? - ["xmlns#{prefix}", uri] + decls["xmlns#{prefix}"] = uri end + decls end def setup_maker_elements(maker) channel.setup_maker(maker) if channel image.setup_maker(maker) if image textinput.setup_maker(maker) if textinput - items.each do |item| - item.setup_maker(maker) - end + super(maker) end end Index: rss/converter.rb =================================================================== --- rss/converter.rb (revision 2900) +++ rss/converter.rb (working copy) @@ -66,7 +66,7 @@ end end - def def_uconv_convert_if_can(meth, to_enc, from_enc) + def def_uconv_convert_if_can(meth, to_enc, from_enc, nkf_arg) begin require "uconv" def_convert(1) do |value| @@ -79,24 +79,27 @@ EOC end rescue LoadError - def_iconv_convert(to_enc, from_enc, 1) + require 'nkf' + def_convert(1) do |value| + "NKF.nkf(#{nkf_arg.dump}, #{value})" + end end end def def_to_euc_jp_from_utf_8 - def_uconv_convert_if_can('u8toeuc', 'EUC-JP', 'UTF-8') + def_uconv_convert_if_can('u8toeuc', 'EUC-JP', 'UTF-8', '-We') end def def_to_utf_8_from_euc_jp - def_uconv_convert_if_can('euctou8', 'UTF-8', 'EUC-JP') + def_uconv_convert_if_can('euctou8', 'UTF-8', 'EUC-JP', '-Ew') end def def_to_shift_jis_from_utf_8 - def_uconv_convert_if_can('u8tosjis', 'Shift_JIS', 'UTF-8') + def_uconv_convert_if_can('u8tosjis', 'Shift_JIS', 'UTF-8', '-Ws') end def def_to_utf_8_from_shift_jis - def_uconv_convert_if_can('sjistou8', 'UTF-8', 'Shift_JIS') + def_uconv_convert_if_can('sjistou8', 'UTF-8', 'Shift_JIS', '-Sw') end def def_to_euc_jp_from_shift_jis Index: rss/content.rb =================================================================== --- rss/content.rb (revision 2900) +++ rss/content.rb (working copy) @@ -15,28 +15,13 @@ def self.append_features(klass) super - - klass.module_eval(<<-EOC, *get_file_and_line_from_caller(1)) - %w(encoded).each do |name| - install_text_element("\#{CONTENT_PREFIX}_\#{name}") - end - EOC - end - def content_validate(tags) - counter = {} - ELEMENTS.each do |name| - counter[name] = 0 + klass.install_must_call_validator(CONTENT_PREFIX, CONTENT_URI) + %w(encoded).each do |name| + klass.install_text_element(name, CONTENT_URI, "?", + "#{CONTENT_PREFIX}_#{name}") end - - tags.each do |tag| - key = "#{CONTENT_PREFIX}_#{tag}" - raise UnknownTagError.new(tag, CONTENT_URI) unless counter.has_key?(key) - counter[key] += 1 - raise TooMuchTagError.new(tag, tag_name) if counter[key] > 1 - end end - end class RDF Index: rss/xmlscanner.rb =================================================================== --- rss/xmlscanner.rb (revision 2900) +++ rss/xmlscanner.rb (working copy) @@ -1,19 +1,29 @@ require 'xmlscan/scanner' +require 'stringio' module RSS class XMLScanParser < BaseParser + class << self + def listener + XMLScanListener + end + end + private - def listener - XMLScanListener - end - def _parse begin - XMLScan::XMLScanner.new(@listener).parse(@rss) + if @rss.is_a?(String) + input = StringIO.new(@rss) + else + input = @rss + end + scanner = XMLScan::XMLScanner.new(@listener) + scanner.parse(input) rescue XMLScan::Error => e - raise NotWellFormedError.new(e.lineno){e.message} + lineno = e.lineno || scanner.lineno || input.lineno + raise NotWellFormedError.new(lineno){e.message} end end @@ -57,7 +67,7 @@ end def on_entityref(ref) - text(ENTITIES[ref]) + text(entity(ref)) end def on_charref(code) @@ -79,7 +89,7 @@ end def on_attr_entityref(ref) - @current_attr << ENTITIES[ref] + @current_attr << entity(ref) end def on_attr_charref(code) @@ -97,6 +107,15 @@ tag_end(name) end + private + def entity(ref) + ent = ENTITIES[ref] + if ent + ent + else + wellformed_error("undefined entity: #{ref}") + end + end end end Index: tempfile.rb =================================================================== --- tempfile.rb (revision 2900) +++ tempfile.rb (working copy) @@ -67,7 +67,7 @@ end def make_tmpname(basename, n) - sprintf('%s%d.%d', basename, $$, n) + sprintf('%s.%d.%d', basename, $$, n) end private :make_tmpname Index: mathn.rb =================================================================== --- mathn.rb (revision 2900) +++ mathn.rb (working copy) @@ -43,6 +43,7 @@ end def prime_division + raise ZeroDivisionError if self == 0 ps = Prime.new value = self pv = [] Index: irb/ext/save-history.rb =================================================================== --- irb/ext/save-history.rb (revision 2900) +++ irb/ext/save-history.rb (working copy) @@ -1,3 +1,4 @@ +#!/usr/local/bin/ruby # # save-history.rb - # $Release Version: 0.9.5$ Index: irb/locale.rb =================================================================== --- irb/locale.rb (revision 2900) +++ irb/locale.rb (working copy) @@ -71,7 +71,7 @@ end def puts(*opts) - ary = opts.collect{|opt| String(opts)} + ary = opts.collect{|opt| String(opt)} super(*ary) end @@ -153,8 +153,8 @@ end def search_file(path, file) - if File.exists?(p1 = path + lc_path(file, "C")) - if File.exists?(p2 = path + lc_path(file)) + if File.exist?(p1 = path + lc_path(file, "C")) + if File.exist?(p2 = path + lc_path(file)) return p2 else end Index: irb/cmd/subirb.rb =================================================================== --- irb/cmd/subirb.rb (revision 2900) +++ irb/cmd/subirb.rb (working copy) @@ -1,3 +1,4 @@ +#!/usr/local/bin/ruby # # multi.rb - # $Release Version: 0.9.5$ Index: irb/init.rb =================================================================== --- irb/init.rb (revision 2900) +++ irb/init.rb (working copy) @@ -1,9 +1,9 @@ # # irb/init.rb - irb initialize module -# $Release Version: 0.9.5$ -# $Revision$ -# $Date$ -# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# $Release Version: 0.9.5$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ruby-lang.org) # # -- # @@ -237,7 +237,7 @@ # enumerate possible rc-file base name generators def IRB.rc_file_generators if irbrc = ENV["IRBRC"] - yield proc{|rc| irbrc} + yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} end if home = ENV["HOME"] yield proc{|rc| home+"/.irb#{rc}"} Index: irb/extend-command.rb =================================================================== --- irb/extend-command.rb (revision 2900) +++ irb/extend-command.rb (working copy) @@ -106,14 +106,14 @@ ] - def EXCB.install_extend_commands + def self.install_extend_commands for args in @EXTEND_COMMANDS def_extend_command(*args) end end # aliases = [commans_alias, flag], ... - def EXCB.def_extend_command(cmd_name, cmd_class, load_file = nil, *aliases) + def self.def_extend_command(cmd_name, cmd_class, load_file = nil, *aliases) case cmd_class when Symbol cmd_class = cmd_class.id2name @@ -172,7 +172,7 @@ "irb_" + method_name + "_org" end - def EXCB.extend_object(obj) + def self.extend_object(obj) unless (class< "\'", "Q" => "\"", "x" => "\`", - "r" => "\/", + "r" => "/", "w" => "]", "W" => "]", "s" => ":" @@ -326,35 +326,35 @@ "\'" => TkSTRING, "\"" => TkSTRING, "\`" => TkXSTRING, - "\/" => TkREGEXP, + "/" => TkREGEXP, "]" => TkDSTRING, ":" => TkSYMBOL } DLtype2Token = { "\"" => TkDSTRING, "\`" => TkDXSTRING, - "\/" => TkDREGEXP, + "/" => TkDREGEXP, } def lex_init() @OP = IRB::SLex.new - @OP.def_rules("\0", "\004", "\032") do + @OP.def_rules("\0", "\004", "\032") do |op, io| Token(TkEND_OF_SCRIPT) end - @OP.def_rules(" ", "\t", "\f", "\r", "\13") do + @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io| @space_seen = true while getc =~ /[ \t\f\r\13]/; end ungetc Token(TkSPACE) end - @OP.def_rule("#") do - |op, io| + @OP.def_rule("#") do |op, io| identify_comment end - @OP.def_rule("=begin", proc{@prev_char_no == 0 && peek(0) =~ /\s/}) do + @OP.def_rule("=begin", + proc{|op, io| @prev_char_no == 0 && peek(0) =~ /\s/}) do |op, io| @ltype = "=" until getc == "\n"; end @@ -366,7 +366,7 @@ Token(TkRD_COMMENT) end - @OP.def_rule("\n") do + @OP.def_rule("\n") do |op, io| print "\\n\n" if RubyLex.debug? case @lex_state when EXPR_BEG, EXPR_FNAME, EXPR_DOT @@ -478,13 +478,13 @@ Token(TkOPASGN, $1) end - @OP.def_rule("+@", proc{@lex_state == EXPR_FNAME}) do + @OP.def_rule("+@", proc{|op, io| @lex_state == EXPR_FNAME}) do |op, io| @lex_state = EXPR_ARG Token(op) end - @OP.def_rule("-@", proc{@lex_state == EXPR_FNAME}) do + @OP.def_rule("-@", proc{|op, io| @lex_state == EXPR_FNAME}) do |op, io| @lex_state = EXPR_ARG Token(op) @@ -509,6 +509,7 @@ end @OP.def_rule(".") do + |op, io| @lex_state = EXPR_BEG if peek(0) =~ /[0-9]/ ungetc @@ -539,6 +540,7 @@ end @OP.def_rule(":") do + |op, io| if @lex_state == EXPR_END || peek(0) =~ /\s/ @lex_state = EXPR_BEG Token(TkCOLON) @@ -549,6 +551,7 @@ end @OP.def_rule("::") do + |op, io| # p @lex_state.id2name, @space_seen if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen @lex_state = EXPR_BEG @@ -576,6 +579,7 @@ end @OP.def_rules("^") do + |op, io| @lex_state = EXPR_BEG Token("^") end @@ -603,16 +607,19 @@ end @OP.def_rule("~") do + |op, io| @lex_state = EXPR_BEG Token("~") end - @OP.def_rule("~@", proc{@lex_state == EXPR_FNAME}) do + @OP.def_rule("~@", proc{|op, io| @lex_state == EXPR_FNAME}) do + |op, io| @lex_state = EXPR_BEG Token("~") end @OP.def_rule("(") do + |op, io| @indent += 1 if @lex_state == EXPR_BEG || @lex_state == EXPR_MID @lex_state = EXPR_BEG @@ -625,17 +632,20 @@ tk = Token(tk_c) end - @OP.def_rule("[]", proc{@lex_state == EXPR_FNAME}) do + @OP.def_rule("[]", proc{|op, io| @lex_state == EXPR_FNAME}) do + |op, io| @lex_state = EXPR_ARG Token("[]") end - @OP.def_rule("[]=", proc{@lex_state == EXPR_FNAME}) do + @OP.def_rule("[]=", proc{|op, io| @lex_state == EXPR_FNAME}) do + |op, io| @lex_state = EXPR_ARG Token("[]=") end @OP.def_rule("[") do + |op, io| @indent += 1 if @lex_state == EXPR_FNAME tk_c = TkfLBRACK @@ -654,6 +664,7 @@ end @OP.def_rule("{") do + |op, io| @indent += 1 if @lex_state != EXPR_END && @lex_state != EXPR_ARG tk_c = TkLBRACE @@ -666,6 +677,7 @@ end @OP.def_rule('\\') do + |op, io| if getc == "\n" @space_seen = true @continue = true @@ -692,10 +704,12 @@ end @OP.def_rule('$') do + |op, io| identify_gvar end @OP.def_rule('@') do + |op, io| if peek(0) =~ /[\w_@]/ ungetc identify_identifier @@ -936,6 +950,7 @@ @lex_state = EXPR_END if peek(0) == "0" && peek(1) !~ /[.eE]/ + getc case peek(0) when /[xX]/ ch = getc @@ -956,7 +971,7 @@ else return Token(TkINTEGER) end - + len0 = true non_digit = false while ch = getc @@ -1086,7 +1101,7 @@ def read_escape case ch = getc when "\n", "\r", "\f" - when "\\", "n", "t", "r", "f", "v", "a", "e", "b" #" + when "\\", "n", "t", "r", "f", "v", "a", "e", "b", "s" #" when /[0-7]/ ungetc ch 3.times do @@ -1121,7 +1136,7 @@ end end - when "C", "c", "^" + when "C", "c" #, "^" if ch == "C" and (ch = getc) != "-" ungetc elsif (ch = getc) == "\\" #" Index: irb/completion.rb =================================================================== --- irb/completion.rb (revision 2900) +++ irb/completion.rb (working copy) @@ -99,18 +99,30 @@ candidates = Symbol.instance_methods(true) select_message(receiver, message, candidates) - when /^([0-9_]+(\.[0-9_]+)?(e[0-9]+)?)\.([^.]*)$/ + when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/ # Numeric receiver = $1 - message = Regexp.quote($4) + message = Regexp.quote($5) begin candidates = eval(receiver, bind).methods rescue Exception - candidates + candidates = [] end select_message(receiver, message, candidates) + when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/ + # Numeric(0xFFFF) + receiver = $1 + message = Regexp.quote($2) + + begin + candidates = eval(receiver, bind).methods + rescue Exception + candidates = [] + end + select_message(receiver, message, candidates) + when /^(\$[^.]*)$/ candidates = global_variables.grep(Regexp.new(Regexp.quote($1))) @@ -138,8 +150,13 @@ # func1.func2 candidates = [] ObjectSpace.each_object(Module){|m| - next if m.name != "IRB::Context" and - /^(IRB|SLex|RubyLex|RubyToken)/ =~ m.name + begin + name = m.name + rescue Exception + name = "" + end + next if name != "IRB::Context" and + /^(IRB|SLex|RubyLex|RubyToken)/ =~ name candidates.concat m.instance_methods(false) } candidates.sort! Index: delegate.rb =================================================================== --- delegate.rb (revision 2900) +++ delegate.rb (working copy) @@ -140,7 +140,7 @@ rescue Exception $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #` $@.delete_if{|s| /^\\(eval\\):/ =~ s} - ::Kernel::raise + Kernel::raise end end EOS @@ -184,6 +184,7 @@ # Reinitializes delegation from a serialized object. def marshal_load(obj) initialize_methods(obj) + __setobj__(obj) end end @@ -290,7 +291,7 @@ } for method in methods begin - klass.module_eval <<-EOS, __FILE__, __LINE__+1 + klass.module_eval <<-EOS def #{method}(*args, &block) begin @_dc_obj.__send__(:#{method}, *args, &block) Index: pstore.rb =================================================================== --- pstore.rb (revision 2900) +++ pstore.rb (working copy) @@ -78,6 +78,11 @@ # end # class PStore + binmode = defined?(File::BINARY) ? File::BINARY : 0 + RDWR_ACCESS = File::RDWR | File::CREAT | binmode + RD_ACCESS = File::RDONLY | binmode + WR_ACCESS = File::WRONLY | File::CREAT | File::TRUNC | binmode + # The error type thrown by all PStore methods. class Error < StandardError end @@ -135,14 +140,15 @@ # raise PStore::Error if called at any other time. # def fetch(name, default=PStore::Error) + in_transaction unless @table.key? name if default==PStore::Error raise PStore::Error, format("undefined root name `%s'", name) else - default + return default end end - self[name] + @table[name] end # # Stores an individual Ruby object or a hierarchy of Ruby objects in the data @@ -286,17 +292,15 @@ content = nil unless read_only - file = File.open(@filename, File::RDWR | File::CREAT) - file.binmode + file = File.open(@filename, RDWR_ACCESS) file.flock(File::LOCK_EX) commit_new(file) if FileTest.exist?(new_file) content = file.read() else begin - file = File.open(@filename, File::RDONLY) - file.binmode + file = File.open(@filename, RD_ACCESS) file.flock(File::LOCK_SH) - content = (File.read(new_file) rescue file.read()) + content = (File.open(new_file, RD_ACCESS) {|n| n.read} rescue file.read()) rescue Errno::ENOENT content = "" end @@ -325,10 +329,7 @@ tmp_file = @filename + ".tmp" content = dump(@table) if !md5 || size != content.size || md5 != Digest::MD5.digest(content) - File.open(tmp_file, "w") {|t| - t.binmode - t.write(content) - } + File.open(tmp_file, WR_ACCESS) {|t| t.write(content)} File.rename(tmp_file, new_file) commit_new(file) end @@ -364,8 +365,7 @@ f.truncate(0) f.rewind new_file = @filename + ".new" - File.open(new_file) do |nf| - nf.binmode + File.open(new_file, RD_ACCESS) do |nf| FileUtils.copy_stream(nf, f) end File.unlink(new_file) Index: logger.rb =================================================================== --- logger.rb (revision 2900) +++ logger.rb (working copy) @@ -1,9 +1,8 @@ # logger.rb - saimple logging utility # Copyright (C) 2000-2003, 2005 NAKAMURA, Hiroshi . +require 'monitor' -# = logger.rb -# # Simple logging utility. # # Author:: NAKAMURA, Hiroshi @@ -13,11 +12,6 @@ # license; either the dual license version in 2003, or any later version. # Revision:: $Id$ # -# See Logger for documentation. -# - - -# # == Description # # The Logger class provides a simple but sophisticated logging utility that @@ -149,7 +143,7 @@ # 2. Log4r (somewhat) compatible interface. # # logger.level = Logger::INFO -# +# # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN # # @@ -174,9 +168,6 @@ # -require 'monitor' - - class Logger VERSION = "1.2.6" /: (\S+),v (\S+)/ =~ %q$Id$ Index: getoptlong.rb =================================================================== --- getoptlong.rb (revision 2900) +++ getoptlong.rb (working copy) @@ -1,18 +1,95 @@ -# -*- Ruby -*- -# Copyright (C) 1998, 1999, 2000 Motoyuki Kasahara # -# You may redistribute it and/or modify it under the same license +# GetoptLong for Ruby +# +# Copyright (C) 1998, 1999, 2000 Motoyuki Kasahara. +# +# You may redistribute and/or modify this library under the same license # terms as Ruby. # +# See GetoptLong for documentation. +# +# Additional documents and the latest version of `getoptlong.rb' can be +# found at http://www.sra.co.jp/people/m-kasahr/ruby/getoptlong/ +# The GetoptLong class allows you to parse command line options similarly to +# the GNU getopt_long() C library call. Note, however, that GetoptLong is a +# pure Ruby implementation. # -# Documents and latest version of `getoptlong.rb' are found at: -# http://www.sra.co.jp/people/m-kasahr/ruby/getoptlong/ +# GetoptLong allows for POSIX-style options like --file as well +# as single letter options like -f # - +# The empty option -- (two minus symbols) is used to end option +# processing. This can be particularly important if options have optional +# arguments. # -# Parse command line options just like GNU getopt_long(). +# Here is a simple example of usage: # +# # == Synopsis +# # +# # hello: greets user, demonstrates command line parsing +# # +# # == Usage +# # +# # hello [OPTION] ... DIR +# # +# # -h, --help: +# # show help +# # +# # --repeat x, -n x: +# # repeat x times +# # +# # --name [name]: +# # greet user by name, if name not supplied default is John +# # +# # DIR: The directory in which to issue the greeting. +# +# require 'getoptlong' +# require 'rdoc/usage' +# +# opts = GetoptLong.new( +# [ '--help', '-h', GetoptLong::NO_ARGUMENT ], +# [ '--repeat', '-n', GetoptLong::REQUIRED_ARGUMENT ], +# [ '--name', GetoptLong::OPTIONAL_ARGUMENT ] +# ) +# +# dir = nil +# name = nil +# repetitions = 1 +# opts.each do |opt, arg| +# case opt +# when '--help' +# RDoc::usage +# when '--repeat' +# repetitions = arg.to_i +# when '--name' +# if arg == '' +# name = 'John' +# else +# name = arg +# end +# end +# end +# +# if ARGV.length != 1 +# puts "Missing dir argument (try --help)" +# exit 0 +# end +# +# dir = ARGV.shift +# +# Dir.chdir(dir) +# for i in (1..repetitions) +# print "Hello" +# if name +# print ", #{name}" +# end +# puts +# end +# +# Example command line: +# +# hello -n 6 --name -- /tmp +# class GetoptLong # # Orderings. @@ -40,8 +117,21 @@ class InvalidOption < Error; end # - # Initializer. + # Set up option processing. # + # The options to support are passed to new() as an array of arrays. + # Each sub-array contains any number of String option names which carry + # the same meaning, and one of the following flags: + # + # GetoptLong::NO_ARGUMENT :: Option does not take an argument. + # + # GetoptLong::REQUIRED_ARGUMENT :: Option always takes an argument. + # + # GetoptLong::OPTIONAL_ARGUMENT :: Option may or may not take an argument. + # + # The first option name is considered to be the preferred (canonical) name. + # Other than that, the elements of each sub-array can be in any order. + # def initialize(*arguments) # # Current ordering. @@ -103,8 +193,54 @@ end # - # Set ordering. + # Set the handling of the ordering of options and arguments. + # A RuntimeError is raised if option processing has already started. # + # The supplied value must be a member of GetoptLong::ORDERINGS. It alters + # the processing of options as follows: + # + # REQUIRE_ORDER : + # + # Options are required to occur before non-options. + # + # Processing of options ends as soon as a word is encountered that has not + # been preceded by an appropriate option flag. + # + # For example, if -a and -b are options which do not take arguments, + # parsing command line arguments of '-a one -b two' would result in + # 'one', '-b', 'two' being left in ARGV, and only ('-a', '') being + # processed as an option/arg pair. + # + # This is the default ordering, if the environment variable + # POSIXLY_CORRECT is set. (This is for compatibility with GNU getopt_long.) + # + # PERMUTE : + # + # Options can occur anywhere in the command line parsed. This is the + # default behavior. + # + # Every sequence of words which can be interpreted as an option (with or + # without argument) is treated as an option; non-option words are skipped. + # + # For example, if -a does not require an argument and -b optionally takes + # an argument, parsing '-a one -b two three' would result in ('-a','') and + # ('-b', 'two') being processed as option/arg pairs, and 'one','three' + # being left in ARGV. + # + # If the ordering is set to PERMUTE but the environment variable + # POSIXLY_CORRECT is set, REQUIRE_ORDER is used instead. This is for + # compatibility with GNU getopt_long. + # + # RETURN_IN_ORDER : + # + # All words on the command line are processed as options. Words not + # preceded by a short or long option flag are passed as arguments + # with an option of '' (empty string). + # + # For example, if -a requires an argument but -b does not, a command line + # of '-a one -b two three' would result in option/arg pairs of ('-a', 'one') + # ('-b', ''), ('', 'two'), ('', 'three') being processed. + # def ordering=(ordering) # # The method is failed if option processing has already started. @@ -134,8 +270,10 @@ attr_reader :ordering # - # Set options + # Set options. Takes the same argument as GetoptLong.new. # + # Raises a RuntimeError if option processing has already started. + # def set_options(*arguments) # # The method is failed if option processing has already started. @@ -208,7 +346,7 @@ end # - # Set/Unset `quit' mode. + # Set/Unset `quiet' mode. # attr_writer :quiet @@ -223,7 +361,7 @@ alias quiet? quiet # - # Terminate option processing. + # Explicitly terminate option processing. # def terminate return nil if @status == STATUS_TERMINATED @@ -243,7 +381,7 @@ end # - # Examine whether option processing is terminated or not. + # Returns true if option processing has terminated, false otherwise. # def terminated? return @status == STATUS_TERMINATED @@ -276,16 +414,24 @@ # alias error? error + # Return the appropriate error message in POSIX-defined format. + # If no error has occurred, returns nil. # - # Return an error message. - # def error_message return @error_message end # - # Get next option name and its argument as an array. + # Get next option name and its argument, as an Array of two elements. # + # The option name is always converted to the first (preferred) + # name given in the original options to GetoptLong.new. + # + # Example: ['--option', 'value'] + # + # Returns nil if the processing is complete (as determined by + # STATUS_TERMINATED). + # def get option_name, option_argument = nil, '' @@ -450,9 +596,16 @@ # alias get_option get - # # Iterator version of `get'. # + # The block is called repeatedly with two arguments: + # The first is the option name. + # The second is the argument which followed it (if any). + # Example: ('--opt', 'value') + # + # The option name is always converted to the first (preferred) + # name given in the original options to GetoptLong.new. + # def each loop do option_name, option_argument = get_option Index: readbytes.rb =================================================================== --- readbytes.rb (revision 2900) +++ readbytes.rb (working copy) @@ -1,17 +1,22 @@ -# readbytes.rb -# -# add IO#readbytes, which reads fixed sized data. -# it guarantees read data size. +# TruncatedDataError is raised when IO#readbytes fails to read enough data. class TruncatedDataError/, :COMMENT) end def add_word_pair(start, stop, name) Index: rdoc/markup/simple_markup/preprocess.rb =================================================================== --- rdoc/markup/simple_markup/preprocess.rb (revision 2900) +++ rdoc/markup/simple_markup/preprocess.rb (working copy) @@ -43,7 +43,12 @@ def include_file(name, indent) if (full_name = find_include_file(name)) content = File.open(full_name) {|f| f.read} - res = content.gsub(/^#?/, indent) + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) + end else $stderr.puts "Couldn't find file to include: '#{name}'" '' Index: rdoc/markup/.document =================================================================== --- rdoc/markup/.document (revision 0) +++ rdoc/markup/.document (revision 0) @@ -0,0 +1,2 @@ +simple_markup +simple_markup.rb Index: rdoc/parsers/parse_f95.rb =================================================================== --- rdoc/parsers/parse_f95.rb (revision 2900) +++ rdoc/parsers/parse_f95.rb (working copy) @@ -1,15 +1,160 @@ -# Parse a Fortran 95 file. +#= parse_f95.rb - Fortran95 Parser +# +#== Overview +# +#"parse_f95.rb" parses Fortran95 files with suffixes "f90", "F90", "f95" +#and "F95". Fortran95 files are expected to be conformed to Fortran95 +#standards. +# +#== Rules +# +#Fundamental rules are same as that of the Ruby parser. +#But comment markers are '!' not '#'. +# +#=== Correspondence between RDoc documentation and Fortran95 programs +# +#"parse_f95.rb" parses main programs, modules, subroutines, functions, +#derived-types, public variables, public constants, +#defined operators and defined assignments. +#These components are described in items of RDoc documentation, as follows. +# +#Files :: Files (same as Ruby) +#Classes :: Modules +#Methods :: Subroutines, functions, variables, constants, derived-types, defined operators, defined assignments +#Required files :: Files in which imported modules, external subroutines and external functions are defined. +#Included Modules :: List of imported modules +#Attributes :: List of derived-types, List of imported modules all of whose components are published again +# +#Components listed in 'Methods' (subroutines, functions, ...) +#defined in modules are described in the item of 'Classes'. +#On the other hand, components defined in main programs or +#as external procedures are described in the item of 'Files'. +# +#=== Components parsed by default +# +#By default, documentation on public components (subroutines, functions, +#variables, constants, derived-types, defined operators, +#defined assignments) are generated. +#With "--all" option, documentation on all components +#are generated (almost same as the Ruby parser). +# +#=== Information parsed automatically +# +#The following information is automatically parsed. +# +#* Types of arguments +#* Types of variables and constants +#* Types of variables in the derived types, and initial values +#* NAMELISTs and types of variables in them, and initial values +# +#Aliases by interface statement are described in the item of 'Methods'. +# +#Components which are imported from other modules and published again +#are described in the item of 'Methods'. +# +#=== Format of comment blocks +# +#Comment blocks should be written as follows. +#Comment blocks are considered to be ended when the line without '!' +#appears. +#The indentation is not necessary. +# +# ! (Top of file) +# ! +# ! Comment blocks for the files. +# ! +# !-- +# ! The comment described in the part enclosed by +# ! "!--" and "!++" is ignored. +# !++ +# ! +# module hogehoge +# ! +# ! Comment blocks for the modules (or the programs). +# ! +# +# private +# +# logical :: a ! a private variable +# real, public :: b ! a public variable +# integer, parameter :: c = 0 ! a public constant +# +# public :: c +# public :: MULTI_ARRAY +# public :: hoge, foo +# +# type MULTI_ARRAY +# ! +# ! Comment blocks for the derived-types. +# ! +# real, pointer :: var(:) =>null() ! Comments block for the variables. +# integer :: num = 0 +# end type MULTI_ARRAY +# +# contains +# +# subroutine hoge( in, & ! Comment blocks between continuation lines are ignored. +# & out ) +# ! +# ! Comment blocks for the subroutines or functions +# ! +# character(*),intent(in):: in ! Comment blocks for the arguments. +# character(*),intent(out),allocatable,target :: in +# ! Comment blocks can be +# ! written under Fortran statements. +# +# character(32) :: file ! This comment parsed as a variable in below NAMELIST. +# integer :: id +# +# namelist /varinfo_nml/ file, id +# ! +# ! Comment blocks for the NAMELISTs. +# ! Information about variables are described above. +# ! +# +# .... +# +# end subroutine hoge +# +# integer function foo( in ) +# ! +# ! This part is considered as comment block. +# +# ! Comment blocks under blank lines are ignored. +# ! +# integer, intent(in):: inA ! This part is considered as comment block. +# +# ! This part is ignored. +# +# end function foo +# +# subroutine hide( in, & +# & out ) !:nodoc: +# ! +# ! If "!:nodoc:" is described at end-of-line in subroutine +# ! statement as above, the subroutine is ignored. +# ! This assignment can be used to modules, subroutines, +# ! functions, variables, constants, derived-types, +# ! defined operators, defined assignments, +# ! list of imported modules ("use" statement). +# ! +# +# .... +# +# end subroutine hide +# +# end module hogehoge +# + require "rdoc/code_objects" module RDoc - # See rdoc/parsers/parse_f95.rb - class Token NO_TEXT = "??".freeze - + def initialize(line_no, char_no) @line_no = line_no @char_no = char_no @@ -26,85 +171,997 @@ end + # See rdoc/parsers/parse_f95.rb + class Fortran95parser extend ParserFactory - parse_files_matching(/\.(f9(0|5)|F)$/) - + parse_files_matching(/\.((f|F)9(0|5)|F)$/) + + @@external_aliases = [] + @@public_methods = [] + + # "false":: Comments are below source code + # "true" :: Comments are upper source code + COMMENTS_ARE_UPPER = false + + # Internal alias message + INTERNAL_ALIAS_MES = "Alias for" + + # External alias message + EXTERNAL_ALIAS_MES = "The entity is" + # prepare to parse a Fortran 95 file def initialize(top_level, file_name, body, options, stats) @body = body @stats = stats + @file_name = file_name @options = options @top_level = top_level @progress = $stderr unless options.quiet end - + # devine code constructs def scan - # modules and programs - if @body =~ /^(module|program)\s+(\w+)/i - progress "m" - f9x_module = @top_level.add_module NormalClass, $2 - f9x_module.record_location @top_level - first_comment, second_comment = $`.gsub(/^!\s?/,"").split "\n\s*\n" - if second_comment - @top_level.comment = first_comment if first_comment - f9x_module.comment = second_comment - else - f9x_module.comment = first_comment if first_comment - end + # remove private comment + remaining_code = remove_private_comments(@body) + + # continuation lines are united to one line + remaining_code = united_to_one_line(remaining_code) + + # semicolons are replaced to line feed + remaining_code = semicolon_to_linefeed(remaining_code) + + # collect comment for file entity + whole_comment, remaining_code = collect_first_comment(remaining_code) + @top_level.comment = whole_comment + + # String "remaining_code" is converted to Array "remaining_lines" + remaining_lines = remaining_code.split("\n") + + # "module" or "program" parts are parsed (new) + # + level_depth = 0 + block_searching_flag = nil + block_searching_lines = [] + pre_comment = [] + module_program_trailing = "" + module_program_name = "" + other_block_level_depth = 0 + other_block_searching_flag = nil + remaining_lines.collect!{|line| + if !block_searching_flag && !other_block_searching_flag + if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i + block_searching_flag = :module + block_searching_lines << line + module_program_name = $1 + module_program_trailing = find_comments($2) + next false + elsif line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i || + line =~ /^\s*?\w/ && !block_start?(line) + block_searching_flag = :program + block_searching_lines << line + module_program_name = $1 || "" + module_program_trailing = find_comments($2) + next false + + elsif block_start?(line) + other_block_searching_flag = true + next line + + elsif line =~ /^\s*?!\s?(.*)/ + pre_comment << line + next line + else + pre_comment = [] + next line + end + elsif other_block_searching_flag + other_block_level_depth += 1 if block_start?(line) + other_block_level_depth -= 1 if block_end?(line) + if other_block_level_depth < 0 + other_block_level_depth = 0 + other_block_searching_flag = nil + end + next line + end + + block_searching_lines << line + level_depth += 1 if block_start?(line) + level_depth -= 1 if block_end?(line) + if level_depth >= 0 + next false + end + + # "module_program_code" is formatted. + # ":nodoc:" flag is checked. + # + module_program_code = block_searching_lines.join("\n") + module_program_code = remove_empty_head_lines(module_program_code) + if module_program_trailing =~ /^:nodoc:/ + # next loop to search next block + level_depth = 0 + block_searching_flag = false + block_searching_lines = [] + pre_comment = [] + next false + end + + # NormalClass is created, and added to @top_level + # + if block_searching_flag == :module + module_name = module_program_name + module_code = module_program_code + module_trailing = module_program_trailing + progress "m" + @stats.num_modules += 1 + f9x_module = @top_level.add_module NormalClass, module_name + f9x_module.record_location @top_level + + f9x_comment = COMMENTS_ARE_UPPER ? + find_comments(pre_comment.join("\n")) + "\n" + module_trailing : + module_trailing + "\n" + find_comments(module_code.sub(/^.*$\n/i, '')) + f9x_module.comment = f9x_comment + parse_program_or_module(f9x_module, module_code) + + TopLevel.all_files.each do |name, toplevel| + if toplevel.include_includes?(module_name, @options.ignore_case) + if !toplevel.include_requires?(@file_name, @options.ignore_case) + toplevel.add_require(Require.new(@file_name, "")) + end + end + toplevel.each_classmodule{|m| + if m.include_includes?(module_name, @options.ignore_case) + if !m.include_requires?(@file_name, @options.ignore_case) + m.add_require(Require.new(@file_name, "")) + end + end + } + end + elsif block_searching_flag == :program + program_name = module_program_name + program_code = module_program_code + program_trailing = module_program_trailing + progress "p" + program_comment = COMMENTS_ARE_UPPER ? + find_comments(pre_comment.join("\n")) + "\n" + program_trailing : + program_trailing + "\n" + find_comments(program_code.sub(/^.*$\n/i, '')) + program_comment = "\n\n= Program #{program_name}\n\n" \ + + program_comment + @top_level.comment << program_comment + parse_program_or_module(@top_level, program_code, :private) + end + + # next loop to search next block + level_depth = 0 + block_searching_flag = false + block_searching_lines = [] + pre_comment = [] + next false + } + + remaining_lines.delete_if{ |line| + line == false + } + + # External subprograms and functions are parsed + # + parse_program_or_module(@top_level, remaining_lines.join("\n"), + :public, true) + + @top_level + end # End of scan + + private + + def parse_program_or_module(container, code, + visibility=:public, external=nil) + return unless container + return unless code + remaining_lines = code.split("\n") + remaining_code = "#{code}" + + # + # Parse variables before "contains" in module + # + level_depth = 0 + before_contains_lines = [] + before_contains_code = nil + before_contains_flag = nil + remaining_lines.each{ |line| + if !before_contains_flag + if line =~ /^\s*?module\s+\w+\s*?(!.*?)?$/i + before_contains_flag = true + end + else + break if line =~ /^\s*?contains\s*?(!.*?)?$/i + level_depth += 1 if block_start?(line) + level_depth -= 1 if block_end?(line) + break if level_depth < 0 + before_contains_lines << line + end + } + before_contains_code = before_contains_lines.join("\n") + if before_contains_code + before_contains_code.gsub!(/^\s*?interface\s+.*?\s+end\s+interface.*?$/im, "") + before_contains_code.gsub!(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "") end - # use modules - remaining_code = @body - while remaining_code =~ /^\s*use\s+(\w+)/i - remaining_code = $~.post_match - progress "." - f9x_module.add_include Include.new($1, "") if f9x_module + # + # Parse global "use" + # + use_check_code = "#{before_contains_code}" + cascaded_modules_list = [] + while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i + use_check_code = $~.pre_match + use_check_code << $~.post_match + used_mod_name = $1.strip.chomp + used_list = $2 || "" + used_trailing = $3 || "" + next if used_trailing =~ /!:nodoc:/ + if !container.include_includes?(used_mod_name, @options.ignore_case) + progress "." + container.add_include Include.new(used_mod_name, "") + end + if ! (used_list =~ /\,\s*?only\s*?:/i ) + cascaded_modules_list << "\#" + used_mod_name + end end - # subroutines - remaining_code = @body - while remaining_code =~ /^\s*subroutine\s+(\w+)\s*\((.*?)\)/im - remaining_code = $~.post_match - subroutine = AnyMethod.new("Text", $1) - subroutine.singleton = false + # + # Parse public and private, and store information. + # This information is used when "add_method" and + # "set_visibility_for" are called. + # + visibility_default, visibility_info = + parse_visibility(remaining_lines.join("\n"), visibility, container) + @@public_methods.concat visibility_info + if visibility_default == :public + if !cascaded_modules_list.empty? + cascaded_modules = + Attr.new("Cascaded Modules", + "Imported modules all of whose components are published again", + "", + cascaded_modules_list.join(", ")) + container.add_attribute(cascaded_modules) + end + end - prematchText = $~.pre_match - params = $2 - params.gsub!(/&/,'') - subroutine.params = params - comment = find_comments prematchText - subroutine.comment = comment if comment + # + # Check rename elements + # + use_check_code = "#{before_contains_code}" + while use_check_code =~ /^\s*?use\s+(\w+)\s*?\,(.+)$/i + use_check_code = $~.pre_match + use_check_code << $~.post_match + used_mod_name = $1.strip.chomp + used_elements = $2.sub(/\s*?only\s*?:\s*?/i, '') + used_elements.split(",").each{ |used| + if /\s*?(\w+)\s*?=>\s*?(\w+)\s*?/ =~ used + local = $1 + org = $2 + @@public_methods.collect!{ |pub_meth| + if local == pub_meth["name"] || + local.upcase == pub_meth["name"].upcase && + @options.ignore_case + pub_meth["name"] = org + pub_meth["local_name"] = local + end + pub_meth + } + end + } + end - subroutine.start_collecting_tokens - remaining_code =~ /^\s*end\s+subroutine/i - code = "subroutine #{subroutine.name} (#{subroutine.params})\n" - code += $~.pre_match - code += "\nend subroutine\n" - subroutine.add_token Token.new(1,1).set_text(code) - - progress "s" - f9x_module.add_method subroutine if f9x_module + # + # Parse private "use" + # + use_check_code = remaining_lines.join("\n") + while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i + use_check_code = $~.pre_match + use_check_code << $~.post_match + used_mod_name = $1.strip.chomp + used_trailing = $3 || "" + next if used_trailing =~ /!:nodoc:/ + if !container.include_includes?(used_mod_name, @options.ignore_case) + progress "." + container.add_include Include.new(used_mod_name, "") + end end - @top_level + container.each_includes{ |inc| + TopLevel.all_files.each do |name, toplevel| + indicated_mod = toplevel.find_symbol(inc.name, + nil, @options.ignore_case) + if indicated_mod + indicated_name = indicated_mod.parent.file_relative_name + if !container.include_requires?(indicated_name, @options.ignore_case) + container.add_require(Require.new(indicated_name, "")) + end + break + end + end + } + # + # Parse derived-types definitions + # + derived_types_comment = "" + remaining_code = remaining_lines.join("\n") + while remaining_code =~ /^\s*? + type[\s\,]+(public|private)?\s*?(::)?\s*? + (\w+)\s*?(!.*?)?$ + (.*?) + ^\s*?end\s+type.*?$ + /imx + remaining_code = $~.pre_match + remaining_code << $~.post_match + typename = $3.chomp.strip + type_elements = $5 || "" + type_code = remove_empty_head_lines($&) + type_trailing = find_comments($4) + next if type_trailing =~ /^:nodoc:/ + type_visibility = $1 + type_comment = COMMENTS_ARE_UPPER ? + find_comments($~.pre_match) + "\n" + type_trailing : + type_trailing + "\n" + find_comments(type_code.sub(/^.*$\n/i, '')) + type_element_visibility_public = true + type_code.split("\n").each{ |line| + if /^\s*?private\s*?$/ =~ line + type_element_visibility_public = nil + break + end + } if type_code + + args_comment = "" + type_args_info = nil + + if @options.show_all + args_comment = find_arguments(nil, type_code, true) + else + type_public_args_list = [] + type_args_info = definition_info(type_code) + type_args_info.each{ |arg| + arg_is_public = type_element_visibility_public + arg_is_public = true if arg.include_attr?("public") + arg_is_public = nil if arg.include_attr?("private") + type_public_args_list << arg.varname if arg_is_public + } + args_comment = find_arguments(type_public_args_list, type_code) + end + + type = AnyMethod.new("type #{typename}", typename) + type.singleton = false + type.params = "" + type.comment = " Derived Type :: \n" + type.comment << args_comment if args_comment + type.comment << type_comment if type_comment + progress "t" + @stats.num_methods += 1 + container.add_method type + + set_visibility(container, typename, visibility_default, @@public_methods) + + if type_visibility + type_visibility.gsub!(/\s/,'') + type_visibility.gsub!(/\,/,'') + type_visibility.gsub!(/:/,'') + type_visibility.downcase! + if type_visibility == "public" + container.set_visibility_for([typename], :public) + elsif type_visibility == "private" + container.set_visibility_for([typename], :private) + end + end + + check_public_methods(type, container.name) + + if @options.show_all + derived_types_comment << ", " unless derived_types_comment.empty? + derived_types_comment << typename + else + if type.visibility == :public + derived_types_comment << ", " unless derived_types_comment.empty? + derived_types_comment << typename + end + end + + end + + if !derived_types_comment.empty? + derived_types_table = + Attr.new("Derived Types", "Derived_Types", "", + derived_types_comment) + container.add_attribute(derived_types_table) + end + + # + # move interface scope + # + interface_code = "" + while remaining_code =~ /^\s*? + interface( + \s+\w+ | + \s+operator\s*?\(.*?\) | + \s+assignment\s*?\(\s*?=\s*?\) + )?\s*?$ + (.*?) + ^\s*?end\s+interface.*?$ + /imx + interface_code << remove_empty_head_lines($&) + "\n" + remaining_code = $~.pre_match + remaining_code << $~.post_match + end + + # + # Parse global constants or variables in modules + # + const_var_defs = definition_info(before_contains_code) + const_var_defs.each{|defitem| + next if defitem.nodoc + const_or_var_type = "Variable" + const_or_var_progress = "v" + if defitem.include_attr?("parameter") + const_or_var_type = "Constant" + const_or_var_progress = "c" + end + const_or_var = AnyMethod.new(const_or_var_type, defitem.varname) + const_or_var.singleton = false + const_or_var.params = "" + self_comment = find_arguments([defitem.varname], before_contains_code) + const_or_var.comment = "" + const_or_var_type + " :: \n" + const_or_var.comment << self_comment if self_comment + progress const_or_var_progress + @stats.num_methods += 1 + container.add_method const_or_var + + set_visibility(container, defitem.varname, visibility_default, @@public_methods) + + if defitem.include_attr?("public") + container.set_visibility_for([defitem.varname], :public) + elsif defitem.include_attr?("private") + container.set_visibility_for([defitem.varname], :private) + end + + check_public_methods(const_or_var, container.name) + + } if const_var_defs + + remaining_lines = remaining_code.split("\n") + + # "subroutine" or "function" parts are parsed (new) + # + level_depth = 0 + block_searching_flag = nil + block_searching_lines = [] + pre_comment = [] + procedure_trailing = "" + procedure_name = "" + procedure_params = "" + procedure_prefix = "" + procedure_result_arg = "" + procedure_type = "" + contains_lines = [] + contains_flag = nil + remaining_lines.collect!{|line| + if !block_searching_flag + # subroutine + if line =~ /^\s*? + (recursive|pure|elemental)?\s*? + subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$ + /ix + block_searching_flag = :subroutine + block_searching_lines << line + + procedure_name = $2.chomp.strip + procedure_params = $3 || "" + procedure_prefix = $1 || "" + procedure_trailing = $4 || "!" + next false + + # function + elsif line =~ /^\s*? + (recursive|pure|elemental)?\s*? + ( + character\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | type\s*?\([\w\s]+?\)\s+ + | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | double\s+precision\s+ + | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + )? + function\s+(\w+)\s*? + (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$ + /ix + block_searching_flag = :function + block_searching_lines << line + + procedure_prefix = $1 || "" + procedure_type = $2 ? $2.chomp.strip : nil + procedure_name = $8.chomp.strip + procedure_params = $9 || "" + procedure_result_arg = $11 ? $11.chomp.strip : procedure_name + procedure_trailing = $12 || "!" + next false + elsif line =~ /^\s*?!\s?(.*)/ + pre_comment << line + next line + else + pre_comment = [] + next line + end + end + contains_flag = true if line =~ /^\s*?contains\s*?(!.*?)?$/ + block_searching_lines << line + contains_lines << line if contains_flag + + level_depth += 1 if block_start?(line) + level_depth -= 1 if block_end?(line) + if level_depth >= 0 + next false + end + + # "procedure_code" is formatted. + # ":nodoc:" flag is checked. + # + procedure_code = block_searching_lines.join("\n") + procedure_code = remove_empty_head_lines(procedure_code) + if procedure_trailing =~ /^!:nodoc:/ + # next loop to search next block + level_depth = 0 + block_searching_flag = nil + block_searching_lines = [] + pre_comment = [] + procedure_trailing = "" + procedure_name = "" + procedure_params = "" + procedure_prefix = "" + procedure_result_arg = "" + procedure_type = "" + contains_lines = [] + contains_flag = nil + next false + end + + # AnyMethod is created, and added to container + # + subroutine_function = nil + if block_searching_flag == :subroutine + subroutine_prefix = procedure_prefix + subroutine_name = procedure_name + subroutine_params = procedure_params + subroutine_trailing = procedure_trailing + subroutine_code = procedure_code + + subroutine_comment = COMMENTS_ARE_UPPER ? + pre_comment.join("\n") + "\n" + subroutine_trailing : + subroutine_trailing + "\n" + subroutine_code.sub(/^.*$\n/i, '') + subroutine = AnyMethod.new("subroutine", subroutine_name) + parse_subprogram(subroutine, subroutine_params, + subroutine_comment, subroutine_code, + before_contains_code, nil, subroutine_prefix) + progress "s" + @stats.num_methods += 1 + container.add_method subroutine + subroutine_function = subroutine + + elsif block_searching_flag == :function + function_prefix = procedure_prefix + function_type = procedure_type + function_name = procedure_name + function_params_org = procedure_params + function_result_arg = procedure_result_arg + function_trailing = procedure_trailing + function_code_org = procedure_code + + function_comment = COMMENTS_ARE_UPPER ? + pre_comment.join("\n") + "\n" + function_trailing : + function_trailing + "\n " + function_code_org.sub(/^.*$\n/i, '') + + function_code = "#{function_code_org}" + if function_type + function_code << "\n" + function_type + " :: " + function_result_arg + end + + function_params = + function_params_org.sub(/^\(/, "\(#{function_result_arg}, ") + + function = AnyMethod.new("function", function_name) + parse_subprogram(function, function_params, + function_comment, function_code, + before_contains_code, true, function_prefix) + + # Specific modification due to function + function.params.sub!(/\(\s*?#{function_result_arg}\s*?,\s*?/, "\( ") + function.params << " result(" + function_result_arg + ")" + function.start_collecting_tokens + function.add_token Token.new(1,1).set_text(function_code_org) + + progress "f" + @stats.num_methods += 1 + container.add_method function + subroutine_function = function + + end + + # The visibility of procedure is specified + # + set_visibility(container, procedure_name, + visibility_default, @@public_methods) + + # The alias for this procedure from external modules + # + check_external_aliases(procedure_name, + subroutine_function.params, + subroutine_function.comment, subroutine_function) if external + check_public_methods(subroutine_function, container.name) + + + # contains_lines are parsed as private procedures + if contains_flag + parse_program_or_module(container, + contains_lines.join("\n"), :private) + end + + # next loop to search next block + level_depth = 0 + block_searching_flag = nil + block_searching_lines = [] + pre_comment = [] + procedure_trailing = "" + procedure_name = "" + procedure_params = "" + procedure_prefix = "" + procedure_result_arg = "" + contains_lines = [] + contains_flag = nil + next false + } # End of remaining_lines.collect!{|line| + + # Array remains_lines is converted to String remains_code again + # + remaining_code = remaining_lines.join("\n") + + # + # Parse interface + # + interface_scope = false + generic_name = "" + interface_code.split("\n").each{ |line| + if /^\s*? + interface( + \s+\w+| + \s+operator\s*?\(.*?\)| + \s+assignment\s*?\(\s*?=\s*?\) + )? + \s*?(!.*?)?$ + /ix =~ line + generic_name = $1 ? $1.strip.chomp : nil + interface_trailing = $2 || "!" + interface_scope = true + interface_scope = false if interface_trailing =~ /!:nodoc:/ +# if generic_name =~ /operator\s*?\((.*?)\)/i +# operator_name = $1 +# if operator_name && !operator_name.empty? +# generic_name = "#{operator_name}" +# end +# end +# if generic_name =~ /assignment\s*?\((.*?)\)/i +# assignment_name = $1 +# if assignment_name && !assignment_name.empty? +# generic_name = "#{assignment_name}" +# end +# end + end + if /^\s*?end\s+interface/i =~ line + interface_scope = false + generic_name = nil + end + # internal alias + if interface_scope && /^\s*?module\s+procedure\s+(.*?)(!.*?)?$/i =~ line + procedures = $1.strip.chomp + procedures_trailing = $2 || "!" + next if procedures_trailing =~ /!:nodoc:/ + procedures.split(",").each{ |proc| + proc.strip! + proc.chomp! + next if generic_name == proc || !generic_name + old_meth = container.find_symbol(proc, nil, @options.ignore_case) + next if !old_meth + nolink = old_meth.visibility == :private ? true : nil + nolink = nil if @options.show_all + new_meth = + initialize_external_method(generic_name, proc, + old_meth.params, nil, + old_meth.comment, + old_meth.clone.token_stream[0].text, + true, nolink) + new_meth.singleton = old_meth.singleton + + progress "i" + @stats.num_methods += 1 + container.add_method new_meth + + set_visibility(container, generic_name, visibility_default, @@public_methods) + + check_public_methods(new_meth, container.name) + + } + end + + # external aliases + if interface_scope + # subroutine + proc = nil + params = nil + procedures_trailing = nil + if line =~ /^\s*? + (recursive|pure|elemental)?\s*? + subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$ + /ix + proc = $2.chomp.strip + generic_name = proc unless generic_name + params = $3 || "" + procedures_trailing = $4 || "!" + + # function + elsif line =~ /^\s*? + (recursive|pure|elemental)?\s*? + ( + character\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | type\s*?\([\w\s]+?\)\s+ + | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | double\s+precision\s+ + | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + )? + function\s+(\w+)\s*? + (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$ + /ix + proc = $8.chomp.strip + generic_name = proc unless generic_name + params = $9 || "" + procedures_trailing = $12 || "!" + else + next + end + next if procedures_trailing =~ /!:nodoc:/ + indicated_method = nil + indicated_file = nil + TopLevel.all_files.each do |name, toplevel| + indicated_method = toplevel.find_local_symbol(proc, @options.ignore_case) + indicated_file = name + break if indicated_method + end + + if indicated_method + external_method = + initialize_external_method(generic_name, proc, + indicated_method.params, + indicated_file, + indicated_method.comment) + + progress "e" + @stats.num_methods += 1 + container.add_method external_method + set_visibility(container, generic_name, visibility_default, @@public_methods) + if !container.include_requires?(indicated_file, @options.ignore_case) + container.add_require(Require.new(indicated_file, "")) + end + check_public_methods(external_method, container.name) + + else + @@external_aliases << { + "new_name" => generic_name, + "old_name" => proc, + "file_or_module" => container, + "visibility" => find_visibility(container, generic_name, @@public_methods) || visibility_default + } + end + end + + } if interface_code # End of interface_code.split("\n").each ... + + # + # Already imported methods are removed from @@public_methods. + # Remainders are assumed to be imported from other modules. + # + @@public_methods.delete_if{ |method| method["entity_is_discovered"]} + + @@public_methods.each{ |pub_meth| + next unless pub_meth["file_or_module"].name == container.name + pub_meth["used_modules"].each{ |used_mod| + TopLevel.all_classes_and_modules.each{ |modules| + if modules.name == used_mod || + modules.name.upcase == used_mod.upcase && + @options.ignore_case + modules.method_list.each{ |meth| + if meth.name == pub_meth["name"] || + meth.name.upcase == pub_meth["name"].upcase && + @options.ignore_case + new_meth = initialize_public_method(meth, + modules.name) + if pub_meth["local_name"] + new_meth.name = pub_meth["local_name"] + end + progress "e" + @stats.num_methods += 1 + container.add_method new_meth + end + } + end + } + } + } + + container + end # End of parse_program_or_module + + # + # Parse arguments, comment, code of subroutine and function. + # Return AnyMethod object. + # + def parse_subprogram(subprogram, params, comment, code, + before_contains=nil, function=nil, prefix=nil) + subprogram.singleton = false + prefix = "" if !prefix + arguments = params.sub(/\(/, "").sub(/\)/, "").split(",") if params + args_comment, params_opt = + find_arguments(arguments, code.sub(/^s*?contains\s*?(!.*?)?$.*/im, ""), + nil, nil, true) + params_opt = "( " + params_opt + " ) " if params_opt + subprogram.params = params_opt || "" + namelist_comment = find_namelists(code, before_contains) + + block_comment = find_comments comment + if function + subprogram.comment = " Function :: #{prefix}\n" + else + subprogram.comment = " Subroutine :: #{prefix}\n" + end + subprogram.comment << args_comment if args_comment + subprogram.comment << block_comment if block_comment + subprogram.comment << namelist_comment if namelist_comment + + # For output source code + subprogram.start_collecting_tokens + subprogram.add_token Token.new(1,1).set_text(code) + + subprogram end + # + # Collect comment for file entity + # + def collect_first_comment(body) + comment = "" + not_comment = "" + comment_start = false + comment_end = false + body.split("\n").each{ |line| + if comment_end + not_comment << line + not_comment << "\n" + elsif /^\s*?!\s?(.*)$/i =~ line + comment_start = true + comment << $1 + comment << "\n" + elsif /^\s*?$/i =~ line + comment_end = true if comment_start && COMMENTS_ARE_UPPER + else + comment_end = true + not_comment << line + not_comment << "\n" + end + } + return comment, not_comment + end + + + # Return comments of definitions of arguments + # + # If "all" argument is true, information of all arguments are returned. + # If "modified_params" is true, list of arguments are decorated, + # for exameple, optional arguments are parenthetic as "[arg]". + # + def find_arguments(args, text, all=nil, indent=nil, modified_params=nil) + return unless args || all + indent = "" unless indent + args = ["all"] if all + params = "" if modified_params + comma = "" + return unless text + args_rdocforms = "\n" + remaining_lines = "#{text}" + definitions = definition_info(remaining_lines) + args.each{ |arg| + arg.strip! + arg.chomp! + definitions.each { |defitem| + if arg == defitem.varname.strip.chomp || all + args_rdocforms << <<-"EOF" + +#{indent}#{defitem.varname.chomp.strip}#{defitem.arraysuffix} #{defitem.inivalue} :: +#{indent} #{defitem.types.chomp.strip} +EOF + if !defitem.comment.chomp.strip.empty? + comment = "" + defitem.comment.split("\n").each{ |line| + comment << " " + line + "\n" + } + args_rdocforms << <<-"EOF" + +#{indent} :: +#{indent} +#{indent} #{comment.chomp.strip} +EOF + end + + if modified_params + if defitem.include_attr?("optional") + params << "#{comma}[#{arg}]" + else + params << "#{comma}#{arg}" + end + comma = ", " + end + end + } + } + if modified_params + return args_rdocforms, params + else + return args_rdocforms + end + end + + # Return comments of definitions of namelists + # + def find_namelists(text, before_contains=nil) + return nil if !text + result = "" + lines = "#{text}" + before_contains = "" if !before_contains + while lines =~ /^\s*?namelist\s+\/\s*?(\w+)\s*?\/([\s\w\,]+)$/i + lines = $~.post_match + nml_comment = COMMENTS_ARE_UPPER ? + find_comments($~.pre_match) : find_comments($~.post_match) + nml_name = $1 + nml_args = $2.split(",") + result << "\n\n=== NAMELIST " + nml_name + "\n\n" + result << nml_comment + "\n" if nml_comment + if lines.split("\n")[0] =~ /^\//i + lines = "namelist " + lines + end + result << find_arguments(nml_args, "#{text}" + "\n" + before_contains) + end + return result + end + + # + # Comments just after module or subprogram, or arguments are + # returnd. If "COMMENTS_ARE_UPPER" is true, comments just before + # modules or subprograms are returnd + # def find_comments text - lines = text.split("\n").reverse + return "" unless text + lines = text.split("\n") + lines.reverse! if COMMENTS_ARE_UPPER comment_block = Array.new lines.each do |line| - break if line =~ /^\s*\w/ - comment_block.unshift line.sub(/^!\s?/,"") + break if line =~ /^\s*?\w/ || line =~ /^\s*?$/ + if COMMENTS_ARE_UPPER + comment_block.unshift line.sub(/^\s*?!\s?/,"") + else + comment_block.push line.sub(/^\s*?!\s?/,"") + end end - nice_lines = comment_block.join("\n").split "\n\s*\n" + nice_lines = comment_block.join("\n").split "\n\s*?\n" + nice_lines[0] ||= "" nice_lines.shift - nice_lines.shift - nice_lines.shift end def progress(char) @@ -114,6 +1171,671 @@ end end + # + # Create method for internal alias + # + def initialize_public_method(method, parent) + return if !method || !parent + + new_meth = AnyMethod.new("External Alias for module", method.name) + new_meth.singleton = method.singleton + new_meth.params = method.params.clone + new_meth.comment = remove_trailing_alias(method.comment.clone) + new_meth.comment << "\n\n#{EXTERNAL_ALIAS_MES} #{parent.strip.chomp}\##{method.name}" + + return new_meth + end + + # + # Create method for external alias + # + # If argument "internal" is true, file is ignored. + # + def initialize_external_method(new, old, params, file, comment, token=nil, + internal=nil, nolink=nil) + return nil unless new || old + + if internal + external_alias_header = "#{INTERNAL_ALIAS_MES} " + external_alias_text = external_alias_header + old + elsif file + external_alias_header = "#{EXTERNAL_ALIAS_MES} " + external_alias_text = external_alias_header + file + "#" + old + else + return nil + end + external_meth = AnyMethod.new(external_alias_text, new) + external_meth.singleton = false + external_meth.params = params + external_comment = remove_trailing_alias(comment) + "\n\n" if comment + external_meth.comment = external_comment || "" + if nolink && token + external_meth.start_collecting_tokens + external_meth.add_token Token.new(1,1).set_text(token) + else + external_meth.comment << external_alias_text + end + + return external_meth + end + + + + # + # Parse visibility + # + def parse_visibility(code, default, container) + result = [] + visibility_default = default || :public + + used_modules = [] + container.includes.each{|i| used_modules << i.name} if container + + remaining_code = code.gsub(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "") + remaining_code.split("\n").each{ |line| + if /^\s*?private\s*?$/ =~ line + visibility_default = :private + break + end + } if remaining_code + + remaining_code.split("\n").each{ |line| + if /^\s*?private\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line + methods = $2.sub(/!.*$/, '') + methods.split(",").each{ |meth| + meth.sub!(/!.*$/, '') + meth.gsub!(/:/, '') + result << { + "name" => meth.chomp.strip, + "visibility" => :private, + "used_modules" => used_modules.clone, + "file_or_module" => container, + "entity_is_discovered" => nil, + "local_name" => nil + } + } + elsif /^\s*?public\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line + methods = $2.sub(/!.*$/, '') + methods.split(",").each{ |meth| + meth.sub!(/!.*$/, '') + meth.gsub!(/:/, '') + result << { + "name" => meth.chomp.strip, + "visibility" => :public, + "used_modules" => used_modules.clone, + "file_or_module" => container, + "entity_is_discovered" => nil, + "local_name" => nil + } + } + end + } if remaining_code + + if container + result.each{ |vis_info| + vis_info["parent"] = container.name + } + end + + return visibility_default, result + end + + # + # Set visibility + # + # "subname" element of "visibility_info" is deleted. + # + def set_visibility(container, subname, visibility_default, visibility_info) + return unless container || subname || visibility_default || visibility_info + not_found = true + visibility_info.collect!{ |info| + if info["name"] == subname || + @options.ignore_case && info["name"].upcase == subname.upcase + if info["file_or_module"].name == container.name + container.set_visibility_for([subname], info["visibility"]) + info["entity_is_discovered"] = true + not_found = false + end + end + info + } + if not_found + return container.set_visibility_for([subname], visibility_default) + else + return container + end + end + + # + # Find visibility + # + def find_visibility(container, subname, visibility_info) + return nil if !subname || !visibility_info + visibility_info.each{ |info| + if info["name"] == subname || + @options.ignore_case && info["name"].upcase == subname.upcase + if info["parent"] == container.name + return info["visibility"] + end + end + } + return nil + end + + # + # Check external aliases + # + def check_external_aliases(subname, params, comment, test=nil) + @@external_aliases.each{ |alias_item| + if subname == alias_item["old_name"] || + subname.upcase == alias_item["old_name"].upcase && + @options.ignore_case + + new_meth = initialize_external_method(alias_item["new_name"], + subname, params, @file_name, + comment) + new_meth.visibility = alias_item["visibility"] + + progress "e" + @stats.num_methods += 1 + alias_item["file_or_module"].add_method(new_meth) + + if !alias_item["file_or_module"].include_requires?(@file_name, @options.ignore_case) + alias_item["file_or_module"].add_require(Require.new(@file_name, "")) + end + end + } + end + + # + # Check public_methods + # + def check_public_methods(method, parent) + return if !method || !parent + @@public_methods.each{ |alias_item| + parent_is_used_module = nil + alias_item["used_modules"].each{ |used_module| + if used_module == parent || + used_module.upcase == parent.upcase && + @options.ignore_case + parent_is_used_module = true + end + } + next if !parent_is_used_module + + if method.name == alias_item["name"] || + method.name.upcase == alias_item["name"].upcase && + @options.ignore_case + + new_meth = initialize_public_method(method, parent) + if alias_item["local_name"] + new_meth.name = alias_item["local_name"] + end + + progress "e" + @stats.num_methods += 1 + alias_item["file_or_module"].add_method new_meth + end + } + end + + # + # Continuous lines are united. + # + # Comments in continuous lines are removed. + # + def united_to_one_line(f90src) + return "" unless f90src + lines = f90src.split("\n") + previous_continuing = false + now_continuing = false + body = "" + lines.each{ |line| + words = line.split("") + next if words.empty? && previous_continuing + commentout = false + brank_flag = true ; brank_char = "" + squote = false ; dquote = false + ignore = false + words.collect! { |char| + if previous_continuing && brank_flag + now_continuing = true + ignore = true + case char + when "!" ; break + when " " ; brank_char << char ; next "" + when "&" + brank_flag = false + now_continuing = false + next "" + else + brank_flag = false + now_continuing = false + ignore = false + next brank_char + char + end + end + ignore = false + + if now_continuing + next "" + elsif !(squote) && !(dquote) && !(commentout) + case char + when "!" ; commentout = true ; next char + when "\""; dquote = true ; next char + when "\'"; squote = true ; next char + when "&" ; now_continuing = true ; next "" + else next char + end + elsif commentout + next char + elsif squote + case char + when "\'"; squote = false ; next char + else next char + end + elsif dquote + case char + when "\""; dquote = false ; next char + else next char + end + end + } + if !ignore && !previous_continuing || !brank_flag + if previous_continuing + body << words.join("") + else + body << "\n" + words.join("") + end + end + previous_continuing = now_continuing ? true : nil + now_continuing = nil + } + return body + end + + + # + # Continuous line checker + # + def continuous_line?(line) + continuous = false + if /&\s*?(!.*)?$/ =~ line + continuous = true + if comment_out?($~.pre_match) + continuous = false + end + end + return continuous + end + + # + # Comment out checker + # + def comment_out?(line) + return nil unless line + commentout = false + squote = false ; dquote = false + line.split("").each { |char| + if !(squote) && !(dquote) + case char + when "!" ; commentout = true ; break + when "\""; dquote = true + when "\'"; squote = true + else next + end + elsif squote + case char + when "\'"; squote = false + else next + end + elsif dquote + case char + when "\""; dquote = false + else next + end + end + } + return commentout + end + + # + # Semicolons are replaced to line feed. + # + def semicolon_to_linefeed(text) + return "" unless text + lines = text.split("\n") + lines.collect!{ |line| + words = line.split("") + commentout = false + squote = false ; dquote = false + words.collect! { |char| + if !(squote) && !(dquote) && !(commentout) + case char + when "!" ; commentout = true ; next char + when "\""; dquote = true ; next char + when "\'"; squote = true ; next char + when ";" ; "\n" + else next char + end + elsif commentout + next char + elsif squote + case char + when "\'"; squote = false ; next char + else next char + end + elsif dquote + case char + when "\""; dquote = false ; next char + else next char + end + end + } + words.join("") + } + return lines.join("\n") + end + + # + # Which "line" is start of block (module, program, block data, + # subroutine, function) statement ? + # + def block_start?(line) + return nil if !line + + if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i || + line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i || + line =~ /^\s*?block\s+data(\s+\w+)?\s*?(!.*?)?$/i || + line =~ \ + /^\s*? + (recursive|pure|elemental)?\s*? + subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$ + /ix || + line =~ \ + /^\s*? + (recursive|pure|elemental)?\s*? + ( + character\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | type\s*?\([\w\s]+?\)\s+ + | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | double\s+precision\s+ + | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+ + )? + function\s+(\w+)\s*? + (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$ + /ix + return true + end + + return nil + end + + # + # Which "line" is end of block (module, program, block data, + # subroutine, function) statement ? + # + def block_end?(line) + return nil if !line + + if line =~ /^\s*?end\s*?(!.*?)?$/i || + line =~ /^\s*?end\s+module(\s+\w+)?\s*?(!.*?)?$/i || + line =~ /^\s*?end\s+program(\s+\w+)?\s*?(!.*?)?$/i || + line =~ /^\s*?end\s+block\s+data(\s+\w+)?\s*?(!.*?)?$/i || + line =~ /^\s*?end\s+subroutine(\s+\w+)?\s*?(!.*?)?$/i || + line =~ /^\s*?end\s+function(\s+\w+)?\s*?(!.*?)?$/i + return true + end + + return nil + end + + # + # Remove "Alias for" in end of comments + # + def remove_trailing_alias(text) + return "" if !text + lines = text.split("\n").reverse + comment_block = Array.new + checked = false + lines.each do |line| + if !checked + if /^\s?#{INTERNAL_ALIAS_MES}/ =~ line || + /^\s?#{EXTERNAL_ALIAS_MES}/ =~ line + checked = true + next + end + end + comment_block.unshift line + end + nice_lines = comment_block.join("\n") + nice_lines ||= "" + return nice_lines + end + + # Empty lines in header are removed + def remove_empty_head_lines(text) + return "" unless text + lines = text.split("\n") + header = true + lines.delete_if{ |line| + header = false if /\S/ =~ line + header && /^\s*?$/ =~ line + } + lines.join("\n") + end + + + # header marker "=", "==", ... are removed + def remove_header_marker(text) + return text.gsub(/^\s?(=+)/, '\1') + end + + def remove_private_comments(body) + body.gsub!(/^\s*!--\s*?$.*?^\s*!\+\+\s*?$/m, '') + return body + end + + + # + # Information of arguments of subroutines and functions in Fortran95 + # + class Fortran95Definition + + # Name of variable + # + attr_reader :varname + + # Types of variable + # + attr_reader :types + + # Initial Value + # + attr_reader :inivalue + + # Suffix of array + # + attr_reader :arraysuffix + + # Comments + # + attr_accessor :comment + + # Flag of non documentation + # + attr_accessor :nodoc + + def initialize(varname, types, inivalue, arraysuffix, comment, + nodoc=false) + @varname = varname + @types = types + @inivalue = inivalue + @arraysuffix = arraysuffix + @comment = comment + @nodoc = nodoc + end + + def to_s + return <<-EOF + +EOF + end + + # + # If attr is included, true is returned + # + def include_attr?(attr) + return if !attr + @types.split(",").each{ |type| + return true if type.strip.chomp.upcase == attr.strip.chomp.upcase + } + return nil + end + + end # End of Fortran95Definition + + # + # Parse string argument "text", and Return Array of + # Fortran95Definition object + # + def definition_info(text) + return nil unless text + lines = "#{text}" + defs = Array.new + comment = "" + trailing_comment = "" + under_comment_valid = false + lines.split("\n").each{ |line| + if /^\s*?!\s?(.*)/ =~ line + if COMMENTS_ARE_UPPER + comment << remove_header_marker($1) + comment << "\n" + elsif defs[-1] && under_comment_valid + defs[-1].comment << "\n" + defs[-1].comment << remove_header_marker($1) + end + next + elsif /^\s*?$/ =~ line + comment = "" + under_comment_valid = false + next + end + type = "" + characters = "" + if line =~ /^\s*? + ( + character\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* + | type\s*?\([\w\s]+?\)[\s\,]* + | integer\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* + | real\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* + | double\s+precision[\s\,]* + | logical\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* + | complex\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* + ) + (.*?::)? + (.+)$ + /ix + characters = $8 + type = $1 + type << $7.gsub(/::/, '').gsub(/^\s*?\,/, '') if $7 + else + under_comment_valid = false + next + end + squote = false ; dquote = false ; bracket = 0 + iniflag = false; commentflag = false + varname = "" ; arraysuffix = "" ; inivalue = "" + start_pos = defs.size + characters.split("").each { |char| + if !(squote) && !(dquote) && bracket <= 0 && !(iniflag) && !(commentflag) + case char + when "!" ; commentflag = true + when "(" ; bracket += 1 ; arraysuffix = char + when "\""; dquote = true + when "\'"; squote = true + when "=" ; iniflag = true ; inivalue << char + when "," + defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment) + varname = "" ; arraysuffix = "" ; inivalue = "" + under_comment_valid = true + when " " ; next + else ; varname << char + end + elsif commentflag + comment << remove_header_marker(char) + trailing_comment << remove_header_marker(char) + elsif iniflag + if dquote + case char + when "\"" ; dquote = false ; inivalue << char + else ; inivalue << char + end + elsif squote + case char + when "\'" ; squote = false ; inivalue << char + else ; inivalue << char + end + elsif bracket > 0 + case char + when "(" ; bracket += 1 ; inivalue << char + when ")" ; bracket -= 1 ; inivalue << char + else ; inivalue << char + end + else + case char + when "," + defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment) + varname = "" ; arraysuffix = "" ; inivalue = "" + iniflag = false + under_comment_valid = true + when "(" ; bracket += 1 ; inivalue << char + when "\""; dquote = true ; inivalue << char + when "\'"; squote = true ; inivalue << char + when "!" ; commentflag = true + else ; inivalue << char + end + end + elsif !(squote) && !(dquote) && bracket > 0 + case char + when "(" ; bracket += 1 ; arraysuffix << char + when ")" ; bracket -= 1 ; arraysuffix << char + else ; arraysuffix << char + end + elsif squote + case char + when "\'"; squote = false ; inivalue << char + else ; inivalue << char + end + elsif dquote + case char + when "\""; dquote = false ; inivalue << char + else ; inivalue << char + end + end + } + defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment) + if trailing_comment =~ /^:nodoc:/ + defs[start_pos..-1].collect!{ |defitem| + defitem.nodoc = true + } + end + varname = "" ; arraysuffix = "" ; inivalue = "" + comment = "" + under_comment_valid = true + trailing_comment = "" + } + return defs + end + + end # class Fortran95parser end # module RDoc Index: rdoc/parsers/parse_rb.rb =================================================================== --- rdoc/parsers/parse_rb.rb (revision 2900) +++ rdoc/parsers/parse_rb.rb (working copy) @@ -1,3 +1,4 @@ +#!/usr/local/bin/ruby # Parse a Ruby source file, building a set of objects # representing the modules, classes, methods, @@ -12,7 +13,6 @@ # by Keiju ISHITSUKA (Nippon Rational Inc.) # -require "tracer" require "e2mmap" require "irb/slex" @@ -560,7 +560,7 @@ "q" => "\'", "Q" => "\"", "x" => "\`", - "r" => "\/", + "r" => "/", "w" => "]" } @@ -575,7 +575,7 @@ "\'" => TkSTRING, "\"" => TkSTRING, "\`" => TkXSTRING, - "\/" => TkREGEXP, + "/" => TkREGEXP, "]" => TkDSTRING } Ltype2Token.default = TkSTRING @@ -583,7 +583,7 @@ DLtype2Token = { "\"" => TkDSTRING, "\`" => TkDXSTRING, - "\/" => TkDREGEXP, + "/" => TkDREGEXP, } def lex_init() @@ -1334,7 +1334,7 @@ end end - when "C", "c", "^" + when "C", "c" #, "^" res << ch if ch == "C" and (ch = getc) != "-" ungetc @@ -1426,16 +1426,23 @@ private - def warn(msg) + def make_message(msg) prefix = "\n" + @input_file_name + ":" if @scanner prefix << "#{@scanner.line_no}:#{@scanner.char_no}: " end - $stderr.puts prefix + msg + return prefix + msg end + def warn(msg) + return if @options.quiet + msg = make_message msg + $stderr.puts msg + end + def error(msg) - warn msg + msg = make_message msg + $stderr.puts msg exit(1) end @@ -1478,7 +1485,7 @@ obj.pop_token end if @token_listeners else - warn("':' not followed by identified or operator") + warn("':' not followed by identifier or operator") tk = tk1 end end @@ -2551,7 +2558,7 @@ break when TkCOMMA else - warn("unexpected token: '#{tk2.inspect}'") if $DEBBUG + warn("unexpected token: '#{tk2.inspect}'") if $DEBUG break end end Index: rdoc/parsers/parse_c.rb =================================================================== --- rdoc/parsers/parse_c.rb (revision 2900) +++ rdoc/parsers/parse_c.rb (working copy) @@ -1,3 +1,78 @@ +# Classes and modules built in to the interpreter. We need +# these to define superclasses of user objects + +require "rdoc/code_objects" +require "rdoc/parsers/parserfactory" +require "rdoc/options" +require "rdoc/rdoc" + +module RDoc + + ## + # Ruby's built-in classes. + + KNOWN_CLASSES = { + "rb_cObject" => "Object", + "rb_cArray" => "Array", + "rb_cBignum" => "Bignum", + "rb_cClass" => "Class", + "rb_cDir" => "Dir", + "rb_cData" => "Data", + "rb_cFalseClass" => "FalseClass", + "rb_cFile" => "File", + "rb_cFixnum" => "Fixnum", + "rb_cFloat" => "Float", + "rb_cHash" => "Hash", + "rb_cInteger" => "Integer", + "rb_cIO" => "IO", + "rb_cModule" => "Module", + "rb_cNilClass" => "NilClass", + "rb_cNumeric" => "Numeric", + "rb_cProc" => "Proc", + "rb_cRange" => "Range", + "rb_cRegexp" => "Regexp", + "rb_cString" => "String", + "rb_cSymbol" => "Symbol", + "rb_cThread" => "Thread", + "rb_cTime" => "Time", + "rb_cTrueClass" => "TrueClass", + "rb_cStruct" => "Struct", + "rb_eException" => "Exception", + "rb_eStandardError" => "StandardError", + "rb_eSystemExit" => "SystemExit", + "rb_eInterrupt" => "Interrupt", + "rb_eSignal" => "Signal", + "rb_eFatal" => "Fatal", + "rb_eArgError" => "ArgError", + "rb_eEOFError" => "EOFError", + "rb_eIndexError" => "IndexError", + "rb_eRangeError" => "RangeError", + "rb_eIOError" => "IOError", + "rb_eRuntimeError" => "RuntimeError", + "rb_eSecurityError" => "SecurityError", + "rb_eSystemCallError" => "SystemCallError", + "rb_eTypeError" => "TypeError", + "rb_eZeroDivError" => "ZeroDivError", + "rb_eNotImpError" => "NotImpError", + "rb_eNoMemError" => "NoMemError", + "rb_eFloatDomainError" => "FloatDomainError", + "rb_eScriptError" => "ScriptError", + "rb_eNameError" => "NameError", + "rb_eSyntaxError" => "SyntaxError", + "rb_eLoadError" => "LoadError", + + "rb_mKernel" => "Kernel", + "rb_mComparable" => "Comparable", + "rb_mEnumerable" => "Enumerable", + "rb_mPrecision" => "Precision", + "rb_mErrno" => "Errno", + "rb_mFileTest" => "FileTest", + "rb_mGC" => "GC", + "rb_mMath" => "Math", + "rb_mProcess" => "Process" + } + + ## # We attempt to parse C extension files. Basically we look for # the standard patterns that you find in extensions: rb_define_class, # rb_define_method and so on. We also try to find the corresponding @@ -52,8 +127,8 @@ # when the Init_xxx method is not named after the class. # # [Document-method: name] - # This comment documents the named method. Use when RDoc cannot outomatically - # find the method from it's declaration + # This comment documents the named method. Use when RDoc cannot + # automatically find the method from it's declaration # # [call-seq: text up to an empty line] # Because C source doesn't give descripive names to Ruby-level parameters, @@ -89,82 +164,9 @@ # */ # - - # Classes and modules built in to the interpreter. We need - # these to define superclasses of user objects - -require "rdoc/code_objects" -require "rdoc/parsers/parserfactory" - - -module RDoc - - KNOWN_CLASSES = { - "rb_cObject" => "Object", - "rb_cArray" => "Array", - "rb_cBignum" => "Bignum", - "rb_cClass" => "Class", - "rb_cDir" => "Dir", - "rb_cData" => "Data", - "rb_cFalseClass" => "FalseClass", - "rb_cFile" => "File", - "rb_cFixnum" => "Fixnum", - "rb_cFloat" => "Float", - "rb_cHash" => "Hash", - "rb_cInteger" => "Integer", - "rb_cIO" => "IO", - "rb_cModule" => "Module", - "rb_cNilClass" => "NilClass", - "rb_cNumeric" => "Numeric", - "rb_cProc" => "Proc", - "rb_cRange" => "Range", - "rb_cRegexp" => "Regexp", - "rb_cString" => "String", - "rb_cSymbol" => "Symbol", - "rb_cThread" => "Thread", - "rb_cTime" => "Time", - "rb_cTrueClass" => "TrueClass", - "rb_cStruct" => "Struct", - "rb_eException" => "Exception", - "rb_eStandardError" => "StandardError", - "rb_eSystemExit" => "SystemExit", - "rb_eInterrupt" => "Interrupt", - "rb_eSignal" => "Signal", - "rb_eFatal" => "Fatal", - "rb_eArgError" => "ArgError", - "rb_eEOFError" => "EOFError", - "rb_eIndexError" => "IndexError", - "rb_eRangeError" => "RangeError", - "rb_eIOError" => "IOError", - "rb_eRuntimeError" => "RuntimeError", - "rb_eSecurityError" => "SecurityError", - "rb_eSystemCallError" => "SystemCallError", - "rb_eTypeError" => "TypeError", - "rb_eZeroDivError" => "ZeroDivError", - "rb_eNotImpError" => "NotImpError", - "rb_eNoMemError" => "NoMemError", - "rb_eFloatDomainError" => "FloatDomainError", - "rb_eScriptError" => "ScriptError", - "rb_eNameError" => "NameError", - "rb_eSyntaxError" => "SyntaxError", - "rb_eLoadError" => "LoadError", - - "rb_mKernel" => "Kernel", - "rb_mComparable" => "Comparable", - "rb_mEnumerable" => "Enumerable", - "rb_mPrecision" => "Precision", - "rb_mErrno" => "Errno", - "rb_mFileTest" => "FileTest", - "rb_mGC" => "GC", - "rb_mMath" => "Math", - "rb_mProcess" => "Process" - - } - - # See rdoc/c_parse.rb - class C_Parser + attr_accessor :progress extend ParserFactory parse_files_matching(/\.(c|cc|cpp|CC)$/) @@ -212,9 +214,15 @@ $stderr.flush end - # remove lines that are commented out that might otherwise get - # picked up when scanning for classes and methods + def remove_private_comments(comment) + comment.gsub!(/\/?\*--(.*?)\/?\*\+\+/m, '') + comment.sub!(/\/?\*--.*/m, '') + end + ## + # removes lines that are commented out that might otherwise get picked up + # when scanning for classes and methods + def remove_commented_out_lines @body.gsub!(%r{//.*rb_define_}, '//') end @@ -255,17 +263,52 @@ @classes[var_name] = cm @known_classes[var_name] = cm.full_name end - - ############################################################ + ## + # Look for class or module documentation above Init_+class_name+(void), + # in a Document-class +class_name+ (or module) comment or above an + # rb_define_class (or module). If a comment is supplied above a matching + # Init_ and a rb_define_class the Init_ comment is used. + # + # /* + # * This is a comment for Foo + # */ + # Init_Foo(void) { + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + # } + # + # /* + # * Document-class: Foo + # * This is a comment for Foo + # */ + # Init_foo(void) { + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + # } + # + # /* + # * This is a comment for Foo + # */ + # VALUE cFoo = rb_define_class("Foo", rb_cObject); def find_class_comment(class_name, class_meth) comment = nil if @body =~ %r{((?>/\*.*?\*/\s+)) - (static\s+)?void\s+Init_#{class_name}\s*\(\)}xmi + (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)}xmi comment = $1 elsif @body =~ %r{Document-(class|module):\s#{class_name}\s*?\n((?>.*?\*/))}m comment = $2 + else + if @body =~ /rb_define_(class|module)/m then + class_name = class_name.split("::").last + comments = [] + @body.split(/(\/\*.*?\*\/)\s*?\n/m).each_with_index do |chunk, index| + comments[index] = chunk + if chunk =~ /rb_define_(class|module).*?"(#{class_name})"/m then + comment = comments[index-1] + break + end + end + end end class_meth.comment = mangle_comment(comment) if comment end @@ -420,7 +463,16 @@ end end - ############################################################ + ## + # Adds constant comments. By providing some_value: at the start ofthe + # comment you can override the C value of the comment to give a friendly + # definition. + # + # /* 300: The perfect score in bowling */ + # rb_define_const(cFoo, "PERFECT", INT2FIX(300); + # + # Will override +INT2FIX(300)+ with the value +300+ in the output RDoc. + # Values may include quotes and escaped colons (\:). def handle_constants(type, var_name, const_name, definition) #@stats.num_constants += 1 @@ -437,14 +489,39 @@ comment = find_const_comment(type, const_name) - con = Constant.new(const_name, definition, mangle_comment(comment)) + # In the case of rb_define_const, the definition and comment are in + # "/* definition: comment */" form. The literal ':' and '\' characters + # can be escaped with a backslash. + if type.downcase == 'const' then + elements = mangle_comment(comment).split(':') + if elements.nil? or elements.empty? then + con = Constant.new(const_name, definition, mangle_comment(comment)) + else + new_definition = elements[0..-2].join(':') + if new_definition.empty? then # Default to literal C definition + new_definition = definition + else + new_definition.gsub!("\:", ":") + new_definition.gsub!("\\", '\\') + end + new_definition.sub!(/\A(\s+)/, '') + new_comment = $1.nil? ? elements.last : "#{$1}#{elements.last.lstrip}" + con = Constant.new(const_name, new_definition, + mangle_comment(new_comment)) + end + else + con = Constant.new(const_name, definition, mangle_comment(comment)) + end + class_obj.add_constant(con) end - ########################################################### + ## + # Finds a comment matching +type+ and +const_name+ either above the + # comment or in the matching Document- section. def find_const_comment(type, const_name) - if @body =~ %r{((?>/\*.*?\*/\s+)) + if @body =~ %r{((?>^\s*/\*.*?\*/\s+)) rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi $1 elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m @@ -516,7 +593,8 @@ type = "singleton_method" end meth_obj = AnyMethod.new("", meth_name) - meth_obj.singleton = type == "singleton_method" + meth_obj.singleton = + %w{singleton_method module_function}.include?(type) p_count = (Integer(param_count) rescue -1) @@ -552,6 +630,8 @@ comment, params = $1, $2 body_text = $& + remove_private_comments(comment) if comment + # see if we can find the whole body re = Regexp.escape(body_text) + '[^(]*^\{.*?^\}' @@ -602,13 +682,15 @@ end - ################################################## - # - # If the comment block contains a section that looks like + ## + # If the comment block contains a section that looks like: + # # call-seq: # Array.new # Array.new(10) - # use it for the parameters + # + # use it for the parameters. + def find_modifiers(comment, meth_obj) if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '') @@ -631,10 +713,11 @@ end end - ############################################################ + ## + # Look for includes of the form: + # + # rb_include_module(rb_cArray, rb_mEnumerable); - # Look for includes of the form - # rb_include_module(rb_cArray, rb_mEnumerable); def do_includes @body.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| if cls = @classes[c] @@ -644,8 +727,7 @@ end end - ############################################################ - + ## # Remove the /*'s and leading asterisks from C comments def mangle_comment(comment) @@ -678,7 +760,8 @@ end end - # Remove #ifdefs that would otherwise confuse us + ## + # Removes #ifdefs that would otherwise confuse us def handle_ifdefs_in(body) body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 } @@ -687,3 +770,4 @@ end end + Index: rdoc/diagram.rb =================================================================== --- rdoc/diagram.rb (revision 2900) +++ rdoc/diagram.rb (working copy) @@ -38,6 +38,7 @@ @options = options @counter = 0 File.makedirs(DOT_PATH) + @diagram_cache = {} end # Draw the diagrams. We traverse the files, drawing a diagram for @@ -55,7 +56,6 @@ @local_names = find_names(i) @global_names = [] @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel', - 'label' => i.file_absolute_name, 'fontname' => FONT, 'fontsize' => '8', 'bgcolor' => 'lightcyan1', @@ -73,7 +73,7 @@ end add_classes(i, graph, i.file_relative_name) - i.diagram = convert_to_png("f_#{file_count}", graph, i.name) + i.diagram = convert_to_png("f_#{file_count}", graph) # now go through and document each top level class and # module independently @@ -83,7 +83,6 @@ @global_names = [] @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel', - 'label' => i.full_name, 'fontname' => FONT, 'fontsize' => '8', 'bgcolor' => 'lightcyan1', @@ -95,8 +94,7 @@ 'fontsize' => 8) draw_module(mod, graph, true) mod.diagram = convert_to_png("m_#{file_count}_#{count}", - graph, - "Module: #{mod.name}") + graph) end end $stderr.puts unless @options.quiet @@ -280,7 +278,9 @@ end - def convert_to_png(file_base, graph, name) + def convert_to_png(file_base, graph) + str = graph.to_s + return @diagram_cache[str] if @diagram_cache[str] op_type = Options.instance.image_format dotfile = File.join(DOT_PATH, file_base) src = dotfile + ".dot" @@ -292,15 +292,17 @@ end File.open(src, 'w+' ) do |f| - f << graph.to_s << "\n" + f << str << "\n" end - system "dot -T#{op_type} #{src} -o #{dot}" + system "dot", "-T#{op_type}", src, "-o", dot # Now construct the imagemap wrapper around # that png - return wrap_in_image_map(src, dot, name) + ret = wrap_in_image_map(src, dot) + @diagram_cache[str] = ret + return ret end # Extract the client-side image map from dot, and use it @@ -308,7 +310,7 @@ # .. combination, suitable for inclusion on # the page - def wrap_in_image_map(src, dot, name) + def wrap_in_image_map(src, dot) res = %{\n} dot_map = `dot -Tismap #{src}` dot_map.each do |area| @@ -320,13 +322,13 @@ xs, ys = [$1.to_i, $3.to_i], [$2.to_i, $4.to_i] url, area_name = $5, $6 - res << %{ \n} + res << %{ \n} end res << "\n" # map_file = src.sub(/.dot/, '.map') # system("dot -Timap #{src} -o #{map_file}") - res << %{} + res << %{} return res end end Index: rdoc/usage.rb =================================================================== --- rdoc/usage.rb (revision 2900) +++ rdoc/usage.rb (working copy) @@ -96,7 +96,7 @@ # Display usage def RDoc.usage_no_exit(*args) - main_program_file, = caller[-1].split(/:\d+/, 2) + main_program_file = caller[-1].sub(/:\d+$/, '') comment = File.open(main_program_file) do |file| find_comment(file) end Index: rdoc/generators/chm_generator.rb =================================================================== --- rdoc/generators/chm_generator.rb (revision 2900) +++ rdoc/generators/chm_generator.rb (working copy) @@ -4,7 +4,7 @@ class CHMGenerator < HTMLGenerator - HHC_PATH = "c:\\Program Files\\HTML Help Workshop\\hhc.exe" + HHC_PATH = "c:/Program Files/HTML Help Workshop/hhc.exe" # Standard generator factory def CHMGenerator.for(options) @@ -103,7 +103,7 @@ # Invoke the windows help compiler to compiler the project def compile_project - system("\"#{HHC_PATH}\" #@project_name") + system(HHC_PATH, @project_name) end end Index: rdoc/generators/ri_generator.rb =================================================================== --- rdoc/generators/ri_generator.rb (revision 2900) +++ rdoc/generators/ri_generator.rb (working copy) @@ -69,7 +69,7 @@ def initialize(options) #:not-new: @options = options - @ri_writer = RI::RiWriter.new(options.op_dir) + @ri_writer = RI::RiWriter.new(".") @markup = SM::SimpleMarkup.new @to_flow = SM::ToFlow.new end Index: rdoc/generators/template/html/old_html.rb =================================================================== --- rdoc/generators/template/html/old_html.rb (revision 2900) +++ rdoc/generators/template/html/old_html.rb (working copy) @@ -497,7 +497,7 @@ Path: %full_path% IF:cvsurl - (CVS) + (CVS) ENDIF:cvsurl @@ -533,7 +533,7 @@ ENDIF:full_path_url IF:cvsurl - (CVS) + (CVS) ENDIF:cvsurl END:infiles @@ -570,7 +570,7 @@ %title% - + %code% Index: rdoc/generators/template/html/kilmer.rb =================================================================== --- rdoc/generators/template/html/kilmer.rb (revision 2900) +++ rdoc/generators/template/html/kilmer.rb (working copy) @@ -94,7 +94,7 @@ %title% - +
%code%