diff --git a/ChangeLog b/ChangeLog index c53e779e54..dadd466266 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Sun Nov 27 05:37:20 2005 Tanaka Akira + + * lib/pathname.rb: use File.basename to decompose pathnames. + experimental Windows support. + Sun Nov 27 00:56:13 2005 NAKAMURA, Hiroshi * lib/wsdl/xmlSchema/complexContent.rb: missing diff --git a/lib/pathname.rb b/lib/pathname.rb index e4e2b01992..c81a70562b 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -15,8 +15,8 @@ # == 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 +# 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. @@ -240,6 +240,47 @@ class Pathname Pathname.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. # @@ -261,118 +302,142 @@ class Pathname # 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 + Pathname.new(prepend_prefix(pre, File.join(*names))) end private :cleanpath_aggressive + # 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 + end + private :has_trailing_separator? + + # 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 + end + private :add_trailing_separator + + 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 + path + end + end + private :del_trailing_separator + 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 << '/' + 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? + 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) + Pathname.new(add_trailing_separator(result)) + else + Pathname.new(result) end end - Pathname.new(path) end private :cleanpath_conservative + def realpath_rec(prefix, unresolved, h) + resolved = [] + until unresolved.empty? + n = unresolved.shift + if n == '.' + next + elsif n == '..' + resolved.pop + else + path = prepend_prefix(prefix, File.join(*(resolved + [n]))) + if h.include? path + if h[path] == :resolving + raise Errno::ELOOP.new(path) + else + prefix, *resolved = h[path] + end + else + 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 + # # 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." - end - force_absolute = args.fetch(0, true) - - 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{[^/]+}) - else - top = '' - unresolved = @path.scan(%r{[^/]+}) - end - resolved = [] - - until unresolved.empty? - case unresolved.last - when '.' - unresolved.pop - when '..' - resolved.unshift unresolved.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{[^/]+}) - else - unresolved[-1,1] = link.scan(%r{[^/]+}) - end - end - next if (filename = unresolved.pop) == '.' - if filename != '..' && resolved.first == '..' - resolved.shift - else - resolved.unshift filename - end - end - end - - if top == '/' - resolved.shift while resolved[0] == '..' - end - - if resolved.empty? - Pathname.new(top.empty? ? '.' : '/') - else - Pathname.new(top + resolved.join('/')) + def realpath + path = @path + prefix, names = split_names(path) + if prefix == '' + prefix, names2 = split_names(Dir.pwd) + names = names2 + names end + prefix, *names = realpath_rec(prefix, names, {}) + Pathname.new(prepend_prefix(prefix, File.join(*names))) end # #parent returns the parent directory. @@ -402,18 +467,22 @@ class Pathname # 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 # @@ -423,7 +492,7 @@ class Pathname # # yields "usr", "bin", and "ruby". # def each_filename # :yield: s - @path.scan(%r{[^/]+}) { yield $& } + split_names(@path).each {|filename| yield filename } end # Iterates over and yields a new Pathname object @@ -445,22 +514,9 @@ class Pathname # It doesn't access actual filesystem. # def descend - paths = [] - v = self - if absolute? - until v.root? - paths << v - v = v.dirname - end - paths << v - else - until v.basename == v - paths << v - v = v.dirname - end - paths << v - end - paths.reverse_each {|path| yield path } + vs = [] + ascend {|v| vs << v } + vs.reverse_each {|v| yield v } end # Iterates over and yields a new Pathname object @@ -482,22 +538,13 @@ class Pathname # It doesn't access actual filesystem. # def ascend - paths = [] - v = self - if absolute? - until v.root? - paths << v - v = v.dirname - end - paths << v - else - until v.basename == v - paths << v - v = v.dirname - end - paths << v + path = @path + yield self + while r = chop_basename(path) + path, name = r + break if path.empty? + yield Pathname.new(del_trailing_separator(path)) end - paths.each {|path| yield path } end # @@ -512,30 +559,47 @@ class Pathname # 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 + 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 - - return Pathname.new(path2) if path1 == '.' - return Pathname.new(path1) if path2 == '.' - - if %r{/\z} =~ path1 - Pathname.new(path1 + path2) + 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 @@ -605,44 +669,42 @@ class Pathname # 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 @@ -966,357 +1028,3 @@ class Pathname # * mixed * end 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 - stat1 = File.stat(__FILE__) - result = Kernel.open(Pathname.new(__FILE__)) {|f| - stat2 = f.stat - assert_equal(stat1.dev, stat2.dev) - assert_equal(stat1.ino, stat2.ino) - assert_equal(stat1.size, stat2.size) - count += 1 - 2 - } - assert_equal(1, count) - assert_equal(2, result) - end - - def test_descend_abs - rs = %w[/ /a /a/b /a/b/c].map {|s| Pathname.new(s) } - Pathname.new("/a/b/c").descend {|v| assert_equal(rs.shift, v) } - assert_equal([], rs) - end - - def test_descend_rel - rs = %w[a a/b a/b/c].map {|s| Pathname.new(s) } - Pathname.new("a/b/c").descend {|v| assert_equal(rs.shift, v) } - assert_equal([], rs) - end - - def test_descend_rel_with_current_dir - rs = %w[. ./a ./a/b ./a/b/c].map {|s| Pathname.new(s) } - Pathname.new("./a/b/c").descend {|v| assert_equal(rs.shift, v) } - assert_equal([], rs) - end - - def test_ascend_abs - rs = %w[/a/b/c /a/b /a /].map {|s| Pathname.new(s) } - Pathname.new("/a/b/c").ascend {|v| assert_equal(rs.shift, v) } - assert_equal([], rs) - end - - def test_ascend_rel - rs = %w[a/b/c a/b a].map {|s| Pathname.new(s) } - Pathname.new("a/b/c").ascend {|v| assert_equal(rs.shift, v) } - assert_equal([], rs) - end - - def test_ascend_rel_with_current_dir - rs = %w[./a/b/c ./a/b ./a .].map {|s| Pathname.new(s) } - Pathname.new("./a/b/c").ascend {|v| assert_equal(rs.shift, v) } - assert_equal([], rs) - end - - end -end diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb new file mode 100644 index 0000000000..5408f81183 --- /dev/null +++ b/test/pathname/test_pathname.rb @@ -0,0 +1,465 @@ +#!/usr/bin/env ruby + +require 'test/unit' +require 'pathname' + +require 'fileutils' +require 'tempfile' + +class TestPathname < Test::Unit::TestCase + def self.define_assertion(name, &block) + @defassert_num ||= {} + @defassert_num[name] ||= 0 + @defassert_num[name] += 1 + define_method("test_#{name}_#{@defassert_num[name]}", &block) + end + + def self.defassert(name, result, *args) + define_assertion(name) { + assert_equal(result, self.send(name, *args), "#{name}(#{args.map {|a| a.inspect }.join(', ')})") + } + end + + DOSISH = File::ALT_SEPARATOR != nil + DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:." + DOSISH_UNC = File.dirname("//") == "//" + + def cleanpath_aggressive(path) + Pathname.new(path).cleanpath.to_s + end + + defassert(:cleanpath_aggressive, '/', '/') + defassert(:cleanpath_aggressive, '.', '') + defassert(:cleanpath_aggressive, '.', '.') + defassert(:cleanpath_aggressive, '..', '..') + defassert(:cleanpath_aggressive, 'a', 'a') + defassert(:cleanpath_aggressive, '/', '/.') + defassert(:cleanpath_aggressive, '/', '/..') + defassert(:cleanpath_aggressive, '/a', '/a') + defassert(:cleanpath_aggressive, '.', './') + defassert(:cleanpath_aggressive, '..', '../') + defassert(:cleanpath_aggressive, 'a', 'a/') + defassert(:cleanpath_aggressive, 'a/b', 'a//b') + defassert(:cleanpath_aggressive, 'a', 'a/.') + defassert(:cleanpath_aggressive, 'a', 'a/./') + defassert(:cleanpath_aggressive, '.', 'a/..') + defassert(:cleanpath_aggressive, '.', 'a/../') + defassert(:cleanpath_aggressive, '/a', '/a/.') + defassert(:cleanpath_aggressive, '..', './..') + defassert(:cleanpath_aggressive, '..', '../.') + defassert(:cleanpath_aggressive, '..', './../') + defassert(:cleanpath_aggressive, '..', '.././') + defassert(:cleanpath_aggressive, '/', '/./..') + defassert(:cleanpath_aggressive, '/', '/../.') + defassert(:cleanpath_aggressive, '/', '/./../') + defassert(:cleanpath_aggressive, '/', '/.././') + defassert(:cleanpath_aggressive, 'a/b/c', 'a/b/c') + defassert(:cleanpath_aggressive, 'b/c', './b/c') + defassert(:cleanpath_aggressive, 'a/c', 'a/./c') + defassert(:cleanpath_aggressive, 'a/b', 'a/b/.') + defassert(:cleanpath_aggressive, '.', 'a/../.') + defassert(:cleanpath_aggressive, '/a', '/../.././../a') + defassert(:cleanpath_aggressive, '../../d', 'a/b/../../../../c/../d') + + if DOSISH_UNC + defassert(:cleanpath_aggressive, '//a/b/c', '//a/b/c/') + else + defassert(:cleanpath_aggressive, '/', '///') + defassert(:cleanpath_aggressive, '/a', '///a') + defassert(:cleanpath_aggressive, '/', '///..') + defassert(:cleanpath_aggressive, '/', '///.') + defassert(:cleanpath_aggressive, '/', '///a/../..') + end + + def cleanpath_conservative(path) + Pathname.new(path).cleanpath(true).to_s + end + + defassert(:cleanpath_conservative, '/', '/') + defassert(:cleanpath_conservative, '.', '') + defassert(:cleanpath_conservative, '.', '.') + defassert(:cleanpath_conservative, '..', '..') + defassert(:cleanpath_conservative, 'a', 'a') + defassert(:cleanpath_conservative, '/', '/.') + defassert(:cleanpath_conservative, '/', '/..') + defassert(:cleanpath_conservative, '/a', '/a') + defassert(:cleanpath_conservative, '.', './') + defassert(:cleanpath_conservative, '..', '../') + defassert(:cleanpath_conservative, 'a/', 'a/') + defassert(:cleanpath_conservative, 'a/b', 'a//b') + defassert(:cleanpath_conservative, 'a/.', 'a/.') + defassert(:cleanpath_conservative, 'a/.', 'a/./') + defassert(:cleanpath_conservative, 'a/..', 'a/../') + defassert(:cleanpath_conservative, '/a/.', '/a/.') + defassert(:cleanpath_conservative, '..', './..') + defassert(:cleanpath_conservative, '..', '../.') + defassert(:cleanpath_conservative, '..', './../') + defassert(:cleanpath_conservative, '..', '.././') + defassert(:cleanpath_conservative, '/', '/./..') + defassert(:cleanpath_conservative, '/', '/../.') + defassert(:cleanpath_conservative, '/', '/./../') + defassert(:cleanpath_conservative, '/', '/.././') + defassert(:cleanpath_conservative, 'a/b/c', 'a/b/c') + defassert(:cleanpath_conservative, 'b/c', './b/c') + defassert(:cleanpath_conservative, 'a/c', 'a/./c') + defassert(:cleanpath_conservative, 'a/b/.', 'a/b/.') + defassert(:cleanpath_conservative, 'a/..', 'a/../.') + defassert(:cleanpath_conservative, '/a', '/../.././../a') + defassert(:cleanpath_conservative, 'a/b/../../../../c/../d', 'a/b/../../../../c/../d') + + if DOSISH_UNC + defassert(:cleanpath_conservative, '//', '//') + else + defassert(:cleanpath_conservative, '/', '//') + end + + # has_trailing_separator?(path) -> bool + def has_trailing_separator?(path) + Pathname.allocate.funcall(:has_trailing_separator?, path) + end + + defassert(:has_trailing_separator?, false, "/") + defassert(:has_trailing_separator?, false, "///") + defassert(:has_trailing_separator?, false, "a") + defassert(:has_trailing_separator?, true, "a/") + + def add_trailing_separator(path) + Pathname.allocate.funcall(:add_trailing_separator, path) + end + + def del_trailing_separator(path) + Pathname.allocate.funcall(:del_trailing_separator, path) + end + + defassert(:del_trailing_separator, "/", "/") + defassert(:del_trailing_separator, "/a", "/a") + defassert(:del_trailing_separator, "/a", "/a/") + defassert(:del_trailing_separator, "/a", "/a//") + defassert(:del_trailing_separator, ".", ".") + defassert(:del_trailing_separator, ".", "./") + defassert(:del_trailing_separator, ".", ".//") + + if DOSISH_DRIVE_LETTER + defassert(:del_trailing_separator, "A:", "A:") + defassert(:del_trailing_separator, "A:/", "A:/") + defassert(:del_trailing_separator, "A:/", "A://") + defassert(:del_trailing_separator, "A:.", "A:.") + defassert(:del_trailing_separator, "A:.", "A:./") + defassert(:del_trailing_separator, "A:.", "A:.//") + end + + if DOSISH_UNC + defassert(:del_trailing_separator, "//", "//") + defassert(:del_trailing_separator, "//a", "//a") + defassert(:del_trailing_separator, "//a", "//a/") + defassert(:del_trailing_separator, "//a", "//a//") + defassert(:del_trailing_separator, "//a/b", "//a/b") + defassert(:del_trailing_separator, "//a/b", "//a/b/") + defassert(:del_trailing_separator, "//a/b", "//a/b//") + defassert(:del_trailing_separator, "//a/b/c", "//a/b/c") + defassert(:del_trailing_separator, "//a/b/c", "//a/b/c/") + defassert(:del_trailing_separator, "//a/b/c", "//a/b/c//") + else + defassert(:del_trailing_separator, "/", "///") + defassert(:del_trailing_separator, "///a", "///a/") + end + + if DOSISH + defassert(:del_trailing_separator, "a", "a\\") + defassert(:del_trailing_separator, "\225\\", "\225\\\\") # SJIS + end + + def plus(path1, path2) # -> path + (Pathname.new(path1) + Pathname.new(path2)).to_s + end + + defassert(:plus, '/', '/', '/') + defassert(:plus, 'a/b', 'a', 'b') + defassert(:plus, 'a', 'a', '.') + defassert(:plus, 'b', '.', 'b') + defassert(:plus, '.', '.', '.') + defassert(:plus, '/b', 'a', '/b') + + defassert(:plus, '/', '/', '..') + defassert(:plus, '.', 'a', '..') + defassert(:plus, 'a', 'a/b', '..') + defassert(:plus, '../..', '..', '..') + defassert(:plus, '/c', '/', '../c') + defassert(:plus, 'c', 'a', '../c') + defassert(:plus, 'a/c', 'a/b', '../c') + defassert(:plus, '../../c', '..', '../c') + + defassert(:plus, 'a//b/d//e', 'a//b/c', '../d//e') + + def relative?(path) + Pathname.new(path).relative? + end + + defassert(:relative?, false, '/') + defassert(:relative?, false, '/a') + defassert(:relative?, false, '/..') + defassert(:relative?, true, 'a') + defassert(:relative?, true, 'a/b') + + if DOSISH_DRIVE_LETTER + defassert(:relative?, false, 'A:') + defassert(:relative?, false, 'A:/') + defassert(:relative?, false, 'A:/a') + end + + if File.dirname('//') == '//' + defassert(:relative?, false, '//') + defassert(:relative?, false, '//a') + defassert(:relative?, false, '//a/') + defassert(:relative?, false, '//a/b') + defassert(:relative?, false, '//a/b/') + defassert(:relative?, false, '//a/b/c') + end + + def relative_path_from(dest_directory, base_directory) + Pathname.new(dest_directory).relative_path_from(Pathname.new(base_directory)).to_s + end + + defassert(:relative_path_from, "../a", "a", "b") + defassert(:relative_path_from, "../a", "a", "b/") + defassert(:relative_path_from, "../a", "a/", "b") + defassert(:relative_path_from, "../a", "a/", "b/") + defassert(:relative_path_from, "../a", "/a", "/b") + defassert(:relative_path_from, "../a", "/a", "/b/") + defassert(:relative_path_from, "../a", "/a/", "/b") + defassert(:relative_path_from, "../a", "/a/", "/b/") + + defassert(:relative_path_from, "../b", "a/b", "a/c") + defassert(:relative_path_from, "../a", "../a", "../b") + + defassert(:relative_path_from, "a", "a", ".") + defassert(:relative_path_from, "..", ".", "a") + + defassert(:relative_path_from, ".", ".", ".") + defassert(:relative_path_from, ".", "..", "..") + defassert(:relative_path_from, "..", "..", ".") + + defassert(:relative_path_from, "c/d", "/a/b/c/d", "/a/b") + defassert(:relative_path_from, "../..", "/a/b", "/a/b/c/d") + defassert(:relative_path_from, "../../../../e", "/e", "/a/b/c/d") + defassert(:relative_path_from, "../b/c", "a/b/c", "a/d") + + defassert(:relative_path_from, "../a", "/../a", "/b") + defassert(:relative_path_from, "../../a", "../a", "b") + defassert(:relative_path_from, ".", "/a/../../b", "/b") + defassert(:relative_path_from, "..", "a/..", "a") + defassert(:relative_path_from, ".", "a/../b", "b") + + defassert(:relative_path_from, "a", "a", "b/..") + defassert(:relative_path_from, "b/c", "b/c", "b/..") + + def self.defassert_raise(name, exc, *args) + define_assertion(name) { + message = "#{name}(#{args.map {|a| a.inspect }.join(', ')})" + assert_raise(exc, message) { self.send(name, *args) } + } + end + + defassert_raise(:relative_path_from, ArgumentError, "/", ".") + defassert_raise(:relative_path_from, ArgumentError, ".", "/") + defassert_raise(:relative_path_from, ArgumentError, "a", "..") + defassert_raise(:relative_path_from, ArgumentError, ".", "..") + + def realpath(path) + Pathname.new(path).realpath.to_s + end + + def test_realpath + begin + File.symlink(nil, nil) + rescue NotImplementedError + return + rescue TypeError + end + dir = "#{Dir.tmpdir}/tst-pathname-#$$" + Dir.mkdir(dir) + begin + File.symlink("not-exist-target", "#{dir}/not-exist") + assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") } + File.symlink("loop", "#{dir}/loop") + assert_raise(Errno::ELOOP) { realpath("#{dir}/loop") } + ensure + FileUtils.rmtree(dir) + end + end + + def descend(path) + Pathname.new(path).enum_for(:descend).map {|v| v.to_s } + end + + defassert(:descend, %w[/ /a /a/b /a/b/c], "/a/b/c") + defassert(:descend, %w[a a/b a/b/c], "a/b/c") + defassert(:descend, %w[. ./a ./a/b ./a/b/c], "./a/b/c") + defassert(:descend, %w[a/], "a/") + + def ascend(path) + Pathname.new(path).enum_for(:ascend).map {|v| v.to_s } + end + + defassert(:ascend, %w[/a/b/c /a/b /a /], "/a/b/c") + defassert(:ascend, %w[a/b/c a/b a], "a/b/c") + defassert(:ascend, %w[./a/b/c ./a/b ./a .], "./a/b/c") + defassert(:ascend, %w[a/], "a/") + + 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 root?(path) + Pathname.new(path).root? + end + + defassert(:root?, true, "/") + defassert(:root?, true, "//") + defassert(:root?, true, "///") + defassert(:root?, false, "") + defassert(:root?, false, "a") + + 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 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 + stat1 = File.stat(__FILE__) + result = Kernel.open(Pathname.new(__FILE__)) {|f| + stat2 = f.stat + assert_equal(stat1.dev, stat2.dev) + assert_equal(stat1.ino, stat2.ino) + assert_equal(stat1.size, stat2.size) + count += 1 + 2 + } + assert_equal(1, count) + assert_equal(2, result) + end +end diff --git a/test/test_pathname.rb b/test/test_pathname.rb deleted file mode 100644 index 981fedd98f..0000000000 --- a/test/test_pathname.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'pathname' -require Pathname.new(__FILE__).dirname.join('inlinetest.rb') -target = __FILE__[/test_(.*\.rb)$/, 1] -InlineTest.loadtest(target)