From: Akinori MUSHA Date: 2008-09-25T18:41:55+09:00 Subject: [ruby-dev:36560] Re: adding Pathname#resolve (was: adding Pathname#abspath()) --pgp-sign-Multipart_Thu_Sep_25_18:42:13_2008-1 Content-Type: text/plain; charset=ISO-2022-JP At Wed, 24 Sep 2008 02:02:59 +0900, Tanaka Akira wrote: > In article <86od2gcvvj.knu@iDaemons.org>, > "Akinori MUSHA" writes: > > >  似た動作の想起を条件にするのは、 realpath を含む名前にするか、 > > あるいは realpath に引数を追加するかの二択と考えてよいでしょうか。 > > real という単語が入ってれば似ているように思います。  しかし、 real という単語が入るのに実際に存在しないパスを返す のは奇妙ではないでしょうか。あくまで realpath という既知の名前 からしか機能は想起されないと思います。real_path は気持ち悪いし、 *_realpath や realpath_* も適当なものが思いつきませんでした。 bsd_realpath, realpath_butlast, realpath_till_penult, almost_realpath, not_quite_realpath, ...  同じ機能は既存の公開および内部メソッドを使っても簡単には実装 できなかったのでなんとか入れたいと思うのですが。  ~ の展開と相対パスの展開を行うメソッドが expand_path なので、 symlink の解決と相対パスの展開を行うメソッドが resolve_path と いうのは悪くないと思っています。realpath との関連はさほど大事 でしょうか。 > >  であれば私は後者を推したいです。フラグ引数が廃止された過去が > > あるので抵抗があったのですが、もう数年経っているのでトラブルは > > ないでしょうね。 > > フラグより違う名前のほうがいいんじゃないかという気がします。 > > この件については動的にどちらかの挙動を選びたいということはな > さそうな気がしますし、名前が挙動を表すとプログラムがわかりや > すくなるので。  それはそうですね。先の実装はフラグを受け付けるようになって いましたが、フラグを取るメソッドは private に隠そうと思います。 -- Akinori MUSHA / http://akinori.org/ * lib/pathname.rb (Pathname#realpath_rec, Pathname#resolve_path): Add Pathname#resolve_path, a variant of #realpath that allows the last component of pathname to be nonexistent. Index: lib/pathname.rb =================================================================== --- lib/pathname.rb (revision 19545) +++ lib/pathname.rb (working copy) @@ -76,9 +76,9 @@ # # === Core methods # -# These methods are effectively manipulating a String, because that's all a path -# is. Except for #mountpoint?, #children, and #realpath, they don't access the -# filesystem. +# These methods are effectively manipulating a String, because that's +# all a path is. Except for #mountpoint?, #children, #resolve_path +# and #realpath, they don't access the filesystem. # # - + # - #join @@ -90,6 +90,7 @@ # - #each_filename # - #cleanpath # - #realpath +# - #resolve_path # - #children # - #mountpoint? # @@ -411,7 +412,7 @@ class Pathname end private :cleanpath_conservative - def realpath_rec(prefix, unresolved, h) + def realpath_rec(prefix, unresolved, h, strict, last = true) resolved = [] until unresolved.empty? n = unresolved.shift @@ -428,14 +429,20 @@ class Pathname prefix, *resolved = h[path] end else - s = File.lstat(path) + begin + s = File.lstat(path) + rescue Errno::ENOENT => e + raise e if strict || !last || !unresolved.empty? + resolved << n + break + end 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) + prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h, strict, unresolved.empty?) else - prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h) + prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h, strict, unresolved.empty?) end else resolved << n @@ -448,22 +455,39 @@ class Pathname 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 + def resolve_path_internal(strict = false) 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, {}) + prefix, *names = realpath_rec(prefix, names, {}, strict) self.class.new(prepend_prefix(prefix, File.join(*names))) end + private :resolve_path_internal + # + # Returns the real (absolute) pathname of +self+ in the actual + # filesystem not containing symlinks or useless dots. + # + # All components of the pathname must exist when this method is + # called. + # + # No arguments should be given; the old behaviour is *obsoleted*. + # + def realpath + resolve_path_internal(true) + end + + # + # Returns the real (absolute) pathname of +self+ in the actual filesystem. + # The real pathname doesn't contain symlinks or useless dots. + # + # The last component of the pathname can be nonexistent. + # + def resolve_path + resolve_path_internal(false) + end # #parent returns the parent directory. # Index: test/pathname/test_pathname.rb =================================================================== --- test/pathname/test_pathname.rb (revision 19545) +++ test/pathname/test_pathname.rb (working copy) @@ -273,6 +273,8 @@ class TestPathname < Test::Unit::TestCas Pathname.new(path).realpath.to_s end + class NoException < Exception; end + def test_realpath begin File.symlink(nil, nil) @@ -281,10 +283,42 @@ class TestPathname < Test::Unit::TestCas rescue TypeError end Dir.mktmpdir('rubytest-pathname') {|dir| + assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist") } File.symlink("not-exist-target", "#{dir}/not-exist") - assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") } + assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist") } + File.symlink("../#{File.basename(dir)}/./not-exist-target", "#{dir}/not-exist2") + assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist2") } + File.open("#{dir}/exist-target", "w") {} + File.symlink("../#{File.basename(dir)}/./exist-target", "#{dir}/exist") + assert_raises(NoException) { realpath("#{dir}/exist"); raise NoException } File.symlink("loop", "#{dir}/loop") - assert_raise(Errno::ELOOP) { realpath("#{dir}/loop") } + assert_raises(Errno::ELOOP) { realpath("#{dir}/loop") } + } + end + + def resolve_path(path) + Pathname.new(path).resolve_path.to_s + end + + def test_resolve_path + begin + File.symlink(nil, nil) + rescue NotImplementedError + return + rescue TypeError + end + Dir.mktmpdir('rubytest-pathname') {|dir| + assert_raises(NoException) { resolve_path("#{dir}/not-exist"); raise NoException } + assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist/not-exist-child") } + File.symlink("not-exist-target", "#{dir}/not-exist") + assert_raises(NoException) { resolve_path("#{dir}/not-exist"); raise NoException } + File.symlink("../#{File.basename(dir)}/./not-exist-target", "#{dir}/not-exist2") + assert_raises(NoException) { resolve_path("#{dir}/not-exist2"); raise NoException } + File.open("#{dir}/exist-target", "w") {} + File.symlink("../#{File.basename(dir)}/./exist-target", "#{dir}/exist") + assert_raises(NoException) { resolve_path("#{dir}/exist"); raise NoException } + File.symlink("loop", "#{dir}/loop") + assert_raises(Errno::ELOOP) { resolve_path("#{dir}/loop") } } end --pgp-sign-Multipart_Thu_Sep_25_18:42:13_2008-1 Content-Type: application/pgp-signature Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (FreeBSD) iEYEABECAAYFAkjbXPUACgkQkgvvx5/Z4e4SQACgjTRAr1J/cZwFw2vQGFE5CFuF ObUAoK862PLRBypREPvi7dRwEoAiNqnW =vadq -----END PGP SIGNATURE----- --pgp-sign-Multipart_Thu_Sep_25_18:42:13_2008-1--