├── .document ├── test ├── lib │ └── helper.rb └── pathname │ ├── test_ractor.rb │ └── test_pathname.rb ├── Gemfile ├── .github ├── dependabot.yml └── workflows │ ├── test.yml │ └── push_gem.yml ├── .gitignore ├── bin ├── setup └── console ├── ext └── pathname │ ├── extconf.rb │ └── pathname.c ├── .git-blame-ignore-revs ├── Rakefile ├── BSDL ├── pathname.gemspec ├── README.md ├── lib ├── pathname.rb └── pathname_builtin.rb └── COPYING /.document: -------------------------------------------------------------------------------- 1 | BSDL 2 | COPYING 3 | README.md 4 | ext/pathname/*.c 5 | lib/*.rb 6 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rake" 6 | gem "rake-compiler" 7 | gem "test-unit" 8 | gem "test-unit-ruby-core" 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | *.bundle 11 | *.so 12 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /ext/pathname/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | 3 | require 'mkmf' 4 | 5 | if RUBY_ENGINE == "ruby" 6 | create_makefile('pathname') 7 | else 8 | File.write("Makefile", dummy_makefile($srcdir).join("")) 9 | end 10 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # This is a file used by GitHub to ignore the following commits on `git blame`. 2 | # 3 | # You can also do the same thing in your local repository with: 4 | # $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs 5 | 6 | # Expand tabs 7 | 93e8aaadc7c7945895b1b0b6d9909134409517ff 8 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "pathname" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/lib" 6 | t.ruby_opts << "-rhelper" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | if RUBY_ENGINE == 'ruby' 11 | require 'rake/extensiontask' 12 | Rake::ExtensionTask.new("pathname") 13 | else 14 | task :compile 15 | end 16 | 17 | task :default => [:compile, :test] 18 | -------------------------------------------------------------------------------- /test/pathname/test_ractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "test/unit" 3 | require "pathname" 4 | 5 | class TestPathnameRactor < Test::Unit::TestCase 6 | def setup 7 | omit unless defined? Ractor 8 | end 9 | 10 | def test_ractor_shareable 11 | assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") 12 | class Ractor 13 | alias value take 14 | end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders 15 | 16 | begin; 17 | $VERBOSE = nil 18 | require "pathname" 19 | r = Ractor.new Pathname("a") do |x| 20 | x.join(Pathname("b"), Pathname("c")) 21 | end 22 | assert_equal(Pathname("a/b/c"), r.value) 23 | 24 | r = Ractor.new Pathname("a") do |a| 25 | Pathname("b").relative_path_from(a) 26 | end 27 | assert_equal(Pathname("../b"), r.value) 28 | end; 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /pathname.gemspec: -------------------------------------------------------------------------------- 1 | name = File.basename(__FILE__, ".gemspec") 2 | version = ["lib", "ext/lib"].find do |dir| 3 | break File.foreach(File.join(__dir__, dir, "#{name}_builtin.rb")) do |line| 4 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 5 | end rescue nil 6 | end 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = name 10 | spec.version = version 11 | spec.authors = ["Tanaka Akira"] 12 | spec.email = ["akr@fsij.org"] 13 | 14 | spec.summary = %q{Representation of the name of a file or directory on the filesystem} 15 | spec.description = %q{Representation of the name of a file or directory on the filesystem} 16 | spec.homepage = "https://github.com/ruby/pathname" 17 | spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") 18 | spec.licenses = ["Ruby", "BSD-2-Clause"] 19 | 20 | spec.metadata["homepage_uri"] = spec.homepage 21 | spec.metadata["source_code_uri"] = spec.homepage 22 | 23 | # Specify which files should be added to the gem when it is released. 24 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 25 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 26 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 27 | end 28 | spec.bindir = "exe" 29 | spec.executables = [] 30 | spec.require_paths = ["lib"] 31 | spec.extensions = %w[ext/pathname/extconf.rb] 32 | end 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | engine: all 10 | 11 | test: 12 | needs: ruby-versions 13 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 18 | os: [ ubuntu-latest, macos-latest, windows-latest ] 19 | exclude: 20 | - { os: windows-latest , ruby: head } 21 | - { os: windows-latest , ruby: truffleruby } 22 | - { os: windows-latest , ruby: truffleruby-head } 23 | # io/console warnings make CI fail 24 | - { os: macos-latest , ruby: jruby } 25 | - { os: macos-latest , ruby: jruby-head } 26 | # Errno::ESRCH: No such process - File.symlink 27 | - { os: windows-latest , ruby: jruby } 28 | - { os: windows-latest , ruby: jruby-head } 29 | include: 30 | - { os: windows-latest , ruby: mingw } 31 | - { os: windows-latest , ruby: mswin } 32 | runs-on: ${{ matrix.os }} 33 | steps: 34 | - uses: actions/checkout@v6 35 | - name: Set up Ruby 36 | uses: ruby/setup-ruby@v1 37 | with: 38 | ruby-version: ${{ matrix.ruby }} 39 | - name: Install dependencies 40 | run: bundle install 41 | - name: Run test 42 | run: rake compile test 43 | 44 | spec: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v6 48 | - uses: ruby/setup-ruby@v1 49 | with: 50 | ruby-version: ruby 51 | - uses: actions/checkout@v6 52 | with: 53 | repository: ruby/spec 54 | path: rubyspec 55 | - name: Clone MSpec 56 | run: git clone https://github.com/ruby/mspec.git ../mspec 57 | - run: bundle install 58 | - run: rake compile 59 | - run: ../mspec/bin/mspec -Ilib rubyspec/library/pathname 60 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/pathname' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/pathname 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@eaecf785f6a34567a6d97f686bbb7bccc1ac1e5c # v1.237.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: ruby 37 | 38 | - name: Set remote URL 39 | run: | 40 | # Attribute commits to the last committer on HEAD 41 | git config --global user.email "$(git log -1 --pretty=format:'%ae')" 42 | git config --global user.name "$(git log -1 --pretty=format:'%an')" 43 | git remote set-url origin "https://x-access-token:${{ github.token }}@github.com/$GITHUB_REPOSITORY" 44 | shell: bash 45 | 46 | - name: Configure trusted publishing credentials 47 | uses: rubygems/configure-rubygems-credentials@v1.0.0 48 | 49 | - name: Install dependencies 50 | run: gem install rake-compiler 51 | shell: bash 52 | 53 | - name: Run release rake task 54 | run: rake release 55 | shell: bash 56 | 57 | - name: Wait for release to propagate 58 | run: gem exec rubygems-await pkg/*.gem 59 | shell: bash 60 | 61 | - name: Create GitHub release 62 | run: | 63 | tag_name="$(git describe --tags --abbrev=0)" 64 | gh release create "${tag_name}" --verify-tag --generate-notes 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pathname 2 | 3 | Pathname represents the name of a file or directory on the filesystem, 4 | but not the file itself. 5 | 6 | The pathname depends on the Operating System: Unix, Windows, etc. 7 | This library works with pathnames of local OS, however non-Unix pathnames 8 | are supported experimentally. 9 | 10 | A Pathname can be relative or absolute. It's not until you try to 11 | reference the file that it even matters whether the file exists or not. 12 | 13 | Pathname is immutable. It has no method for destructive update. 14 | 15 | The goal of this class is to manipulate file path information in a neater 16 | way than standard Ruby provides. The examples below demonstrate the 17 | difference. 18 | 19 | *All* functionality from File, FileTest, and some from Dir and FileUtils is 20 | included, in an unsurprising way. It is essentially a facade for all of 21 | these, and more. 22 | 23 | ## Installation 24 | 25 | Add this line to your application's Gemfile: 26 | 27 | ```ruby 28 | gem 'pathname' 29 | ``` 30 | 31 | And then execute: 32 | 33 | $ bundle install 34 | 35 | Or install it yourself as: 36 | 37 | $ gem install pathname 38 | 39 | ## Usage 40 | 41 | ```ruby 42 | require 'pathname' 43 | pn = Pathname.new("/usr/bin/ruby") 44 | size = pn.size # 27662 45 | isdir = pn.directory? # false 46 | dir = pn.dirname # Pathname:/usr/bin 47 | base = pn.basename # Pathname:ruby 48 | dir, base = pn.split # [Pathname:/usr/bin, Pathname:ruby] 49 | data = pn.read 50 | pn.open { |f| _ } 51 | pn.each_line { |line| _ } 52 | ``` 53 | 54 | ## Development 55 | 56 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to compile this and run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 57 | 58 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 59 | 60 | ## Contributing 61 | 62 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/pathname. 63 | -------------------------------------------------------------------------------- /lib/pathname.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # = pathname.rb 4 | # 5 | # Object-Oriented Pathname Class 6 | # 7 | # Author:: Tanaka Akira 8 | # Documentation:: Author and Gavin Sinclair 9 | # 10 | # For documentation, see class Pathname. 11 | # 12 | 13 | if defined?(::Pathname) # Clear builtin Pathname 14 | # :stopdoc: 15 | class ::Object 16 | remove_const :Pathname 17 | end 18 | 19 | # Remove module_function Pathname 20 | class << ::Kernel 21 | undef Pathname 22 | end 23 | module ::Kernel 24 | undef Pathname 25 | end 26 | 27 | $".delete('pathname.so') 28 | # :startdoc: 29 | end 30 | 31 | require 'pathname.so' if RUBY_ENGINE == 'ruby' 32 | 33 | require_relative 'pathname_builtin' 34 | 35 | class Pathname # * Find * 36 | # 37 | # Iterates over the directory tree in a depth first manner, yielding a 38 | # Pathname for each file under "this" directory. 39 | # 40 | # Returns an Enumerator if no block is given. 41 | # 42 | # Since it is implemented by the standard library module Find, Find.prune can 43 | # be used to control the traversal. 44 | # 45 | # If +self+ is +.+, yielded pathnames begin with a filename in the 46 | # current directory, not +./+. 47 | # 48 | # See Find.find 49 | # 50 | def find(ignore_error: true) # :yield: pathname 51 | return to_enum(__method__, ignore_error: ignore_error) unless block_given? 52 | require 'find' 53 | if @path == '.' 54 | Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) } 55 | else 56 | Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) } 57 | end 58 | end 59 | end 60 | 61 | 62 | class Pathname # * FileUtils * 63 | # Recursively deletes a directory, including all directories beneath it. 64 | # 65 | # See FileUtils.rm_rf 66 | def rmtree(noop: nil, verbose: nil, secure: nil) 67 | # The name "rmtree" is borrowed from File::Path of Perl. 68 | # File::Path provides "mkpath" and "rmtree". 69 | require 'fileutils' 70 | FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure) 71 | self 72 | end 73 | end 74 | 75 | class Pathname # * tmpdir * 76 | # Creates a tmp directory and wraps the returned path in a Pathname object. 77 | # 78 | # See Dir.mktmpdir 79 | def self.mktmpdir 80 | require 'tmpdir' unless defined?(Dir.mktmpdir) 81 | if block_given? 82 | Dir.mktmpdir do |dir| 83 | dir = self.new(dir) 84 | yield dir 85 | end 86 | else 87 | self.new(Dir.mktmpdir) 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /ext/pathname/pathname.c: -------------------------------------------------------------------------------- 1 | #include "ruby.h" 2 | 3 | static VALUE rb_cPathname; 4 | static ID id_at_path; 5 | static ID id_sub; 6 | 7 | static VALUE 8 | get_strpath(VALUE obj) 9 | { 10 | VALUE strpath; 11 | strpath = rb_ivar_get(obj, id_at_path); 12 | if (!RB_TYPE_P(strpath, T_STRING)) 13 | rb_raise(rb_eTypeError, "unexpected @path"); 14 | return strpath; 15 | } 16 | 17 | /* 18 | * Provides a case-sensitive comparison operator for pathnames. 19 | * 20 | * Pathname.new('/usr') <=> Pathname.new('/usr/bin') 21 | * #=> -1 22 | * Pathname.new('/usr/bin') <=> Pathname.new('/usr/bin') 23 | * #=> 0 24 | * Pathname.new('/usr/bin') <=> Pathname.new('/USR/BIN') 25 | * #=> 1 26 | * 27 | * It will return +-1+, +0+ or +1+ depending on the value of the left argument 28 | * relative to the right argument. Or it will return +nil+ if the arguments 29 | * are not comparable. 30 | */ 31 | static VALUE 32 | path_cmp(VALUE self, VALUE other) 33 | { 34 | VALUE s1, s2; 35 | char *p1, *p2; 36 | char *e1, *e2; 37 | if (!rb_obj_is_kind_of(other, rb_cPathname)) 38 | return Qnil; 39 | s1 = get_strpath(self); 40 | s2 = get_strpath(other); 41 | p1 = RSTRING_PTR(s1); 42 | p2 = RSTRING_PTR(s2); 43 | e1 = p1 + RSTRING_LEN(s1); 44 | e2 = p2 + RSTRING_LEN(s2); 45 | while (p1 < e1 && p2 < e2) { 46 | int c1, c2; 47 | c1 = (unsigned char)*p1++; 48 | c2 = (unsigned char)*p2++; 49 | if (c1 == '/') c1 = '\0'; 50 | if (c2 == '/') c2 = '\0'; 51 | if (c1 != c2) { 52 | if (c1 < c2) 53 | return INT2FIX(-1); 54 | else 55 | return INT2FIX(1); 56 | } 57 | } 58 | if (p1 < e1) 59 | return INT2FIX(1); 60 | if (p2 < e2) 61 | return INT2FIX(-1); 62 | return INT2FIX(0); 63 | } 64 | 65 | /* 66 | * Return a pathname which is substituted by String#sub. 67 | * 68 | * path1 = Pathname.new('/usr/bin/perl') 69 | * path1.sub('perl', 'ruby') 70 | * #=> # 71 | */ 72 | static VALUE 73 | path_sub(int argc, VALUE *argv, VALUE self) 74 | { 75 | VALUE str = get_strpath(self); 76 | 77 | if (rb_block_given_p()) { 78 | str = rb_block_call(str, id_sub, argc, argv, 0, 0); 79 | } 80 | else { 81 | str = rb_funcallv(str, id_sub, argc, argv); 82 | } 83 | return rb_class_new_instance(1, &str, rb_obj_class(self)); 84 | } 85 | 86 | static void init_ids(void); 87 | 88 | void 89 | Init_pathname(void) 90 | { 91 | #ifdef HAVE_RB_EXT_RACTOR_SAFE 92 | rb_ext_ractor_safe(true); 93 | #endif 94 | 95 | init_ids(); 96 | InitVM(pathname); 97 | } 98 | 99 | void 100 | InitVM_pathname(void) 101 | { 102 | rb_cPathname = rb_define_class("Pathname", rb_cObject); 103 | rb_define_method(rb_cPathname, "<=>", path_cmp, 1); 104 | rb_define_method(rb_cPathname, "sub", path_sub, -1); 105 | } 106 | 107 | void 108 | init_ids(void) 109 | { 110 | #undef rb_intern 111 | id_at_path = rb_intern("@path"); 112 | id_sub = rb_intern("sub"); 113 | } 114 | -------------------------------------------------------------------------------- /lib/pathname_builtin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # = pathname.rb 4 | # 5 | # Object-Oriented Pathname Class 6 | # 7 | # Author:: Tanaka Akira 8 | # Documentation:: Author and Gavin Sinclair 9 | # 10 | # For documentation, see class Pathname. 11 | # 12 | 13 | # 14 | # Pathname represents the name of a file or directory on the filesystem, 15 | # but not the file itself. 16 | # 17 | # The pathname depends on the Operating System: Unix, Windows, etc. 18 | # This library works with pathnames of local OS, however non-Unix pathnames 19 | # are supported experimentally. 20 | # 21 | # A Pathname can be relative or absolute. It's not until you try to 22 | # reference the file that it even matters whether the file exists or not. 23 | # 24 | # Pathname is immutable. It has no method for destructive update. 25 | # 26 | # The goal of this class is to manipulate file path information in a neater 27 | # way than standard Ruby provides. The examples below demonstrate the 28 | # difference. 29 | # 30 | # *All* functionality from File, FileTest, and some from Dir and FileUtils is 31 | # included, in an unsurprising way. It is essentially a facade for all of 32 | # these, and more. 33 | # 34 | # == Examples 35 | # 36 | # === Example 1: Using Pathname 37 | # 38 | # require 'pathname' 39 | # pn = Pathname.new("/usr/bin/ruby") 40 | # size = pn.size # 27662 41 | # isdir = pn.directory? # false 42 | # dir = pn.dirname # Pathname:/usr/bin 43 | # base = pn.basename # Pathname:ruby 44 | # dir, base = pn.split # [Pathname:/usr/bin, Pathname:ruby] 45 | # data = pn.read 46 | # pn.open { |f| _ } 47 | # pn.each_line { |line| _ } 48 | # 49 | # === Example 2: Using standard Ruby 50 | # 51 | # pn = "/usr/bin/ruby" 52 | # size = File.size(pn) # 27662 53 | # isdir = File.directory?(pn) # false 54 | # dir = File.dirname(pn) # "/usr/bin" 55 | # base = File.basename(pn) # "ruby" 56 | # dir, base = File.split(pn) # ["/usr/bin", "ruby"] 57 | # data = File.read(pn) 58 | # File.open(pn) { |f| _ } 59 | # File.foreach(pn) { |line| _ } 60 | # 61 | # === Example 3: Special features 62 | # 63 | # p1 = Pathname.new("/usr/lib") # Pathname:/usr/lib 64 | # p2 = p1 + "ruby/1.8" # Pathname:/usr/lib/ruby/1.8 65 | # p3 = p1.parent # Pathname:/usr 66 | # p4 = p2.relative_path_from(p3) # Pathname:lib/ruby/1.8 67 | # pwd = Pathname.pwd # Pathname:/home/gavin 68 | # pwd.absolute? # true 69 | # p5 = Pathname.new "." # Pathname:. 70 | # p5 = p5 + "music/../articles" # Pathname:music/../articles 71 | # p5.cleanpath # Pathname:articles 72 | # p5.realpath # Pathname:/home/gavin/articles 73 | # p5.children # [Pathname:/home/gavin/articles/linux, ...] 74 | # 75 | # == Breakdown of functionality 76 | # 77 | # === Core methods 78 | # 79 | # These methods are effectively manipulating a String, because that's 80 | # all a path is. None of these access the file system except for 81 | # #mountpoint?, #children, #each_child, #realdirpath and #realpath. 82 | # 83 | # - + 84 | # - #join 85 | # - #parent 86 | # - #root? 87 | # - #absolute? 88 | # - #relative? 89 | # - #relative_path_from 90 | # - #each_filename 91 | # - #cleanpath 92 | # - #realpath 93 | # - #realdirpath 94 | # - #children 95 | # - #each_child 96 | # - #mountpoint? 97 | # 98 | # === File status predicate methods 99 | # 100 | # These methods are a facade for FileTest: 101 | # - #blockdev? 102 | # - #chardev? 103 | # - #directory? 104 | # - #executable? 105 | # - #executable_real? 106 | # - #exist? 107 | # - #file? 108 | # - #grpowned? 109 | # - #owned? 110 | # - #pipe? 111 | # - #readable? 112 | # - #world_readable? 113 | # - #readable_real? 114 | # - #setgid? 115 | # - #setuid? 116 | # - #size 117 | # - #size? 118 | # - #socket? 119 | # - #sticky? 120 | # - #symlink? 121 | # - #writable? 122 | # - #world_writable? 123 | # - #writable_real? 124 | # - #zero? 125 | # 126 | # === File property and manipulation methods 127 | # 128 | # These methods are a facade for File: 129 | # - #each_line(*args, &block) 130 | # - #read(*args) 131 | # - #binread(*args) 132 | # - #readlines(*args) 133 | # - #sysopen(*args) 134 | # - #write(*args) 135 | # - #binwrite(*args) 136 | # - #atime 137 | # - #birthtime 138 | # - #ctime 139 | # - #mtime 140 | # - #chmod(mode) 141 | # - #lchmod(mode) 142 | # - #chown(owner, group) 143 | # - #lchown(owner, group) 144 | # - #fnmatch(pattern, *args) 145 | # - #fnmatch?(pattern, *args) 146 | # - #ftype 147 | # - #make_link(old) 148 | # - #open(*args, &block) 149 | # - #readlink 150 | # - #rename(to) 151 | # - #stat 152 | # - #lstat 153 | # - #make_symlink(old) 154 | # - #truncate(length) 155 | # - #utime(atime, mtime) 156 | # - #lutime(atime, mtime) 157 | # - #basename(*args) 158 | # - #dirname 159 | # - #extname 160 | # - #expand_path(*args) 161 | # - #split 162 | # 163 | # === Directory methods 164 | # 165 | # These methods are a facade for Dir: 166 | # - Pathname.glob(*args) 167 | # - Pathname.getwd / Pathname.pwd 168 | # - #rmdir 169 | # - #entries 170 | # - #each_entry(&block) 171 | # - #mkdir(*args) 172 | # - #opendir(*args) 173 | # 174 | # === Utilities 175 | # 176 | # These methods are a mixture of Find, FileUtils, and others: 177 | # - #find(&block) 178 | # - #mkpath 179 | # - #rmtree 180 | # - #unlink / #delete 181 | # 182 | # 183 | # == Method documentation 184 | # 185 | # As the above section shows, most of the methods in Pathname are facades. The 186 | # documentation for these methods generally just says, for instance, "See 187 | # FileTest.writable?", as you should be familiar with the original method 188 | # anyway, and its documentation (e.g. through +ri+) will contain more 189 | # information. In some cases, a brief description will follow. 190 | # 191 | class Pathname 192 | 193 | # The version string. 194 | VERSION = "0.4.0" 195 | 196 | # :stopdoc: 197 | 198 | if File::FNM_SYSCASE.nonzero? 199 | # Avoid #zero? here because #casecmp can return nil. 200 | private def same_paths?(a, b) a.casecmp(b) == 0 end 201 | else 202 | private def same_paths?(a, b) a == b end 203 | end 204 | 205 | attr_reader :path 206 | protected :path 207 | 208 | # :startdoc: 209 | 210 | # 211 | # Create a Pathname object from the given String (or String-like object). 212 | # If +path+ contains a NUL character (\0), an ArgumentError is raised. 213 | # 214 | def initialize(path) 215 | @path = File.path(path).dup 216 | rescue TypeError => e 217 | raise e.class, "Pathname.new requires a String, #to_path or #to_str", cause: nil 218 | end 219 | 220 | # 221 | # Freze self. 222 | # 223 | def freeze 224 | super 225 | @path.freeze 226 | self 227 | end 228 | 229 | # 230 | # Compare this pathname with +other+. The comparison is string-based. 231 | # Be aware that two different paths (foo.txt and ./foo.txt) 232 | # can refer to the same file. 233 | # 234 | def ==(other) 235 | return false unless Pathname === other 236 | other.path == @path 237 | end 238 | alias === == 239 | alias eql? == 240 | 241 | unless method_defined?(:<=>, false) 242 | # Provides for comparing pathnames, case-sensitively. 243 | def <=>(other) 244 | return nil unless Pathname === other 245 | @path.tr('/', "\0") <=> other.path.tr('/', "\0") 246 | end 247 | end 248 | 249 | def hash # :nodoc: 250 | @path.hash 251 | end 252 | 253 | # Return the path as a String. 254 | def to_s 255 | @path.dup 256 | end 257 | 258 | # to_path is implemented so Pathname objects are usable with File.open, etc. 259 | alias to_path to_s 260 | 261 | def inspect # :nodoc: 262 | "#<#{self.class}:#{@path}>" 263 | end 264 | 265 | unless method_defined?(:sub, false) 266 | # Return a pathname which is substituted by String#sub. 267 | def sub(pattern, *args, **kwargs, &block) 268 | if block 269 | path = @path.sub(pattern, *args, **kwargs) {|*sub_args| 270 | begin 271 | old = Thread.current[:pathname_sub_matchdata] 272 | Thread.current[:pathname_sub_matchdata] = $~ 273 | eval("$~ = Thread.current[:pathname_sub_matchdata]", block.binding) 274 | ensure 275 | Thread.current[:pathname_sub_matchdata] = old 276 | end 277 | yield(*sub_args) 278 | } 279 | else 280 | path = @path.sub(pattern, *args, **kwargs) 281 | end 282 | self.class.new(path) 283 | end 284 | end 285 | 286 | # Return a pathname with +repl+ added as a suffix to the basename. 287 | # 288 | # If self has no extension part, +repl+ is appended. 289 | # 290 | # Pathname.new('/usr/bin/shutdown').sub_ext('.rb') 291 | # #=> # 292 | def sub_ext(repl) 293 | ext = File.extname(@path) 294 | 295 | # File.extname("foo.bar:stream") returns ".bar" on NTFS and not ".bar:stream" 296 | # (see ruby_enc_find_extname()). 297 | # The behavior of Pathname#sub_ext is to replace everything 298 | # from the start of the extname until the end of the path with repl. 299 | unless @path.end_with?(ext) 300 | ext = @path[@path.rindex(ext)..] 301 | end 302 | 303 | self.class.new(@path.chomp(ext) + repl) 304 | end 305 | 306 | if File::ALT_SEPARATOR 307 | # Separator list string. 308 | SEPARATOR_LIST = Regexp.quote "#{File::ALT_SEPARATOR}#{File::SEPARATOR}" 309 | # Regexp that matches a separator. 310 | SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/ 311 | else 312 | SEPARATOR_LIST = Regexp.quote File::SEPARATOR 313 | SEPARATOR_PAT = /#{SEPARATOR_LIST}/ 314 | end 315 | SEPARATOR_LIST.freeze 316 | SEPARATOR_PAT.freeze 317 | private_constant :SEPARATOR_LIST, :SEPARATOR_LIST 318 | 319 | if File.dirname('A:') == 'A:.' # DOSish drive letter 320 | # Regexp that matches an absolute path. 321 | ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/ 322 | else 323 | ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/ 324 | end 325 | ABSOLUTE_PATH.freeze 326 | private_constant :ABSOLUTE_PATH 327 | 328 | # :startdoc: 329 | 330 | # Creates a full path, including any intermediate directories that don't yet 331 | # exist. 332 | # 333 | # See FileUtils.mkpath and FileUtils.mkdir_p 334 | def mkpath(mode: nil) 335 | path = @path == '/' ? @path : @path.chomp('/') 336 | 337 | stack = [] 338 | until File.directory?(path) || File.dirname(path) == path 339 | stack.push path 340 | path = File.dirname(path) 341 | end 342 | 343 | stack.reverse_each do |dir| 344 | dir = dir == '/' ? dir : dir.chomp('/') 345 | if mode 346 | Dir.mkdir dir, mode 347 | File.chmod mode, dir 348 | else 349 | Dir.mkdir dir 350 | end 351 | rescue SystemCallError 352 | raise unless File.directory?(dir) 353 | end 354 | 355 | self 356 | end 357 | 358 | # chop_basename(path) -> [pre-basename, basename] or nil 359 | def chop_basename(path) # :nodoc: 360 | base = File.basename(path) 361 | if /\A#{SEPARATOR_PAT}?\z/o.match?(base) 362 | return nil 363 | else 364 | return path[0, path.rindex(base)], base 365 | end 366 | end 367 | private :chop_basename 368 | 369 | # split_names(path) -> prefix, [name, ...] 370 | def split_names(path) # :nodoc: 371 | names = [] 372 | while r = chop_basename(path) 373 | path, basename = r 374 | names.unshift basename 375 | end 376 | return path, names 377 | end 378 | private :split_names 379 | 380 | def prepend_prefix(prefix, relpath) # :nodoc: 381 | if relpath.empty? 382 | File.dirname(prefix) 383 | elsif SEPARATOR_PAT.match?(prefix) 384 | prefix = File.dirname(prefix) 385 | prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a' 386 | prefix + relpath 387 | else 388 | prefix + relpath 389 | end 390 | end 391 | private :prepend_prefix 392 | 393 | # Returns clean pathname of +self+ with consecutive slashes and useless dots 394 | # removed. The filesystem is not accessed. 395 | # 396 | # If +consider_symlink+ is +true+, then a more conservative algorithm is used 397 | # to avoid breaking symbolic linkages. This may retain more +..+ 398 | # entries than absolutely necessary, but without accessing the filesystem, 399 | # this can't be avoided. 400 | # 401 | # See Pathname#realpath. 402 | # 403 | def cleanpath(consider_symlink=false) 404 | if consider_symlink 405 | cleanpath_conservative 406 | else 407 | cleanpath_aggressive 408 | end 409 | end 410 | 411 | # 412 | # Clean the path simply by resolving and removing excess +.+ and +..+ entries. 413 | # Nothing more, nothing less. 414 | # 415 | def cleanpath_aggressive # :nodoc: 416 | path = @path 417 | names = [] 418 | pre = path 419 | while r = chop_basename(pre) 420 | pre, base = r 421 | case base 422 | when '.' 423 | when '..' 424 | names.unshift base 425 | else 426 | if names[0] == '..' 427 | names.shift 428 | else 429 | names.unshift base 430 | end 431 | end 432 | end 433 | pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR 434 | if SEPARATOR_PAT.match?(File.basename(pre)) 435 | names.shift while names[0] == '..' 436 | end 437 | self.class.new(prepend_prefix(pre, File.join(*names))) 438 | end 439 | private :cleanpath_aggressive 440 | 441 | # has_trailing_separator?(path) -> bool 442 | def has_trailing_separator?(path) # :nodoc: 443 | if r = chop_basename(path) 444 | pre, basename = r 445 | pre.length + basename.length < path.length 446 | else 447 | false 448 | end 449 | end 450 | private :has_trailing_separator? 451 | 452 | # add_trailing_separator(path) -> path 453 | def add_trailing_separator(path) # :nodoc: 454 | if File.basename(path + 'a') == 'a' 455 | path 456 | else 457 | File.join(path, "") # xxx: Is File.join is appropriate to add separator? 458 | end 459 | end 460 | private :add_trailing_separator 461 | 462 | def del_trailing_separator(path) # :nodoc: 463 | if r = chop_basename(path) 464 | pre, basename = r 465 | pre + basename 466 | elsif /#{SEPARATOR_PAT}+\z/o =~ path 467 | $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o] 468 | else 469 | path 470 | end 471 | end 472 | private :del_trailing_separator 473 | 474 | def cleanpath_conservative # :nodoc: 475 | path = @path 476 | names = [] 477 | pre = path 478 | while r = chop_basename(pre) 479 | pre, base = r 480 | names.unshift base if base != '.' 481 | end 482 | pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR 483 | if SEPARATOR_PAT.match?(File.basename(pre)) 484 | names.shift while names[0] == '..' 485 | end 486 | if names.empty? 487 | self.class.new(File.dirname(pre)) 488 | else 489 | if names.last != '..' && File.basename(path) == '.' 490 | names << '.' 491 | end 492 | result = prepend_prefix(pre, File.join(*names)) 493 | if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path) 494 | self.class.new(add_trailing_separator(result)) 495 | else 496 | self.class.new(result) 497 | end 498 | end 499 | end 500 | private :cleanpath_conservative 501 | 502 | # Returns the parent directory. 503 | # 504 | # This is same as self + '..'. 505 | def parent 506 | self + '..' 507 | end 508 | 509 | # Returns +true+ if +self+ points to a mountpoint. 510 | def mountpoint? 511 | begin 512 | stat1 = self.lstat 513 | stat2 = self.parent.lstat 514 | stat1.dev != stat2.dev || stat1.ino == stat2.ino 515 | rescue Errno::ENOENT 516 | false 517 | end 518 | end 519 | 520 | # 521 | # Predicate method for root directories. Returns +true+ if the 522 | # pathname consists of consecutive slashes. 523 | # 524 | # It doesn't access the filesystem. So it may return +false+ for some 525 | # pathnames which points to roots such as /usr/... 526 | # 527 | def root? 528 | chop_basename(@path) == nil && SEPARATOR_PAT.match?(@path) 529 | end 530 | 531 | # Predicate method for testing whether a path is absolute. 532 | # 533 | # It returns +true+ if the pathname begins with a slash. 534 | # 535 | # p = Pathname.new('/im/sure') 536 | # p.absolute? 537 | # #=> true 538 | # 539 | # p = Pathname.new('not/so/sure') 540 | # p.absolute? 541 | # #=> false 542 | def absolute? 543 | ABSOLUTE_PATH.match? @path 544 | end 545 | 546 | # The opposite of Pathname#absolute? 547 | # 548 | # It returns +false+ if the pathname begins with a slash. 549 | # 550 | # p = Pathname.new('/im/sure') 551 | # p.relative? 552 | # #=> false 553 | # 554 | # p = Pathname.new('not/so/sure') 555 | # p.relative? 556 | # #=> true 557 | def relative? 558 | !absolute? 559 | end 560 | 561 | # 562 | # Iterates over each component of the path. 563 | # 564 | # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... } 565 | # # yields "usr", "bin", and "ruby". 566 | # 567 | # Returns an Enumerator if no block was given. 568 | # 569 | # enum = Pathname.new("/usr/bin/ruby").each_filename 570 | # # ... do stuff ... 571 | # enum.each { |e| ... } 572 | # # yields "usr", "bin", and "ruby". 573 | # 574 | def each_filename # :yield: filename 575 | return to_enum(__method__) unless block_given? 576 | _, names = split_names(@path) 577 | names.each {|filename| yield filename } 578 | nil 579 | end 580 | 581 | # Iterates over and yields a new Pathname object 582 | # for each element in the given path in descending order. 583 | # 584 | # Pathname.new('/path/to/some/file.rb').descend {|v| p v} 585 | # # 586 | # # 587 | # # 588 | # # 589 | # # 590 | # 591 | # Pathname.new('path/to/some/file.rb').descend {|v| p v} 592 | # # 593 | # # 594 | # # 595 | # # 596 | # 597 | # Returns an Enumerator if no block was given. 598 | # 599 | # enum = Pathname.new("/usr/bin/ruby").descend 600 | # # ... do stuff ... 601 | # enum.each { |e| ... } 602 | # # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby. 603 | # 604 | # It doesn't access the filesystem. 605 | # 606 | def descend 607 | return to_enum(__method__) unless block_given? 608 | vs = [] 609 | ascend {|v| vs << v } 610 | vs.reverse_each {|v| yield v } 611 | nil 612 | end 613 | 614 | # Iterates over and yields a new Pathname object 615 | # for each element in the given path in ascending order. 616 | # 617 | # Pathname.new('/path/to/some/file.rb').ascend {|v| p v} 618 | # # 619 | # # 620 | # # 621 | # # 622 | # # 623 | # 624 | # Pathname.new('path/to/some/file.rb').ascend {|v| p v} 625 | # # 626 | # # 627 | # # 628 | # # 629 | # 630 | # Returns an Enumerator if no block was given. 631 | # 632 | # enum = Pathname.new("/usr/bin/ruby").ascend 633 | # # ... do stuff ... 634 | # enum.each { |e| ... } 635 | # # yields Pathnames /usr/bin/ruby, /usr/bin, /usr, and /. 636 | # 637 | # It doesn't access the filesystem. 638 | # 639 | def ascend 640 | return to_enum(__method__) unless block_given? 641 | path = @path 642 | yield self 643 | while r = chop_basename(path) 644 | path, = r 645 | break if path.empty? 646 | yield self.class.new(del_trailing_separator(path)) 647 | end 648 | end 649 | 650 | # 651 | # Appends a pathname fragment to +self+ to produce a new Pathname object. 652 | # Since +other+ is considered as a path relative to +self+, if +other+ is 653 | # an absolute path, the new Pathname object is created from just +other+. 654 | # 655 | # p1 = Pathname.new("/usr") # Pathname:/usr 656 | # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby 657 | # p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd 658 | # 659 | # # / is aliased to +. 660 | # p4 = p1 / "bin/ruby" # Pathname:/usr/bin/ruby 661 | # p5 = p1 / "/etc/passwd" # Pathname:/etc/passwd 662 | # 663 | # This method doesn't access the file system; it is pure string manipulation. 664 | # 665 | def +(other) 666 | other = Pathname.new(other) unless Pathname === other 667 | Pathname.new(plus(@path, other.path)) 668 | end 669 | alias / + 670 | 671 | def plus(path1, path2) # -> path # :nodoc: 672 | prefix2 = path2 673 | index_list2 = [] 674 | basename_list2 = [] 675 | while r2 = chop_basename(prefix2) 676 | prefix2, basename2 = r2 677 | index_list2.unshift prefix2.length 678 | basename_list2.unshift basename2 679 | end 680 | return path2 if prefix2 != '' 681 | prefix1 = path1 682 | while true 683 | while !basename_list2.empty? && basename_list2.first == '.' 684 | index_list2.shift 685 | basename_list2.shift 686 | end 687 | break unless r1 = chop_basename(prefix1) 688 | prefix1, basename1 = r1 689 | next if basename1 == '.' 690 | if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..' 691 | prefix1 = prefix1 + basename1 692 | break 693 | end 694 | index_list2.shift 695 | basename_list2.shift 696 | end 697 | r1 = chop_basename(prefix1) 698 | if !r1 && (r1 = SEPARATOR_PAT.match?(File.basename(prefix1))) 699 | while !basename_list2.empty? && basename_list2.first == '..' 700 | index_list2.shift 701 | basename_list2.shift 702 | end 703 | end 704 | if !basename_list2.empty? 705 | suffix2 = path2[index_list2.first..-1] 706 | r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2 707 | else 708 | r1 ? prefix1 : File.dirname(prefix1) 709 | end 710 | end 711 | private :plus 712 | 713 | # 714 | # Joins the given pathnames onto +self+ to create a new Pathname object. 715 | # This is effectively the same as using Pathname#+ to append +self+ and 716 | # all arguments sequentially. 717 | # 718 | # path0 = Pathname.new("/usr") # Pathname:/usr 719 | # path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby 720 | # # is the same as 721 | # path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby 722 | # path0 == path1 723 | # #=> true 724 | # 725 | def join(*args) 726 | return self if args.empty? 727 | result = args.pop 728 | result = Pathname.new(result) unless Pathname === result 729 | return result if result.absolute? 730 | args.reverse_each {|arg| 731 | arg = Pathname.new(arg) unless Pathname === arg 732 | result = arg + result 733 | return result if result.absolute? 734 | } 735 | self + result 736 | end 737 | 738 | # 739 | # Returns the children of the directory (files and subdirectories, not 740 | # recursive) as an array of Pathname objects. 741 | # 742 | # By default, the returned pathnames will have enough information to access 743 | # the files. If you set +with_directory+ to +false+, then the returned 744 | # pathnames will contain the filename only. 745 | # 746 | # For example: 747 | # pn = Pathname("/usr/lib/ruby/1.8") 748 | # pn.children 749 | # # -> [ Pathname:/usr/lib/ruby/1.8/English.rb, 750 | # Pathname:/usr/lib/ruby/1.8/Env.rb, 751 | # Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ] 752 | # pn.children(false) 753 | # # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ] 754 | # 755 | # Note that the results never contain the entries +.+ and +..+ in 756 | # the directory because they are not children. 757 | # 758 | def children(with_directory=true) 759 | with_directory = false if @path == '.' 760 | result = [] 761 | Dir.foreach(@path) {|e| 762 | next if e == '.' || e == '..' 763 | if with_directory 764 | result << self.class.new(File.join(@path, e)) 765 | else 766 | result << self.class.new(e) 767 | end 768 | } 769 | result 770 | end 771 | 772 | # Iterates over the children of the directory 773 | # (files and subdirectories, not recursive). 774 | # 775 | # It yields Pathname object for each child. 776 | # 777 | # By default, the yielded pathnames will have enough information to access 778 | # the files. 779 | # 780 | # If you set +with_directory+ to +false+, then the returned pathnames will 781 | # contain the filename only. 782 | # 783 | # Pathname("/usr/local").each_child {|f| p f } 784 | # #=> # 785 | # # # 786 | # # # 787 | # # # 788 | # # # 789 | # # # 790 | # # # 791 | # # # 792 | # 793 | # Pathname("/usr/local").each_child(false) {|f| p f } 794 | # #=> # 795 | # # # 796 | # # # 797 | # # # 798 | # # # 799 | # # # 800 | # # # 801 | # # # 802 | # 803 | # Note that the results never contain the entries +.+ and +..+ in 804 | # the directory because they are not children. 805 | # 806 | # See Pathname#children 807 | # 808 | def each_child(with_directory=true, &b) 809 | children(with_directory).each(&b) 810 | end 811 | 812 | # 813 | # Returns a relative path from the given +base_directory+ to the receiver. 814 | # 815 | # If +self+ is absolute, then +base_directory+ must be absolute too. 816 | # 817 | # If +self+ is relative, then +base_directory+ must be relative too. 818 | # 819 | # This method doesn't access the filesystem. It assumes no symlinks. 820 | # 821 | # ArgumentError is raised when it cannot find a relative path. 822 | # 823 | # Note that this method does not handle situations where the case sensitivity 824 | # of the filesystem in use differs from the operating system default. 825 | # 826 | def relative_path_from(base_directory) 827 | base_directory = Pathname.new(base_directory) unless base_directory.is_a? Pathname 828 | dest_directory = self.cleanpath.path 829 | base_directory = base_directory.cleanpath.path 830 | dest_prefix = dest_directory 831 | dest_names = [] 832 | while r = chop_basename(dest_prefix) 833 | dest_prefix, basename = r 834 | dest_names.unshift basename if basename != '.' 835 | end 836 | base_prefix = base_directory 837 | base_names = [] 838 | while r = chop_basename(base_prefix) 839 | base_prefix, basename = r 840 | base_names.unshift basename if basename != '.' 841 | end 842 | unless same_paths?(dest_prefix, base_prefix) 843 | raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}" 844 | end 845 | while !dest_names.empty? && 846 | !base_names.empty? && 847 | same_paths?(dest_names.first, base_names.first) 848 | dest_names.shift 849 | base_names.shift 850 | end 851 | if base_names.include? '..' 852 | raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" 853 | end 854 | base_names.fill('..') 855 | relpath_names = base_names + dest_names 856 | if relpath_names.empty? 857 | Pathname.new('.') 858 | else 859 | Pathname.new(File.join(*relpath_names)) 860 | end 861 | end 862 | end 863 | 864 | class Pathname # * File * 865 | # 866 | # #each_line iterates over the line in the file. It yields a String object 867 | # for each line. 868 | # 869 | # This method has existed since 1.8.1. 870 | # 871 | def each_line(...) # :yield: line 872 | File.foreach(@path, ...) 873 | end 874 | 875 | # See File.read. Returns all data from the file, or the first +N+ bytes 876 | # if specified. 877 | def read(...) File.read(@path, ...) end 878 | 879 | # See File.binread. Returns all the bytes from the file, or the first +N+ 880 | # if specified. 881 | def binread(...) File.binread(@path, ...) end 882 | 883 | # See File.readlines. Returns all the lines from the file. 884 | def readlines(...) File.readlines(@path, ...) end 885 | 886 | # See File.sysopen. 887 | def sysopen(...) File.sysopen(@path, ...) end 888 | 889 | # Writes +contents+ to the file. See File.write. 890 | def write(...) File.write(@path, ...) end 891 | 892 | # Writes +contents+ to the file, opening it in binary mode. 893 | # 894 | # See File.binwrite. 895 | def binwrite(...) File.binwrite(@path, ...) end 896 | 897 | # See File.atime. Returns last access time. 898 | def atime() File.atime(@path) end 899 | 900 | # Returns the birth time for the file. 901 | # If the platform doesn't have birthtime, raises NotImplementedError. 902 | # 903 | # See File.birthtime. 904 | def birthtime() File.birthtime(@path) end 905 | 906 | # See File.ctime. Returns last (directory entry, not file) change time. 907 | def ctime() File.ctime(@path) end 908 | 909 | # See File.mtime. Returns last modification time. 910 | def mtime() File.mtime(@path) end 911 | 912 | # See File.chmod. Changes permissions. 913 | def chmod(mode) File.chmod(mode, @path) end 914 | 915 | # See File.lchmod. 916 | def lchmod(mode) File.lchmod(mode, @path) end 917 | 918 | # See File.chown. Change owner and group of file. 919 | def chown(owner, group) File.chown(owner, group, @path) end 920 | 921 | # See File.lchown. 922 | def lchown(owner, group) File.lchown(owner, group, @path) end 923 | 924 | # See File.fnmatch. Return +true+ if the receiver matches the given 925 | # pattern. 926 | def fnmatch(pattern, ...) File.fnmatch(pattern, @path, ...) end 927 | 928 | # See File.fnmatch? (same as #fnmatch). 929 | def fnmatch?(pattern, ...) File.fnmatch?(pattern, @path, ...) end 930 | 931 | # See File.ftype. Returns "type" of file ("file", "directory", 932 | # etc). 933 | def ftype() File.ftype(@path) end 934 | 935 | # See File.link. Creates a hard link. 936 | def make_link(old) File.link(old, @path) end 937 | 938 | # See File.open. Opens the file for reading or writing. 939 | def open(...) # :yield: file 940 | File.open(@path, ...) 941 | end 942 | 943 | # See File.readlink. Read symbolic link. 944 | def readlink() self.class.new(File.readlink(@path)) end 945 | 946 | # See File.rename. Rename the file. 947 | def rename(to) File.rename(@path, to) end 948 | 949 | # See File.stat. Returns a File::Stat object. 950 | def stat() File.stat(@path) end 951 | 952 | # See File.lstat. 953 | def lstat() File.lstat(@path) end 954 | 955 | # See File.symlink. Creates a symbolic link. 956 | def make_symlink(old) File.symlink(old, @path) end 957 | 958 | # See File.truncate. Truncate the file to +length+ bytes. 959 | def truncate(length) File.truncate(@path, length) end 960 | 961 | # See File.utime. Update the access and modification times. 962 | def utime(atime, mtime) File.utime(atime, mtime, @path) end 963 | 964 | # Update the access and modification times of the file. 965 | # 966 | # Same as Pathname#utime, but does not follow symbolic links. 967 | # 968 | # See File.lutime. 969 | def lutime(atime, mtime) File.lutime(atime, mtime, @path) end 970 | 971 | # See File.basename. Returns the last component of the path. 972 | def basename(...) self.class.new(File.basename(@path, ...)) end 973 | 974 | # See File.dirname. Returns all but the last component of the path. 975 | def dirname() self.class.new(File.dirname(@path)) end 976 | 977 | # See File.extname. Returns the file's extension. 978 | def extname() File.extname(@path) end 979 | 980 | # See File.expand_path. 981 | def expand_path(...) self.class.new(File.expand_path(@path, ...)) end 982 | 983 | # See File.split. Returns the #dirname and the #basename in an 984 | # Array. 985 | def split() 986 | array = File.split(@path) 987 | raise TypeError, 'wrong argument type nil (expected Array)' unless Array === array 988 | array.map {|f| self.class.new(f) } 989 | end 990 | 991 | # Returns the real (absolute) pathname for +self+ in the actual filesystem. 992 | # 993 | # Does not contain symlinks or useless dots, +..+ and +.+. 994 | # 995 | # All components of the pathname must exist when this method is called. 996 | def realpath(...) self.class.new(File.realpath(@path, ...)) end 997 | 998 | # Returns the real (absolute) pathname of +self+ in the actual filesystem. 999 | # 1000 | # Does not contain symlinks or useless dots, +..+ and +.+. 1001 | # 1002 | # The last component of the real pathname can be nonexistent. 1003 | def realdirpath(...) self.class.new(File.realdirpath(@path, ...)) end 1004 | end 1005 | 1006 | 1007 | class Pathname # * FileTest * 1008 | 1009 | # See FileTest.blockdev?. 1010 | def blockdev?() FileTest.blockdev?(@path) end 1011 | 1012 | # See FileTest.chardev?. 1013 | def chardev?() FileTest.chardev?(@path) end 1014 | 1015 | # Tests the file is empty. 1016 | # 1017 | # See Dir#empty? and FileTest.empty?. 1018 | def empty? 1019 | if FileTest.directory?(@path) 1020 | Dir.empty?(@path) 1021 | else 1022 | File.empty?(@path) 1023 | end 1024 | end 1025 | 1026 | # See FileTest.executable?. 1027 | def executable?() FileTest.executable?(@path) end 1028 | 1029 | # See FileTest.executable_real?. 1030 | def executable_real?() FileTest.executable_real?(@path) end 1031 | 1032 | # See FileTest.exist?. 1033 | def exist?() FileTest.exist?(@path) end 1034 | 1035 | # See FileTest.grpowned?. 1036 | def grpowned?() FileTest.grpowned?(@path) end 1037 | 1038 | # See FileTest.directory?. 1039 | def directory?() FileTest.directory?(@path) end 1040 | 1041 | # See FileTest.file?. 1042 | def file?() FileTest.file?(@path) end 1043 | 1044 | # See FileTest.pipe?. 1045 | def pipe?() FileTest.pipe?(@path) end 1046 | 1047 | # See FileTest.socket?. 1048 | def socket?() FileTest.socket?(@path) end 1049 | 1050 | # See FileTest.owned?. 1051 | def owned?() FileTest.owned?(@path) end 1052 | 1053 | # See FileTest.readable?. 1054 | def readable?() FileTest.readable?(@path) end 1055 | 1056 | # See FileTest.world_readable?. 1057 | def world_readable?() File.world_readable?(@path) end 1058 | 1059 | # See FileTest.readable_real?. 1060 | def readable_real?() FileTest.readable_real?(@path) end 1061 | 1062 | # See FileTest.setuid?. 1063 | def setuid?() FileTest.setuid?(@path) end 1064 | 1065 | # See FileTest.setgid?. 1066 | def setgid?() FileTest.setgid?(@path) end 1067 | 1068 | # See FileTest.size. 1069 | def size() FileTest.size(@path) end 1070 | 1071 | # See FileTest.size?. 1072 | def size?() FileTest.size?(@path) end 1073 | 1074 | # See FileTest.sticky?. 1075 | def sticky?() FileTest.sticky?(@path) end 1076 | 1077 | # See FileTest.symlink?. 1078 | def symlink?() FileTest.symlink?(@path) end 1079 | 1080 | # See FileTest.writable?. 1081 | def writable?() FileTest.writable?(@path) end 1082 | 1083 | # See FileTest.world_writable?. 1084 | def world_writable?() File.world_writable?(@path) end 1085 | 1086 | # See FileTest.writable_real?. 1087 | def writable_real?() FileTest.writable_real?(@path) end 1088 | 1089 | # See FileTest.zero?. 1090 | def zero?() FileTest.zero?(@path) end 1091 | end 1092 | 1093 | 1094 | class Pathname # * Dir * 1095 | # See Dir.glob. Returns or yields Pathname objects. 1096 | def Pathname.glob(*args, **kwargs) # :yield: pathname 1097 | if block_given? 1098 | Dir.glob(*args, **kwargs) {|f| yield self.new(f) } 1099 | else 1100 | Dir.glob(*args, **kwargs).map {|f| self.new(f) } 1101 | end 1102 | end 1103 | 1104 | # Returns or yields Pathname objects. 1105 | # 1106 | # Pathname("ruby-2.4.2").glob("R*.md") 1107 | # #=> [#, #] 1108 | # 1109 | # See Dir.glob. 1110 | # This method uses the +base+ keyword argument of Dir.glob. 1111 | def glob(*args, **kwargs) # :yield: pathname 1112 | if block_given? 1113 | Dir.glob(*args, **kwargs, base: @path) {|f| yield self + f } 1114 | else 1115 | Dir.glob(*args, **kwargs, base: @path).map {|f| self + f } 1116 | end 1117 | end 1118 | 1119 | # See Dir.getwd. Returns the current working directory as a Pathname. 1120 | def Pathname.getwd() self.new(Dir.getwd) end 1121 | class << self 1122 | alias pwd getwd 1123 | end 1124 | 1125 | # Return the entries (files and subdirectories) in the directory, each as a 1126 | # Pathname object. 1127 | def entries() Dir.entries(@path).map {|f| self.class.new(f) } end 1128 | 1129 | # Iterates over the entries (files and subdirectories) in the directory. It 1130 | # yields a Pathname object for each entry. 1131 | # 1132 | # This method has existed since 1.8.1. 1133 | def each_entry(&block) # :yield: pathname 1134 | return to_enum(__method__) unless block_given? 1135 | Dir.foreach(@path) {|f| yield self.class.new(f) } 1136 | end 1137 | 1138 | # See Dir.mkdir. Create the referenced directory. 1139 | def mkdir(...) Dir.mkdir(@path, ...) end 1140 | 1141 | # See Dir.rmdir. Remove the referenced directory. 1142 | def rmdir() Dir.rmdir(@path) end 1143 | 1144 | # See Dir.open. 1145 | def opendir(&block) # :yield: dir 1146 | Dir.open(@path, &block) 1147 | end 1148 | end 1149 | 1150 | class Pathname # * mixed * 1151 | # Removes a file or directory, using File.unlink or 1152 | # Dir.unlink as necessary. 1153 | def unlink() 1154 | Dir.unlink @path 1155 | rescue Errno::ENOTDIR 1156 | File.unlink @path 1157 | end 1158 | alias delete unlink 1159 | end 1160 | 1161 | class Pathname 1162 | undef =~ if Kernel.method_defined?(:=~) 1163 | end 1164 | 1165 | module Kernel 1166 | # Creates a Pathname object. 1167 | def Pathname(path) # :doc: 1168 | return path if Pathname === path 1169 | Pathname.new(path) 1170 | end 1171 | module_function :Pathname 1172 | end 1173 | -------------------------------------------------------------------------------- /test/pathname/test_pathname.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test/unit' 4 | require 'pathname' 5 | 6 | require 'fileutils' 7 | require 'tmpdir' 8 | 9 | 10 | class TestPathname < Test::Unit::TestCase 11 | def self.define_assertion(name, linenum, &block) 12 | name = "test_#{name}_#{linenum}" 13 | define_method(name, &block) 14 | end 15 | 16 | def self.get_linenum 17 | if loc = caller_locations(2, 1) 18 | loc[0].lineno 19 | else 20 | nil 21 | end 22 | end 23 | 24 | def self.defassert(name, result, *args) 25 | define_assertion(name, get_linenum) { 26 | mesg = "#{name}(#{args.map {|a| a.inspect }.join(', ')})" 27 | assert_nothing_raised(mesg) { 28 | assert_equal(result, self.send(name, *args), mesg) 29 | } 30 | } 31 | end 32 | 33 | def self.defassert_raise(name, exc, *args) 34 | define_assertion(name, get_linenum) { 35 | message = "#{name}(#{args.map {|a| a.inspect }.join(', ')})" 36 | assert_raise(exc, message) { self.send(name, *args) } 37 | } 38 | end 39 | 40 | DOSISH = File::ALT_SEPARATOR != nil 41 | DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:." 42 | DOSISH_UNC = File.dirname("//") == "//" 43 | 44 | def cleanpath_aggressive(path) 45 | Pathname.new(path).cleanpath.to_s 46 | end 47 | 48 | defassert(:cleanpath_aggressive, '/', '/') 49 | defassert(:cleanpath_aggressive, '.', '') 50 | defassert(:cleanpath_aggressive, '.', '.') 51 | defassert(:cleanpath_aggressive, '..', '..') 52 | defassert(:cleanpath_aggressive, 'a', 'a') 53 | defassert(:cleanpath_aggressive, '/', '/.') 54 | defassert(:cleanpath_aggressive, '/', '/..') 55 | defassert(:cleanpath_aggressive, '/a', '/a') 56 | defassert(:cleanpath_aggressive, '.', './') 57 | defassert(:cleanpath_aggressive, '..', '../') 58 | defassert(:cleanpath_aggressive, 'a', 'a/') 59 | defassert(:cleanpath_aggressive, 'a/b', 'a//b') 60 | defassert(:cleanpath_aggressive, 'a', 'a/.') 61 | defassert(:cleanpath_aggressive, 'a', 'a/./') 62 | defassert(:cleanpath_aggressive, '.', 'a/..') 63 | defassert(:cleanpath_aggressive, '.', 'a/../') 64 | defassert(:cleanpath_aggressive, '/a', '/a/.') 65 | defassert(:cleanpath_aggressive, '..', './..') 66 | defassert(:cleanpath_aggressive, '..', '../.') 67 | defassert(:cleanpath_aggressive, '..', './../') 68 | defassert(:cleanpath_aggressive, '..', '.././') 69 | defassert(:cleanpath_aggressive, '/', '/./..') 70 | defassert(:cleanpath_aggressive, '/', '/../.') 71 | defassert(:cleanpath_aggressive, '/', '/./../') 72 | defassert(:cleanpath_aggressive, '/', '/.././') 73 | defassert(:cleanpath_aggressive, 'a/b/c', 'a/b/c') 74 | defassert(:cleanpath_aggressive, 'b/c', './b/c') 75 | defassert(:cleanpath_aggressive, 'a/c', 'a/./c') 76 | defassert(:cleanpath_aggressive, 'a/b', 'a/b/.') 77 | defassert(:cleanpath_aggressive, '.', 'a/../.') 78 | defassert(:cleanpath_aggressive, '/a', '/../.././../a') 79 | defassert(:cleanpath_aggressive, '../../d', 'a/b/../../../../c/../d') 80 | 81 | if DOSISH_UNC 82 | defassert(:cleanpath_aggressive, '//a/b/c', '//a/b/c/') 83 | else 84 | defassert(:cleanpath_aggressive, '/', '///') 85 | defassert(:cleanpath_aggressive, '/a', '///a') 86 | defassert(:cleanpath_aggressive, '/', '///..') 87 | defassert(:cleanpath_aggressive, '/', '///.') 88 | defassert(:cleanpath_aggressive, '/', '///a/../..') 89 | end 90 | 91 | if DOSISH 92 | defassert(:cleanpath_aggressive, 'c:/foo/bar', 'c:\\foo\\bar') 93 | end 94 | 95 | def cleanpath_conservative(path) 96 | Pathname.new(path).cleanpath(true).to_s 97 | end 98 | 99 | defassert(:cleanpath_conservative, '/', '/') 100 | defassert(:cleanpath_conservative, '.', '') 101 | defassert(:cleanpath_conservative, '.', '.') 102 | defassert(:cleanpath_conservative, '..', '..') 103 | defassert(:cleanpath_conservative, 'a', 'a') 104 | defassert(:cleanpath_conservative, '/', '/.') 105 | defassert(:cleanpath_conservative, '/', '/..') 106 | defassert(:cleanpath_conservative, '/a', '/a') 107 | defassert(:cleanpath_conservative, '.', './') 108 | defassert(:cleanpath_conservative, '..', '../') 109 | defassert(:cleanpath_conservative, 'a/', 'a/') 110 | defassert(:cleanpath_conservative, 'a/b', 'a//b') 111 | defassert(:cleanpath_conservative, 'a/.', 'a/.') 112 | defassert(:cleanpath_conservative, 'a/.', 'a/./') 113 | defassert(:cleanpath_conservative, 'a/..', 'a/../') 114 | defassert(:cleanpath_conservative, '/a/.', '/a/.') 115 | defassert(:cleanpath_conservative, '..', './..') 116 | defassert(:cleanpath_conservative, '..', '../.') 117 | defassert(:cleanpath_conservative, '..', './../') 118 | defassert(:cleanpath_conservative, '..', '.././') 119 | defassert(:cleanpath_conservative, '/', '/./..') 120 | defassert(:cleanpath_conservative, '/', '/../.') 121 | defassert(:cleanpath_conservative, '/', '/./../') 122 | defassert(:cleanpath_conservative, '/', '/.././') 123 | defassert(:cleanpath_conservative, 'a/b/c', 'a/b/c') 124 | defassert(:cleanpath_conservative, 'b/c', './b/c') 125 | defassert(:cleanpath_conservative, 'a/c', 'a/./c') 126 | defassert(:cleanpath_conservative, 'a/b/.', 'a/b/.') 127 | defassert(:cleanpath_conservative, 'a/..', 'a/../.') 128 | defassert(:cleanpath_conservative, '/a', '/../.././../a') 129 | defassert(:cleanpath_conservative, 'a/b/../../../../c/../d', 'a/b/../../../../c/../d') 130 | 131 | if DOSISH 132 | defassert(:cleanpath_conservative, 'c:/foo/bar', 'c:\\foo\\bar') 133 | end 134 | 135 | if DOSISH_UNC 136 | defassert(:cleanpath_conservative, '//', '//') 137 | else 138 | defassert(:cleanpath_conservative, '/', '//') 139 | end 140 | 141 | # has_trailing_separator?(path) -> bool 142 | def has_trailing_separator?(path) 143 | Pathname.allocate.__send__(:has_trailing_separator?, path) 144 | end 145 | 146 | defassert(:has_trailing_separator?, false, "/") 147 | defassert(:has_trailing_separator?, false, "///") 148 | defassert(:has_trailing_separator?, false, "a") 149 | defassert(:has_trailing_separator?, true, "a/") 150 | 151 | def add_trailing_separator(path) 152 | Pathname.allocate.__send__(:add_trailing_separator, path) 153 | end 154 | 155 | def del_trailing_separator(path) 156 | Pathname.allocate.__send__(:del_trailing_separator, path) 157 | end 158 | 159 | defassert(:del_trailing_separator, "/", "/") 160 | defassert(:del_trailing_separator, "/a", "/a") 161 | defassert(:del_trailing_separator, "/a", "/a/") 162 | defassert(:del_trailing_separator, "/a", "/a//") 163 | defassert(:del_trailing_separator, ".", ".") 164 | defassert(:del_trailing_separator, ".", "./") 165 | defassert(:del_trailing_separator, ".", ".//") 166 | 167 | if DOSISH_DRIVE_LETTER 168 | defassert(:del_trailing_separator, "A:", "A:") 169 | defassert(:del_trailing_separator, "A:/", "A:/") 170 | defassert(:del_trailing_separator, "A:/", "A://") 171 | defassert(:del_trailing_separator, "A:.", "A:.") 172 | defassert(:del_trailing_separator, "A:.", "A:./") 173 | defassert(:del_trailing_separator, "A:.", "A:.//") 174 | end 175 | 176 | if DOSISH_UNC 177 | defassert(:del_trailing_separator, "//", "//") 178 | defassert(:del_trailing_separator, "//a", "//a") 179 | defassert(:del_trailing_separator, "//a", "//a/") 180 | defassert(:del_trailing_separator, "//a", "//a//") 181 | defassert(:del_trailing_separator, "//a/b", "//a/b") 182 | defassert(:del_trailing_separator, "//a/b", "//a/b/") 183 | defassert(:del_trailing_separator, "//a/b", "//a/b//") 184 | defassert(:del_trailing_separator, "//a/b/c", "//a/b/c") 185 | defassert(:del_trailing_separator, "//a/b/c", "//a/b/c/") 186 | defassert(:del_trailing_separator, "//a/b/c", "//a/b/c//") 187 | else 188 | defassert(:del_trailing_separator, "/", "///") 189 | defassert(:del_trailing_separator, "///a", "///a/") 190 | end 191 | 192 | if DOSISH 193 | defassert(:del_trailing_separator, "a", "a\\") 194 | defassert(:del_trailing_separator, "\225\\".dup.force_encoding("cp932"), "\225\\\\".dup.force_encoding("cp932")) 195 | defassert(:del_trailing_separator, "\225".dup.force_encoding("cp437"), "\225\\\\".dup.force_encoding("cp437")) 196 | end 197 | 198 | def test_plus 199 | assert_kind_of(Pathname, Pathname("a") + Pathname("b")) 200 | end 201 | 202 | def plus(path1, path2) # -> path 203 | (Pathname.new(path1) + Pathname.new(path2)).to_s 204 | end 205 | 206 | defassert(:plus, '/', '/', '/') 207 | defassert(:plus, 'a/b', 'a', 'b') 208 | defassert(:plus, 'a', 'a', '.') 209 | defassert(:plus, 'b', '.', 'b') 210 | defassert(:plus, '.', '.', '.') 211 | defassert(:plus, '/b', 'a', '/b') 212 | 213 | defassert(:plus, '/', '/', '..') 214 | defassert(:plus, '.', 'a', '..') 215 | defassert(:plus, 'a', 'a/b', '..') 216 | defassert(:plus, '../..', '..', '..') 217 | defassert(:plus, '/c', '/', '../c') 218 | defassert(:plus, 'c', 'a', '../c') 219 | defassert(:plus, 'a/c', 'a/b', '../c') 220 | defassert(:plus, '../../c', '..', '../c') 221 | 222 | defassert(:plus, 'a//b/d//e', 'a//b/c', '../d//e') 223 | 224 | defassert(:plus, '//foo/var/bar', '//foo/var', 'bar') 225 | 226 | def test_slash 227 | assert_kind_of(Pathname, Pathname("a") / Pathname("b")) 228 | end 229 | 230 | def test_parent 231 | assert_equal(Pathname("."), Pathname("a").parent) 232 | end 233 | 234 | def parent(path) # -> path 235 | Pathname.new(path).parent.to_s 236 | end 237 | 238 | defassert(:parent, '/', '/') 239 | defassert(:parent, '/', '/a') 240 | defassert(:parent, '/a', '/a/b') 241 | defassert(:parent, '/a/b', '/a/b/c') 242 | defassert(:parent, '.', 'a') 243 | defassert(:parent, 'a', 'a/b') 244 | defassert(:parent, 'a/b', 'a/b/c') 245 | defassert(:parent, '..', '.') 246 | defassert(:parent, '../..', '..') 247 | 248 | def test_join 249 | r = Pathname("a").join(Pathname("b"), Pathname("c")) 250 | assert_equal(Pathname("a/b/c"), r) 251 | r = Pathname("/a").join(Pathname("b"), Pathname("c")) 252 | assert_equal(Pathname("/a/b/c"), r) 253 | r = Pathname("/a").join(Pathname("/b"), Pathname("c")) 254 | assert_equal(Pathname("/b/c"), r) 255 | r = Pathname("/a").join(Pathname("/b"), Pathname("/c")) 256 | assert_equal(Pathname("/c"), r) 257 | r = Pathname("/a").join("/b", "/c") 258 | assert_equal(Pathname("/c"), r) 259 | r = Pathname("/foo/var").join() 260 | assert_equal(Pathname("/foo/var"), r) 261 | end 262 | 263 | def test_absolute 264 | assert_equal(true, Pathname("/").absolute?) 265 | assert_equal(false, Pathname("a").absolute?) 266 | end 267 | 268 | def relative?(path) 269 | Pathname.new(path).relative? 270 | end 271 | 272 | defassert(:relative?, true, '') 273 | defassert(:relative?, false, '/') 274 | defassert(:relative?, false, '/a') 275 | defassert(:relative?, false, '/..') 276 | defassert(:relative?, true, 'a') 277 | defassert(:relative?, true, 'a/b') 278 | 279 | defassert(:relative?, !DOSISH_DRIVE_LETTER, 'A:.') 280 | defassert(:relative?, !DOSISH_DRIVE_LETTER, 'A:') 281 | defassert(:relative?, !DOSISH_DRIVE_LETTER, 'A:/') 282 | defassert(:relative?, !DOSISH_DRIVE_LETTER, 'A:/a') 283 | 284 | if File.dirname('//') == '//' 285 | defassert(:relative?, false, '//') 286 | defassert(:relative?, false, '//a') 287 | defassert(:relative?, false, '//a/') 288 | defassert(:relative?, false, '//a/b') 289 | defassert(:relative?, false, '//a/b/') 290 | defassert(:relative?, false, '//a/b/c') 291 | end 292 | 293 | def relative_path_from(dest_directory, base_directory) 294 | Pathname.new(dest_directory).relative_path_from(base_directory).to_s 295 | end 296 | 297 | defassert(:relative_path_from, "../a", Pathname.new("a"), "b") 298 | defassert(:relative_path_from, "../a", "a", "b") 299 | defassert(:relative_path_from, "../a", "a", "b/") 300 | defassert(:relative_path_from, "../a", "a/", "b") 301 | defassert(:relative_path_from, "../a", "a/", "b/") 302 | defassert(:relative_path_from, "../a", "/a", "/b") 303 | defassert(:relative_path_from, "../a", "/a", "/b/") 304 | defassert(:relative_path_from, "../a", "/a/", "/b") 305 | defassert(:relative_path_from, "../a", "/a/", "/b/") 306 | 307 | defassert(:relative_path_from, "../b", "a/b", "a/c") 308 | defassert(:relative_path_from, "../a", "../a", "../b") 309 | 310 | defassert(:relative_path_from, "a", "a", ".") 311 | defassert(:relative_path_from, "..", ".", "a") 312 | 313 | defassert(:relative_path_from, ".", ".", ".") 314 | defassert(:relative_path_from, ".", "..", "..") 315 | defassert(:relative_path_from, "..", "..", ".") 316 | 317 | defassert(:relative_path_from, "c/d", "/a/b/c/d", "/a/b") 318 | defassert(:relative_path_from, "../..", "/a/b", "/a/b/c/d") 319 | defassert(:relative_path_from, "../../../../e", "/e", "/a/b/c/d") 320 | defassert(:relative_path_from, "../b/c", "a/b/c", "a/d") 321 | 322 | defassert(:relative_path_from, "../a", "/../a", "/b") 323 | defassert(:relative_path_from, "../../a", "../a", "b") 324 | defassert(:relative_path_from, ".", "/a/../../b", "/b") 325 | defassert(:relative_path_from, "..", "a/..", "a") 326 | defassert(:relative_path_from, ".", "a/../b", "b") 327 | 328 | defassert(:relative_path_from, "a", "a", "b/..") 329 | defassert(:relative_path_from, "b/c", "b/c", "b/..") 330 | 331 | defassert_raise(:relative_path_from, ArgumentError, "/", ".") 332 | defassert_raise(:relative_path_from, ArgumentError, ".", "/") 333 | defassert_raise(:relative_path_from, ArgumentError, "a", "..") 334 | defassert_raise(:relative_path_from, ArgumentError, ".", "..") 335 | 336 | def with_tmpchdir(base=nil) 337 | Dir.mktmpdir(base) {|d| 338 | d = Pathname.new(d).realpath.to_s 339 | Dir.chdir(d) { 340 | yield d 341 | } 342 | } 343 | end 344 | 345 | def has_symlink? 346 | begin 347 | File.symlink("", "") 348 | rescue NotImplementedError 349 | return false 350 | rescue Errno::ENOENT 351 | return true 352 | rescue Errno::EACCES 353 | return false 354 | end 355 | return true 356 | end 357 | 358 | def has_hardlink? 359 | begin 360 | with_tmpchdir("rubytest-pathname") {|dir| 361 | File.write("dummy", "dummy") 362 | File.link("dummy", "hardlink") 363 | } 364 | rescue NotImplementedError 365 | return false 366 | rescue Errno::EACCES 367 | return false 368 | end 369 | return true 370 | end 371 | 372 | def realpath(path, basedir=nil) 373 | Pathname.new(path).realpath(*basedir).to_s 374 | end 375 | 376 | def test_realpath 377 | omit "not working yet" if RUBY_ENGINE == "jruby" 378 | return if !has_symlink? 379 | with_tmpchdir('rubytest-pathname') {|dir| 380 | assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") } 381 | File.symlink("not-exist-target", "#{dir}/not-exist") 382 | assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") } 383 | 384 | File.symlink("loop", "#{dir}/loop") 385 | assert_raise(Errno::ELOOP) { realpath("#{dir}/loop") } 386 | assert_raise(Errno::ELOOP) { realpath("#{dir}/loop", dir) } 387 | 388 | File.symlink("../#{File.basename(dir)}/./not-exist-target", "#{dir}/not-exist2") 389 | assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist2") } 390 | 391 | File.open("#{dir}/exist-target", "w") {} 392 | File.symlink("../#{File.basename(dir)}/./exist-target", "#{dir}/exist2") 393 | assert_nothing_raised { realpath("#{dir}/exist2") } 394 | 395 | File.symlink("loop-relative", "loop-relative") 396 | assert_raise(Errno::ELOOP) { realpath("#{dir}/loop-relative") } 397 | 398 | Dir.mkdir("exist") 399 | assert_equal("#{dir}/exist", realpath("exist")) 400 | assert_raise(Errno::ELOOP) { realpath("../loop", "#{dir}/exist") } 401 | 402 | File.symlink("loop1/loop1", "loop1") 403 | assert_raise(Errno::ELOOP) { realpath("#{dir}/loop1") } 404 | 405 | File.symlink("loop2", "loop3") 406 | File.symlink("loop3", "loop2") 407 | assert_raise(Errno::ELOOP) { realpath("#{dir}/loop2") } 408 | 409 | Dir.mkdir("b") 410 | 411 | File.symlink("b", "c") 412 | assert_equal("#{dir}/b", realpath("c")) 413 | assert_equal("#{dir}/b", realpath("c/../c")) 414 | assert_equal("#{dir}/b", realpath("c/../c/../c/.")) 415 | 416 | File.symlink("..", "b/d") 417 | assert_equal("#{dir}/b", realpath("c/d/c/d/c")) 418 | 419 | File.symlink("#{dir}/b", "e") 420 | assert_equal("#{dir}/b", realpath("e")) 421 | 422 | Dir.mkdir("f") 423 | Dir.mkdir("f/g") 424 | File.symlink("f/g", "h") 425 | assert_equal("#{dir}/f/g", realpath("h")) 426 | File.chmod(0000, "f") 427 | next if File.readable?("f") 428 | assert_raise(Errno::EACCES) { realpath("h") } 429 | File.chmod(0755, "f") 430 | } 431 | end 432 | 433 | def realdirpath(path) 434 | Pathname.new(path).realdirpath.to_s 435 | end 436 | 437 | def test_realdirpath 438 | omit "not working yet" if RUBY_ENGINE == "jruby" 439 | return if !has_symlink? 440 | Dir.mktmpdir('rubytest-pathname') {|dir| 441 | rdir = realpath(dir) 442 | assert_equal("#{rdir}/not-exist", realdirpath("#{dir}/not-exist")) 443 | assert_raise(Errno::ENOENT) { realdirpath("#{dir}/not-exist/not-exist-child") } 444 | File.symlink("not-exist-target", "#{dir}/not-exist") 445 | assert_equal("#{rdir}/not-exist-target", realdirpath("#{dir}/not-exist")) 446 | File.symlink("../#{File.basename(dir)}/./not-exist-target", "#{dir}/not-exist2") 447 | assert_equal("#{rdir}/not-exist-target", realdirpath("#{dir}/not-exist2")) 448 | File.open("#{dir}/exist-target", "w") {} 449 | File.symlink("../#{File.basename(dir)}/./exist-target", "#{dir}/exist") 450 | assert_equal("#{rdir}/exist-target", realdirpath("#{dir}/exist")) 451 | File.symlink("loop", "#{dir}/loop") 452 | assert_raise(Errno::ELOOP) { realdirpath("#{dir}/loop") } 453 | } 454 | end 455 | 456 | def descend(path) 457 | Pathname.new(path).descend.map(&:to_s) 458 | end 459 | 460 | defassert(:descend, %w[/ /a /a/b /a/b/c], "/a/b/c") 461 | defassert(:descend, %w[a a/b a/b/c], "a/b/c") 462 | defassert(:descend, %w[. ./a ./a/b ./a/b/c], "./a/b/c") 463 | defassert(:descend, %w[a/], "a/") 464 | 465 | def ascend(path) 466 | Pathname.new(path).ascend.map(&:to_s) 467 | end 468 | 469 | defassert(:ascend, %w[/a/b/c /a/b /a /], "/a/b/c") 470 | defassert(:ascend, %w[a/b/c a/b a], "a/b/c") 471 | defassert(:ascend, %w[./a/b/c ./a/b ./a .], "./a/b/c") 472 | defassert(:ascend, %w[a/], "a/") 473 | 474 | def test_blockless_ascend_is_enumerator 475 | assert_kind_of(Enumerator, Pathname.new('a').ascend) 476 | end 477 | 478 | def test_blockless_descend_is_enumerator 479 | assert_kind_of(Enumerator, Pathname.new('a').descend) 480 | end 481 | 482 | def test_initialize 483 | p1 = Pathname.new('a') 484 | assert_equal('a', p1.to_s) 485 | p2 = Pathname.new(p1) 486 | assert_equal(p1, p2) 487 | 488 | obj = Object.new 489 | assert_raise_with_message(TypeError, /#to_path or #to_str/) { Pathname.new(obj) } 490 | 491 | obj = Object.new 492 | def obj.to_path; "a/path"; end 493 | assert_equal("a/path", Pathname.new(obj).to_s) 494 | 495 | obj = Object.new 496 | def obj.to_str; "a/b"; end 497 | assert_equal("a/b", Pathname.new(obj).to_s) 498 | end 499 | 500 | def test_initialize_nul 501 | omit "https://github.com/truffleruby/truffleruby/issues/4047" if RUBY_ENGINE == "truffleruby" 502 | assert_raise(ArgumentError) { Pathname.new("a\0") } 503 | end 504 | 505 | def test_initialize_encoding 506 | omit "https://github.com/jruby/jruby/issues/9120" if RUBY_ENGINE == "jruby" 507 | omit "https://github.com/truffleruby/truffleruby/issues/4047" if RUBY_ENGINE == "truffleruby" 508 | assert_raise(Encoding::CompatibilityError) { Pathname.new("a".encode(Encoding::UTF_32BE)) } 509 | end 510 | 511 | def test_global_constructor 512 | p = Pathname.new('a') 513 | assert_equal(p, Pathname('a')) 514 | assert_same(p, Pathname(p)) 515 | end 516 | 517 | class AnotherStringLike # :nodoc: 518 | def initialize(s) @s = s end 519 | def to_str() @s end 520 | def ==(other) @s == other end 521 | end 522 | 523 | def test_equality 524 | obj = Pathname.new("a") 525 | str = "a" 526 | sym = :a 527 | ano = AnotherStringLike.new("a") 528 | assert_equal(false, obj == str) 529 | assert_equal(false, str == obj) 530 | assert_equal(false, obj == ano) 531 | assert_equal(false, ano == obj) 532 | assert_equal(false, obj == sym) 533 | assert_equal(false, sym == obj) 534 | 535 | obj2 = Pathname.new("a") 536 | assert_equal(true, obj == obj2) 537 | assert_equal(true, obj === obj2) 538 | assert_equal(true, obj.eql?(obj2)) 539 | end 540 | 541 | def test_hashkey 542 | h = {} 543 | h[Pathname.new("a")] = 1 544 | h[Pathname.new("a")] = 2 545 | assert_equal(1, h.size) 546 | end 547 | 548 | def assert_pathname_cmp(e, s1, s2) 549 | p1 = Pathname.new(s1) 550 | p2 = Pathname.new(s2) 551 | r = p1 <=> p2 552 | assert(e == r, 553 | "#{p1.inspect} <=> #{p2.inspect}: <#{e}> expected but was <#{r}>") 554 | end 555 | def test_comparison 556 | assert_pathname_cmp( 0, "a", "a") 557 | assert_pathname_cmp( 1, "b", "a") 558 | assert_pathname_cmp(-1, "a", "b") 559 | ss = %w( 560 | a 561 | a/ 562 | a/b 563 | a. 564 | a0 565 | ) 566 | s1 = ss.shift 567 | ss.each {|s2| 568 | assert_pathname_cmp(-1, s1, s2) 569 | s1 = s2 570 | } 571 | end 572 | 573 | def test_comparison_string 574 | assert_equal(nil, Pathname.new("a") <=> "a") 575 | assert_equal(nil, "a" <=> Pathname.new("a")) 576 | end 577 | 578 | def pathsub(path, pat, repl) Pathname.new(path).sub(pat, repl).to_s end 579 | defassert(:pathsub, "a.o", "a.c", /\.c\z/, ".o") 580 | 581 | def pathsubext(path, repl) Pathname.new(path).sub_ext(repl).to_s end 582 | defassert(:pathsubext, 'a.o', 'a.c', '.o') 583 | defassert(:pathsubext, 'a.o', 'a.c++', '.o') 584 | defassert(:pathsubext, 'a.png', 'a.gif', '.png') 585 | defassert(:pathsubext, 'ruby.tar.bz2', 'ruby.tar.gz', '.bz2') 586 | defassert(:pathsubext, 'd/a.o', 'd/a.c', '.o') 587 | defassert(:pathsubext, 'foo', 'foo.exe', '') 588 | defassert(:pathsubext, 'lex.yy.o', 'lex.yy.c', '.o') 589 | defassert(:pathsubext, 'fooaa.o', 'fooaa', '.o') 590 | defassert(:pathsubext, 'd.e/aa.o', 'd.e/aa', '.o') 591 | defassert(:pathsubext, 'long_enough.bug-3664', 'long_enough.not_to_be_embedded[ruby-core:31640]', '.bug-3664') 592 | 593 | def test_sub_matchdata 594 | result = Pathname("abc.gif").sub(/\..*/) { 595 | assert_not_nil($~) 596 | assert_equal(".gif", $~[0]) 597 | ".png" 598 | } 599 | assert_equal("abc.png", result.to_s) 600 | end 601 | 602 | def root?(path) 603 | Pathname.new(path).root? 604 | end 605 | 606 | defassert(:root?, true, "/") 607 | defassert(:root?, true, "//") 608 | defassert(:root?, true, "///") 609 | defassert(:root?, false, "") 610 | defassert(:root?, false, "a") 611 | 612 | def test_mountpoint? 613 | r = Pathname("/").mountpoint? 614 | assert_include([true, false], r) 615 | end 616 | 617 | def test_mountpoint_enoent 618 | r = Pathname("/nonexistent").mountpoint? 619 | assert_equal false, r 620 | end 621 | 622 | def test_destructive_update 623 | path = Pathname.new("a") 624 | path.to_s.replace "b" 625 | assert_equal(Pathname.new("a"), path) 626 | end 627 | 628 | def test_null_character 629 | omit "https://github.com/truffleruby/truffleruby/issues/4047" if RUBY_ENGINE == "truffleruby" 630 | assert_raise(ArgumentError) { Pathname.new("\0") } 631 | end 632 | 633 | def test_freeze 634 | obj = Pathname.new("a"); assert_same(obj, obj.freeze) 635 | 636 | assert_equal(false, Pathname.new("a" ) .frozen?) 637 | assert_equal(false, Pathname.new("a".freeze) .frozen?) 638 | assert_equal(true, Pathname.new("a" ).freeze .frozen?) 639 | assert_equal(true, Pathname.new("a".freeze).freeze .frozen?) 640 | assert_equal(false, Pathname.new("a" ) .to_s.frozen?) 641 | assert_equal(false, Pathname.new("a".freeze) .to_s.frozen?) 642 | assert_equal(false, Pathname.new("a" ).freeze.to_s.frozen?) 643 | assert_equal(false, Pathname.new("a".freeze).freeze.to_s.frozen?) 644 | end 645 | 646 | def test_to_s 647 | str = "a" 648 | obj = Pathname.new(str) 649 | assert_equal(str, obj.to_s) 650 | assert_not_same(str, obj.to_s) 651 | assert_not_same(obj.to_s, obj.to_s) 652 | end 653 | 654 | def test_kernel_open 655 | count = 0 656 | result = Kernel.open(Pathname.new(__FILE__)) {|f| 657 | assert_file.identical?(__FILE__, f) 658 | count += 1 659 | 2 660 | } 661 | assert_equal(1, count) 662 | assert_equal(2, result) 663 | end 664 | 665 | def test_each_filename 666 | result = [] 667 | Pathname.new("/usr/bin/ruby").each_filename {|f| result << f } 668 | assert_equal(%w[usr bin ruby], result) 669 | assert_equal(%w[usr bin ruby], Pathname.new("/usr/bin/ruby").each_filename.to_a) 670 | end 671 | 672 | def test_kernel_pathname 673 | assert_equal(Pathname.new("a"), Pathname("a")) 674 | end 675 | 676 | def test_children 677 | with_tmpchdir('rubytest-pathname') {|dir| 678 | open("a", "w") {} 679 | open("b", "w") {} 680 | Dir.mkdir("d") 681 | open("d/x", "w") {} 682 | open("d/y", "w") {} 683 | assert_equal([Pathname("a"), Pathname("b"), Pathname("d")], Pathname(".").children.sort) 684 | assert_equal([Pathname("d/x"), Pathname("d/y")], Pathname("d").children.sort) 685 | assert_equal([Pathname("x"), Pathname("y")], Pathname("d").children(false).sort) 686 | } 687 | end 688 | 689 | def test_each_child 690 | with_tmpchdir('rubytest-pathname') {|dir| 691 | open("a", "w") {} 692 | open("b", "w") {} 693 | Dir.mkdir("d") 694 | open("d/x", "w") {} 695 | open("d/y", "w") {} 696 | a = []; Pathname(".").each_child {|v| a << v }; a.sort! 697 | assert_equal([Pathname("a"), Pathname("b"), Pathname("d")], a) 698 | a = []; Pathname("d").each_child {|v| a << v }; a.sort! 699 | assert_equal([Pathname("d/x"), Pathname("d/y")], a) 700 | a = []; Pathname("d").each_child(false) {|v| a << v }; a.sort! 701 | assert_equal([Pathname("x"), Pathname("y")], a) 702 | } 703 | end 704 | 705 | def test_each_line 706 | omit "not working yet" if RUBY_ENGINE == "jruby" 707 | with_tmpchdir('rubytest-pathname') {|dir| 708 | open("a", "w") {|f| f.puts 1, 2 } 709 | a = [] 710 | Pathname("a").each_line {|line| a << line } 711 | assert_equal(["1\n", "2\n"], a) 712 | 713 | a = [] 714 | Pathname("a").each_line("2") {|line| a << line } 715 | assert_equal(["1\n2", "\n"], a) 716 | 717 | a = [] 718 | Pathname("a").each_line(1) {|line| a << line } 719 | assert_equal(["1", "\n", "2", "\n"], a) 720 | 721 | a = [] 722 | Pathname("a").each_line("2", 1) {|line| a << line } 723 | assert_equal(["1", "\n", "2", "\n"], a) 724 | 725 | a = [] 726 | enum = Pathname("a").each_line 727 | enum.each {|line| a << line } 728 | assert_equal(["1\n", "2\n"], a) 729 | } 730 | end 731 | 732 | def test_each_line_opts 733 | omit "not working yet" if RUBY_ENGINE == "jruby" 734 | with_tmpchdir('rubytest-pathname') {|dir| 735 | open("a", "w") {|f| f.puts 1, 2 } 736 | a = [] 737 | Pathname("a").each_line(chomp: true) {|line| a << line } 738 | assert_equal(["1", "2"], a) 739 | 740 | a = [] 741 | Pathname("a").each_line("2", chomp: true) {|line| a << line } 742 | assert_equal(["1\n", "\n"], a) 743 | 744 | a = [] 745 | Pathname("a").each_line(1, chomp: true) {|line| a << line } 746 | assert_equal(["1", "", "2", ""], a) 747 | 748 | a = [] 749 | Pathname("a").each_line("2", 1, chomp: true) {|line| a << line } 750 | assert_equal(["1", "\n", "", "\n"], a) 751 | 752 | a = [] 753 | enum = Pathname("a").each_line(chomp: true) 754 | enum.each {|line| a << line } 755 | assert_equal(["1", "2"], a) 756 | } 757 | end 758 | 759 | def test_readlines 760 | with_tmpchdir('rubytest-pathname') {|dir| 761 | open("a", "w") {|f| f.puts 1, 2 } 762 | a = Pathname("a").readlines 763 | assert_equal(["1\n", "2\n"], a) 764 | } 765 | end 766 | 767 | def test_readlines_opts 768 | with_tmpchdir('rubytest-pathname') {|dir| 769 | open("a", "w") {|f| f.puts 1, 2 } 770 | a = Pathname("a").readlines 1, chomp: true 771 | assert_equal(["1", "", "2", ""], a) 772 | } 773 | end 774 | 775 | def test_read 776 | with_tmpchdir('rubytest-pathname') {|dir| 777 | open("a", "w") {|f| f.puts 1, 2 } 778 | assert_equal("1\n2\n", Pathname("a").read) 779 | } 780 | end 781 | 782 | def test_binread 783 | with_tmpchdir('rubytest-pathname') {|dir| 784 | open("a", "w") {|f| f.write "abc" } 785 | str = Pathname("a").binread 786 | assert_equal("abc", str) 787 | assert_equal(Encoding::ASCII_8BIT, str.encoding) 788 | } 789 | end 790 | 791 | def test_write 792 | with_tmpchdir('rubytest-pathname') {|dir| 793 | path = Pathname("a") 794 | path.write "abc" 795 | assert_equal("abc", path.read) 796 | } 797 | end 798 | 799 | def test_write_opts 800 | with_tmpchdir('rubytest-pathname') {|dir| 801 | path = Pathname("a") 802 | path.write "abc", mode: "w" 803 | assert_equal("abc", path.read) 804 | } 805 | end 806 | 807 | def test_binwrite 808 | with_tmpchdir('rubytest-pathname') {|dir| 809 | path = Pathname("a") 810 | path.binwrite "abc\x80" 811 | assert_equal("abc\x80".b, path.binread) 812 | } 813 | end 814 | 815 | def test_binwrite_opts 816 | with_tmpchdir('rubytest-pathname') {|dir| 817 | path = Pathname("a") 818 | path.binwrite "abc\x80", mode: 'w' 819 | assert_equal("abc\x80".b, path.binread) 820 | } 821 | end 822 | 823 | def test_sysopen 824 | with_tmpchdir('rubytest-pathname') {|dir| 825 | open("a", "w") {|f| f.write "abc" } 826 | fd = Pathname("a").sysopen 827 | io = IO.new(fd) 828 | begin 829 | assert_equal("abc", io.read) 830 | ensure 831 | io.close 832 | end 833 | } 834 | end 835 | 836 | def test_atime 837 | assert_kind_of(Time, Pathname(__FILE__).atime) 838 | end 839 | 840 | def test_birthtime 841 | omit "no File.birthtime" if RUBY_PLATFORM =~ /android/ or !File.respond_to?(:birthtime) 842 | # Check under a (probably) local filesystem. 843 | # Remote filesystems often may not support birthtime. 844 | with_tmpchdir('rubytest-pathname') do |dir| 845 | open("a", "w") {} 846 | assert_kind_of(Time, Pathname("a").birthtime) 847 | rescue Errno::EPERM 848 | # Docker prohibits statx syscall by the default. 849 | omit("statx(2) is prohibited by seccomp") 850 | rescue Errno::ENOSYS 851 | omit("statx(2) is not supported on this filesystem") 852 | rescue NotImplementedError 853 | # assert_raise(NotImplementedError) do 854 | # File.birthtime("a") 855 | # end 856 | end 857 | end 858 | 859 | def test_ctime 860 | assert_kind_of(Time, Pathname(__FILE__).ctime) 861 | end 862 | 863 | def test_mtime 864 | assert_kind_of(Time, Pathname(__FILE__).mtime) 865 | end 866 | 867 | def test_chmod 868 | with_tmpchdir('rubytest-pathname') {|dir| 869 | open("a", "w") {|f| f.write "abc" } 870 | path = Pathname("a") 871 | old = path.stat.mode 872 | path.chmod(0444) 873 | assert_equal(0444, path.stat.mode & 0777) 874 | path.chmod(old) 875 | } 876 | end 877 | 878 | def test_lchmod 879 | return if !has_symlink? 880 | with_tmpchdir('rubytest-pathname') {|dir| 881 | open("a", "w") {|f| f.write "abc" } 882 | File.symlink("a", "l") 883 | path = Pathname("l") 884 | old = path.lstat.mode 885 | begin 886 | path.lchmod(0444) 887 | rescue NotImplementedError, Errno::EOPNOTSUPP 888 | next 889 | end 890 | assert_equal(0444, path.lstat.mode & 0777) 891 | path.chmod(old) 892 | } 893 | end 894 | 895 | def test_chown 896 | with_tmpchdir('rubytest-pathname') {|dir| 897 | open("a", "w") {|f| f.write "abc" } 898 | path = Pathname("a") 899 | old_uid = path.stat.uid 900 | old_gid = path.stat.gid 901 | begin 902 | path.chown(0, 0) 903 | rescue Errno::EPERM 904 | next 905 | end 906 | assert_equal(0, path.stat.uid) 907 | assert_equal(0, path.stat.gid) 908 | path.chown(old_uid, old_gid) 909 | } 910 | end 911 | 912 | def test_lchown 913 | return if !has_symlink? 914 | with_tmpchdir('rubytest-pathname') {|dir| 915 | open("a", "w") {|f| f.write "abc" } 916 | File.symlink("a", "l") 917 | path = Pathname("l") 918 | old_uid = path.stat.uid 919 | old_gid = path.stat.gid 920 | begin 921 | path.lchown(0, 0) 922 | rescue Errno::EPERM 923 | next 924 | end 925 | assert_equal(0, path.stat.uid) 926 | assert_equal(0, path.stat.gid) 927 | path.lchown(old_uid, old_gid) 928 | } 929 | end 930 | 931 | def test_fnmatch 932 | path = Pathname("a") 933 | assert_equal(true, path.fnmatch("*")) 934 | assert_equal(false, path.fnmatch("*.*")) 935 | assert_equal(false, Pathname(".foo").fnmatch("*")) 936 | assert_equal(true, Pathname(".foo").fnmatch("*", File::FNM_DOTMATCH)) 937 | end 938 | 939 | def test_fnmatch? 940 | path = Pathname("a") 941 | assert_equal(true, path.fnmatch?("*")) 942 | assert_equal(false, path.fnmatch?("*.*")) 943 | end 944 | 945 | def test_ftype 946 | with_tmpchdir('rubytest-pathname') {|dir| 947 | open("f", "w") {|f| f.write "abc" } 948 | assert_equal("file", Pathname("f").ftype) 949 | Dir.mkdir("d") 950 | assert_equal("directory", Pathname("d").ftype) 951 | } 952 | end 953 | 954 | def test_make_link 955 | return if !has_hardlink? 956 | with_tmpchdir('rubytest-pathname') {|dir| 957 | open("a", "w") {|f| f.write "abc" } 958 | Pathname("l").make_link(Pathname("a")) 959 | assert_equal("abc", Pathname("l").read) 960 | } 961 | end 962 | 963 | def test_open 964 | with_tmpchdir('rubytest-pathname') {|dir| 965 | open("a", "w") {|f| f.write "abc" } 966 | path = Pathname("a") 967 | 968 | path.open {|f| 969 | assert_equal("abc", f.read) 970 | } 971 | 972 | path.open("r") {|f| 973 | assert_equal("abc", f.read) 974 | } 975 | 976 | path.open(mode: "r") {|f| 977 | assert_equal("abc", f.read) 978 | } 979 | 980 | Pathname("b").open("w", 0444) {|f| f.write "def" } 981 | assert_equal(0444 & ~File.umask, File.stat("b").mode & 0777) 982 | assert_equal("def", File.read("b")) 983 | 984 | Pathname("c").open("w", 0444, **{}) {|f| f.write "ghi" } 985 | assert_equal(0444 & ~File.umask, File.stat("c").mode & 0777) 986 | assert_equal("ghi", File.read("c")) 987 | 988 | g = path.open 989 | assert_equal("abc", g.read) 990 | g.close 991 | 992 | g = path.open(mode: "r") 993 | assert_equal("abc", g.read) 994 | g.close 995 | } 996 | end 997 | 998 | def test_readlink 999 | return if !has_symlink? 1000 | with_tmpchdir('rubytest-pathname') {|dir| 1001 | open("a", "w") {|f| f.write "abc" } 1002 | File.symlink("a", "l") 1003 | assert_equal(Pathname("a"), Pathname("l").readlink) 1004 | } 1005 | end 1006 | 1007 | def test_rename 1008 | with_tmpchdir('rubytest-pathname') {|dir| 1009 | open("a", "w") {|f| f.write "abc" } 1010 | Pathname("a").rename(Pathname("b")) 1011 | assert_equal("abc", File.read("b")) 1012 | } 1013 | end 1014 | 1015 | def test_stat 1016 | with_tmpchdir('rubytest-pathname') {|dir| 1017 | open("a", "w") {|f| f.write "abc" } 1018 | s = Pathname("a").stat 1019 | assert_equal(3, s.size) 1020 | } 1021 | end 1022 | 1023 | def test_lstat 1024 | return if !has_symlink? 1025 | with_tmpchdir('rubytest-pathname') {|dir| 1026 | open("a", "w") {|f| f.write "abc" } 1027 | File.symlink("a", "l") 1028 | s = Pathname("l").lstat 1029 | assert_equal(true, s.symlink?) 1030 | s = Pathname("l").stat 1031 | assert_equal(false, s.symlink?) 1032 | assert_equal(3, s.size) 1033 | s = Pathname("a").lstat 1034 | assert_equal(false, s.symlink?) 1035 | assert_equal(3, s.size) 1036 | } 1037 | end 1038 | 1039 | def test_make_symlink 1040 | return if !has_symlink? 1041 | with_tmpchdir('rubytest-pathname') {|dir| 1042 | open("a", "w") {|f| f.write "abc" } 1043 | Pathname("l").make_symlink(Pathname("a")) 1044 | s = Pathname("l").lstat 1045 | assert_equal(true, s.symlink?) 1046 | } 1047 | end 1048 | 1049 | def test_truncate 1050 | with_tmpchdir('rubytest-pathname') {|dir| 1051 | open("a", "w") {|f| f.write "abc" } 1052 | Pathname("a").truncate(2) 1053 | assert_equal("ab", File.read("a")) 1054 | } 1055 | end 1056 | 1057 | def test_utime 1058 | with_tmpchdir('rubytest-pathname') {|dir| 1059 | open("a", "w") {|f| f.write "abc" } 1060 | atime = Time.utc(2000) 1061 | mtime = Time.utc(1999) 1062 | Pathname("a").utime(atime, mtime) 1063 | s = File.stat("a") 1064 | assert_equal(atime, s.atime) 1065 | assert_equal(mtime, s.mtime) 1066 | } 1067 | end 1068 | 1069 | def test_lutime 1070 | return if !has_symlink? 1071 | with_tmpchdir('rubytest-pathname') {|dir| 1072 | open("a", "w") {|f| f.write "abc" } 1073 | atime = File.atime("a") 1074 | mtime = File.mtime("a") 1075 | latime = Time.utc(2000) 1076 | lmtime = Time.utc(1999) 1077 | File.symlink("a", "l") 1078 | begin 1079 | Pathname("l").lutime(latime, lmtime) 1080 | rescue NotImplementedError 1081 | next 1082 | end 1083 | s = File.lstat("a") 1084 | ls = File.lstat("l") 1085 | assert_equal(atime, s.atime) 1086 | assert_equal(mtime, s.mtime) 1087 | assert_equal(latime, ls.atime) 1088 | assert_equal(lmtime, ls.mtime) 1089 | } 1090 | end 1091 | 1092 | def test_basename 1093 | assert_equal(Pathname("basename"), Pathname("dirname/basename").basename) 1094 | assert_equal(Pathname("bar"), Pathname("foo/bar.x").basename(".x")) 1095 | end 1096 | 1097 | def test_dirname 1098 | assert_equal(Pathname("dirname"), Pathname("dirname/basename").dirname) 1099 | end 1100 | 1101 | def test_extname 1102 | assert_equal(".ext", Pathname("basename.ext").extname) 1103 | end 1104 | 1105 | def test_expand_path 1106 | drv = DOSISH_DRIVE_LETTER ? Dir.pwd.sub(%r(/.*), '') : "" 1107 | assert_equal(Pathname(drv + "/a"), Pathname("/a").expand_path) 1108 | assert_equal(Pathname(drv + "/a"), Pathname("a").expand_path("/")) 1109 | assert_equal(Pathname(drv + "/a"), Pathname("a").expand_path(Pathname("/"))) 1110 | assert_equal(Pathname(drv + "/b"), Pathname("/b").expand_path(Pathname("/a"))) 1111 | assert_equal(Pathname(drv + "/a/b"), Pathname("b").expand_path(Pathname("/a"))) 1112 | end 1113 | 1114 | def test_split 1115 | assert_equal([Pathname("dirname"), Pathname("basename")], Pathname("dirname/basename").split) 1116 | 1117 | assert_separately([], <<-'end;') 1118 | require 'pathname' 1119 | 1120 | mod = Module.new do 1121 | def split(_arg) 1122 | end 1123 | end 1124 | 1125 | File.singleton_class.prepend(mod) 1126 | 1127 | assert_raise(TypeError) do 1128 | Pathname('/').split 1129 | end 1130 | end; 1131 | end 1132 | 1133 | def test_blockdev? 1134 | with_tmpchdir('rubytest-pathname') {|dir| 1135 | open("f", "w") {|f| f.write "abc" } 1136 | assert_equal(false, Pathname("f").blockdev?) 1137 | } 1138 | end 1139 | 1140 | def test_chardev? 1141 | with_tmpchdir('rubytest-pathname') {|dir| 1142 | open("f", "w") {|f| f.write "abc" } 1143 | assert_equal(false, Pathname("f").chardev?) 1144 | } 1145 | end 1146 | 1147 | def test_executable? 1148 | with_tmpchdir('rubytest-pathname') {|dir| 1149 | open("f", "w") {|f| f.write "abc" } 1150 | assert_equal(false, Pathname("f").executable?) 1151 | } 1152 | end 1153 | 1154 | def test_executable_real? 1155 | with_tmpchdir('rubytest-pathname') {|dir| 1156 | open("f", "w") {|f| f.write "abc" } 1157 | assert_equal(false, Pathname("f").executable_real?) 1158 | } 1159 | end 1160 | 1161 | def test_exist? 1162 | with_tmpchdir('rubytest-pathname') {|dir| 1163 | open("f", "w") {|f| f.write "abc" } 1164 | assert_equal(true, Pathname("f").exist?) 1165 | } 1166 | end 1167 | 1168 | def test_grpowned? 1169 | omit "Unix file owner test" if DOSISH 1170 | with_tmpchdir('rubytest-pathname') {|dir| 1171 | open("f", "w") {|f| f.write "abc" } 1172 | File.chown(-1, Process.gid, "f") 1173 | assert_equal(true, Pathname("f").grpowned?) 1174 | } 1175 | end 1176 | 1177 | def test_directory? 1178 | with_tmpchdir('rubytest-pathname') {|dir| 1179 | open("f", "w") {|f| f.write "abc" } 1180 | assert_equal(false, Pathname("f").directory?) 1181 | Dir.mkdir("d") 1182 | assert_equal(true, Pathname("d").directory?) 1183 | } 1184 | end 1185 | 1186 | def test_file? 1187 | with_tmpchdir('rubytest-pathname') {|dir| 1188 | open("f", "w") {|f| f.write "abc" } 1189 | assert_equal(true, Pathname("f").file?) 1190 | Dir.mkdir("d") 1191 | assert_equal(false, Pathname("d").file?) 1192 | } 1193 | end 1194 | 1195 | def test_pipe? 1196 | with_tmpchdir('rubytest-pathname') {|dir| 1197 | open("f", "w") {|f| f.write "abc" } 1198 | assert_equal(false, Pathname("f").pipe?) 1199 | } 1200 | end 1201 | 1202 | def test_socket? 1203 | with_tmpchdir('rubytest-pathname') {|dir| 1204 | open("f", "w") {|f| f.write "abc" } 1205 | assert_equal(false, Pathname("f").socket?) 1206 | } 1207 | end 1208 | 1209 | def test_owned? 1210 | with_tmpchdir('rubytest-pathname') {|dir| 1211 | open("f", "w") {|f| f.write "abc" } 1212 | assert_equal(true, Pathname("f").owned?) 1213 | } 1214 | end 1215 | 1216 | def test_readable? 1217 | with_tmpchdir('rubytest-pathname') {|dir| 1218 | open("f", "w") {|f| f.write "abc" } 1219 | assert_equal(true, Pathname("f").readable?) 1220 | } 1221 | end 1222 | 1223 | def test_world_readable? 1224 | omit "Unix file mode bit test" if DOSISH 1225 | with_tmpchdir('rubytest-pathname') {|dir| 1226 | open("f", "w") {|f| f.write "abc" } 1227 | File.chmod(0400, "f") 1228 | assert_equal(nil, Pathname("f").world_readable?) 1229 | File.chmod(0444, "f") 1230 | assert_equal(0444, Pathname("f").world_readable?) 1231 | } 1232 | end 1233 | 1234 | def test_readable_real? 1235 | with_tmpchdir('rubytest-pathname') {|dir| 1236 | open("f", "w") {|f| f.write "abc" } 1237 | assert_equal(true, Pathname("f").readable_real?) 1238 | } 1239 | end 1240 | 1241 | def test_setuid? 1242 | with_tmpchdir('rubytest-pathname') {|dir| 1243 | open("f", "w") {|f| f.write "abc" } 1244 | assert_equal(false, Pathname("f").setuid?) 1245 | } 1246 | end 1247 | 1248 | def test_setgid? 1249 | with_tmpchdir('rubytest-pathname') {|dir| 1250 | open("f", "w") {|f| f.write "abc" } 1251 | assert_equal(false, Pathname("f").setgid?) 1252 | } 1253 | end 1254 | 1255 | def test_size 1256 | with_tmpchdir('rubytest-pathname') {|dir| 1257 | open("f", "w") {|f| f.write "abc" } 1258 | assert_equal(3, Pathname("f").size) 1259 | open("z", "w") {|f| } 1260 | assert_equal(0, Pathname("z").size) 1261 | assert_raise(Errno::ENOENT) { Pathname("not-exist").size } 1262 | } 1263 | end 1264 | 1265 | def test_size? 1266 | with_tmpchdir('rubytest-pathname') {|dir| 1267 | open("f", "w") {|f| f.write "abc" } 1268 | assert_equal(3, Pathname("f").size?) 1269 | open("z", "w") {|f| } 1270 | assert_equal(nil, Pathname("z").size?) 1271 | assert_equal(nil, Pathname("not-exist").size?) 1272 | } 1273 | end 1274 | 1275 | def test_sticky? 1276 | omit "Unix file mode bit test" if DOSISH 1277 | with_tmpchdir('rubytest-pathname') {|dir| 1278 | open("f", "w") {|f| f.write "abc" } 1279 | assert_equal(false, Pathname("f").sticky?) 1280 | } 1281 | end 1282 | 1283 | def test_symlink? 1284 | with_tmpchdir('rubytest-pathname') {|dir| 1285 | open("f", "w") {|f| f.write "abc" } 1286 | assert_equal(false, Pathname("f").symlink?) 1287 | } 1288 | end 1289 | 1290 | def test_writable? 1291 | with_tmpchdir('rubytest-pathname') {|dir| 1292 | open("f", "w") {|f| f.write "abc" } 1293 | assert_equal(true, Pathname("f").writable?) 1294 | } 1295 | end 1296 | 1297 | def test_world_writable? 1298 | omit "Unix file mode bit test" if DOSISH 1299 | with_tmpchdir('rubytest-pathname') {|dir| 1300 | open("f", "w") {|f| f.write "abc" } 1301 | File.chmod(0600, "f") 1302 | assert_equal(nil, Pathname("f").world_writable?) 1303 | File.chmod(0666, "f") 1304 | assert_equal(0666, Pathname("f").world_writable?) 1305 | } 1306 | end 1307 | 1308 | def test_writable_real? 1309 | with_tmpchdir('rubytest-pathname') {|dir| 1310 | open("f", "w") {|f| f.write "abc" } 1311 | assert_equal(true, Pathname("f").writable?) 1312 | } 1313 | end 1314 | 1315 | def test_zero? 1316 | with_tmpchdir('rubytest-pathname') {|dir| 1317 | open("f", "w") {|f| f.write "abc" } 1318 | assert_equal(false, Pathname("f").zero?) 1319 | open("z", "w") {|f| } 1320 | assert_equal(true, Pathname("z").zero?) 1321 | assert_equal(false, Pathname("not-exist").zero?) 1322 | } 1323 | end 1324 | 1325 | def test_empty? 1326 | with_tmpchdir('rubytest-pathname') {|dir| 1327 | open("nonemptyfile", "w") {|f| f.write "abc" } 1328 | open("emptyfile", "w") {|f| } 1329 | Dir.mkdir("nonemptydir") 1330 | open("nonemptydir/somefile", "w") {|f| } 1331 | Dir.mkdir("emptydir") 1332 | assert_equal(true, Pathname("emptyfile").empty?) 1333 | assert_equal(false, Pathname("nonemptyfile").empty?) 1334 | assert_equal(true, Pathname("emptydir").empty?) 1335 | assert_equal(false, Pathname("nonemptydir").empty?) 1336 | } 1337 | end 1338 | 1339 | def test_s_glob 1340 | with_tmpchdir('rubytest-pathname') {|dir| 1341 | open("f", "w") {|f| f.write "abc" } 1342 | Dir.mkdir("d") 1343 | assert_equal([Pathname("d"), Pathname("f")], Pathname.glob("*").sort) 1344 | a = [] 1345 | Pathname.glob("*") {|path| a << path } 1346 | a.sort! 1347 | assert_equal([Pathname("d"), Pathname("f")], a) 1348 | } 1349 | end 1350 | 1351 | def test_s_glob_3args 1352 | # Note: truffleruby should behave like CRuby 3.1+, but it's not the case currently 1353 | expect = (RUBY_VERSION >= "3.1" && RUBY_ENGINE != "truffleruby") ? [Pathname("."), Pathname("f")] : [Pathname("."), Pathname(".."), Pathname("f")] 1354 | with_tmpchdir('rubytest-pathname') {|dir| 1355 | open("f", "w") {|f| f.write "abc" } 1356 | Dir.chdir("/") { 1357 | assert_equal( 1358 | expect, 1359 | Pathname.glob("*", File::FNM_DOTMATCH, base: dir).sort) 1360 | } 1361 | } 1362 | end 1363 | 1364 | def test_mktmpdir 1365 | Pathname.mktmpdir do |dir| 1366 | assert_equal Pathname(dir), dir 1367 | assert dir.directory? 1368 | assert dir.exist? 1369 | end 1370 | end 1371 | 1372 | def test_s_getwd 1373 | wd = Pathname.getwd 1374 | assert_kind_of(Pathname, wd) 1375 | end 1376 | 1377 | def test_s_pwd 1378 | wd = Pathname.pwd 1379 | assert_kind_of(Pathname, wd) 1380 | end 1381 | 1382 | def test_glob 1383 | with_tmpchdir('rubytest-pathname') {|dir| 1384 | Dir.mkdir("d") 1385 | open("d/f", "w") {|f| f.write "abc" } 1386 | Dir.mkdir("d/e") 1387 | assert_equal([Pathname("d/e"), Pathname("d/f")], Pathname("d").glob("*").sort) 1388 | a = [] 1389 | Pathname("d").glob("*") {|path| a << path } 1390 | a.sort! 1391 | assert_equal([Pathname("d/e"), Pathname("d/f")], a) 1392 | } 1393 | end 1394 | 1395 | def test_entries 1396 | with_tmpchdir('rubytest-pathname') {|dir| 1397 | open("a", "w") {} 1398 | open("b", "w") {} 1399 | assert_equal([Pathname("."), Pathname(".."), Pathname("a"), Pathname("b")], Pathname(".").entries.sort) 1400 | } 1401 | end 1402 | 1403 | def test_each_entry 1404 | with_tmpchdir('rubytest-pathname') {|dir| 1405 | open("a", "w") {} 1406 | open("b", "w") {} 1407 | a = [] 1408 | Pathname(".").each_entry {|v| a << v } 1409 | assert_equal([Pathname("."), Pathname(".."), Pathname("a"), Pathname("b")], a.sort) 1410 | } 1411 | end 1412 | 1413 | def test_each_entry_enumerator 1414 | with_tmpchdir('rubytest-pathname') {|dir| 1415 | open("a", "w") {} 1416 | open("b", "w") {} 1417 | a = [] 1418 | e = Pathname(".").each_entry 1419 | assert_kind_of(Enumerator, e) 1420 | e.each {|v| a << v } 1421 | assert_equal([Pathname("."), Pathname(".."), Pathname("a"), Pathname("b")], a.sort) 1422 | } 1423 | end 1424 | 1425 | def test_mkdir 1426 | with_tmpchdir('rubytest-pathname') {|dir| 1427 | Pathname("d").mkdir 1428 | assert_file.directory?("d") 1429 | Pathname("e").mkdir(0770) 1430 | assert_file.directory?("e") 1431 | } 1432 | end 1433 | 1434 | def test_rmdir 1435 | with_tmpchdir('rubytest-pathname') {|dir| 1436 | Pathname("d").mkdir 1437 | assert_file.directory?("d") 1438 | Pathname("d").rmdir 1439 | assert_file.not_exist?("d") 1440 | } 1441 | end 1442 | 1443 | def test_opendir 1444 | with_tmpchdir('rubytest-pathname') {|dir| 1445 | open("a", "w") {} 1446 | open("b", "w") {} 1447 | a = [] 1448 | Pathname(".").opendir {|d| 1449 | d.each {|e| a << e } 1450 | } 1451 | assert_equal([".", "..", "a", "b"], a.sort) 1452 | } 1453 | end 1454 | 1455 | def test_find 1456 | with_tmpchdir('rubytest-pathname') {|dir| 1457 | open("a", "w") {} 1458 | open("b", "w") {} 1459 | Dir.mkdir("d") 1460 | open("d/x", "w") {} 1461 | open("d/y", "w") {} 1462 | a = []; Pathname(".").find {|v| a << v }; a.sort! 1463 | assert_equal([Pathname("."), Pathname("a"), Pathname("b"), Pathname("d"), Pathname("d/x"), Pathname("d/y")], a) 1464 | a = []; Pathname("d").find {|v| a << v }; a.sort! 1465 | assert_equal([Pathname("d"), Pathname("d/x"), Pathname("d/y")], a) 1466 | a = Pathname(".").find.sort 1467 | assert_equal([Pathname("."), Pathname("a"), Pathname("b"), Pathname("d"), Pathname("d/x"), Pathname("d/y")], a) 1468 | a = Pathname("d").find.sort 1469 | assert_equal([Pathname("d"), Pathname("d/x"), Pathname("d/y")], a) 1470 | 1471 | begin 1472 | File.unlink("d/y") 1473 | File.chmod(0600, "d") 1474 | a = []; Pathname(".").find(ignore_error: true) {|v| a << v }; a.sort! 1475 | assert_equal([Pathname("."), Pathname("a"), Pathname("b"), Pathname("d"), Pathname("d/x")], a) 1476 | a = []; Pathname("d").find(ignore_error: true) {|v| a << v }; a.sort! 1477 | assert_equal([Pathname("d"), Pathname("d/x")], a) 1478 | 1479 | omit "no meaning test on Windows" if /mswin|mingw/ =~ RUBY_PLATFORM 1480 | omit 'skipped in root privilege' if Process.uid == 0 1481 | a = []; 1482 | assert_raise_with_message(Errno::EACCES, %r{d/x}) do 1483 | Pathname(".").find(ignore_error: false) {|v| a << v } 1484 | end 1485 | a.sort! 1486 | assert_equal([Pathname("."), Pathname("a"), Pathname("b"), Pathname("d"), Pathname("d/x")], a) 1487 | a = []; 1488 | assert_raise_with_message(Errno::EACCES, %r{d/x}) do 1489 | Pathname("d").find(ignore_error: false) {|v| a << v } 1490 | end 1491 | a.sort! 1492 | assert_equal([Pathname("d"), Pathname("d/x")], a) 1493 | ensure 1494 | File.chmod(0700, "d") 1495 | end 1496 | } 1497 | end 1498 | 1499 | def assert_mode(val, mask, path, mesg = nil) 1500 | st = File.stat(path) 1501 | assert_equal(val.to_s(8), (st.mode & mask).to_s(8), st.inspect) 1502 | end 1503 | 1504 | def test_mkpath 1505 | with_tmpchdir('rubytest-pathname') {|dir| 1506 | path = Pathname("a/b/c/d") 1507 | assert_equal(path, path.mkpath) 1508 | assert_file.directory?("a/b/c/d") 1509 | unless File.stat(dir).world_readable? 1510 | # mktmpdir should make unreadable 1511 | Pathname("x/y/z").mkpath(mode: 0775) 1512 | assert_mode(0775, 0777, "x") 1513 | assert_mode(0775, 0777, "x/y") 1514 | assert_mode(0775, 0777, "x/y/z") 1515 | end 1516 | } 1517 | end 1518 | 1519 | def test_rmtree 1520 | with_tmpchdir('rubytest-pathname') {|dir| 1521 | Pathname("a/b/c/d").mkpath 1522 | assert_file.exist?("a/b/c/d") 1523 | path = Pathname("a") 1524 | assert_equal(path, path.rmtree) 1525 | assert_file.not_exist?("a") 1526 | } 1527 | end 1528 | 1529 | def test_unlink 1530 | with_tmpchdir('rubytest-pathname') {|dir| 1531 | open("f", "w") {|f| f.write "abc" } 1532 | Pathname("f").unlink 1533 | assert_file.not_exist?("f") 1534 | Dir.mkdir("d") 1535 | Pathname("d").unlink 1536 | assert_file.not_exist?("d") 1537 | } 1538 | end 1539 | 1540 | def test_matchop 1541 | assert_raise(NoMethodError) { Pathname("a") =~ /a/ } 1542 | end 1543 | 1544 | def test_file_basename 1545 | assert_equal("bar", File.basename(Pathname.new("foo/bar"))) 1546 | end 1547 | 1548 | def test_file_dirname 1549 | assert_equal("foo", File.dirname(Pathname.new("foo/bar"))) 1550 | end 1551 | 1552 | def test_file_split 1553 | assert_equal(["foo", "bar"], File.split(Pathname.new("foo/bar"))) 1554 | end 1555 | 1556 | def test_file_extname 1557 | assert_equal(".baz", File.extname(Pathname.new("bar.baz"))) 1558 | end 1559 | 1560 | def test_file_fnmatch 1561 | assert_file.fnmatch("*.*", Pathname.new("bar.baz")) 1562 | end 1563 | 1564 | def test_relative_path_from_casefold 1565 | assert_separately([], <<-'end;') # do 1566 | module File::Constants 1567 | remove_const :FNM_SYSCASE 1568 | FNM_SYSCASE = FNM_CASEFOLD 1569 | end 1570 | require 'pathname' 1571 | foo = Pathname.new("fo\u{f6}") 1572 | bar = Pathname.new("b\u{e4}r".encode("ISO-8859-1")) 1573 | assert_instance_of(Pathname, foo.relative_path_from(bar)) 1574 | end; 1575 | end 1576 | 1577 | def test_relative_path_from_mock 1578 | assert_equal( 1579 | Pathname.new("../bar"), 1580 | Pathname.new("/foo/bar").relative_path_from(Pathname.new("/foo/baz"))) 1581 | assert_equal( 1582 | Pathname.new("../bar"), 1583 | Pathname.new("/foo/bar").relative_path_from("/foo/baz")) 1584 | obj = Object.new 1585 | def obj.cleanpath() Pathname.new("/foo/baz") end 1586 | def obj.is_a?(m) m == Pathname end 1587 | assert_equal( 1588 | Pathname.new("../bar"), 1589 | Pathname.new("/foo/bar").relative_path_from(obj)) 1590 | end 1591 | end 1592 | --------------------------------------------------------------------------------