├── .appveyor.yml ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── .mailmap ├── .rubocop.yml ├── .rustfmt.toml ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── Contributors.md ├── Gemfile ├── MIT-LICENSE.txt ├── README.md ├── Rakefile ├── assets ├── chop_basename_benchmark.jpg └── tweet.png ├── bin ├── console └── setup ├── ext └── Rakefile ├── faster_path.gemspec ├── lib ├── faster_path.rb └── faster_path │ ├── optional │ ├── monkeypatches.rb │ └── refinements.rb │ ├── thermite_initialize.rb │ └── version.rb ├── spec ├── default.mspec └── init.rb ├── src ├── basename.rs ├── chop_basename.rs ├── cleanpath_aggressive.rs ├── cleanpath_conservative.rs ├── debug.rs ├── dirname.rs ├── extname.rs ├── helpers.rs ├── lib.rs ├── memrnchr.rs ├── path_parsing.rs ├── pathname.rs ├── pathname_sys.rs ├── plus.rs ├── prepend_prefix.rs ├── relative_path_from.rs └── rust_arch_bits.rs └── test ├── absolute_test.rb ├── add_trailing_separator_test.rb ├── basename_test.rb ├── benches ├── absolute_benchmark.rb ├── add_trailing_separator_benchmark.rb ├── basename_benchmark.rb ├── children_benchmark.rb ├── children_compat_benchmark.rb ├── chop_basename_benchmark.rb ├── cleanpath_aggressive_benchmark.rb ├── cleanpath_conservative_benchmark.rb ├── del_trailing_separator_benchmark.rb ├── directory_benchmark.rb ├── dirname_benchmark.rb ├── entries_benchmark.rb ├── entries_compat_benchmark.rb ├── extname_benchmark.rb ├── has_trailing_separator_benchmark.rb ├── join_benchmark.rb ├── plus_benchmark.rb ├── relative_benchmark.rb └── relative_path_from_benchmark.rb ├── benchmark_helper.rb ├── blank_test.rb ├── children_test.rb ├── chop_basename_test.rb ├── cleanpath_aggressive_test.rb ├── cleanpath_conservative_test.rb ├── del_trailing_separator_test.rb ├── directory_test.rb ├── dirname_test.rb ├── entries_test.rb ├── extname_test.rb ├── has_trailing_separator_test.rb ├── join_test.rb ├── monkeypatches └── faster_path_test.rb ├── output_type_compatibility_test.rb ├── pbench ├── pbench.rb └── pbench_suite.rb ├── plus_test.rb ├── refinements ├── absoulte_test.rb ├── add_trailing_separator_test.rb ├── basename_test.rb ├── chop_basename_test.rb ├── directory_test.rb ├── dirname_test.rb ├── extname_test.rb ├── has_trailing_separator_test.rb ├── plus_test.rb └── relative_test.rb ├── relative_path_from_test.rb ├── relative_test.rb ├── ripple_effects ├── cleanpath_aggressive_test.rb ├── cleanpath_conservative_test.rb ├── del_trailing_separator_test.rb ├── has_trailing_separator_test.rb ├── join_test.rb └── plus_test.rb ├── support └── bundler │ ├── helpers.rb │ └── path.rb ├── test_helper.rb └── thermite_test.rb /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # based on rutie and rust_blank .appveyor.yml files 2 | platform: 3 | - x86 4 | - x64 5 | environment: 6 | matrix: 7 | - RUST_VERSION: stable 8 | BUILD_TARGET: gnu 9 | RUBY_VERSION: 23 10 | - RUST_VERSION: beta 11 | BUILD_TARGET: gnu 12 | RUBY_VERSION: 23 13 | - RUST_VERSION: nightly 14 | BUILD_TARGET: gnu 15 | RUBY_VERSION: 23 16 | cache: 17 | - target\debug\build 18 | - target\debug\deps 19 | - '%USERPROFILE%\.cargo' 20 | install: 21 | - ps: | 22 | $env:PATH += ";C:\rust\bin"; 23 | if ($env:platform -eq 'x86') { 24 | $env:RUBY_DIR = "Ruby${env:RUBY_VERSION}" 25 | $arch_expanded = "i686-pc-windows-${env:BUILD_TARGET}"; 26 | $env:ARCH = "x86"; 27 | $env:bits = "32"; 28 | } else { 29 | $env:RUBY_DIR = "Ruby${env:RUBY_VERSION}-x64" 30 | $arch_expanded = "x86_64-pc-windows-${env:BUILD_TARGET}"; 31 | $env:ARCH = "amd64"; 32 | $env:bits ="64"; 33 | } 34 | $env:WIN_RUBY_BIN = "C:\${env:RUBY_DIR}\bin"; 35 | $env:PATH = "${env:WIN_RUBY_BIN};${env:PATH}"; 36 | if ($env:BUILD_TARGET -eq 'gnu') { 37 | $env:PATH += ";C:\msys64\mingw${env:bits}\bin"; 38 | gcc --version; 39 | } 40 | if ($env:RUST_VERSION -eq 'stable') { 41 | echo "Downloading $channel channel manifest"; 42 | Start-FileDownload "https://static.rust-lang.org/dist/channel-rust-stable"; 43 | 44 | $env:RUST_VERSION = Get-Content channel-rust-stable | Select -first 1 | %{$_.split('-')[1]} 45 | } 46 | $env:rust_installer = "rust-${env:RUST_VERSION}-${arch_expanded}.exe"; 47 | $tag_suffix = echo "${env:APPVEYOR_REPO_TAG_NAME}" | Select-String -pattern "-rust$" 48 | if ($tag_suffix) { 49 | $env:RUST_TAG = "1"; 50 | } 51 | - curl --show-error --location --retry 5 --output rust-installer.exe https://static.rust-lang.org/dist/%rust_installer% 52 | - .\rust-installer.exe /VERYSILENT /NORESTART /DIR="C:\rust" 53 | - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %ARCH% 54 | - rustc -vV 55 | - cargo -vV 56 | - ruby --version 57 | - gem --version 58 | - rake --version 59 | - bundle --version 60 | - bundle config --local path vendor/bundle 61 | - bundle install 62 | build: false 63 | test_script: 64 | - bundle exec rake --trace test 65 | matrix: 66 | allow_failures: 67 | - RUST_VERSION: nightly 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Feature Request 2 | 3 | You're more than welcome to open as many feature requests as you'd like. 4 | 5 | ## Error Reporting 6 | 7 | In order to make your issue as helpful as possible please include the results 8 | from the rake command `rake sysinfo`. Give a clear description on how to 9 | reproduce any problems. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /target/ 3 | /.yardoc 4 | /Gemfile.lock 5 | /_yardoc/ 6 | /coverage/ 7 | /doc/ 8 | /log/ 9 | /pkg/ 10 | /spec/reports/ 11 | /tmp/ 12 | mkmf.log 13 | **/*.swp 14 | **/*.gem 15 | .idea 16 | /rubyspec_temp/ 17 | *.dll 18 | *.dylib 19 | *.so 20 | .ruby-version 21 | /tags 22 | spec/ruby_spec/* 23 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spec/ruby_spec"] 2 | path = spec/ruby_spec 3 | url = https://github.com/ruby/spec 4 | [submodule "spec/mspec"] 5 | path = spec/mspec 6 | url = https://github.com/ruby/mspec 7 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Daniel P. Clark <6ftdan@gmail.com> danielpclark 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Style *** 2 | 3 | control_style = "Legacy" 4 | chain_indent = "Visual" 5 | fn_brace_style = "PreferSameLine" 6 | fn_args_layout = "Visual" 7 | fn_args_density = "CompressedIfEmpty" 8 | where_layout = "HorizontalVertical" 9 | where_pred_indent = "Visual" 10 | where_style = "Legacy" 11 | use_try_shorthand = true 12 | 13 | # Tabs *** 14 | 15 | tab_spaces = 2 16 | hard_tabs = false 17 | 18 | # Spaces *** 19 | 20 | space_before_bound = false 21 | space_after_bound_colon = true 22 | space_before_type_annotation = false 23 | space_after_type_annotation_colon = true 24 | space_after_struct_lit_field_colon = true 25 | space_before_struct_lit_field_colon = false 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | os: 4 | - linux 5 | - osx 6 | cache: 7 | bundler: true 8 | directories: 9 | - $HOME/.cargo 10 | - $TRAVIS_BUILD_DIR/target 11 | rvm: 12 | - 2.4.6 13 | - 2.5.5 14 | - 2.6.3 15 | env: 16 | global: 17 | - NO_LINK_RUTIE=1 18 | - RUST_BACKTRACE=full 19 | - PATH=$PATH:$HOME/.cargo/bin 20 | - secure: UCarEfq9wDpD6FV0dIdfDMCWfeLoKBAM2PS+xZ82K1uoFBMtwet/UuEuz06OnX6B9k2TwV0aqQpkUuo6o+h9YoHKAGz1/O5iB5uJaHcoII8yL43LH4YUADqupsgbvFzUCdcLuKCvDVG5RD96tt5XX87UdyZMgHvBV41wKOoUF+0ZyIWq8sKFPlItM+oYN55gY+PZ5ZadsOOkWDbTAG/LuxDi1GJxb/ObiYkjk4b93xzKEx1t7H2LSs8/f8dg0w1gnM6bVXQHjdXGuAw8jqy6IoszFh2oKDyttix4yB5l5xxj/NwO/aJNmOLBRNf611WskH6RLKWteCxwyY0gIZnDlC7q+SMYJtDapuffvqqKdlC5ECcVNimw9D3DwuxizP/5IGoC5+X+RIJiWBx/RJgFV2dw22/XvC62rOMttQL1K1dUByMRJL1pjLoauofbTdJgZgdrb1eDxGNyxL+Rg8za2wqs5NtENnXY6RIX4NvSXUPE9vRLdYnmGtH5hLF36hRh5AwvgOZ544xYT2ss6FxUJNo/zphlE7zfZrD+waeUBRUabzi6S5xEBXainj01UjtfVXRDtcjV9vbh5pYYjZsX8lLZAdW8OZeKxhaOv7zQyUtAwMHE/sGfKj1yQSDh2fscY48mepV7xUPVE7jRgUDk1ayt2a+WeGE2DCR3KBv5gkk= 21 | matrix: 22 | - WITH_REGRESSION=true 23 | - TEST_MONKEYPATCHES=true WITH_REGRESSION=true 24 | - RUST_BACKTRACE=1 ENCODING=true TEST_MONKEYPATCHES=true WITH_REGRESSION=true 25 | matrix: 26 | allow_failures: 27 | - env: RUST_BACKTRACE=1 ENCODING=true TEST_MONKEYPATCHES=true WITH_REGRESSION=true 28 | - os: linux 29 | rvm: 2.6.3 30 | env: LONG_RUN=20 31 | script: bundle exec rake pbench 32 | - os: linux 33 | rvm: 2.4.6 34 | if: tag =~ ^v0 35 | env: 36 | - os: linux 37 | rvm: 2.5.5 38 | if: tag =~ ^v0 39 | env: 40 | - os: linux 41 | rvm: 2.6.3 42 | if: tag =~ ^v0 43 | env: 44 | - os: osx 45 | rvm: 2.4.6 46 | if: tag =~ ^v0 47 | env: 48 | - os: osx 49 | rvm: 2.5.5 50 | if: tag =~ ^v0 51 | env: 52 | - os: osx 53 | rvm: 2.6.3 54 | if: tag =~ ^v0 55 | env: 56 | include: 57 | - os: linux 58 | rvm: 2.6.3 59 | env: LONG_RUN=20 60 | script: bundle exec rake pbench 61 | - os: linux 62 | rvm: 2.4.6 63 | if: tag =~ ^v0 64 | env: 65 | - os: linux 66 | rvm: 2.5.5 67 | if: tag =~ ^v0 68 | env: 69 | - os: linux 70 | rvm: 2.6.3 71 | if: tag =~ ^v0 72 | env: 73 | - os: osx 74 | rvm: 2.4.6 75 | if: tag =~ ^v0 76 | env: 77 | - os: osx 78 | rvm: 2.5.5 79 | if: tag =~ ^v0 80 | env: 81 | - os: osx 82 | rvm: 2.6.3 83 | if: tag =~ ^v0 84 | env: 85 | before_install: 86 | - curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable 87 | - gem install bundler 88 | deploy: 89 | provider: releases 90 | api_key: 91 | secure: NhCKpRLm/aOLOoFuoXHkc1AgNIoIRlVrZs08VxJ7Hbjw0/iUX4Uq+0DloX9NVRKupd3oVH/p6gNnPQEkOdpDmx+4x8KdlwX3uYLOQ8LZTjjarOb+3StDsl748Zm1dnO69SkVHZPQ6m6W2ITg065eH82pF83QUp6Th7MLuF8f9F4Dlgdt4thHtqU43VWWRGQAT0/haLZhdKczCQJDuWrDbPHNCZwgCdm59htSmQHL1FyWAehymGy+iI9fKvs+ZifRqV/PxvC3nJSEKFbFW/aq/1LvA0AL2GKjClODiklDGdMoDsFFH6t889ZYMgIzOITP7O3cpN2+Y5tMy03U8Yf4hKg8M0fPoA1ufJ2k+OGNEFOcLeWfbwZrdLEBsm+U8vZ/r2pvz4837IA/vrHbFUFnT8ynv+JZfyaNwKNh7durRgOJYHpwsxqe/PSjlQocL1Z0FBeMVKQYViIz4g/rgBzQHwyrzou6FSGhk3kYRMD2+gPuZ+pj/hnWXc/fF3UvtAcFOxuQrk07OA7T9sZhHhA+/lQCYhVNA1YQen01dkX1yYmA+jpZvZ3p6gyhmvHksEmrJIu912QPMWFehtjPlF4te7FppQYDdjBpyePIZs0GLz6Puf5YjVu6E2knfHQzTIM3nvSko17D9fDv7d/1oXuMgiOlM0YzRPeEtr6TtGwee98= 92 | file: faster_path-*.tar.gz 93 | file_glob: true 94 | skip_cleanup: true 95 | on: 96 | condition: -z ${TEST_MONKEYPATCHES} 97 | repo: danielpclark/faster_path 98 | tags: true 99 | after_success: bundle exec rake thermite:tarball 100 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "faster_path" 5 | version = "0.0.1" 6 | dependencies = [ 7 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "rutie 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "lazy_static" 14 | version = "1.3.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "libc" 19 | version = "0.2.58" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "memchr" 24 | version = "2.2.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [[package]] 28 | name = "rutie" 29 | version = "0.6.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | dependencies = [ 32 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 34 | ] 35 | 36 | [metadata] 37 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 38 | "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" 39 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 40 | "checksum rutie 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "695eb534c22e60d5723d57be6b3b6a4e9367e8b203e607a19eb94d03cb32ea34" 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "faster_path" 3 | version = "0.0.1" 4 | authors = ["Daniel P. Clark <6ftdan@gmail.com>"] 5 | description = "Alternative to Pathname" 6 | homepage = "https://github.com/danielpclark/faster_path" 7 | repository = "https://github.com/danielpclark/faster_path" 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | 11 | [package.metadata.thermite] 12 | github_releases = true 13 | 14 | [lib] 15 | name = "faster_path" 16 | crate-type = ["dylib"] 17 | 18 | [dependencies] 19 | rutie = "0.6.0" 20 | lazy_static = "1.3" 21 | memchr = "2.2" 22 | -------------------------------------------------------------------------------- /Contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | - Daniel P. Clark <6ftdan@gmail.com> 4 | - Matt Blewitt 5 | - Gleb Mazovetskiy 6 | - Dmitry Gritsay 7 | - Mark Lee 8 | - Florian Gilcher 9 | - igor 10 | - mrageh 11 | - Ben Striegel 12 | - Julien Blanchard 13 | - dylee 14 | - mattbeedle 15 | - stereobooster 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' do 2 | # Specify your gem's dependencies in faster_path.gemspec 3 | gemspec 4 | 5 | group :test do 6 | gem 'coveralls', require: false 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Daniel P. Clark & FasterPath Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FasterPath 2 | [![Gem Version](https://badge.fury.io/rb/faster_path.svg)](https://badge.fury.io/rb/faster_path) 3 | [![TravisCI Build Status](https://travis-ci.org/danielpclark/faster_path.svg?branch=master)](https://travis-ci.org/danielpclark/faster_path) 4 | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/10ul0gk3cwhlt2lj/branch/master?svg=true)](https://ci.appveyor.com/project/danielpclark/faster-path/branch/master) 5 | [![Latest Tag](https://img.shields.io/github/tag/danielpclark/faster_path.svg)](https://github.com/danielpclark/faster_path/tags) 6 | [![Commits Since Last Release](https://img.shields.io/github/commits-since/danielpclark/faster_path/v0.3.10.svg)](https://github.com/danielpclark/faster_path/pulse) 7 | [![Binary Release](https://img.shields.io/github/release/danielpclark/faster_path.svg)](https://github.com/danielpclark/faster_path/releases) 8 | [![Coverage Status](https://coveralls.io/repos/github/danielpclark/faster_path/badge.svg?branch=master)](https://coveralls.io/github/danielpclark/faster_path?branch=master) 9 | [![Inline docs](http://inch-ci.org/github/danielpclark/faster_path.svg?branch=master)](http://inch-ci.org/github/danielpclark/faster_path) 10 | [![Code Triagers Badge](https://www.codetriage.com/danielpclark/faster_path/badges/users.svg)](https://www.codetriage.com/danielpclark/faster_path) 11 | 12 | #### This gem shaves off more than 30% of my Rails application page load time. 13 | 14 | The primary **GOAL** of this project is to improve performance in the most heavily used areas of Ruby as 15 | path relation and file lookup is currently a huge bottleneck in performance. As this is the case the 16 | path performance updates will likely not be limited to just changing the Pathname class but also will 17 | be offering changes in related methods and classes. 18 | 19 | Users will have the option to write their apps directly for this library, or they can choose to either 20 | refine or monkeypatch the existing standard library. Refinements are narrowed to scope and monkeypatching will 21 | be a sledge hammer ;-) 22 | 23 | ## Why 24 | 25 | I read a blog post about the new Sprockets 3.0 series being faster than the 2.0 series so I tried it out. It was not faster but rather it made my website take 31.8% longer to load. So I reverted back to the 2.0 series and I did a check on Rails on what methods were being called the most and where the application spends 26 | most of its time. It turns out roughly 80% _(as far as I can tell)_ of the time spent and calls made 27 | are file Path handling. This is shocking, but it only gets worse when handling assets. **That is 28 | why we need to deal with these load heavy methods in the most efficient manner!** 29 | 30 | Here's a snippet of a Rails stack profile with some of the most used and time expensive methods. 31 | 32 | ``` 33 | Booting: development 34 | Endpoint: "/" 35 | user system total real 36 | 100 requests 26.830000 1.780000 28.610000 ( 28.866952) 37 | Running `stackprof tmp/2016-06-09T00:42:10-04:00-stackprof-cpu-myapp.dump`. Execute `stackprof --help` for more info 38 | ================================== 39 | Mode: cpu(1000) 40 | Samples: 7184 (0.03% miss rate) 41 | GC: 1013 (14.10%) 42 | ================================== 43 | TOTAL (pct) SAMPLES (pct) FRAME 44 | 1894 (26.4%) 1894 (26.4%) Pathname#chop_basename 45 | 1466 (20.4%) 305 (4.2%) Pathname#plus 46 | 1628 (22.7%) 162 (2.3%) Pathname#+ 47 | 234 (3.3%) 117 (1.6%) ActionView::PathResolver#find_template_paths 48 | 2454 (34.2%) 62 (0.9%) Pathname#join 49 | 57 (0.8%) 52 (0.7%) ActiveSupport::FileUpdateChecker#watched 50 | 760 (10.6%) 47 (0.7%) Pathname#relative? 51 | 131 (1.8%) 25 (0.3%) ActiveSupport::FileUpdateChecker#max_mtime 52 | 88 (1.2%) 21 (0.3%) Sprockets::Asset#dependency_fresh? 53 | 18 (0.3%) 18 (0.3%) ActionView::Helpers::AssetUrlHelper#compute_asset_extname 54 | 108 (1.5%) 14 (0.2%) ActionView::Helpers::AssetUrlHelper#asset_path 55 | ``` 56 | 57 | Here are some additional stats. From Rails loading to my home page, these methods are called _(not directly, Rails & gems call them)_ this many times. And the home page has minimal content. 58 | ```ruby 59 | Pathname#to_s called 29172 times. 60 | Pathname#<=> called 24963 times. 61 | Pathname#chop_basename called 24456 times 62 | Pathname#initialize called 23103 times. 63 | File#initialize called 23102 times. 64 | Pathname#absolute? called 4840 times. 65 | Pathname#+ called 4606 times. 66 | Pathname#plus called 4606 times. 67 | Pathname#join called 4600 times. 68 | Pathname#extname called 4291 times. 69 | Pathname#hash called 4207 times. 70 | Pathname#to_path called 2706 times. 71 | Pathname#directory? called 2396 times. 72 | Pathname#entries called 966 times. 73 | Dir#each called 966 times. 74 | Pathname#basename called 424 times. 75 | Pathname#prepend_prefix called 392 times. 76 | Pathname#cleanpath called 392 times. 77 | Pathname#cleanpath_aggressive called 392 times. 78 | Pathname#split called 161 times. 79 | Pathname#open called 153 times. 80 | Pathname#exist? called 152 times. 81 | Pathname#sub called 142 times. 82 | ``` 83 | 84 | After digging further I've found that Pathname is heavily used in Sprockets 2 but in Sprockets 3 they switched to calling Ruby's faster methods from `File#initialize` and `Dir#each`. It appears they've written all of the path handling on top of these themselves in Ruby. They achieved some performance gain by switching to rawer code methods, but then they lost more than that in performance by the **many** method calls built on top of that. 85 | 86 | If you want to see the best results in Rails with this gem you will likely need to be using the Sprockets 2.0 series. Otherwise this library would need to rewrite Sprockets itself. 87 | 88 | I've said this about Sprockets but this required two other gems to be updated as well. These are the gems and versions I upgraded and consider group 1 (Sprockets 2) and group 2 (Sprockets 3). My data is based on method calls rather than source code. 89 | 90 | |Sprockets 2 Group|Sprockets 3 Group| 91 | |:---:|:---:| 92 | |sprockets 2.12.4|sprockets 3.6| 93 | |sass 3.2.19|sass 5.0.4| 94 | |bootstrap-sass 3.3.4.1|bootstrap-sass 3.3.6| 95 | 96 | ## Performance Specifics 97 | 98 | The headline for the amount for improvement on this library is specific to only the improvement made with the method `chop_basename`. Just so you know; in my initial release I had a bug in which that method immediately returned nothing. Now the good thing about this is that it gave me some very valuable information. First I found that all my Rails site tests still passed. Second I found that all my assets no longer loaded in the website. And third, and most importantly, I found my Rails web pages loaded just more than 66% faster without the cost of time that `chop_basename` took. 99 | 100 | **That's right; the path handling for assets in your website \*consumes more than 2/3rds of your websites page load time.** 101 | 102 | So now we have some real numbers to work with We can be generoues and use 66% as our margin of area to improve over _(for `chop_basename` specifically, not counting the benefit from improving the performance in other file path related methods)_. That means we want to remove as much of that percentage from the overall systems page load time. The original headline boasts over 33% performance improvement — that was when `chop_basename` was improved by just over 50%. Now `chop_basename` is improved by 83.4%. That alone should make your site run 55.044% faster now _(given your performance profile stats are similar to mine)_. 103 | 104 | ## What Rails Versions Will This Apply To? 105 | 106 | As mentioned earlier Sprockets, which handles assets, changed away from using `Pathname` at all when moving from major version 2 to 3. So if you're using Sprockets 3 or later you won't reap the biggest performance rewards from using this gem for now _(it's my goal to have this project become a core feature that Rails depends on and yes… that's a big ask)_. That is unless you write you're own implementation to re-integrate the use of `Pathname` and `FasterPath` into your asset handling library. For now just know that the Sprockets 2 series primarily works with Rails 4.1 and earlier. It may work in later Rails versions but I have not investigated this. 107 | 108 | ## Status 109 | 110 | * Rust compilation is working 111 | * Methods are stable 112 | * Thoroughly tested 113 | * Testers and developers are most welcome 114 | * Windows & encoding support is underway! 115 | 116 | ## Installation 117 | 118 | Ensure Rust is installed: 119 | 120 | [Rust Downloads](https://www.rust-lang.org/downloads.html) 121 | 122 | ``` 123 | curl -sSf https://static.rust-lang.org/rustup.sh | sh 124 | ``` 125 | 126 | Add this line to your application's Gemfile: 127 | 128 | ```ruby 129 | gem 'faster_path', '~> 0.3.10' 130 | ``` 131 | 132 | And then execute: 133 | 134 | $ bundle 135 | 136 | Or install it yourself as: 137 | 138 | $ gem install faster_path 139 | 140 | ## Visual Benchmarks 141 | 142 | Benchmarks in Faster Path now produce visual graph charts of performance improvements. 143 | When you run `export GRAPH=1; bundle && rake bench` the graph art will be placed in `doc/graph/`. Here's the performance 144 | improvement result for the `chop_basename` method. 145 | 146 | ![Visual Benchmark](https://raw.githubusercontent.com/danielpclark/faster_path/master/assets/chop_basename_benchmark.jpg "Visual Benchmark") 147 | 148 | ## Usage 149 | 150 | Add the proper require to your project. 151 | 152 | ```ruby 153 | require "faster_path" 154 | ``` 155 | 156 | Current methods implemented: 157 | 158 | |FasterPath Rust Implementation|Ruby 2.5.0 Implementation|Time Shaved Off| 159 | |---|---|:---:| 160 | | `FasterPath.absolute?` | `Pathname#absolute?` | 95.3% | 161 | | `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 48.4% | 162 | | `FasterPath.basename` | `File.basename` | 12.0% | 163 | | `FasterPath.children` | `Pathname#children` | 34.4% | 164 | | `FasterPath.chop_basename` | `Pathname#chop_basename` | 83.4% | 165 | | `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` | 94.1% | 166 | | `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` | 93.5% | 167 | | `FasterPath.del_trailing_separator` | `Pathname#del_trailing_separator` | 85.4% | 168 | | `FasterPath.directory?` | `Pathname#directory?` | 6.4% | 169 | | `FasterPath.dirname` | `File.dirname` | 55.4% | 170 | | `FasterPath.entries` | `Pathname#entries` | 41.0% | 171 | | `FasterPath.extname` | `File.extname` | 63.1% | 172 | | `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 88.9% | 173 | | `FasterPath.plus` | `Pathname#join` | 79.1% | 174 | | `FasterPath.plus` | `Pathname#plus` | 94.7% | 175 | | `FasterPath.relative?` | `Pathname#relative?` | 92.6% | 176 | | `FasterPath.relative_path_from` | `Pathname#relative_path_from` | 93.3% | 177 | 178 | You may choose to use the methods directly, or scope change to rewrite behavior on the 179 | standard library with the included refinements, or even call a method to monkeypatch 180 | everything everywhere. 181 | 182 | For the scoped **refinements** you will need to 183 | 184 | ``` 185 | require "faster_path/optional/refinements" 186 | using FasterPath::RefinePathname 187 | ``` 188 | 189 | And for the sledgehammer of monkey patching you can do 190 | 191 | ``` 192 | require "faster_path/optional/monkeypatches" 193 | FasterPath.sledgehammer_everything! 194 | ``` 195 | 196 | ## Optional Rust implementations 197 | 198 | **These are stable, not performant, and not included in `Pathname` by default.** 199 | 200 | These will **not** be included by default in monkey-patches. To try them with monkeypatching use the environment flag of `WITH_REGRESSION`. These methods are here to be improved upon. 201 | 202 | |FasterPath Implementation|Ruby Implementation| 203 | |---|---| 204 | | `FasterPath.entries_compat` | `Pathname.entries` | 205 | | `FasterPath.children_compat` | `Pathname.children` | 206 | 207 | It's been my observation (and some others) that the Rust implementation of the C code for `File` has similar results but 208 | performance seems to vary based on CPU cache on possibly 64bit/32bit system environments. These are not included by default when the monkey patch method `FasterPath.sledgehammer_everything!` is executed. 209 | 210 | ## Getting Started with Development 211 | 212 | The primary methods to target are mostly listed in the **Why** section above. You may find the Ruby 213 | source code useful for Pathname's [Ruby source](https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/ext/pathname/lib/pathname.rb), 214 | [C source](https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/ext/pathname/pathname.c), 215 | [tests](https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/test/pathname/test_pathname.rb), 216 | and checkout the [documentation](http://ruby-doc.org/stdlib-2.3.1/libdoc/pathname/rdoc/Pathname.html). 217 | 218 | Methods will be written as exclusively in Rust as possible. Even just writing a **not** in Ruby with a 219 | Rust method like `!absolute?` _(not absolute)_ drops 39% of the performance already gained in Rust. 220 | Whenever feasible implement it in Rust. 221 | 222 | After checking out the repo, make sure you have Rust installed, then run `bundle`. 223 | Run `rake test` to run the tests, and `rake bench` for benchmarks. 224 | 225 | ### Building and running tests 226 | 227 | First, bundle the gem's development dependencies by running `bundle`. Rust compilation is included in the current rake commands. 228 | 229 | FasterPath is tested with [The Ruby Spec Suite](https://github.com/ruby/spec) to ensure it is compatible with the 230 | native implementation, and also has its own test suite testing its monkey-patching and refinements functionality. 231 | 232 | To run all the tests at once, simply run `rake`. 233 | To run all the ruby spec tests, run `mspec`. 234 | 235 | To run an individual test or benchmark from FasterPath's own suite: 236 | 237 | ```sh 238 | # An individual test file: 239 | ruby -I lib:test test/benches/absolute_benchmark.rb 240 | # All tests: 241 | rake minitest 242 | ``` 243 | 244 | To run an individual ruby spec test, run `mspec` with a path relative to `spec/ruby_spec`, e.g.: 245 | 246 | ```sh 247 | # A path to a file or a directory: 248 | mspec core/file/basename_spec.rb 249 | # Tests most relevant to FasterPath: 250 | mspec core/file library/pathname 251 | # All tests: 252 | mspec 253 | ``` 254 | 255 | ## Contributing 256 | 257 | Bug reports and pull requests are welcome on GitHub at https://github.com/danielpclark/faster_path. 258 | 259 | 260 | ## License 261 | 262 | [MIT License](http://opensource.org/licenses/MIT) or APACHE 2.0 at your pleasure. 263 | 264 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | require 'thermite/tasks' 4 | require 'faster_path/thermite_initialize' 5 | 6 | desc 'System Details' 7 | task :sysinfo do 8 | puts "** FasterPath - faster_path v#{FasterPath::VERSION} **" 9 | puts RUBY_DESCRIPTION 10 | puts `rustc -Vv` 11 | puts `ldd --version`.scan(/(.*)\n/).first if RbConfig::CONFIG["host_os"].to_s[/linux/] 12 | puts `cargo -Vv` 13 | deps = Regexp.new(/name = "([\w\-]+)"\nversion = "(\d+\.\d+\.\d+)"/) 14 | puts "** Rust dependencies **" 15 | IO.read("Cargo.lock"). 16 | scan(deps). 17 | delete_if {|i| i[0] == "faster_path" }. 18 | each {|name, version| puts "#{name.ljust(20)}#{version}"} 19 | puts "** Ruby dependencies **" 20 | deps = IO.read("Gemfile.lock") 21 | puts deps[(deps =~ /DEPENDENCIES/)..-1].sub("\n\n", "\n") 22 | puts "RAKE\n #{Rake::VERSION}" 23 | end 24 | 25 | thermite = Thermite::Tasks.new 26 | 27 | desc "Generate Contriburs.md Manifest" 28 | task :contrib do 29 | puts "Generating Contriburs.md Manifest" 30 | exec "printf '# Contributors\n 31 | ' > Contributors.md;git shortlog -s -n -e | sed 's/^......./- /' >> Contributors.md" 32 | end 33 | 34 | desc 'Build + clean up Rust extension' 35 | task build_lib: 'thermite:build' do 36 | thermite.run_cargo 'clean' 37 | end 38 | 39 | desc 'Code Quality Check' 40 | task :lint do 41 | puts 42 | puts 'Quality check starting...' 43 | sh 'rubocop' 44 | puts 45 | end 46 | 47 | desc "Run Rust Tests" 48 | task :cargo do 49 | ruby_lib = RbConfig::CONFIG['libdir'] 50 | sh( 51 | { 52 | 'LD_LIBRARY_PATH' => ruby_lib, 53 | 'DYLD_LIBRARY_PATH' => ruby_lib 54 | }, 55 | 'cargo -vv test -- --nocapture' 56 | ) 57 | end 58 | 59 | Rake::TestTask.new(minitest: :build_lib) do |t| 60 | t.libs = %w[lib test] 61 | t.pattern = 'test/**/*_test.rb' 62 | end 63 | 64 | task :init_mspec do |_t| 65 | if Dir.open('spec/mspec').entries.-([".", ".."]).empty? 66 | `git submodule init` 67 | `git submodule update` 68 | end 69 | end 70 | 71 | task test: [:cargo, :minitest, :lint, :pbench, :init_mspec] do |_t| 72 | exec 'spec/mspec/bin/mspec --format spec core/file/basename core/file/extname core/file/dirname library/pathname' 73 | end 74 | 75 | desc "Full mspec results w/o encoding" 76 | task mspec_full: :init_mspec do 77 | exec %(bash -c "TEST_MONKEYPATCHES=true WITH_REGRESSION=true spec/mspec/bin/mspec --format spec core/file/basename core/file/extname core/file/dirname library/pathname") 78 | end 79 | 80 | desc "Full mspec results w/ encoding" 81 | task mspec_encoding_full: :init_mspec do 82 | exec %(bash -c "ENCODING=1 TEST_MONKEYPATCHES=true WITH_REGRESSION=true mspec --format spec core/file/basename core/file/extname core/file/dirname library/pathname") 83 | end 84 | 85 | Rake::TestTask.new(bench: :build_lib) do |t| 86 | if ENV['GRAPH'] 87 | `bundle install` 88 | end 89 | t.libs = %w[lib test] 90 | t.pattern = 'test/**/*_benchmark.rb' 91 | end 92 | 93 | Rake::TestTask.new(pbench: :build_lib) do |t| 94 | if ARGV.last == '--long-run' 95 | ENV['LONG_RUN'] = '10' 96 | end 97 | t.libs = %w[lib test test/pbench] 98 | t.pattern = 'test/pbench/pbench_suite.rb' 99 | end 100 | 101 | task default: :test 102 | -------------------------------------------------------------------------------- /assets/chop_basename_benchmark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielpclark/faster_path/726c7f75de64ec11b75259dff50248b1fc28ef22/assets/chop_basename_benchmark.jpg -------------------------------------------------------------------------------- /assets/tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielpclark/faster_path/726c7f75de64ec11b75259dff50248b1fc28ef22/assets/tweet.png -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "faster_path" 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 15 | -------------------------------------------------------------------------------- /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/Rakefile: -------------------------------------------------------------------------------- 1 | require 'thermite/tasks' 2 | require_relative '../lib/faster_path/thermite_initialize' 3 | 4 | project_toplevel_dir = File.dirname(__dir__) 5 | Thermite::Tasks.new(cargo_project_path: project_toplevel_dir, 6 | ruby_project_path: project_toplevel_dir) 7 | 8 | task default: 'thermite:build' 9 | -------------------------------------------------------------------------------- /faster_path.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'faster_path/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'faster_path' 8 | spec.version = FasterPath::VERSION 9 | spec.authors = ['Daniel P. Clark'] 10 | spec.email = ['6ftdan@gmail.com'] 11 | spec.summary = 'Reimplementation of Pathname for better performance' 12 | spec.description = 'FasterPath is a reimplementation of Pathname for better performance.' 13 | spec.homepage = 'https://github.com/danielpclark/faster_path' 14 | spec.license = 'MIT OR Apache-2.0' 15 | 16 | spec.files = [ 17 | 'Cargo.lock', 'Cargo.toml', 'Gemfile', 'MIT-LICENSE.txt', 'README.md', 'Rakefile', 18 | 'bin/console', 'bin/setup', 'ext/Rakefile', 'faster_path.gemspec', 'lib/faster_path.rb', 19 | 'lib/faster_path/version.rb', 'lib/faster_path/thermite_initialize.rb', 20 | 'lib/faster_path/optional/monkeypatches.rb', 'lib/faster_path/optional/refinements.rb' 21 | ] 22 | spec.files += Dir['src/**/*'] 23 | 24 | spec.extensions = ['ext/Rakefile'] 25 | spec.require_paths = ['lib'] 26 | 27 | spec.add_dependency 'bundler', '>= 1.12' 28 | spec.add_dependency 'rake', '~> 12.3' 29 | spec.add_dependency 'thermite', '0.13.0' 30 | spec.add_development_dependency 'read_source', '~> 0.2.6' 31 | spec.add_development_dependency 'minitest', '~> 5.11' 32 | spec.add_development_dependency 'minitest-reporters', '1.1' 33 | spec.add_development_dependency 'color_pound_spec_reporter', '~> 0.0.9' 34 | spec.add_development_dependency 'rubocop', '0.53' 35 | spec.add_development_dependency 'stop_watch', '~> 1.0' 36 | if !ENV['CI'] && ENV['GRAPH'] 37 | spec.add_development_dependency 'gruff', '~> 0.7.0' 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/faster_path.rb: -------------------------------------------------------------------------------- 1 | require 'faster_path/version' 2 | require 'pathname' 3 | require 'thermite/config' 4 | require 'faster_path/thermite_initialize' 5 | require 'fiddle' 6 | require 'fiddle/import' 7 | 8 | # FasterPath module behaves as a singleton object with the alternative method 9 | # implementations for `Pathname`, and some for `File`, available directly on it. 10 | # 11 | # New projects are recommend to reference methods defined directly on `FasterPath`. 12 | # Existing websites may use the `FasterPath.sledgehammer_everything!` method to 13 | # directly injet the more performant implementations of path handling in to their 14 | # existing code ecosystem. To do so you will need to 15 | # `require 'faster_path/optional/monkeypatches'` beforehand. 16 | module FasterPath 17 | FFI_LIBRARY = begin 18 | toplevel_dir = File.dirname(__dir__) 19 | config = Thermite::Config.new(cargo_project_path: toplevel_dir, 20 | ruby_project_path: toplevel_dir) 21 | config.ruby_extension_path 22 | end 23 | 24 | Fiddle::Function. 25 | new(Fiddle.dlopen(FFI_LIBRARY)['Init_faster_pathname'], [], Fiddle::TYPE_VOIDP). 26 | call 27 | 28 | Public.class_eval do 29 | private_class_method :basename 30 | private_class_method :children 31 | private_class_method :children_compat 32 | end 33 | 34 | # The Rust architecture bit width: 64bits or 32bits 35 | # @return [Integer] 36 | def self.rust_arch_bits 37 | Rust.rust_arch_bits 38 | end 39 | 40 | # The Ruby architecture bit width: 64bits or 32bits 41 | # @return [Integer] 42 | def self.ruby_arch_bits 43 | 1.size * 8 44 | end 45 | 46 | # A very fast way to check if a string is blank 47 | # @param str [#to_s] 48 | # @return [true, false] 49 | def self.blank?(str) 50 | "#{str}".strip.empty? 51 | end 52 | 53 | # Rust implementation of File.basename 54 | # @param pth [String] the path to evaluate 55 | # @param ext [String] any extension to remove 56 | # @return [String] 57 | def self.basename(pth, ext="") 58 | Public.send(:basename, pth, ext) 59 | end 60 | 61 | # Rust implementation of Pathname.children 62 | # @param pth [String] the path to evaluate 63 | # @param with_directory [true, false] whether to include directory in results 64 | # @return [Array] 65 | def self.children(pth, with_directory=true) 66 | Public.send(:children, pth, with_directory) 67 | end 68 | 69 | # Rust implementation of Pathname.children 70 | # @param pth [String] the path to evaluate 71 | # @param with_directory [true, false] whether to include directory in results 72 | # @return [Array] 73 | def self.children_compat(pth, with_directory=true) 74 | Public.send(:children_compat, pth, with_directory) 75 | end 76 | 77 | # @private 78 | module Rust 79 | extend Fiddle::Importer 80 | dlload FFI_LIBRARY 81 | extern 'int rust_arch_bits()' 82 | end 83 | private_constant :Rust 84 | end 85 | -------------------------------------------------------------------------------- /lib/faster_path/optional/monkeypatches.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | # :nodoc: 4 | module FasterPath 5 | # Core module for applying monkeypatches to `Pathname` and `File` 6 | module MonkeyPatches 7 | # Monkeypatch just `File` specific methods 8 | # rubocop:disable Metrics/CyclomaticComplexity 9 | # rubocop:disable Metrics/PerceivedComplexity 10 | def self._ruby_core_file! 11 | ::File.class_eval do 12 | # @private :nodoc: 13 | def self.basename(pth, ext = '') 14 | pth = pth.to_path if pth.respond_to? :to_path 15 | raise TypeError unless pth.is_a?(String) && ext.is_a?(String) 16 | FasterPath.basename(pth, ext) 17 | end 18 | 19 | # @private :nodoc: 20 | def self.extname(pth) 21 | pth = pth.to_path if pth.respond_to? :to_path 22 | raise TypeError unless pth.is_a? String 23 | FasterPath.extname(pth) 24 | end 25 | 26 | # @private :nodoc: 27 | def self.dirname(pth) 28 | pth = pth.to_path if pth.respond_to? :to_path 29 | raise TypeError unless pth.is_a? String 30 | FasterPath.dirname(pth) 31 | end 32 | end 33 | end 34 | 35 | # Monkeypatch just `Pathname` specific methods 36 | # rubocop:disable Metrics/MethodLength 37 | def self._ruby_library_pathname! 38 | ::Pathname.class_eval do 39 | # @private :nodoc: 40 | def absolute? 41 | FasterPath.absolute?(@path) 42 | end 43 | 44 | # @private :nodoc: 45 | def add_trailing_separator(pth) 46 | FasterPath.add_trailing_separator(pth) 47 | end 48 | private :add_trailing_separator 49 | 50 | # @private :nodoc: 51 | def children(with_dir=true) 52 | FasterPath.children_compat(@path, with_dir) 53 | end if !!ENV['WITH_REGRESSION'] 54 | 55 | # @private :nodoc: 56 | def chop_basename(pth) 57 | FasterPath.chop_basename(pth) 58 | end 59 | private :chop_basename 60 | 61 | # @private :nodoc: 62 | def cleanpath_aggressive 63 | Pathname.new(FasterPath.cleanpath_aggressive(@path)) 64 | end 65 | private :cleanpath_aggressive 66 | 67 | # @private :nodoc: 68 | def cleanpath_conservative 69 | Pathname.new(FasterPath.cleanpath_conservative(@path)) 70 | end 71 | private :cleanpath_conservative 72 | 73 | # @private :nodoc: 74 | def del_trailing_separator(pth) 75 | FasterPath.del_trailing_separator(pth) 76 | end 77 | private :del_trailing_separator 78 | 79 | # @private :nodoc: 80 | def directory? 81 | FasterPath.directory?(@path) 82 | end 83 | 84 | # @private :nodoc: 85 | def entries 86 | FasterPath.entries_compat(@path) 87 | end if !!ENV['WITH_REGRESSION'] 88 | 89 | # @private :nodoc: 90 | def has_trailing_separator?(pth) 91 | FasterPath.has_trailing_separator?(pth) 92 | end 93 | private :has_trailing_separator? 94 | 95 | # @private :nodoc: 96 | def join(*args) 97 | FasterPath.join(self, *args) 98 | end 99 | 100 | # @private :nodoc: 101 | def plus(pth, pth2) 102 | FasterPath.plus(pth, pth2) 103 | end 104 | private :plus 105 | 106 | # @private :nodoc: 107 | def relative? 108 | FasterPath.relative?(@path) 109 | end 110 | 111 | # @private :nodoc: 112 | def relative_path_from(other) 113 | FasterPath.relative_path_from(@path, other) 114 | end 115 | end 116 | end 117 | end 118 | private_constant :MonkeyPatches 119 | 120 | # Applies all performant monkeypatches to both `Pathname` and `File` 121 | def self.sledgehammer_everything! 122 | MonkeyPatches._ruby_core_file! 123 | MonkeyPatches._ruby_library_pathname! 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/faster_path/optional/refinements.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | # @private :nodoc: 4 | module FasterPath 5 | # @private :nodoc: 6 | module RefineFile 7 | refine File do 8 | # @private :nodoc: 9 | def self.basename(pth, ext = '') 10 | pth = pth.to_path if pth.respond_to? :to_path 11 | raise TypeError unless pth.is_a?(String) && ext.is_a?(String) 12 | FasterPath.basename(pth, ext) 13 | end 14 | 15 | # @private :nodoc: 16 | def self.extname(pth) 17 | pth = pth.to_path if pth.respond_to? :to_path 18 | raise TypeError unless pth.is_a? String 19 | FasterPath.extname(pth) 20 | end 21 | 22 | # @private :nodoc: 23 | def self.dirname(pth) 24 | pth = pth.to_path if pth.respond_to? :to_path 25 | raise TypeError unless pth.is_a? String 26 | FasterPath.dirname(pth) 27 | end 28 | end 29 | end 30 | 31 | # @private :nodoc: 32 | module RefinePathname 33 | refine Pathname do 34 | # @private :nodoc: 35 | def absolute? 36 | FasterPath.absolute?(@path) 37 | end 38 | 39 | # @private :nodoc: 40 | def add_trailing_separator(pth) 41 | FasterPath.add_trailing_separator(pth) 42 | end 43 | private :add_trailing_separator 44 | 45 | # @private :nodoc: 46 | def children(with_dir=true) 47 | FasterPath.children_compat(@path, with_dir) 48 | end if !!ENV['WITH_REGRESSION'] 49 | 50 | # @private :nodoc: 51 | def chop_basename(pth) 52 | FasterPath.chop_basename(pth) 53 | end 54 | private :chop_basename 55 | 56 | # @private :nodoc: 57 | def cleanpath_aggressive 58 | Pathname.new(FasterPath.cleanpath_aggressive(@path)) 59 | end 60 | private :cleanpath_aggressive 61 | 62 | # @private :nodoc: 63 | def cleanpath_conservative 64 | Pathname.new(FasterPath.cleanpath_conservative(@path)) 65 | end 66 | private :cleanpath_conservative 67 | 68 | # @private :nodoc: 69 | def del_trailing_separator(pth) 70 | FasterPath.del_trailing_separator(pth) 71 | end 72 | private :del_trailing_separator 73 | 74 | # @private :nodoc: 75 | def directory? 76 | FasterPath.directory?(@path) 77 | end 78 | 79 | # @private :nodoc: 80 | def entries 81 | FasterPath.entries_compat(@path) 82 | end if !!ENV['WITH_REGRESSION'] 83 | 84 | # @private :nodoc: 85 | def has_trailing_separator?(pth) 86 | FasterPath.has_trailing_separator?(pth) 87 | end 88 | 89 | # @private :nodoc: 90 | def join(*args) 91 | FasterPath.join(self, *args) 92 | end 93 | 94 | # @private :nodoc: 95 | def plus(pth, pth2) 96 | FasterPath.plus(pth, pth2) 97 | end 98 | private :plus 99 | 100 | # @private :nodoc: 101 | def relative? 102 | FasterPath.relative?(@path) 103 | end 104 | 105 | # @private :nodoc: 106 | def relative_path_from(other) 107 | FasterPath.relative_path_from(@path, other) 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/faster_path/thermite_initialize.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake/tasklib' 3 | require_relative './version' 4 | 5 | # @private :nodoc: 6 | module Thermite 7 | # @private :nodoc: 8 | class Config 9 | # @private :nodoc: 10 | def ruby_version 11 | "ruby#{RUBY_VERSION}" 12 | end 13 | end 14 | 15 | # @private :nodoc: 16 | class Tasks < Rake::TaskLib 17 | # @private :nodoc: 18 | def github_download_uri(_tag, version) 19 | "#{github_uri}/releases/download/v#{FasterPath::VERSION}/#{config.tarball_filename(version)}" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/faster_path/version.rb: -------------------------------------------------------------------------------- 1 | module FasterPath 2 | VERSION = "0.3.10" 3 | end 4 | -------------------------------------------------------------------------------- /spec/default.mspec: -------------------------------------------------------------------------------- 1 | load 'spec/ruby_spec/default.mspec' 2 | 3 | class MSpecScript 4 | set :requires, ["-r#{File.expand_path('init', File.dirname(__FILE__))}"] 5 | set :prefix, 'spec/ruby_spec' 6 | end 7 | 8 | MSpec.disable_feature :encoding unless ENV['ENCODING'] 9 | -------------------------------------------------------------------------------- /spec/init.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift File.expand_path('../lib', File.dirname(__FILE__)) 3 | require 'faster_path' 4 | require 'faster_path/optional/monkeypatches' 5 | FasterPath.sledgehammer_everything! if ENV['TEST_MONKEYPATCHES'].to_s['true'] 6 | -------------------------------------------------------------------------------- /src/basename.rs: -------------------------------------------------------------------------------- 1 | extern crate memchr; 2 | 3 | use self::memchr::memrchr; 4 | 5 | use path_parsing::{find_last_sep_pos, find_last_non_sep_pos}; 6 | 7 | pub fn basename<'a>(path: &'a str, ext: &str) -> &'a str { 8 | let bytes: &[u8] = path.as_bytes(); 9 | let mut left: usize = 0; 10 | let mut right: usize = bytes.len(); 11 | if let Some(last_slash_pos) = find_last_sep_pos(bytes) { 12 | if last_slash_pos == right - 1 { 13 | if let Some(pos) = find_last_non_sep_pos(&bytes[..last_slash_pos]) { 14 | right = pos + 1; 15 | } else { 16 | return "/"; 17 | } 18 | if let Some(pos) = find_last_sep_pos(&bytes[..right]) { 19 | left = pos + 1; 20 | } 21 | } else { 22 | left = last_slash_pos + 1; 23 | } 24 | } 25 | &path[left..left + ext_end(&bytes[left..right], ext)] 26 | } 27 | 28 | fn ext_end(slice: &[u8], ext: &str) -> usize { 29 | if ext.len() >= slice.len() || slice == b"." || slice == b".." { 30 | return slice.len(); 31 | } 32 | let ext_bytes = ext.as_bytes(); 33 | if ext_bytes.len() == 2 && *ext_bytes.get(1).unwrap() == b'*' { 34 | match memrchr(*ext_bytes.get(0).unwrap(), slice) { 35 | Some(end) if end != 0 => return end, 36 | _ => {} 37 | }; 38 | } else if slice.ends_with(ext_bytes) { 39 | return slice.len() - ext_bytes.len(); 40 | } 41 | slice.len() 42 | } 43 | 44 | #[test] 45 | fn non_dot_asterisk_ext() { 46 | // This is undocumented Ruby functionality. We match it in case some code out there relies on it. 47 | assert_eq!(basename("abc", "b*"), "a"); 48 | assert_eq!(basename("abc", "abc"), "abc"); 49 | assert_eq!(basename("abc", "a*"), "abc"); 50 | assert_eq!(basename("playlist", "l*"), "play"); 51 | // Treated as literal "*": 52 | assert_eq!(basename("playlist", "yl*"), "playlist"); 53 | assert_eq!(basename("playl*", "yl*"), "pla"); 54 | } 55 | 56 | #[test] 57 | fn empty() { 58 | assert_eq!(basename("", ""), ""); 59 | assert_eq!(basename("", ".*"), ""); 60 | assert_eq!(basename("", ".a"), ""); 61 | } 62 | 63 | #[test] 64 | fn sep() { 65 | assert_eq!(basename("/", ""), "/"); 66 | assert_eq!(basename("//", ""), "/"); 67 | } 68 | 69 | #[test] 70 | fn trailing_dot() { 71 | assert_eq!(basename("file.test.", ""), "file.test."); 72 | assert_eq!(basename("file.test.", "."), "file.test"); 73 | assert_eq!(basename("file.test.", ".*"), "file.test"); 74 | } 75 | 76 | #[test] 77 | fn trailing_dot_dot() { 78 | assert_eq!(basename("a..", ".."), "a"); 79 | assert_eq!(basename("a..", ".*"), "a."); 80 | } 81 | 82 | #[test] 83 | fn dot() { 84 | assert_eq!(basename(".", ""), "."); 85 | assert_eq!(basename(".", "."), "."); 86 | assert_eq!(basename(".", ".*"), "."); 87 | } 88 | 89 | #[test] 90 | fn dot_dot() { 91 | assert_eq!(basename("..", ""), ".."); 92 | assert_eq!(basename("..", ".*"), ".."); 93 | assert_eq!(basename("..", ".."), ".."); 94 | assert_eq!(basename("..", "..."), ".."); 95 | } 96 | 97 | #[test] 98 | fn non_dot_ext() { 99 | assert_eq!(basename("abc", "bc"), "a"); 100 | } 101 | 102 | #[test] 103 | fn basename_eq_ext() { 104 | assert_eq!(basename(".x", ".x"), ".x"); 105 | assert_eq!(basename(".x", ".*"), ".x"); 106 | } 107 | 108 | #[test] 109 | fn absolute() { 110 | assert_eq!(basename("/a/b///c", ""), "c"); 111 | } 112 | 113 | #[test] 114 | fn trailing_slashes_absolute() { 115 | assert_eq!(basename("/a/b///c//////", ""), "c"); 116 | } 117 | 118 | #[test] 119 | fn relative() { 120 | assert_eq!(basename("b///c", ""), "c"); 121 | } 122 | 123 | #[test] 124 | fn trailing_slashes_relative() { 125 | assert_eq!(basename("b/c//", ""), "c"); 126 | } 127 | 128 | #[test] 129 | fn root() { 130 | assert_eq!(basename("//c", ""), "c"); 131 | } 132 | 133 | #[test] 134 | fn trailing_slashes_root() { 135 | assert_eq!(basename("//c//", ""), "c"); 136 | } 137 | 138 | #[test] 139 | fn trailing_slashes_relative_root() { 140 | assert_eq!(basename("c//", ""), "c"); 141 | } 142 | 143 | #[test] 144 | fn edge_case_all_seps() { 145 | assert_eq!("/", basename("///", ".*")); 146 | } 147 | -------------------------------------------------------------------------------- /src/chop_basename.rs: -------------------------------------------------------------------------------- 1 | use path_parsing::{find_last_non_sep_pos, find_last_sep_pos}; 2 | use std::str; 3 | 4 | pub fn chop_basename<'a>(input: &'a str) -> Option<(&'a str, &'a str)> { 5 | let bytes = input.as_bytes(); 6 | let len = find_last_non_sep_pos(&bytes)? + 1; 7 | let base_start = find_last_sep_pos(&bytes[..len]).map_or(0, |pos| pos + 1); 8 | if base_start == len { 9 | return None; 10 | } 11 | Some((&input[0..base_start], &input[base_start..len])) 12 | } 13 | 14 | #[test] 15 | fn it_chops_the_basename_and_dirname() { 16 | assert_eq!(chop_basename(""), None ); 17 | assert_eq!(chop_basename("/"), None ); 18 | assert_eq!(chop_basename("."), Some(("", ".")) ); 19 | assert_eq!(chop_basename("asdf/asdf"), Some(("asdf/", "asdf")) ); 20 | assert_eq!(chop_basename("asdf.txt"), Some(("", "asdf.txt")) ); 21 | assert_eq!(chop_basename("asdf/"), Some(("", "asdf")) ); 22 | assert_eq!(chop_basename("/asdf/"), Some(("/", "asdf")) ); 23 | assert_eq!(chop_basename("a///b"), Some(("a///", "b")) ); 24 | assert_eq!(chop_basename("a///b//"), Some(("a///", "b")) ); 25 | assert_eq!(chop_basename("/a///b//"), Some(("/a///", "b")) ); 26 | assert_eq!(chop_basename("/a///b//"), Some(("/a///", "b")) ); 27 | 28 | assert_eq!(chop_basename("./../..///.../..//"), Some(("./../..///.../", ".."))); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/cleanpath_aggressive.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use prepend_prefix::prepend_prefix; 3 | use basename::basename; 4 | use chop_basename::chop_basename; 5 | use path_parsing::{SEP_STR, contains_sep}; 6 | 7 | pub fn cleanpath_aggressive(path: &str) -> Cow { 8 | let mut names: Vec<&str> = vec![]; 9 | let mut prefix = path; 10 | while let Some((ref p, ref base)) = chop_basename(&prefix) { 11 | prefix = p; 12 | match base.as_ref() { 13 | "." => {} 14 | ".." => names.push(base), 15 | _ => { 16 | if names.last() == Some(&"..") { 17 | names.pop(); 18 | } else { 19 | names.push(base); 20 | } 21 | } 22 | } 23 | } 24 | // // Windows Feature 25 | // 26 | // ```ruby 27 | // pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR 28 | // ``` 29 | // 30 | if contains_sep(basename(&prefix, "").as_bytes()) { 31 | let len = names.iter().rposition(|&c| c != "..").map_or(0, |pos| pos + 1); 32 | names.truncate(len); 33 | } 34 | names.reverse(); 35 | prepend_prefix(&prefix, &names.join(&SEP_STR)) 36 | } 37 | 38 | #[test] 39 | fn it_aggressively_cleans_the_path() { 40 | assert_eq!(cleanpath_aggressive("/") , "/"); 41 | assert_eq!(cleanpath_aggressive("") , "."); 42 | assert_eq!(cleanpath_aggressive(".") , "."); 43 | assert_eq!(cleanpath_aggressive("..") , ".."); 44 | assert_eq!(cleanpath_aggressive("a") , "a"); 45 | assert_eq!(cleanpath_aggressive("/.") , "/"); 46 | assert_eq!(cleanpath_aggressive("/..") , "/"); 47 | assert_eq!(cleanpath_aggressive("/a") , "/a"); 48 | assert_eq!(cleanpath_aggressive("./") , "."); 49 | assert_eq!(cleanpath_aggressive("../") , ".."); 50 | assert_eq!(cleanpath_aggressive("a/") , "a"); 51 | assert_eq!(cleanpath_aggressive("a//b") , "a/b"); 52 | assert_eq!(cleanpath_aggressive("a/.") , "a"); 53 | assert_eq!(cleanpath_aggressive("a/./") , "a"); 54 | assert_eq!(cleanpath_aggressive("a/..") , "."); 55 | assert_eq!(cleanpath_aggressive("a/../") , "."); 56 | assert_eq!(cleanpath_aggressive("/a/.") , "/a"); 57 | assert_eq!(cleanpath_aggressive("./..") , ".."); 58 | assert_eq!(cleanpath_aggressive("../.") , ".."); 59 | assert_eq!(cleanpath_aggressive("./../") , ".."); 60 | assert_eq!(cleanpath_aggressive(".././") , ".."); 61 | assert_eq!(cleanpath_aggressive("/./..") , "/"); 62 | assert_eq!(cleanpath_aggressive("/../.") , "/"); 63 | assert_eq!(cleanpath_aggressive("/./../") , "/"); 64 | assert_eq!(cleanpath_aggressive("/.././") , "/"); 65 | assert_eq!(cleanpath_aggressive("a/b/c") , "a/b/c"); 66 | assert_eq!(cleanpath_aggressive("./b/c") , "b/c"); 67 | assert_eq!(cleanpath_aggressive("a/./c") , "a/c"); 68 | assert_eq!(cleanpath_aggressive("a/b/.") , "a/b"); 69 | assert_eq!(cleanpath_aggressive("a/../.") , "."); 70 | assert_eq!(cleanpath_aggressive("/../.././../a") , "/a"); 71 | assert_eq!(cleanpath_aggressive("a/b/../../../../c/../d"), "../../d"); 72 | } 73 | 74 | // Future Windows Support 75 | // 76 | // DOSISH = File::ALT_SEPARATOR != nil 77 | // DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:." 78 | // DOSISH_UNC = File.dirname("//") == "//" 79 | // 80 | // if DOSISH_UNC 81 | // defassert(:cleanpath_aggressive, '//a/b/c', '//a/b/c/') 82 | // else 83 | // defassert(:cleanpath_aggressive, '/', '///') 84 | // defassert(:cleanpath_aggressive, '/a', '///a') 85 | // defassert(:cleanpath_aggressive, '/', '///..') 86 | // defassert(:cleanpath_aggressive, '/', '///.') 87 | // defassert(:cleanpath_aggressive, '/', '///a/../..') 88 | // end 89 | // 90 | // if DOSISH 91 | // defassert(:cleanpath_aggressive, 'c:/foo/bar', 'c:\\foo\\bar') 92 | // end 93 | -------------------------------------------------------------------------------- /src/cleanpath_conservative.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::path::MAIN_SEPARATOR; 3 | use prepend_prefix::prepend_prefix; 4 | use basename::basename; 5 | use dirname::dirname; 6 | use chop_basename::chop_basename; 7 | use path_parsing::{SEP_STR, contains_sep}; 8 | 9 | pub fn cleanpath_conservative(path: &str) -> Cow { 10 | let mut names: Vec<&str> = vec![]; 11 | let mut prefix = path; 12 | while let Some((ref p, ref base)) = chop_basename(&prefix) { 13 | prefix = p; 14 | if base != &"." { 15 | names.push(base); 16 | } 17 | } 18 | // // Windows Feature 19 | // 20 | // ```ruby 21 | // pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR 22 | // ``` 23 | // 24 | if contains_sep(basename(&prefix, "").as_bytes()) { 25 | let len = names.iter().rposition(|&c| c != "..").map_or(0, |pos| pos + 1); 26 | names.truncate(len); 27 | } 28 | 29 | let last_name = match names.first() { 30 | Some(&name) => name, 31 | None => return dirname(&prefix).into(), 32 | }; 33 | 34 | if last_name != ".." && basename(&path, "") == "." { 35 | names.reverse(); 36 | names.push("."); 37 | } else if last_name != "." && last_name != ".." && 38 | chop_basename(path).map(|(a, b)| a.len() + b.len()).unwrap() < path.len() { 39 | return format!("{}{}", last_name, MAIN_SEPARATOR).into(); 40 | } else { 41 | names.reverse(); 42 | } 43 | prepend_prefix(&prefix, &names.join(&SEP_STR)) 44 | } 45 | 46 | #[test] 47 | fn it_conservatively_cleans_the_path() { 48 | assert_eq!(cleanpath_conservative("/"), "/"); 49 | assert_eq!(cleanpath_conservative(""), "."); 50 | assert_eq!(cleanpath_conservative("."), "."); 51 | assert_eq!(cleanpath_conservative(".."), ".."); 52 | assert_eq!(cleanpath_conservative("a"), "a"); 53 | assert_eq!(cleanpath_conservative("/."), "/"); 54 | assert_eq!(cleanpath_conservative("/.."), "/"); 55 | assert_eq!(cleanpath_conservative("/a"), "/a"); 56 | assert_eq!(cleanpath_conservative("./"), "."); 57 | assert_eq!(cleanpath_conservative("../"), ".."); 58 | assert_eq!(cleanpath_conservative("a/"), "a/"); 59 | assert_eq!(cleanpath_conservative("a//b"), "a/b"); 60 | assert_eq!(cleanpath_conservative("a/."), "a/."); 61 | assert_eq!(cleanpath_conservative("a/./"), "a/."); 62 | assert_eq!(cleanpath_conservative("a/../"), "a/.."); 63 | assert_eq!(cleanpath_conservative("/a/."), "/a/."); 64 | assert_eq!(cleanpath_conservative("./.."), ".."); 65 | assert_eq!(cleanpath_conservative("../."), ".."); 66 | assert_eq!(cleanpath_conservative("./../"), ".."); 67 | assert_eq!(cleanpath_conservative(".././"), ".."); 68 | assert_eq!(cleanpath_conservative("/./.."), "/"); 69 | assert_eq!(cleanpath_conservative("/../."), "/"); 70 | assert_eq!(cleanpath_conservative("/./../"), "/"); 71 | assert_eq!(cleanpath_conservative("/.././"), "/"); 72 | assert_eq!(cleanpath_conservative("a/b/c"), "a/b/c"); 73 | assert_eq!(cleanpath_conservative("./b/c"), "b/c"); 74 | assert_eq!(cleanpath_conservative("a/./c"), "a/c"); 75 | assert_eq!(cleanpath_conservative("a/b/."), "a/b/."); 76 | assert_eq!(cleanpath_conservative("a/../."), "a/.."); 77 | assert_eq!(cleanpath_conservative("/../.././../a"), "/a"); 78 | assert_eq!(cleanpath_conservative("a/b/../../../../c/../d"), "a/b/../../../../c/../d"); 79 | 80 | // Future Windows Support 81 | // 82 | // DOSISH = File::ALT_SEPARATOR != nil 83 | // DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:." 84 | // DOSISH_UNC = File.dirname("//") == "//" 85 | // 86 | // 87 | // if DOSISH 88 | // assert_eq!(cleanpath_conservative, 'c:/foo/bar', 'c:\\foo\\bar') 89 | // end 90 | // 91 | // if DOSISH_UNC 92 | // assert_eq!(cleanpath_conservative, '//', '//') 93 | // else 94 | // assert_eq!(cleanpath_conservative, '/', '//') 95 | // end 96 | } 97 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use rutie::{ 2 | RString, 3 | AnyObject, 4 | Object, 5 | }; 6 | 7 | #[derive(Debug)] 8 | pub struct RubyDebugInfo { 9 | object_id: String, 10 | class: String, 11 | inspect: String, 12 | } 13 | 14 | impl From for RubyDebugInfo { 15 | fn from(ao: AnyObject) -> Self { 16 | let object_id = ao.send("object_id", &[]).send("to_s", &[]). 17 | try_convert_to::().unwrap_or(RString::new_utf8("Failed to get object_id!")).to_string(); 18 | let class = ao.send("class", &[]).send("to_s", &[]). 19 | try_convert_to::().unwrap_or(RString::new_utf8("Failed to get class!")).to_string(); 20 | let inspect = ao.send("inspect", &[]). 21 | try_convert_to::().unwrap_or(RString::new_utf8("Failed to get inspect!")).to_string(); 22 | 23 | RubyDebugInfo { 24 | object_id: object_id, 25 | class: class, 26 | inspect: inspect, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/dirname.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | use path_parsing::{find_last_sep_pos, find_last_non_sep_pos}; 3 | 4 | pub fn dirname(path: &str) -> &str { 5 | let bytes = path.as_bytes(); 6 | let mut last_slash_pos = match find_last_sep_pos(bytes) { 7 | Some(pos) => pos, 8 | _ => return ".", 9 | }; 10 | // Skip trailing slashes. 11 | if last_slash_pos == bytes.len() - 1 { 12 | let last_non_slash_pos = match find_last_non_sep_pos(&bytes[..last_slash_pos]) { 13 | Some(pos) => pos, 14 | _ => return "/" 15 | }; 16 | last_slash_pos = match find_last_sep_pos(&bytes[..last_non_slash_pos]) { 17 | Some(pos) => pos, 18 | _ => return "." 19 | }; 20 | }; 21 | if let Some(end) = find_last_non_sep_pos(&bytes[..last_slash_pos]) { 22 | &path[..end + 1] 23 | } else { 24 | "/" 25 | } 26 | } 27 | 28 | #[test] 29 | fn absolute() { 30 | assert_eq!(dirname("/a/b///c"), "/a/b"); 31 | } 32 | 33 | #[test] 34 | fn trailing_slashes_absolute() { 35 | assert_eq!(dirname("/a/b///c//////"), "/a/b"); 36 | } 37 | 38 | #[test] 39 | fn relative() { 40 | assert_eq!(dirname("b///c"), "b"); 41 | } 42 | 43 | #[test] 44 | fn trailing_slashes_relative() { 45 | assert_eq!(dirname("b/c//"), "b"); 46 | } 47 | 48 | #[test] 49 | fn root() { 50 | assert_eq!(dirname("//c"), "/"); 51 | } 52 | 53 | #[test] 54 | fn trailing_slashes_root() { 55 | assert_eq!(dirname("//c//"), "/"); 56 | } 57 | 58 | #[test] 59 | fn trailing_slashes_relative_root() { 60 | assert_eq!(dirname("c//"), "."); 61 | } 62 | 63 | #[test] 64 | fn returns_dot_for_empty_string() { 65 | assert_eq!(dirname(""), "."); 66 | } 67 | -------------------------------------------------------------------------------- /src/extname.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | use path_parsing::{SEP, find_last_non_sep_pos}; 3 | 4 | pub fn extname(path: &str) -> &str { 5 | let end = match find_last_non_sep_pos(path.as_bytes()) { 6 | Some(pos) => pos + 1, 7 | _ => return "", 8 | }; 9 | let bytes = &path.as_bytes()[..end]; 10 | for (pos, c) in bytes.iter().enumerate().rev() { 11 | match *c { 12 | b'.' => { 13 | let prev = bytes.get(pos - 1); 14 | if pos == end - 1 || prev == None || prev == Some(&SEP) { 15 | return ""; 16 | } else { 17 | return &path[pos..end] 18 | }; 19 | } 20 | SEP => return "", 21 | _ => {} 22 | } 23 | }; 24 | "" 25 | } 26 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | use rutie::{RString, Object, Class, AnyObject}; 2 | use debug::RubyDebugInfo; 3 | use rutie; 4 | 5 | type MaybeString = Result; 6 | 7 | pub trait TryFrom: Sized { 8 | type Error; 9 | 10 | fn try_from(value: T) -> Result; 11 | } 12 | 13 | #[cfg(windows)] 14 | #[inline] 15 | pub fn is_same_path(a: &str, b: &str) -> bool { 16 | a.to_uppercase() == b.to_uppercase() 17 | } 18 | 19 | #[cfg(not(windows))] 20 | #[inline] 21 | pub fn is_same_path(a: &str, b: &str) -> bool { 22 | a == b 23 | } 24 | 25 | pub fn anyobject_to_string(item: AnyObject) -> Result { 26 | let result = &item; 27 | if Class::from_existing("String").case_equals(result) { 28 | return Ok(RString::from(result.value()).to_string()) 29 | } 30 | 31 | if Class::from_existing("Pathname").case_equals(result) { 32 | return Ok(result.instance_variable_get("@path"). 33 | try_convert_to::(). 34 | unwrap_or(RString::new_utf8("")). 35 | to_string()) 36 | } 37 | 38 | if result.respond_to("to_path") { 39 | return Ok(RString::from(result.send("to_path", &[]).value()).to_string()) 40 | } 41 | 42 | Ok(RString::from(result.send("to_s", &[]).value()).to_string()) 43 | } 44 | 45 | pub fn to_str(maybe_string: &MaybeString) -> &str { 46 | match maybe_string { 47 | &Ok(ref rutie_string) => rutie_string.to_str(), 48 | &Err(_) => "", 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Daniel P. Clark & Other FasterPath Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | #[macro_use] 8 | extern crate rutie; 9 | 10 | #[macro_use] 11 | extern crate lazy_static; 12 | 13 | module!(FasterPath); 14 | 15 | mod debug; 16 | mod helpers; 17 | mod pathname; 18 | mod basename; 19 | mod chop_basename; 20 | mod cleanpath_aggressive; 21 | mod cleanpath_conservative; 22 | mod dirname; 23 | mod extname; 24 | mod pathname_sys; 25 | mod plus; 26 | mod prepend_prefix; 27 | pub mod rust_arch_bits; 28 | mod memrnchr; 29 | mod path_parsing; 30 | mod relative_path_from; 31 | 32 | use pathname::Pathname; 33 | 34 | use rutie::{Module, Object, RString, Boolean, AnyObject, VM}; 35 | 36 | use pathname_sys::*; 37 | 38 | methods!( 39 | FasterPath, 40 | _itself, 41 | 42 | fn pub_add_trailing_separator(pth: RString) -> RString { 43 | pathname::pn_add_trailing_separator(pth) 44 | } 45 | 46 | fn pub_is_absolute(pth: RString) -> Boolean { 47 | pathname::pn_is_absolute(pth) 48 | } 49 | 50 | // fn r_ascend(){} 51 | 52 | fn pub_basename(pth: RString, ext: RString) -> RString { 53 | pathname::pn_basename(pth, ext) 54 | } 55 | 56 | fn pub_children(pth: RString, with_dir: Boolean) -> AnyObject { 57 | pathname::pn_children(pth, with_dir). 58 | map_err(|e| VM::raise_ex(e) ).unwrap() 59 | } 60 | 61 | fn pub_children_compat(pth: RString, with_dir: Boolean) -> AnyObject { 62 | pathname::pn_children_compat(pth, with_dir). 63 | map_err(|e| VM::raise_ex(e) ).unwrap() 64 | } 65 | 66 | fn pub_chop_basename(pth: RString) -> AnyObject { 67 | pathname::pn_chop_basename(pth) 68 | } 69 | 70 | // fn r_cleanpath(){ pub_cleanpath(r_to_path()) } 71 | // fn pub_cleanpath(pth: RString){} 72 | 73 | fn pub_cleanpath_aggressive(pth: RString) -> RString { 74 | pathname::pn_cleanpath_aggressive(pth) 75 | } 76 | 77 | fn pub_cleanpath_conservative(pth: RString) -> RString { 78 | pathname::pn_cleanpath_conservative(pth) 79 | } 80 | 81 | fn pub_del_trailing_separator(pth: RString) -> RString { 82 | pathname::pn_del_trailing_separator(pth) 83 | } 84 | 85 | // fn r_descend(){} 86 | 87 | fn pub_is_directory(pth: RString) -> Boolean { 88 | pathname::pn_is_directory(pth) 89 | } 90 | 91 | fn pub_dirname(pth: RString) -> RString { 92 | pathname::pn_dirname(pth) 93 | } 94 | 95 | // fn r_each_child(){} 96 | // fn pub_each_child(){} 97 | 98 | // fn pub_each_filename(pth: RString) -> NilClass { 99 | // pathname::pn_each_filename(pth) 100 | // } 101 | 102 | // pub_entries returns an array of String objects 103 | fn pub_entries(pth: RString) -> AnyObject { 104 | pathname::pn_entries(pth). 105 | map_err(|e| VM::raise_ex(e) ).unwrap() 106 | } 107 | 108 | // pub_entries_compat returns an array of Pathname objects 109 | fn pub_entries_compat(pth: RString) -> AnyObject { 110 | pathname::pn_entries_compat(pth). 111 | map_err(|e| VM::raise_ex(e) ).unwrap() 112 | } 113 | 114 | fn pub_extname(pth: RString) -> RString { 115 | pathname::pn_extname(pth) 116 | } 117 | 118 | // fn r_find(ignore_error: Boolean){} 119 | // fn pub_find(pth: RString ,ignore_error: Boolean){} 120 | 121 | fn pub_has_trailing_separator(pth: RString) -> Boolean { 122 | pathname::pn_has_trailing_separator(pth) 123 | } 124 | 125 | // fn pub_mkpath(pth: RString) -> NilClass { 126 | // pathname::pn_mkpath(pth) 127 | // } 128 | 129 | // fn r_is_mountpoint(){ pub_is_mountpount(r_to_path()) } 130 | // fn pub_is_mountpoint(pth: RString){} 131 | 132 | // fn r_parent(){ pub_parent(r_to_path()) } 133 | // fn pub_parent(pth: RString){} 134 | 135 | // also need impl + 136 | fn pub_plus(pth1: RString, pth2: RString) -> RString { 137 | pathname::pn_plus(pth1, pth2) 138 | } 139 | 140 | // fn r_prepend_prefix(prefix: RString, relpath: RString){} 141 | 142 | fn pub_is_relative(pth: RString) -> Boolean { 143 | pathname::pn_is_relative(pth) 144 | } 145 | 146 | // fn r_root(){ pub_root(r_to_path()) } 147 | // fn pub_root(pth: RString){} 148 | 149 | // fn r_split_names(pth: RString){} 150 | 151 | fn pub_relative_path_from(itself: RString, base_directory: AnyObject) -> Pathname { 152 | let to_string = |i: AnyObject| { RString::from(i.send("to_s", &[]).value()) }; 153 | 154 | pathname::pn_relative_path_from(itself, base_directory.map(to_string)). 155 | map_err(|e| VM::raise_ex(e) ).unwrap() 156 | } 157 | 158 | // fn pub_rmtree(pth: RString) -> NilClass { 159 | // pathname::pn_rmtree(pth) 160 | // } 161 | ); 162 | 163 | #[allow(non_snake_case)] 164 | #[no_mangle] 165 | pub extern "C" fn Init_faster_pathname() { 166 | Module::from_existing("FasterPath").define(|itself| { 167 | itself.def_self("absolute?", pub_is_absolute); 168 | itself.def_self("add_trailing_separator", pub_add_trailing_separator); 169 | itself.def_self("del_trailing_separator", pub_del_trailing_separator); 170 | itself.def_self("chop_basename", pub_chop_basename); 171 | itself.def_self("cleanpath_aggressive", pub_cleanpath_aggressive); 172 | itself.def_self("cleanpath_conservative", pub_cleanpath_conservative); 173 | itself.def_self("directory?", pub_is_directory); 174 | itself.def_self("dirname", pub_dirname); 175 | itself.def_self("entries", pub_entries); 176 | itself.def_self("entries_compat", pub_entries_compat); 177 | itself.def_self("extname", pub_extname); 178 | itself.def_self("has_trailing_separator?", pub_has_trailing_separator); 179 | itself.def_self("join", pub_join); 180 | itself.def_self("plus", pub_plus); 181 | itself.def_self("relative?", pub_is_relative); 182 | itself.def_self("relative_path_from", pub_relative_path_from); 183 | itself.define_nested_class("Public", None); 184 | }); 185 | 186 | // For methods requiring addition Ruby-side behavior 187 | Module::from_existing("FasterPath").get_nested_class("Public").define(|itself| { 188 | itself.def_self("basename", pub_basename); 189 | itself.def_self("children", pub_children); 190 | itself.def_self("children_compat", pub_children_compat); 191 | }); 192 | } 193 | -------------------------------------------------------------------------------- /src/memrnchr.rs: -------------------------------------------------------------------------------- 1 | // The code below is based on the fallback `memrchr` implementation from the respective crate. 2 | // 3 | // We use this mainly to skip repeated `/`. If there is only one slash, `memrnchr` performs the same 4 | // as a naive version (e.g. `rposition`). However, it is much faster in pathological cases. 5 | 6 | use std::mem::size_of; 7 | 8 | // Returns the byte offset of the last byte that is NOT equal to the given one. 9 | #[inline(always)] 10 | pub fn memrnchr(x: u8, text: &[u8]) -> Option { 11 | // Scan for a single byte value by reading two `usize` words at a time. 12 | // 13 | // Split `text` in three parts 14 | // - unaligned tail, after the last word aligned address in text 15 | // - body, scan by 2 words at a time 16 | // - the first remaining bytes, < 2 word size 17 | let len = text.len(); 18 | let ptr = text.as_ptr(); 19 | 20 | // search to an aligned boundary 21 | let end_align = (ptr as usize + len) & (size_of::() - 1); 22 | let mut offset; 23 | if end_align > 0 { 24 | offset = if end_align >= len { 0 } else { len - end_align }; 25 | if let Some(index) = memrnchr_naive(x, &text[offset..]) { 26 | return Some(offset + index); 27 | } 28 | } else { 29 | offset = len; 30 | } 31 | 32 | // search the body of the text 33 | let repeated_x = repeat_byte(x); 34 | while offset >= 2 * size_of::() { 35 | debug_assert_eq!((ptr as usize + offset) % size_of::(), 0); 36 | unsafe { 37 | let u = *(ptr.offset(offset as isize - 2 * size_of::() as isize) as *const usize); 38 | let v = *(ptr.offset(offset as isize - size_of::() as isize) as *const usize); 39 | if u & repeated_x != usize::max_value() || v & repeated_x != usize::max_value() { 40 | break; 41 | } 42 | } 43 | offset -= 2 * size_of::(); 44 | } 45 | 46 | // find the byte before the point the body loop stopped 47 | memrnchr_naive(x, &text[..offset]) 48 | } 49 | 50 | #[inline(always)] 51 | fn memrnchr_naive(x: u8, text: &[u8]) -> Option { 52 | text.iter().rposition(|c| *c != x) 53 | } 54 | 55 | #[cfg(target_pointer_width = "32")] 56 | #[inline] 57 | fn repeat_byte(b: u8) -> usize { 58 | let mut rep = (b as usize) << 8 | b as usize; 59 | rep = rep << 16 | rep; 60 | rep 61 | } 62 | 63 | #[cfg(target_pointer_width = "64")] 64 | #[inline] 65 | fn repeat_byte(b: u8) -> usize { 66 | let mut rep = (b as usize) << 8 | b as usize; 67 | rep = rep << 16 | rep; 68 | rep = rep << 32 | rep; 69 | rep 70 | } 71 | -------------------------------------------------------------------------------- /src/path_parsing.rs: -------------------------------------------------------------------------------- 1 | extern crate memchr; 2 | 3 | use self::memchr::{memchr, memrchr}; 4 | use memrnchr::memrnchr; 5 | use std::path::MAIN_SEPARATOR; 6 | use std::str; 7 | 8 | pub const SEP: u8 = MAIN_SEPARATOR as u8; 9 | lazy_static! { 10 | pub static ref SEP_STR: &'static str = str::from_utf8(&[SEP]).unwrap(); 11 | } 12 | 13 | // Returns the byte offset of the last byte that equals MAIN_SEPARATOR. 14 | #[inline(always)] 15 | pub fn find_last_sep_pos(bytes: &[u8]) -> Option { 16 | memrchr(SEP, bytes) 17 | } 18 | 19 | // Returns the byte offset of the last byte that is not MAIN_SEPARATOR. 20 | #[inline(always)] 21 | pub fn find_last_non_sep_pos(bytes: &[u8]) -> Option { 22 | memrnchr(SEP, bytes) 23 | } 24 | 25 | // Whether the given byte sequence contains a MAIN_SEPARATOR. 26 | #[inline(always)] 27 | pub fn contains_sep(bytes: &[u8]) -> bool { 28 | memchr(SEP, bytes) != None 29 | } 30 | -------------------------------------------------------------------------------- /src/pathname.rs: -------------------------------------------------------------------------------- 1 | use helpers::*; 2 | use basename; 3 | use chop_basename; 4 | use cleanpath_aggressive; 5 | use cleanpath_conservative; 6 | use dirname; 7 | use extname; 8 | use plus; 9 | use relative_path_from; 10 | use debug; 11 | use helpers::{TryFrom, to_str}; 12 | use path_parsing::{SEP, find_last_non_sep_pos}; 13 | 14 | use rutie; 15 | use rutie::{ 16 | RString, 17 | Boolean, 18 | Array, 19 | AnyObject, 20 | NilClass, 21 | Object, 22 | Class, 23 | VerifiedObject, 24 | Exception as Exc, 25 | AnyException as Exception, 26 | }; 27 | use rutie::types::{Value, ValueType}; 28 | use std::borrow::Cow; 29 | use std::path::{MAIN_SEPARATOR, Path}; 30 | use std::fs; 31 | 32 | type MaybeArray = Result; 33 | type MaybeString = Result; 34 | type MaybeBoolean = Result; 35 | 36 | pub struct Pathname { 37 | value: Value 38 | } 39 | 40 | impl Pathname { 41 | pub fn new(path: &str) -> Pathname { 42 | let arguments = [RString::new_utf8(path).to_any_object()]; 43 | let instance = Class::from_existing("Pathname").new_instance(&arguments); 44 | 45 | Pathname { value: instance.value() } 46 | } 47 | 48 | pub fn to_any_object(&self) -> AnyObject { 49 | AnyObject::from(self.value()) 50 | } 51 | } 52 | 53 | impl From for Pathname { 54 | fn from(value: Value) -> Self { 55 | Pathname { value } 56 | } 57 | } 58 | 59 | impl TryFrom for Pathname { 60 | type Error = debug::RubyDebugInfo; 61 | fn try_from(obj: AnyObject) -> Result { 62 | if Class::from_existing("String").case_equals(&obj) { 63 | Ok(Pathname::new(&RString::from(obj.value()).to_str())) 64 | } else if Class::from_existing("Pathname").case_equals(&obj) { 65 | Ok(Pathname::from(obj.value())) 66 | } else if obj.respond_to("to_path") { 67 | Ok(Pathname::new(&RString::from(obj.send("to_path", &[]).value()).to_str())) 68 | } else { 69 | Err(Self::Error::from(obj)) 70 | } 71 | } 72 | } 73 | 74 | impl Object for Pathname { 75 | #[inline] 76 | fn value(&self) -> Value { 77 | self.value 78 | } 79 | } 80 | 81 | impl VerifiedObject for Pathname { 82 | fn is_correct_type(object: &T) -> bool { 83 | object.value().ty() == ValueType::Class && 84 | Class::from_existing("Pathname").case_equals(object) 85 | } 86 | 87 | fn error_message() -> &'static str { 88 | "Error converting to Pathname" 89 | } 90 | } 91 | 92 | pub fn pn_add_trailing_separator(pth: MaybeString) -> RString { 93 | let p = pth.unwrap(); 94 | let x = format!("{}{}", p.to_str(), "a"); 95 | match x.rsplit_terminator(MAIN_SEPARATOR).next() { 96 | Some("a") => p, 97 | _ => RString::new_utf8(format!("{}{}", p.to_str(), MAIN_SEPARATOR).as_str()) 98 | } 99 | } 100 | 101 | pub fn pn_is_absolute(pth: MaybeString) -> Boolean { 102 | Boolean::new(to_str(&pth).as_bytes().get(0) == Some(&SEP)) 103 | } 104 | 105 | // pub fn pn_ascend(){} 106 | 107 | pub fn pn_basename(pth: MaybeString, ext: MaybeString) -> RString { 108 | RString::new_utf8(basename::basename(to_str(&pth), to_str(&ext))) 109 | } 110 | 111 | pub fn pn_children(pth: MaybeString, with_dir: MaybeBoolean) -> Result { 112 | let path = pth.unwrap_or(RString::new_utf8(".")); 113 | let path = path.to_str(); 114 | 115 | if let Ok(entries) = fs::read_dir(path) { 116 | let mut with_directory = with_dir.unwrap_or(Boolean::new(true)).to_bool(); 117 | if path == "." { 118 | with_directory = false; 119 | } 120 | 121 | let mut arr = Array::with_capacity(entries.size_hint().1.unwrap_or(0)); 122 | for entry in entries { 123 | if with_directory { 124 | match entry { 125 | Ok(v) => { arr.push(RString::new_utf8(v.path().to_str().unwrap())); }, 126 | _ => {} 127 | }; 128 | } else { 129 | match entry { 130 | Ok(v) => { arr.push(RString::new_utf8(v.file_name().to_str().unwrap())); }, 131 | _ => {} 132 | }; 133 | } 134 | } 135 | 136 | Ok(arr.to_any_object()) 137 | } else { 138 | let msg = format!("No such file or directory @ dir_initialize - {}", path); 139 | Err(Exception::new("Errno::NOENT", Some(&msg))) 140 | } 141 | } 142 | 143 | pub fn pn_children_compat(pth: MaybeString, with_dir: MaybeBoolean) -> Result { 144 | let path = to_str(&pth); 145 | 146 | if let Ok(entries) = fs::read_dir(path) { 147 | let mut with_directory = with_dir.unwrap_or(Boolean::new(true)).to_bool(); 148 | if path == "." { 149 | with_directory = false; 150 | } 151 | 152 | let mut arr = Array::with_capacity(entries.size_hint().1.unwrap_or(0)); 153 | for entry in entries { 154 | if with_directory { 155 | if let Ok(v) = entry { 156 | arr.push(Pathname::new(v.path().to_str().unwrap())); 157 | }; 158 | } else { 159 | if let Ok(v) = entry { 160 | arr.push(Pathname::new(v.file_name().to_str().unwrap())); 161 | }; 162 | } 163 | } 164 | 165 | Ok(arr.to_any_object()) 166 | } else { 167 | let msg = format!("No such file or directory @ dir_initialize - {}", path); 168 | Err(Exception::new("Errno::NOENT", Some(&msg))) 169 | } 170 | } 171 | 172 | pub fn pn_chop_basename(pth: MaybeString) -> AnyObject { 173 | match chop_basename::chop_basename(to_str(&pth)) { 174 | Some((dirname, basename)) => { 175 | let mut arr = Array::with_capacity(2); 176 | arr.push(RString::new_utf8(&dirname)); 177 | arr.push(RString::new_utf8(&basename)); 178 | arr.to_any_object() 179 | }, 180 | None => NilClass::new().to_any_object() 181 | } 182 | } 183 | 184 | // pub fn pn_cleanpath(pth: MaybeString){} 185 | 186 | pub fn pn_cleanpath_aggressive(pth: MaybeString) -> RString { 187 | RString::new_utf8(&cleanpath_aggressive::cleanpath_aggressive(to_str(&pth))) 188 | } 189 | 190 | pub fn pn_cleanpath_conservative(pth: MaybeString) -> RString { 191 | RString::new_utf8(&cleanpath_conservative::cleanpath_conservative(to_str(&pth))) 192 | } 193 | 194 | pub fn pn_del_trailing_separator(pth: MaybeString) -> RString { 195 | { 196 | let path = to_str(&pth); 197 | if path.is_empty() { 198 | return RString::new_utf8("/"); 199 | } 200 | let pos = match find_last_non_sep_pos(path.as_bytes()) { 201 | Some(pos) => pos, 202 | None => return RString::new_utf8("/"), 203 | }; 204 | if pos != path.len() - 1 { 205 | return RString::new_utf8(&path[..pos + 1]); 206 | } 207 | } 208 | pth.unwrap() 209 | } 210 | 211 | // pub fn pn_descend(){} 212 | 213 | pub fn pn_is_directory(pth: MaybeString) -> Boolean { 214 | Boolean::new(Path::new(to_str(&pth)).is_dir()) 215 | } 216 | 217 | pub fn pn_dirname(pth: MaybeString) -> RString { 218 | RString::new_utf8(dirname::dirname(to_str(&pth))) 219 | } 220 | 221 | // pub fn pn_each_child(){} 222 | 223 | // pub fn pn_each_filename(pth: MaybeString) -> NilClass { 224 | // NilClass::new() 225 | // } 226 | 227 | pub fn pn_entries(pth: MaybeString) -> Result { 228 | let path = to_str(&pth); 229 | if let Ok(files) = fs::read_dir(path) { 230 | let mut arr = Array::with_capacity(files.size_hint().1.unwrap_or(0) + 2); 231 | 232 | arr.push(RString::new_utf8(".")); 233 | arr.push(RString::new_utf8("..")); 234 | 235 | for file in files { 236 | arr.push(RString::new_utf8(file.unwrap().file_name().to_str().unwrap())); 237 | } 238 | 239 | Ok(arr.to_any_object()) 240 | } else { 241 | let msg = format!("No such file or directory @ dir_initialize - {}", path); 242 | Err(Exception::new("Errno::NOENT", Some(&msg))) 243 | } 244 | } 245 | 246 | pub fn pn_entries_compat(pth: MaybeString) -> Result { 247 | let path = to_str(&pth); 248 | if let Ok(files) = fs::read_dir(path) { 249 | let mut arr = Array::with_capacity(files.size_hint().1.unwrap_or(0) + 2); 250 | 251 | arr.push(Pathname::new(".")); 252 | arr.push(Pathname::new("..")); 253 | 254 | for file in files { 255 | arr.push(Pathname::new(file.unwrap().file_name().to_str().unwrap())); 256 | } 257 | 258 | Ok(arr.to_any_object()) 259 | } else { 260 | let msg = format!("No such file or directory @ dir_initialize - {}", path); 261 | Err(Exception::new("Errno::NOENT", Some(&msg))) 262 | } 263 | } 264 | 265 | pub fn pn_extname(pth: MaybeString) -> RString { 266 | RString::new_utf8(extname::extname(to_str(&pth))) 267 | } 268 | 269 | // pub fn pn_find(pth: MaybeString, ignore_error: Boolean){} 270 | 271 | pub fn pn_has_trailing_separator(pth: MaybeString) -> Boolean { 272 | let v = to_str(&pth); 273 | match chop_basename::chop_basename(v) { 274 | Some((a,b)) => Boolean::new(a.len() + b.len() < v.len()), 275 | _ => Boolean::new(false) 276 | } 277 | } 278 | 279 | pub fn pn_join(args: MaybeArray) -> AnyObject { 280 | let paths = args.unwrap().into_iter().map(|arg| anyobject_to_string(arg).unwrap()).collect::>(); 281 | let mut paths_iter = paths.iter().rev(); 282 | let mut result = Cow::Borrowed(paths_iter.next().unwrap().as_str()); 283 | for part in paths_iter { 284 | result = plus::plus_paths(&part, result.as_ref()); 285 | if result.as_bytes().first() == Some(&SEP) { 286 | break; 287 | } 288 | } 289 | Pathname::new(&result).to_any_object() 290 | } 291 | 292 | // pub fn pn_mkpath(pth: MaybeString) -> NilClass { 293 | // NilClass::new() 294 | // } 295 | 296 | // pub fn pn_is_mountpoint(pth: MaybeString){} 297 | 298 | // pub fn pn_parent(pth: MaybeString){} 299 | 300 | pub fn pn_plus(pth1: MaybeString, pth2: MaybeString) -> RString { 301 | RString::new_utf8(&plus::plus_paths(to_str(&pth1), to_str(&pth2))) 302 | } 303 | 304 | // pub fn pn_prepend_prefix(prefix: MaybeString, relpath: MaybeString){} 305 | 306 | pub fn pn_is_relative(pth: MaybeString) -> Boolean { 307 | let path = match &pth { 308 | &Ok(ref rutie_string) => rutie_string.to_str(), 309 | &Err(_) => return Boolean::new(false), 310 | }; 311 | Boolean::new(path.as_bytes().get(0) != Some(&SEP)) 312 | } 313 | 314 | // pub fn pn_root(pth: MaybeString){} 315 | 316 | // pub fn pn_split_names(pth: MaybeString){} 317 | 318 | pub fn pn_relative_path_from(itself: MaybeString, base_directory: MaybeString) -> Result { 319 | relative_path_from::relative_path_from(itself, base_directory) 320 | } 321 | 322 | // pub fn pn_rmtree(pth: MaybeString) -> NilClass { 323 | // NilClass::new() 324 | // } 325 | // 326 | -------------------------------------------------------------------------------- /src/pathname_sys.rs: -------------------------------------------------------------------------------- 1 | use rutie::{AnyObject, Array}; 2 | use rutie::types::{Argc, Value}; 3 | use rutie::util::str_to_cstring; 4 | use rutie::rubysys::class; 5 | use ::pathname; 6 | use std::mem; 7 | 8 | // rutie doesn't support splat arguments yet natively 9 | pub extern fn pub_join(argc: Argc, argv: *const AnyObject, _: AnyObject) -> AnyObject { 10 | let args = Value::from(0); 11 | 12 | unsafe { 13 | let p_argv: *const Value = mem::transmute(argv); 14 | 15 | class::rb_scan_args( 16 | argc, 17 | p_argv, 18 | str_to_cstring("*").as_ptr(), 19 | &args 20 | ) 21 | }; 22 | 23 | pathname::pn_join(Ok(Array::from(args))) 24 | } 25 | -------------------------------------------------------------------------------- /src/plus.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::str; 3 | use std::path::MAIN_SEPARATOR; 4 | 5 | use chop_basename::chop_basename; 6 | use path_parsing::SEP; 7 | 8 | pub fn plus_paths<'a>(path1: &'a str, path2: &str) -> Cow<'a, str> { 9 | let mut prefix2 = path2; 10 | let mut index_list2: Vec = vec![]; 11 | let mut basename_list2: Vec<&str> = vec![]; 12 | loop { 13 | match chop_basename(prefix2) { 14 | None => { break; } 15 | Some((pfx2, basename2)) => { 16 | prefix2 = pfx2; 17 | index_list2.push(pfx2.len()); 18 | basename_list2.push(basename2); 19 | } 20 | } 21 | } 22 | if !prefix2.is_empty() { 23 | return path2.to_string().into(); 24 | }; 25 | 26 | let result_prefix: Cow; 27 | let mut prefix1 = path1; 28 | loop { 29 | let mut new_len = basename_list2.len() - count_trailing(".", &basename_list2); 30 | index_list2.truncate(new_len); 31 | basename_list2.truncate(new_len); 32 | match chop_basename(prefix1) { 33 | None => { 34 | result_prefix = prefix1.into(); 35 | break; 36 | } 37 | Some((pfx1, basename1)) => { 38 | prefix1 = pfx1; 39 | if basename1 == "." { continue; }; 40 | if basename1 == ".." || basename_list2.last() != Some(&"..") { 41 | result_prefix = [prefix1, basename1].concat().into(); 42 | break; 43 | } 44 | } 45 | } 46 | if new_len > 0 { 47 | new_len -= 1; 48 | index_list2.truncate(new_len); 49 | basename_list2.truncate(new_len); 50 | } 51 | } 52 | 53 | if !result_prefix.is_empty() && result_prefix.as_bytes().iter().cloned().all(|b| b == SEP) { 54 | let new_len = basename_list2.len() - count_trailing("..", &basename_list2); 55 | index_list2.truncate(new_len); 56 | basename_list2.truncate(new_len); 57 | } 58 | if let Some(last_index2) = index_list2.last() { 59 | let suffix = &path2[*last_index2..]; 60 | match (result_prefix.as_bytes().last(), suffix.as_bytes().first()) { 61 | (Some(&SEP), Some(&SEP)) => [&result_prefix, &suffix[1..]].concat().into(), 62 | (Some(&SEP), Some(_)) | (Some(_), Some(&SEP)) => [&result_prefix, suffix].concat().into(), 63 | (None, Some(_)) => suffix.to_string().into(), 64 | _ => format!("{}{}{}", result_prefix.as_ref(), MAIN_SEPARATOR, suffix).into(), 65 | } 66 | } else { 67 | if result_prefix.is_empty() { 68 | ".".into() 69 | } else { 70 | result_prefix 71 | } 72 | } 73 | } 74 | 75 | #[inline(always)] 76 | fn count_trailing(x: &str, xs: &Vec<&str>) -> usize { 77 | xs.iter().rev().take_while(|&c| c == &x).count() 78 | } 79 | 80 | #[test] 81 | fn it_will_plus_same_as_ruby() { 82 | assert_eq!("/" , plus_paths("/" , "/")); 83 | assert_eq!("a/b" , plus_paths("a" , "b")); 84 | assert_eq!("a" , plus_paths("a" , ".")); 85 | assert_eq!("b" , plus_paths("." , "b")); 86 | assert_eq!("." , plus_paths("." , ".")); 87 | assert_eq!("/b" , plus_paths("a" , "/b")); 88 | 89 | assert_eq!("/" , plus_paths("/" , "..")); 90 | assert_eq!("////" , plus_paths("////", "")); 91 | assert_eq!("." , plus_paths("a" , "..")); 92 | assert_eq!("a" , plus_paths("a/b", "..")); 93 | assert_eq!("../.." , plus_paths(".." , "..")); 94 | assert_eq!("/c" , plus_paths("/" , "../c")); 95 | assert_eq!("c" , plus_paths("a" , "../c")); 96 | assert_eq!("a/c" , plus_paths("a/b", "../c")); 97 | assert_eq!("../../c", plus_paths(".." , "../c")); 98 | 99 | assert_eq!("a//b/d//e", plus_paths("a//b/c", "../d//e")); 100 | 101 | assert_eq!("//foo/var/bar", plus_paths("//foo/var", "bar")); 102 | } 103 | -------------------------------------------------------------------------------- /src/prepend_prefix.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use dirname::dirname; 3 | use path_parsing::{SEP, contains_sep}; 4 | use std::path::MAIN_SEPARATOR; 5 | 6 | pub fn prepend_prefix<'a>(prefix: &'a str, relpath: &str) -> Cow<'a, str> { 7 | if relpath.is_empty() { 8 | dirname(prefix).into() 9 | } else if contains_sep(prefix.as_bytes()) { 10 | let prefix_dirname = dirname(prefix); 11 | match prefix_dirname.as_bytes().last() { 12 | None => relpath.to_string().into(), 13 | Some(&SEP) => format!("{}{}", prefix_dirname, relpath).into(), 14 | _ => format!("{}{}{}", prefix_dirname, MAIN_SEPARATOR, relpath).into() 15 | } 16 | } else { 17 | format!("{}{}", prefix, relpath).into() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/relative_path_from.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | use helpers::{is_same_path, to_str}; 3 | use path_parsing::SEP_STR; 4 | use cleanpath_aggressive::cleanpath_aggressive; 5 | use chop_basename::chop_basename; 6 | use pathname::Pathname; 7 | use rutie; 8 | use rutie::{Exception as Exc, AnyException as Exception}; 9 | 10 | type MaybeString = Result; 11 | 12 | pub fn relative_path_from(itself: MaybeString, base_directory: MaybeString) -> Result { 13 | let dest_directory = cleanpath_aggressive(to_str(&itself)); 14 | let base_directory = cleanpath_aggressive(to_str(&base_directory)); 15 | 16 | let (dest_prefix, mut dest_names) = to_names(dest_directory.as_ref()); 17 | let (base_prefix, mut base_names) = to_names(base_directory.as_ref()); 18 | 19 | if !is_same_path(&dest_prefix, &base_prefix) { 20 | return Err( 21 | Exception::new( 22 | "ArgumentError", 23 | Some(&format!("different prefix: {} and {}", dest_prefix, base_prefix)), 24 | ) 25 | ); 26 | } 27 | 28 | // Remove shared tail 29 | { 30 | let num_same = dest_names.iter().rev().zip(base_names.iter().rev()). 31 | take_while(|&(dest, base)| dest == base).count(); 32 | let num_dest_names = dest_names.len(); 33 | dest_names.truncate(num_dest_names - num_same); 34 | let num_base_names = base_names.len(); 35 | base_names.truncate(num_base_names - num_same); 36 | }; 37 | 38 | if base_names.contains(&"..") { 39 | return Err( 40 | Exception::new( 41 | "ArgumentError", 42 | Some(&format!("base_directory has ..: {}", base_directory)), 43 | ) 44 | ); 45 | } 46 | 47 | if base_names.is_empty() && dest_names.is_empty() { 48 | Ok(Pathname::new(".")) 49 | } else { 50 | Ok(Pathname::new(&iter::repeat("..").take(base_names.len()).chain(dest_names.into_iter().rev()). 51 | collect::>().join(&SEP_STR))) 52 | } 53 | } 54 | 55 | #[inline(always)] 56 | fn to_names(path: &str) -> (&str, Vec<&str>) { 57 | let mut result: Vec<&str> = vec![]; 58 | let mut prefix = path; 59 | loop { 60 | match chop_basename(&prefix) { 61 | Some((ref dest, ref basename)) => { 62 | prefix = dest; 63 | if basename != &"." { 64 | result.push(basename); 65 | } 66 | } 67 | None => return (prefix, result), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/rust_arch_bits.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn rust_arch_bits() -> i32 { 3 | use std::mem::size_of; 4 | let s = size_of::() * 8; 5 | s as i32 6 | } 7 | 8 | #[test] 9 | fn it_is_32_or_64(){ 10 | assert!(rust_arch_bits() == 32 || rust_arch_bits() == 64); 11 | } 12 | -------------------------------------------------------------------------------- /test/absolute_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AbsoluteTest < Minitest::Test 4 | def test_it_safely_takes_nil 5 | refute FasterPath.absolute? nil 6 | end 7 | 8 | def test_it_determins_absolute_path 9 | assert FasterPath.absolute?("/hello") 10 | refute FasterPath.absolute?("goodbye") 11 | end 12 | 13 | def test_it_returns_similar_results_to_pathname_absolute? 14 | ["", ".", "/", ".asdf", "/asdf/asdf", "/asdf/asdf.asdf", "asdf/asdf.asd"].each do |pth| 15 | assert_equal Pathname.new(pth).absolute?, 16 | FasterPath.absolute?(pth) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/add_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AddTrailingSeparatorTest < Minitest::Test 4 | def result_pair(str) 5 | [ 6 | Pathname.allocate.send(:add_trailing_separator, str), 7 | FasterPath.add_trailing_separator(str) 8 | ] 9 | end 10 | 11 | def test_add_trailing_separator 12 | assert_equal FasterPath.add_trailing_separator(''), '' 13 | assert_equal FasterPath.add_trailing_separator('/'), '/' 14 | assert_equal FasterPath.add_trailing_separator('//'), '//' 15 | assert_equal FasterPath.add_trailing_separator('hello'), 'hello/' 16 | assert_equal FasterPath.add_trailing_separator('hello/'), 'hello/' 17 | assert_equal FasterPath.add_trailing_separator('/hello/'), '/hello/' 18 | assert_equal FasterPath.add_trailing_separator('/hello//'), '/hello//' 19 | assert_equal FasterPath.add_trailing_separator('.'), './' 20 | assert_equal FasterPath.add_trailing_separator('./'), './' 21 | assert_equal FasterPath.add_trailing_separator('.//'), './/' 22 | end 23 | 24 | def test_add_trailing_separator_in_dosish_context 25 | if File.dirname('A:') == 'A:.' 26 | assert_equal FasterPath.add_trailing_separator('A:'), 'A:/' 27 | assert_equal FasterPath.add_trailing_separator('A:/'), 'A:/' 28 | assert_equal FasterPath.add_trailing_separator('A://'), 'A://' 29 | assert_equal FasterPath.add_trailing_separator('A:.'), 'A:./' 30 | assert_equal FasterPath.add_trailing_separator('A:./'), 'A:./' 31 | assert_equal FasterPath.add_trailing_separator('A:.//'), 'A:.//' 32 | end 33 | end 34 | 35 | def test_add_trailing_separator_against_pathname_implementation1 36 | assert_equal(*result_pair('')) 37 | assert_equal(*result_pair('/')) 38 | assert_equal(*result_pair('//')) 39 | assert_equal(*result_pair('hello')) 40 | assert_equal(*result_pair('hello/')) 41 | assert_equal(*result_pair('/hello/')) 42 | assert_equal(*result_pair('/hello//')) 43 | end 44 | 45 | def test_add_trailing_separator_against_pathname_implementation2 46 | assert_equal(*result_pair('.')) 47 | assert_equal(*result_pair('./')) 48 | assert_equal(*result_pair('.//')) 49 | assert_equal(*result_pair("aa/a//a")) 50 | assert_equal(*result_pair("/aaa/a//a")) 51 | assert_equal(*result_pair("/aaa/a//a/a")) 52 | assert_equal(*result_pair("/aaa/a//a/a")) 53 | assert_equal(*result_pair("a//aaa/a//a/a")) 54 | assert_equal(*result_pair("a//aaa/a//a/aaa")) 55 | assert_equal(*result_pair("/aaa/a//a/aaa/a")) 56 | assert_equal(*result_pair("a//aaa/a//a/aaa/a")) 57 | assert_equal(*result_pair("a//aaa/a//a/aaa////")) 58 | assert_equal(*result_pair("a/a//aaa/a//a/aaa/a")) 59 | assert_equal(*result_pair("////a//aaa/a//a/aaa/a")) 60 | assert_equal(*result_pair("////a//aaa/a//a/aaa////")) 61 | end 62 | 63 | def test_add_trailing_separator_against_pathname_implementation3 64 | assert_equal(*result_pair(".")) 65 | assert_equal(*result_pair(".././")) 66 | assert_equal(*result_pair(".///..")) 67 | assert_equal(*result_pair("/././/")) 68 | assert_equal(*result_pair("//../././")) 69 | assert_equal(*result_pair(".///.../..")) 70 | assert_equal(*result_pair("/././/.//.")) 71 | assert_equal(*result_pair("/...//../././")) 72 | assert_equal(*result_pair("/..///.../..//")) 73 | assert_equal(*result_pair("/./././/.//...")) 74 | assert_equal(*result_pair("/...//.././././/.")) 75 | assert_equal(*result_pair("./../..///.../..//")) 76 | assert_equal(*result_pair("///././././/.//...")) 77 | assert_equal(*result_pair("./../..///.../..//././")) 78 | assert_equal(*result_pair("///././././/.//....///")) 79 | assert_equal(*result_pair("http://www.example.com")) 80 | assert_equal(*result_pair("foor for thought")) 81 | assert_equal(*result_pair("2gb63b@%TY25GHawefb3/g3qb")) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/basename_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BasenameTest < Minitest::Test 4 | def setup 5 | @dir = Dir.mktmpdir("rubytest-file") 6 | File.chown(-1, Process.gid, @dir) 7 | end 8 | 9 | def teardown 10 | GC.start 11 | FileUtils.remove_entry_secure @dir 12 | end 13 | 14 | # Tests copied from https://searchcode.com/codesearch/view/12785140/ 15 | def test_it_creates_basename_correctly 16 | assert_equal FasterPath.basename('/home/gumby/work/ruby.rb'), 'ruby.rb' 17 | assert_equal FasterPath.basename('/home/gumby/work/ruby.rb', '.rb'), 'ruby' 18 | assert_equal FasterPath.basename('/home/gumby/work/ruby.rb', '.*'), 'ruby' 19 | assert_equal FasterPath.basename('ruby.rb', '.*'), 'ruby' 20 | assert_equal FasterPath.basename('/ruby.rb', '.*'), 'ruby' 21 | assert_equal FasterPath.basename('ruby.rbx', '.*'), 'ruby' 22 | assert_equal FasterPath.basename('ruby.rbx', '.rb'), 'ruby.rbx' 23 | assert_equal FasterPath.basename('ruby.rb', ''), 'ruby.rb' 24 | assert_equal FasterPath.basename('ruby.rbx', '.rb*'), 'ruby.rbx' 25 | assert_equal FasterPath.basename('ruby.rbx'), 'ruby.rbx' 26 | 27 | # Try some extensions w/o a '.' 28 | assert_equal FasterPath.basename('ruby.rbx', 'rbx'), 'ruby.' 29 | assert_equal FasterPath.basename('ruby.rbx', 'x'), 'ruby.rb' 30 | assert_equal FasterPath.basename('ruby.rbx', '*'), 'ruby.rbx' 31 | 32 | # A couple of regressions: 33 | assert_equal FasterPath.basename('', ''), '' 34 | assert_equal FasterPath.basename('/'), '/' 35 | assert_equal FasterPath.basename('//'), '/' 36 | assert_equal FasterPath.basename('//dir///base//'), 'base' 37 | assert_equal FasterPath.basename('.x', '.x'), '.x' 38 | 39 | # returns the basename for unix suffix 40 | assert_equal FasterPath.basename("bar.c", ".c"), "bar" 41 | assert_equal FasterPath.basename("bar.txt", ".txt"), "bar" 42 | assert_equal FasterPath.basename("/bar.txt", ".txt"), "bar" 43 | assert_equal FasterPath.basename("/foo/bar.txt", ".txt"), "bar" 44 | assert_equal FasterPath.basename("bar.txt", ".exe"), "bar.txt" 45 | assert_equal FasterPath.basename("bar.txt.exe", ".exe"), "bar.txt" 46 | assert_equal FasterPath.basename("bar.txt.exe", ".txt"), "bar.txt.exe" 47 | assert_equal FasterPath.basename("bar.txt", ".*"), "bar" 48 | assert_equal FasterPath.basename("bar.txt.exe", ".*"), "bar.txt" 49 | assert_equal FasterPath.basename("bar.txt.exe", ".txt.exe"), "bar" 50 | end 51 | 52 | def test_it_does_the_same_as_file_basename 53 | assert_equal FasterPath.basename('/home/gumby/work/ruby.rb'), 'ruby.rb' 54 | assert_equal FasterPath.basename('/home/gumby/work/ruby.rb', '.rb'), 'ruby' 55 | assert_equal FasterPath.basename('/home/gumby/work/ruby.rb', '.*'), 'ruby' 56 | assert_equal FasterPath.basename('ruby.rb', '.*'), 'ruby' 57 | assert_equal FasterPath.basename('/ruby.rb', '.*'), 'ruby' 58 | assert_equal FasterPath.basename('ruby.rbx', '.*'), 'ruby' 59 | assert_equal FasterPath.basename('ruby.rbx', '.rb'), 'ruby.rbx' 60 | assert_equal FasterPath.basename('ruby.rb', ''), 'ruby.rb' 61 | assert_equal FasterPath.basename('ruby.rbx', '.rb*'), 'ruby.rbx' 62 | assert_equal FasterPath.basename('ruby.rbx'), 'ruby.rbx' 63 | 64 | # Try some extensions w/o a '.' 65 | assert_equal FasterPath.basename('ruby.rbx', 'rbx'), 'ruby.' 66 | assert_equal FasterPath.basename('ruby.rbx', 'x'), 'ruby.rb' 67 | assert_equal FasterPath.basename('ruby.rbx', '*'), 'ruby.rbx' 68 | 69 | # A couple of regressions: 70 | assert_equal FasterPath.basename('', ''), '' 71 | assert_equal FasterPath.basename('/'), '/' 72 | assert_equal FasterPath.basename('//'), '/' 73 | assert_equal FasterPath.basename('//dir///base//'), 'base' 74 | end 75 | 76 | def test_basename_official 77 | assert_equal(FasterPath.basename(regular_file).sub(/\.test$/, ""), FasterPath.basename(regular_file, ".test")) 78 | assert_equal(FasterPath.basename(utf8_file).sub(/\.test$/, ""), FasterPath.basename(utf8_file, ".test")) 79 | assert_equal("", s = FasterPath.basename("")) 80 | refute_predicate(s, :frozen?, '[ruby-core:24199]') 81 | assert_equal("foo", s = FasterPath.basename("foo")) 82 | refute_predicate(s, :frozen?, '[ruby-core:24199]') 83 | assert_equal("foo", FasterPath.basename("foo", ".ext")) 84 | assert_equal("foo", FasterPath.basename("foo.ext", ".ext")) 85 | assert_equal("foo", FasterPath.basename("foo.ext", ".*")) 86 | end 87 | 88 | def test_basename_official_ntfs 89 | if NTFS 90 | [regular_file, utf8_file].each do |file| 91 | basename = FasterPath.basename(file) 92 | assert_equal(basename, FasterPath.basename(file + " ")) 93 | assert_equal(basename, FasterPath.basename(file + ".")) 94 | assert_equal(basename, FasterPath.basename(file + "::$DATA")) 95 | basename.chomp!(".test") 96 | assert_equal(basename, FasterPath.basename(file + " ", ".test")) 97 | assert_equal(basename, FasterPath.basename(file + ".", ".test")) 98 | assert_equal(basename, FasterPath.basename(file + "::$DATA", ".test")) 99 | assert_equal(basename, FasterPath.basename(file + " ", ".*")) 100 | assert_equal(basename, FasterPath.basename(file + ".", ".*")) 101 | assert_equal(basename, FasterPath.basename(file + "::$DATA", ".*")) 102 | end 103 | else 104 | [regular_file, utf8_file].each do |file| 105 | basename = FasterPath.basename(file) 106 | assert_equal(basename + " ", FasterPath.basename(file + " ")) 107 | assert_equal(basename + ".", FasterPath.basename(file + ".")) 108 | assert_equal(basename + "::$DATA", FasterPath.basename(file + "::$DATA")) 109 | assert_equal(basename + " ", FasterPath.basename(file + " ", ".test")) 110 | assert_equal(basename + ".", FasterPath.basename(file + ".", ".test")) 111 | assert_equal(basename + "::$DATA", FasterPath.basename(file + "::$DATA", ".test")) 112 | assert_equal(basename, FasterPath.basename(file + ".", ".*")) 113 | basename.chomp!(".test") 114 | assert_equal(basename, FasterPath.basename(file + " ", ".*")) 115 | assert_equal(basename, FasterPath.basename(file + "::$DATA", ".*")) 116 | end 117 | end 118 | end 119 | 120 | def test_basename_official_encoding 121 | if File::ALT_SEPARATOR == '\\' 122 | a = "foo/\225\\\\" 123 | [%W[cp437 \225], %W[cp932 \225\\]].each do |cp, expected| 124 | assert_equal(expected.force_encoding(cp), FasterPath.basename(a.dup.force_encoding(cp)), cp) 125 | end 126 | end 127 | assert_incompatible_encoding {|d| FasterPath.basename(d)} 128 | assert_incompatible_encoding {|d| FasterPath.basename(d, ".*")} 129 | assert_raises(Encoding::CompatibilityError) {FasterPath.basename("foo.ext", ".*".encode("utf-16le"))} 130 | s = "foo\x93_a".force_encoding("cp932") 131 | assert_equal(s, FasterPath.basename(s, "_a")) 132 | s = "\u4032.\u3024" 133 | assert_equal(s, FasterPath.basename(s, ".\x95\\".force_encoding("cp932"))) 134 | end if ENV['ENCODING'].to_s['true'] 135 | end 136 | -------------------------------------------------------------------------------- /test/benches/absolute_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class AbsoluteBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | @one = "/hello" 7 | @two = "goodbye" 8 | end 9 | 10 | def teardown 11 | super 12 | graph_benchmarks 13 | end 14 | 15 | def bench_rust_absolute? 16 | benchmark :rust do 17 | FasterPath.absolute?(@one) 18 | FasterPath.absolute?(@two) 19 | end 20 | end 21 | 22 | def bench_ruby_absolute? 23 | one = Pathname.new(@one) 24 | two = Pathname.new(@two) 25 | benchmark :ruby do 26 | one.absolute? 27 | two.absolute? 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/benches/add_trailing_separator_benchmark.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark_helper' 2 | 3 | class AddTrailingSeparatorBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_add_trailing_separator 14 | benchmark :rust do 15 | FasterPath.add_trailing_separator('/hello/world') 16 | FasterPath.add_trailing_separator('/hello/world/') 17 | end 18 | end 19 | 20 | def bench_ruby_add_trailing_separator 21 | benchmark :ruby do 22 | Pathname.allocate.send(:add_trailing_separator, '/hello/world') 23 | Pathname.allocate.send(:add_trailing_separator, '/hello/world/') 24 | end 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /test/benches/basename_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class BasenameBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_basename 14 | benchmark :rust do 15 | FasterPath.basename("/hello/world") 16 | FasterPath.basename('/home/gumby/work/ruby.rb', '.rb') 17 | FasterPath.basename('/home/gumby/work/ruby.rb', '.*') 18 | FasterPath.basename('ruby.rbx', 'rbx') 19 | FasterPath.basename('ruby.rbx', 'x') 20 | FasterPath.basename('ruby.rbx', '*') 21 | end 22 | end 23 | 24 | def bench_ruby_basename 25 | benchmark :ruby do 26 | File.basename("/hello/world") 27 | File.basename('/home/gumby/work/ruby.rb', '.rb') 28 | File.basename('/home/gumby/work/ruby.rb', '.*') 29 | File.basename('ruby.rbx', 'rbx') 30 | File.basename('ruby.rbx', 'x') 31 | File.basename('ruby.rbx', '*') 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/benches/children_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class ChildrenBenchmark < BenchmarkHelper 4 | def self.bench_range 5 | [20, 40, 60, 80, 100] 6 | end 7 | 8 | def setup 9 | @file ||= __FILE__ 10 | @one = "." 11 | @two = "../" 12 | end 13 | 14 | def teardown 15 | super 16 | graph_benchmarks 17 | end 18 | 19 | def bench_rust_children 20 | benchmark :rust do 21 | FasterPath.children(@one) 22 | FasterPath.children(@two, false) 23 | end 24 | end 25 | 26 | def bench_ruby_children 27 | one = Pathname.new(@one) 28 | two = Pathname.new(@two) 29 | benchmark :ruby do 30 | one.children 31 | two.children(false) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/benches/children_compat_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class ChildrenCompatBenchmark < BenchmarkHelper 4 | def self.bench_range 5 | [20, 40, 60, 80, 100] 6 | end 7 | 8 | def setup 9 | @file ||= __FILE__ 10 | @one = "." 11 | @two = "../" 12 | end 13 | 14 | def teardown 15 | super 16 | graph_benchmarks 17 | end 18 | 19 | def bench_rust_children_compat 20 | benchmark :rust do 21 | FasterPath.children_compat(@one) 22 | FasterPath.children_compat(@two, false) 23 | end 24 | end 25 | 26 | def bench_ruby_children______ 27 | one = Pathname.new(@one) 28 | two = Pathname.new(@two) 29 | benchmark :ruby do 30 | one.children 31 | two.children(false) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/benches/chop_basename_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class ChopBasenameBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_chop_basename 14 | benchmark :rust do 15 | FasterPath.chop_basename "/hello/world.txt" 16 | FasterPath.chop_basename "world.txt" 17 | FasterPath.chop_basename "" 18 | end 19 | end 20 | 21 | def bench_ruby_chop_basename 22 | benchmark :ruby do 23 | Pathname.new("").send :chop_basename, "/hello/world.txt" 24 | Pathname.new("").send :chop_basename, "world.txt" 25 | Pathname.new("").send :chop_basename, "" 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/benches/cleanpath_aggressive_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class CleanpathAggressiveBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | @one = 'a/../.' 7 | @two = 'a/b/../../../../c/../d' 8 | end 9 | 10 | def teardown 11 | super 12 | graph_benchmarks 13 | end 14 | 15 | def bench_rust_cleanpath_aggressive 16 | benchmark :rust do 17 | FasterPath.cleanpath_aggressive(@one) 18 | FasterPath.cleanpath_aggressive(@two) 19 | end 20 | end 21 | 22 | def bench_ruby_cleanpath_aggressive 23 | one = Pathname.new(@one) 24 | two = Pathname.new(@two) 25 | benchmark :ruby do 26 | one.send :cleanpath_aggressive 27 | two.send :cleanpath_aggressive 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/benches/cleanpath_conservative_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class CleanpathConservativeBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | @one = 'a/../.' 7 | @two = 'a/b/../../../../c/../d' 8 | end 9 | 10 | def teardown 11 | super 12 | graph_benchmarks 13 | end 14 | 15 | def bench_rust_cleanpath_conservative 16 | benchmark :rust do 17 | FasterPath.cleanpath_conservative(@one) 18 | FasterPath.cleanpath_conservative(@two) 19 | end 20 | end 21 | 22 | def bench_ruby_cleanpath_conservative 23 | one = Pathname.new(@one) 24 | two = Pathname.new(@two) 25 | benchmark :ruby do 26 | one.send :cleanpath_conservative 27 | two.send :cleanpath_conservative 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/benches/del_trailing_separator_benchmark.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark_helper' 2 | 3 | class DelTrailingSeparatorBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_del_trailing_separator 14 | benchmark :rust do 15 | FasterPath.del_trailing_separator('/hello/world') 16 | FasterPath.del_trailing_separator('/hello/world/') 17 | end 18 | end 19 | 20 | def bench_ruby_del_trailing_separator 21 | benchmark :ruby do 22 | Pathname.allocate.send(:del_trailing_separator, '/hello/world') 23 | Pathname.allocate.send(:del_trailing_separator, '/hello/world/') 24 | end 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /test/benches/directory_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class DirectoryBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_directory? 14 | benchmark :rust do 15 | FasterPath.directory?("/hello") 16 | FasterPath.directory?("goodbye") 17 | end 18 | end 19 | 20 | def bench_ruby_directory? 21 | benchmark :ruby do 22 | Pathname.new("/hello").directory? 23 | Pathname.new("goodbye").directory? 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/benches/dirname_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class DirnameBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_dirname 14 | benchmark :rust do 15 | FasterPath.dirname "/really/long/path/name/which/ruby/doesnt/like/bar.txt" 16 | FasterPath.dirname "/foo/" 17 | FasterPath.dirname "." 18 | end 19 | end 20 | 21 | def bench_ruby_dirname 22 | benchmark :ruby do 23 | File.dirname "/really/long/path/name/which/ruby/doesnt/like/bar.txt" 24 | File.dirname "/foo/" 25 | File.dirname "." 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/benches/entries_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class EntriesBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def self.bench_range 14 | [2000, 4000, 6000, 8000, 10_000] 15 | end 16 | 17 | def bench_rust_entries 18 | benchmark :rust do 19 | FasterPath.entries(".") 20 | FasterPath.entries("src") 21 | end 22 | end 23 | 24 | def bench_ruby_entries 25 | benchmark :ruby do 26 | Pathname.new(".").entries 27 | Pathname.new("src").entries 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/benches/entries_compat_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class EntriesCompatBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def self.bench_range 14 | [2000, 4000, 6000, 8000, 10_000] 15 | end 16 | 17 | def bench_rust_entries_compat 18 | benchmark :rust do 19 | FasterPath.entries_compat(".") 20 | FasterPath.entries_compat("src") 21 | end 22 | end 23 | 24 | def bench_ruby_entries______ 25 | benchmark :ruby do 26 | Pathname.new(".").entries 27 | Pathname.new("src").entries 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/benches/extname_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class ExtnameBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def cases 14 | %w[ 15 | verylongfilename_verylongfilename_verylongfilename_verylongfilename.rb 16 | /very/long/path/name/very/long/path/name/very/long/path/name/file.rb 17 | /ext/mail.rb 18 | lots/of/trailing/slashes.rb///////////////////// 19 | .hiddenfile 20 | very_long_extension_verylongextensionverylongextensionverylongextensionverylongextension.rb 21 | ] + [''] 22 | end 23 | 24 | def bench_rust_extname 25 | benchmark :rust do 26 | cases.each { |path| FasterPath.extname(path) } 27 | end 28 | end 29 | 30 | def bench_ruby_extname 31 | benchmark :ruby do 32 | cases.each { |path| File.extname(path) } 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/benches/has_trailing_separator_benchmark.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark_helper' 2 | 3 | class HasTrailingSeparatorBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_has_trailing_separator 14 | benchmark :rust do 15 | FasterPath.has_trailing_separator? '////a//aaa/a//a/aaa////' 16 | FasterPath.has_trailing_separator? 'hello/' 17 | end 18 | end 19 | 20 | def bench_ruby_has_trailing_separator 21 | benchmark :ruby do 22 | Pathname.allocate.send :has_trailing_separator?, '////a//aaa/a//a/aaa////' 23 | Pathname.allocate.send :has_trailing_separator?, 'hello/' 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/benches/join_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class JoinBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | @pathname = Pathname.new(".").freeze 7 | end 8 | 9 | def teardown 10 | super 11 | graph_benchmarks 12 | end 13 | 14 | def bench_rust_join 15 | benchmark :rust do 16 | FasterPath.join('.', 'b') 17 | FasterPath.join('.', '../d//e') 18 | end 19 | end 20 | 21 | def bench_ruby_join 22 | benchmark :ruby do 23 | @pathname.send(:join, 'b') 24 | @pathname.send(:join, '../d//e') 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/benches/plus_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class PlusBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_plus 14 | benchmark :rust do 15 | FasterPath.plus('a', 'b') 16 | FasterPath.plus('.', 'b') 17 | FasterPath.plus('a//b/c', '../d//e') 18 | end 19 | end 20 | 21 | def bench_ruby_plus 22 | benchmark :ruby do 23 | Pathname.allocate.send(:plus, 'a', 'b') 24 | Pathname.allocate.send(:plus, '.', 'b') 25 | Pathname.allocate.send(:plus, 'a//b/c', '../d//e') 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/benches/relative_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class RelativeBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | end 7 | 8 | def teardown 9 | super 10 | graph_benchmarks 11 | end 12 | 13 | def bench_rust_relative? 14 | benchmark :rust do 15 | FasterPath.relative?("/hello") 16 | FasterPath.relative?("goodbye") 17 | end 18 | end 19 | 20 | def bench_ruby_relative? 21 | benchmark :ruby do 22 | Pathname.new("/hello").relative? 23 | Pathname.new("goodbye").relative? 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/benches/relative_path_from_benchmark.rb: -------------------------------------------------------------------------------- 1 | require "benchmark_helper" 2 | 3 | class RelativePathFromBenchmark < BenchmarkHelper 4 | def setup 5 | @file ||= __FILE__ 6 | @one = "/a/b/c/d" 7 | @two = "/a/b" 8 | end 9 | 10 | def teardown 11 | super 12 | graph_benchmarks 13 | end 14 | 15 | def bench_rust_relative_path_from 16 | benchmark :rust do 17 | FasterPath.relative_path_from "/a/b/c/d", "/a/b" 18 | FasterPath.relative_path_from "/a/b", "/a/b/c/d" 19 | end 20 | end 21 | 22 | def bench_ruby_relative_path_from 23 | one = Pathname.new(@one) 24 | two = Pathname.new(@two) 25 | benchmark :ruby do 26 | one.relative_path_from two 27 | two.relative_path_from one 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/benchmark_helper.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "minitest/benchmark" 3 | require 'fileutils' 4 | require 'stop_watch' 5 | if ENV['GRAPH'] 6 | require 'gruff' 7 | puts "Generating graphs..." 8 | else 9 | puts "Not generating graphs.\nSet GRAPH environment variable if you wish to generate graphs." 10 | end 11 | 12 | class BenchmarkHelper < Minitest::Benchmark 13 | def self.bench_range 14 | [20_000, 40_000, 60_000, 80_000, 100_000] 15 | end 16 | 17 | def benchmark lang 18 | assert_performance_constant do |n| 19 | send(lang).mark 20 | n.times do 21 | yield 22 | end 23 | $stdout.flush 24 | end 25 | send(lang).mark 26 | end 27 | 28 | def graph_benchmarks 29 | if rust.time? && ruby.time? && ENV['GRAPH'] 30 | g = Gruff::Line.new 31 | g.title = graph_title 32 | g.labels = generate_benchmark_range_labels 33 | 34 | g.data(:ruby, graph_times(:ruby)) 35 | g.data(:rust, graph_times(:rust)) 36 | 37 | g.write( output_file ) 38 | end 39 | end 40 | 41 | private 42 | def test_name 43 | File.basename(@file, '.rb') 44 | end 45 | 46 | def graph_title 47 | test_name.split('_').map(&:capitalize).join(' ') 48 | end 49 | 50 | def output_file 51 | path = File.join(File.expand_path('..', __dir__), 'doc', 'graph') 52 | 53 | FileUtils.mkdir_p path 54 | 55 | File.join path, "#{test_name}.png" 56 | end 57 | 58 | def ranges_for_benchmarks 59 | instance_exec do 60 | self.class.bench_range if defined?(self.class.bench_range) 61 | end || BenchmarkHelper.bench_range 62 | end 63 | 64 | def generate_benchmark_range_labels 65 | ranges_for_benchmarks. 66 | each_with_object({}). 67 | with_index do |(val, hash), idx| 68 | hash[ idx.succ ] = commafy val 69 | end.merge({0 => 0}) 70 | end 71 | 72 | Languages = Struct.new(:ruby, :rust) do 73 | def initialize 74 | super(StopWatch::Timer.new, StopWatch::Timer.new) 75 | end 76 | end 77 | 78 | TIMERS = Hash.new. 79 | tap do |t| 80 | t.default_proc = \ 81 | ->(hash, key){ hash[key] = Languages.new } 82 | end 83 | 84 | def timers 85 | TIMERS[@file] 86 | end 87 | 88 | def ruby 89 | timers.ruby 90 | end 91 | 92 | def rust 93 | timers.rust 94 | end 95 | 96 | def graph_times lang 97 | send(lang).times.unshift(0) 98 | end 99 | 100 | def commafy num 101 | num.to_s.chars.reverse. 102 | each_with_object(""). 103 | with_index do |(val, str), idx| 104 | str.prepend((idx%3).zero? ? val + ',' : val) 105 | end.chop 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /test/blank_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BlankTest < Minitest::Test 4 | def test_it_is_blank? 5 | assert FasterPath.blank? " " 6 | assert FasterPath.blank? "" 7 | assert FasterPath.blank? nil 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/children_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ChildrenTest < Minitest::Test 4 | def test_pathnames_existing_behavior 5 | assert_kind_of Pathname, Pathname.new('.').children.first 6 | end 7 | 8 | def test_children_returns_string_objects 9 | assert_kind_of String, FasterPath.children('.').first 10 | end 11 | 12 | def test_children_compat_returns_pathname_objects 13 | assert_kind_of Pathname, FasterPath.children_compat('.').first 14 | end 15 | 16 | def test_children_compat_returns_similar_results_to_pathname_children 17 | [".", "/", "../"].each do |pth| 18 | assert_equal Pathname.new(pth).children, 19 | FasterPath.children_compat(pth) 20 | end 21 | end 22 | 23 | def test_children_returns_similar_string_results_to_pathname_children 24 | [".", "/", "../"].each do |pth| 25 | assert_equal Pathname.new(pth).children.map(&:to_s), 26 | FasterPath.children(pth) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/chop_basename_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ChopBasenameTest < Minitest::Test 4 | def test_nil_inputs 5 | assert_nil FasterPath.chop_basename(nil) 6 | end 7 | 8 | def test_it_chops_basename_ 9 | result = FasterPath.chop_basename("/hello/world.txt") 10 | assert_equal result.length, 2 11 | assert_equal result.last, "world.txt" 12 | assert_equal result.first, "/hello/" 13 | result = FasterPath.chop_basename("world.txt") 14 | assert_equal result.length, 2 15 | assert_equal result.last, "world.txt" 16 | assert_equal result.first, "" 17 | result = FasterPath.chop_basename("") 18 | refute result 19 | assert result.nil? 20 | end 21 | 22 | def test_it_returns_similar_results_to_pathname_chop_basename 23 | ["hello/world/123.txt", "/hello/world.txt", "hello.txt", "h"]. 24 | each do |str| 25 | pcb = Pathname.new("").send :chop_basename, str 26 | fpcb = FasterPath.chop_basename str 27 | Array(pcb).zip(Array(fpcb)).each do |a, b| 28 | assert_equal a, b, "a: #{a} and b: #{b}" 29 | end 30 | end 31 | end 32 | 33 | def test_it_returns_similar_results_to_pathname_chop_basename_for_slash 34 | ["", File::SEPARATOR, File::SEPARATOR*2]. 35 | each do |str| 36 | pcb = Pathname.new("").send :chop_basename, str 37 | fpcb = FasterPath.chop_basename str 38 | Array(pcb).zip(Array(fpcb)).each do |a, b| 39 | assert_equal a, b, "a: #{a} and b: #{b}" 40 | assert_nil a 41 | assert_nil b 42 | end 43 | end 44 | end 45 | 46 | def test_it_returns_similar_results_to_pathname_chop_basename_for_dot_files 47 | [".hello/world/123.txt", "./hello/world.txt", ".hello.txt", ".h", 48 | ".hello/.world/.123.txt", "./hello/.world.txt", ".hello.txt", "../.h"]. 49 | each do |str| 50 | pcb = Pathname.new("").send :chop_basename, str 51 | fpcb = FasterPath.chop_basename str 52 | Array(pcb).zip(Array(fpcb)).each do |a, b| 53 | assert_equal a, b, "a: #{a} and b: #{b}" 54 | end 55 | end 56 | end 57 | 58 | def test_of_ayes 59 | result_pair = lambda do |str| 60 | [ 61 | Pathname.new("").send(:chop_basename, str), 62 | FasterPath.chop_basename(str) 63 | ] 64 | end 65 | assert_equal( *result_pair.("aa/a//a") ) 66 | assert_equal( *result_pair.("/aaa/a//a") ) 67 | assert_equal( *result_pair.("/aaa/a//a/a") ) 68 | assert_equal( *result_pair.("/aaa/a//a/a") ) 69 | assert_equal( *result_pair.("a//aaa/a//a/a") ) 70 | assert_equal( *result_pair.("a//aaa/a//a/aaa") ) 71 | assert_equal( *result_pair.("/aaa/a//a/aaa/a") ) 72 | assert_equal( *result_pair.("a//aaa/a//a/aaa/a") ) 73 | assert_equal( *result_pair.("a//aaa/a//a/aaa////") ) 74 | assert_equal( *result_pair.("a/a//aaa/a//a/aaa/a") ) 75 | assert_equal( *result_pair.("////a//aaa/a//a/aaa/a") ) 76 | assert_equal( *result_pair.("////a//aaa/a//a/aaa////") ) 77 | end 78 | 79 | def test_of_bees 80 | result_pair = lambda do |str| 81 | [ 82 | Pathname.new("").send(:chop_basename, str), 83 | FasterPath.chop_basename(str) 84 | ] 85 | end 86 | assert_equal( *result_pair.(".") ) 87 | assert_equal( *result_pair.(".././") ) 88 | assert_equal( *result_pair.(".///..") ) 89 | assert_equal( *result_pair.("/././/") ) 90 | assert_equal( *result_pair.("//../././") ) 91 | assert_equal( *result_pair.(".///.../..") ) 92 | assert_equal( *result_pair.("/././/.//.") ) 93 | assert_equal( *result_pair.("/...//../././") ) 94 | assert_equal( *result_pair.("/..///.../..//") ) 95 | assert_equal( *result_pair.("/./././/.//...") ) 96 | assert_equal( *result_pair.("/...//.././././/.") ) 97 | assert_equal( *result_pair.("./../..///.../..//") ) 98 | assert_equal( *result_pair.("///././././/.//...") ) 99 | assert_equal( *result_pair.("./../..///.../..//././") ) 100 | assert_equal( *result_pair.("///././././/.//....///") ) 101 | end 102 | 103 | def test_of_seas 104 | result_pair = lambda do |str| 105 | [ 106 | Pathname.new("").send(:chop_basename, str), 107 | FasterPath.chop_basename(str) 108 | ] 109 | end 110 | assert_equal( *result_pair.("http://www.example.com") ) 111 | assert_equal( *result_pair.("foor for thought") ) 112 | assert_equal( *result_pair.("2gb63b@%TY25GHawefb3/g3qb")) 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /test/cleanpath_aggressive_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CleanpathAggressiveTest < Minitest::Test 4 | def test_it_creates_cleanpath_aggressive_correctly_part_1 5 | assert_equal FasterPath.cleanpath_aggressive('/' ), '/' 6 | assert_equal FasterPath.cleanpath_aggressive('' ), '.' 7 | assert_equal FasterPath.cleanpath_aggressive('.' ), '.' 8 | assert_equal FasterPath.cleanpath_aggressive('..' ), '..' 9 | assert_equal FasterPath.cleanpath_aggressive('a' ), 'a' 10 | assert_equal FasterPath.cleanpath_aggressive('/.' ), '/' 11 | assert_equal FasterPath.cleanpath_aggressive('/..' ), '/' 12 | assert_equal FasterPath.cleanpath_aggressive('/a' ), '/a' 13 | assert_equal FasterPath.cleanpath_aggressive('./' ), '.' 14 | assert_equal FasterPath.cleanpath_aggressive('../' ), '..' 15 | assert_equal FasterPath.cleanpath_aggressive('a/' ), 'a' 16 | assert_equal FasterPath.cleanpath_aggressive('a//b' ), 'a/b' 17 | assert_equal FasterPath.cleanpath_aggressive('a/.' ), 'a' 18 | assert_equal FasterPath.cleanpath_aggressive('a/./' ), 'a' 19 | assert_equal FasterPath.cleanpath_aggressive('a/..' ), '.' 20 | assert_equal FasterPath.cleanpath_aggressive('a/../' ), '.' 21 | end 22 | 23 | def test_it_creates_cleanpath_aggressive_correctly_part_2 24 | assert_equal FasterPath.cleanpath_aggressive('/a/.' ), '/a' 25 | assert_equal FasterPath.cleanpath_aggressive('./..' ), '..' 26 | assert_equal FasterPath.cleanpath_aggressive('../.' ), '..' 27 | assert_equal FasterPath.cleanpath_aggressive('./../' ), '..' 28 | assert_equal FasterPath.cleanpath_aggressive('.././' ), '..' 29 | assert_equal FasterPath.cleanpath_aggressive('/./..' ), '/' 30 | assert_equal FasterPath.cleanpath_aggressive('/../.' ), '/' 31 | assert_equal FasterPath.cleanpath_aggressive('/./../' ), '/' 32 | assert_equal FasterPath.cleanpath_aggressive('/.././' ), '/' 33 | assert_equal FasterPath.cleanpath_aggressive('a/b/c' ), 'a/b/c' 34 | assert_equal FasterPath.cleanpath_aggressive('./b/c' ), 'b/c' 35 | assert_equal FasterPath.cleanpath_aggressive('a/./c' ), 'a/c' 36 | assert_equal FasterPath.cleanpath_aggressive('a/b/.' ), 'a/b' 37 | assert_equal FasterPath.cleanpath_aggressive('a/../.' ), '.' 38 | assert_equal FasterPath.cleanpath_aggressive('/../.././../a' ), '/a' 39 | assert_equal FasterPath.cleanpath_aggressive('a/b/../../../../c/../d'), '../../d' 40 | end 41 | 42 | def test_windows_compat 43 | if DOSISH_UNC 44 | assert_equal FasterPath.cleanpath_aggressive('//a/b/c/'), '//a/b/c' 45 | else 46 | assert_equal FasterPath.cleanpath_aggressive('///'), '/' 47 | assert_equal FasterPath.cleanpath_aggressive('///a'), '/a' 48 | assert_equal FasterPath.cleanpath_aggressive('///..'), '/' 49 | assert_equal FasterPath.cleanpath_aggressive('///.'), '/' 50 | assert_equal FasterPath.cleanpath_aggressive('///a/../..'), '/' 51 | end 52 | 53 | if DOSISH 54 | assert_equal FasterPath.cleanpath_aggressive('c:\\foo\\bar'), 'c:/foo/bar' 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/cleanpath_conservative_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CleanpathConservativeTest < Minitest::Test 4 | def test_it_creates_cleanpath_conservative_correctly_part_1 5 | assert_equal FasterPath.cleanpath_conservative("/"), "/" 6 | assert_equal FasterPath.cleanpath_conservative(""), "." 7 | assert_equal FasterPath.cleanpath_conservative("."), "." 8 | assert_equal FasterPath.cleanpath_conservative(".."), ".." 9 | assert_equal FasterPath.cleanpath_conservative("a"), "a" 10 | assert_equal FasterPath.cleanpath_conservative("/."), "/" 11 | assert_equal FasterPath.cleanpath_conservative("/.."), "/" 12 | assert_equal FasterPath.cleanpath_conservative("/a"), "/a" 13 | assert_equal FasterPath.cleanpath_conservative("./"), "." 14 | assert_equal FasterPath.cleanpath_conservative("../"), ".." 15 | assert_equal FasterPath.cleanpath_conservative("a/"), "a/" 16 | assert_equal FasterPath.cleanpath_conservative("a//b"), "a/b" 17 | assert_equal FasterPath.cleanpath_conservative("a/."), "a/." 18 | assert_equal FasterPath.cleanpath_conservative("a/./"), "a/." 19 | assert_equal FasterPath.cleanpath_conservative("a/../"), "a/.." 20 | assert_equal FasterPath.cleanpath_conservative("/a/."), "/a/." 21 | end 22 | 23 | def test_it_creates_cleanpath_conservative_correctly_part_2 24 | assert_equal FasterPath.cleanpath_conservative("./.."), ".." 25 | assert_equal FasterPath.cleanpath_conservative("../."), ".." 26 | assert_equal FasterPath.cleanpath_conservative("./../"), ".." 27 | assert_equal FasterPath.cleanpath_conservative(".././"), ".." 28 | assert_equal FasterPath.cleanpath_conservative("/./.."), "/" 29 | assert_equal FasterPath.cleanpath_conservative("/../."), "/" 30 | assert_equal FasterPath.cleanpath_conservative("/./../"), "/" 31 | assert_equal FasterPath.cleanpath_conservative("/.././"), "/" 32 | assert_equal FasterPath.cleanpath_conservative("a/b/c"), "a/b/c" 33 | assert_equal FasterPath.cleanpath_conservative("./b/c"), "b/c" 34 | assert_equal FasterPath.cleanpath_conservative("a/./c"), "a/c" 35 | assert_equal FasterPath.cleanpath_conservative("a/b/."), "a/b/." 36 | assert_equal FasterPath.cleanpath_conservative("a/../."), "a/.." 37 | assert_equal FasterPath.cleanpath_conservative("/../.././../a"), "/a" 38 | assert_equal FasterPath.cleanpath_conservative("a/b/../../../../c/../d"), "a/b/../../../../c/../d" 39 | end 40 | 41 | def test_windows_compat 42 | if DOSISH 43 | assert_equal FasterPath.cleanpath_conservative('c:/foo/bar'), 'c:\\foo\\bar' 44 | end 45 | 46 | if DOSISH_UNC 47 | assert_equal FasterPath.cleanpath_conservative('//'), '//' 48 | else 49 | assert_equal FasterPath.cleanpath_conservative('//'), '/' 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/del_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DelTrailingSeparatorTest < Minitest::Test 4 | def test_del_trailing_separator 5 | assert_equal FasterPath.del_trailing_separator(""), "" 6 | assert_equal FasterPath.del_trailing_separator("/"), "/" 7 | assert_equal FasterPath.del_trailing_separator("/a"), "/a" 8 | assert_equal FasterPath.del_trailing_separator("/a/"), "/a" 9 | assert_equal FasterPath.del_trailing_separator("/a//"), "/a" 10 | assert_equal FasterPath.del_trailing_separator("."), "." 11 | assert_equal FasterPath.del_trailing_separator("./"), "." 12 | assert_equal FasterPath.del_trailing_separator(".//"), "." 13 | end 14 | 15 | if DOSISH_DRIVE_LETTER 16 | def test_del_trailing_separator_dos_drive_letter 17 | assert_equal FasterPath.del_trailing_separator("A:"), "A:" 18 | assert_equal FasterPath.del_trailing_separator("A:/"), "A:/" 19 | assert_equal FasterPath.del_trailing_separator("A://"), "A:/" 20 | assert_equal FasterPath.del_trailing_separator("A:."), "A:." 21 | assert_equal FasterPath.del_trailing_separator("A:./"), "A:." 22 | assert_equal FasterPath.del_trailing_separator("A:.//"), "A:." 23 | end 24 | end 25 | 26 | def test_del_trailing_separator_dos_unc 27 | if DOSISH_UNC 28 | assert_equal FasterPath.del_trailing_separator("//"), "//" 29 | assert_equal FasterPath.del_trailing_separator("//a"), "//a" 30 | assert_equal FasterPath.del_trailing_separator("//a/"), "//a" 31 | assert_equal FasterPath.del_trailing_separator("//a//"), "//a" 32 | assert_equal FasterPath.del_trailing_separator("//a/b"), "//a/b" 33 | assert_equal FasterPath.del_trailing_separator("//a/b/"), "//a/b" 34 | assert_equal FasterPath.del_trailing_separator("//a/b//"), "//a/b" 35 | assert_equal FasterPath.del_trailing_separator("//a/b/c"), "//a/b/c" 36 | assert_equal FasterPath.del_trailing_separator("//a/b/c/"), "//a/b/c" 37 | assert_equal FasterPath.del_trailing_separator("//a/b/c//"), "//a/b/c" 38 | else 39 | assert_equal FasterPath.del_trailing_separator("///"), "/" 40 | assert_equal FasterPath.del_trailing_separator("///a/"), "///a" 41 | end 42 | end 43 | 44 | if DOSISH 45 | def test_del_trailing_separator_dos 46 | assert_equal FasterPath.del_trailing_separator("a\\"), "a" 47 | assert_equal FasterPath.del_trailing_separator("\225\\\\".dup.force_encoding("cp932")), "\225\\".dup.force_encoding("cp932") 48 | assert_equal FasterPath.del_trailing_separator("\225\\\\".dup.force_encoding("cp437")), "\225".dup.force_encoding("cp437") 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/directory_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DirectoryTest < Minitest::Test 4 | def setup 5 | @dir = Dir.mktmpdir("rubytest-file") 6 | File.chown(-1, Process.gid, @dir) 7 | end 8 | 9 | def teardown 10 | GC.start 11 | FileUtils.remove_entry_secure @dir 12 | end 13 | 14 | def test_nil_for_directory? 15 | refute FasterPath.directory? nil 16 | end 17 | 18 | def test_of_ayes 19 | result_pair = lambda do |str| 20 | [ 21 | Pathname.new(str).send(:directory?), 22 | FasterPath.directory?(str) 23 | ] 24 | end 25 | assert_equal( *result_pair.("aa/a//a") ) 26 | assert_equal( *result_pair.("/aaa/a//a") ) 27 | assert_equal( *result_pair.("/aaa/a//a/a") ) 28 | assert_equal( *result_pair.("/aaa/a//a/a") ) 29 | assert_equal( *result_pair.("a//aaa/a//a/a") ) 30 | assert_equal( *result_pair.("a//aaa/a//a/aaa") ) 31 | assert_equal( *result_pair.("/aaa/a//a/aaa/a") ) 32 | assert_equal( *result_pair.("a//aaa/a//a/aaa/a") ) 33 | assert_equal( *result_pair.("a//aaa/a//a/aaa////") ) 34 | assert_equal( *result_pair.("a/a//aaa/a//a/aaa/a") ) 35 | assert_equal( *result_pair.("////a//aaa/a//a/aaa/a") ) 36 | assert_equal( *result_pair.("////a//aaa/a//a/aaa////") ) 37 | end 38 | 39 | def test_of_bees 40 | result_pair = lambda do |str| 41 | [ 42 | Pathname.new(str).send(:directory?), 43 | FasterPath.directory?(str) 44 | ] 45 | end 46 | assert_equal( *result_pair.(".") ) 47 | assert_equal( *result_pair.(".././") ) 48 | assert_equal( *result_pair.(".///..") ) 49 | assert_equal( *result_pair.("/././/") ) 50 | assert_equal( *result_pair.("//../././") ) 51 | assert_equal( *result_pair.(".///.../..") ) 52 | assert_equal( *result_pair.("/././/.//.") ) 53 | assert_equal( *result_pair.("/...//../././") ) 54 | assert_equal( *result_pair.("/..///.../..//") ) 55 | assert_equal( *result_pair.("/./././/.//...") ) 56 | assert_equal( *result_pair.("/...//.././././/.") ) 57 | assert_equal( *result_pair.("./../..///.../..//") ) 58 | assert_equal( *result_pair.("///././././/.//...") ) 59 | assert_equal( *result_pair.("./../..///.../..//././") ) 60 | assert_equal( *result_pair.("///././././/.//....///") ) 61 | end 62 | 63 | def test_of_seas 64 | result_pair = lambda do |str| 65 | [ 66 | Pathname.new(str).send(:directory?), 67 | FasterPath.directory?(str) 68 | ] 69 | end 70 | assert_equal( *result_pair.("http://www.example.com") ) 71 | assert_equal( *result_pair.("foor for thought") ) 72 | assert_equal( *result_pair.("2gb63b@%TY25GHawefb3/g3qb")) 73 | end 74 | 75 | def test_directory? 76 | with_tmpchdir('rubytest-pathname') do |_dir| 77 | open("f", "w") {|f| f.write "abc" } 78 | assert_equal(false, FasterPath.directory?("f")) 79 | Dir.mkdir("d") 80 | assert_equal(true, FasterPath.directory?("d")) 81 | end 82 | end 83 | 84 | def test_directory_p 85 | assert FasterPath.directory?(@dir) 86 | refute FasterPath.directory?(@dir+"/...") 87 | refute FasterPath.directory?(regular_file) 88 | refute FasterPath.directory?(utf8_file) 89 | refute FasterPath.directory?(nofile) 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/dirname_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DirnameTest < Minitest::Test 4 | def test_it_returns_all_the_components_of_filename_except_the_last_one 5 | assert_equal('/home', FasterPath.dirname('/home/jason')) 6 | assert_equal('/home/jason', FasterPath.dirname('/home/jason/poot.txt')) 7 | assert_equal('.', FasterPath.dirname('poot.txt')) 8 | assert_equal('/holy///schnikies', FasterPath.dirname('/holy///schnikies//w00t.bin')) 9 | assert_equal('.', FasterPath.dirname('')) 10 | assert_equal('/', FasterPath.dirname('/')) 11 | assert_equal('/foo', FasterPath.dirname('/foo/foo')) 12 | end 13 | 14 | def test_it_returns_a_string 15 | assert_kind_of(String, FasterPath.dirname("foo")) 16 | end 17 | 18 | def test_it_does_not_modify_its_argument 19 | x = "/usr/bin" 20 | FasterPath.dirname(x) 21 | assert_equal("/usr/bin", x) 22 | end 23 | 24 | def test_it_ignores_a_trailing_slash 25 | assert_equal('/foo', FasterPath.dirname('/foo/bar/')) 26 | end 27 | 28 | def test_it_returns_the_return_all_the_components_of_filename_except_the_last_one_unix_format 29 | assert_equal(".", FasterPath.dirname("foo")) 30 | assert_equal("/", FasterPath.dirname("/foo")) 31 | assert_equal("/foo", FasterPath.dirname("/foo/bar")) 32 | assert_equal("/foo", FasterPath.dirname("/foo/bar.txt")) 33 | assert_equal("/foo/bar", FasterPath.dirname("/foo/bar/baz")) 34 | end 35 | 36 | def test_it_returns_the_return_all_the_components_of_filename_except_the_last_one_edge_cases 37 | assert_equal(".", FasterPath.dirname("")) 38 | assert_equal(".", FasterPath.dirname(".")) 39 | assert_equal(".", FasterPath.dirname("./")) 40 | assert_equal("./b", FasterPath.dirname("./b/./")) 41 | assert_equal(".", FasterPath.dirname("..")) 42 | assert_equal(".", FasterPath.dirname("../")) 43 | assert_equal("/", FasterPath.dirname("/")) 44 | assert_equal("/", FasterPath.dirname("/.")) 45 | assert_equal("/", FasterPath.dirname("/foo/")) 46 | assert_equal("/foo", FasterPath.dirname("/foo/.")) 47 | assert_equal("/foo", FasterPath.dirname("/foo/./")) 48 | assert_equal("/foo/..", FasterPath.dirname("/foo/../.")) 49 | assert_equal("foo", FasterPath.dirname("foo/../")) 50 | end 51 | 52 | def test_dirname_official 53 | @dir = Dir.mktmpdir("rubytest-file") 54 | File.chown(-1, Process.gid, @dir) 55 | 56 | assert_equal(@dir, FasterPath.dirname(regular_file)) 57 | assert_equal(@dir, FasterPath.dirname(utf8_file)) 58 | assert_equal(".", FasterPath.dirname("")) 59 | assert_incompatible_encoding {|d| FasterPath.dirname(d)} if ENV['ENCODING'].to_s['true'] 60 | if File::ALT_SEPARATOR == '\\' 61 | a = "\225\\\\foo" 62 | [%W[cp437 \225], %W[cp932 \225\\]].each do |cp, expected| 63 | assert_equal(expected.force_encoding(cp), FasterPath.dirname(a.dup.force_encoding(cp)), cp) 64 | end 65 | end 66 | 67 | GC.start 68 | FileUtils.remove_entry_secure @dir 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/entries_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class EntriesTest < Minitest::Test 4 | def test_pathnames_existing_behavior 5 | assert_kind_of Pathname, Pathname.new('.').entries.first 6 | end 7 | 8 | def test_entries_returns_string_objects 9 | assert_kind_of String, FasterPath.entries('.').first 10 | end 11 | 12 | def test_entries_compat_returns_pathname_objects 13 | assert_kind_of Pathname, FasterPath.entries_compat('.').first 14 | end 15 | 16 | def test_entries_compat_returns_similar_results_to_pathname_entries 17 | ['.', 'lib', 'src'].each do |pth| 18 | assert_equal Pathname.new(pth).entries.sort, 19 | FasterPath.entries_compat(pth).sort 20 | end 21 | end 22 | 23 | def test_entries_returns_similar_string_results_to_pathname_entries 24 | ['.', 'lib', 'src'].each do |pth| 25 | assert_equal Pathname.new(pth).entries.sort.map(&:to_s), 26 | FasterPath.entries(pth).sort 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/extname_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ExtnameTest < Minitest::Test 4 | def setup 5 | @dir = Dir.mktmpdir("rubytest-file") 6 | File.chown(-1, Process.gid, @dir) 7 | end 8 | 9 | def teardown 10 | GC.start 11 | FileUtils.remove_entry_secure @dir 12 | end 13 | 14 | def test_extname_official 15 | assert_equal(".test", FasterPath.extname(regular_file)) 16 | assert_equal(".test", FasterPath.extname(utf8_file)) 17 | prefixes = ["", "/", ".", "/.", "bar/.", "/bar/."] 18 | infixes = ["", " ", "."] 19 | infixes2 = infixes + [".ext "] 20 | appendixes = [""] 21 | if NTFS 22 | appendixes << " " << "." << "::$DATA" << "::$DATA.bar" 23 | end 24 | prefixes.each do |prefix| 25 | appendixes.each do |appendix| 26 | infixes.each do |infix| 27 | path = "#{prefix}foo#{infix}#{appendix}" 28 | assert_equal("", FasterPath.extname(path), "FasterPath.extname(#{path.inspect})") 29 | end 30 | infixes2.each do |infix| 31 | path = "#{prefix}foo#{infix}.ext#{appendix}" 32 | assert_equal(".ext", FasterPath.extname(path), "FasterPath.extname(#{path.inspect})") 33 | end 34 | end 35 | end 36 | # bug3175 = '[ruby-core:29627]' 37 | # assert_equal(".rb", FasterPath.extname("/tmp//bla.rb"), bug3175) 38 | assert_incompatible_encoding {|d| FasterPath.extname(d)} if ENV['ENCODING'].to_s['true'] 39 | end 40 | 41 | def test_extname 42 | assert_equal ".rb", FasterPath.extname("foo.rb") 43 | assert_equal ".rb", FasterPath.extname("/foo/bar.rb") 44 | assert_equal ".c", FasterPath.extname("/foo.rb/bar.c") 45 | assert_equal "", FasterPath.extname("bar") 46 | assert_equal "", FasterPath.extname(".bashrc") 47 | assert_equal "", FasterPath.extname("./foo.bar/baz") 48 | assert_equal ".conf", FasterPath.extname(".app.conf") 49 | end 50 | 51 | def test_extname_edge_cases 52 | assert_equal "", FasterPath.extname("") 53 | assert_equal "", FasterPath.extname(".") 54 | assert_equal "", FasterPath.extname("/") 55 | assert_equal "", FasterPath.extname("/.") 56 | assert_equal "", FasterPath.extname("..") 57 | assert_equal "", FasterPath.extname("...") 58 | assert_equal "", FasterPath.extname("....") 59 | assert_equal "", FasterPath.extname(".foo.") 60 | assert_equal "", FasterPath.extname("foo.") 61 | assert_equal "", FasterPath.extname("..foo") 62 | end 63 | 64 | def test_substitutability_of_rust_and_ruby_impls 65 | result_pair = lambda do |str| 66 | [ 67 | File.send(:extname, str), 68 | FasterPath.extname(str) 69 | ] 70 | end 71 | assert_equal( *result_pair.("foo.rb") ) 72 | assert_equal( *result_pair.("/foo/bar.rb") ) 73 | assert_equal( *result_pair.("/foo.rb/bar.c") ) 74 | assert_equal( *result_pair.("bar") ) 75 | assert_equal( *result_pair.(".bashrc") ) 76 | assert_equal( *result_pair.("./foo.bar/baz") ) 77 | assert_equal( *result_pair.(".app.conf") ) 78 | assert_equal( *result_pair.("") ) 79 | assert_equal( *result_pair.(".") ) 80 | assert_equal( *result_pair.("/") ) 81 | assert_equal( *result_pair.("/.") ) 82 | assert_equal( *result_pair.("..") ) 83 | assert_equal( *result_pair.("...") ) 84 | assert_equal( *result_pair.("....") ) 85 | assert_equal( *result_pair.(".foo.") ) 86 | assert_equal( *result_pair.("foo.") ) 87 | assert_equal( *result_pair.("foo.rb/") ) 88 | assert_equal( *result_pair.("foo.rb//") ) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/has_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class HasTrailingSeparatorTest < Minitest::Test 4 | def result_pair(str) 5 | [ 6 | Pathname.allocate.send(:has_trailing_separator?, str), 7 | FasterPath.has_trailing_separator?(str) 8 | ] 9 | end 10 | 11 | def test_has_trailing_separator_against_pathname_implementation1 12 | assert_equal(*result_pair('')) 13 | assert_equal(*result_pair('/')) 14 | assert_equal(*result_pair('//')) 15 | assert_equal(*result_pair('///')) 16 | assert_equal(*result_pair('hello')) 17 | assert_equal(*result_pair('hello/')) 18 | assert_equal(*result_pair('/hello/')) 19 | assert_equal(*result_pair('/hello//')) 20 | end 21 | 22 | def test_has_trailing_separator_against_pathname_implementation2 23 | assert_equal(*result_pair('.')) 24 | assert_equal(*result_pair('./')) 25 | assert_equal(*result_pair('.//')) 26 | assert_equal(*result_pair("aa/a//a")) 27 | assert_equal(*result_pair("/aaa/a//a")) 28 | assert_equal(*result_pair("/aaa/a//a/a")) 29 | assert_equal(*result_pair("/aaa/a//a/a")) 30 | assert_equal(*result_pair("a//aaa/a//a/a")) 31 | assert_equal(*result_pair("a//aaa/a//a/aaa")) 32 | assert_equal(*result_pair("/aaa/a//a/aaa/a")) 33 | assert_equal(*result_pair("a//aaa/a//a/aaa/a")) 34 | assert_equal(*result_pair("a//aaa/a//a/aaa////")) 35 | assert_equal(*result_pair("a/a//aaa/a//a/aaa/a")) 36 | assert_equal(*result_pair("////a//aaa/a//a/aaa/a")) 37 | assert_equal(*result_pair("////a//aaa/a//a/aaa////")) 38 | end 39 | 40 | def test_has_trailing_separator_against_pathname_implementation3 41 | assert_equal(*result_pair(".")) 42 | assert_equal(*result_pair(".././")) 43 | assert_equal(*result_pair(".///..")) 44 | assert_equal(*result_pair("/././/")) 45 | assert_equal(*result_pair("//../././")) 46 | assert_equal(*result_pair(".///.../..")) 47 | assert_equal(*result_pair("/././/.//.")) 48 | assert_equal(*result_pair("/...//../././")) 49 | assert_equal(*result_pair("/..///.../..//")) 50 | assert_equal(*result_pair("/./././/.//...")) 51 | assert_equal(*result_pair("/...//.././././/.")) 52 | assert_equal(*result_pair("./../..///.../..//")) 53 | assert_equal(*result_pair("///././././/.//...")) 54 | assert_equal(*result_pair("./../..///.../..//././")) 55 | assert_equal(*result_pair("///././././/.//....///")) 56 | assert_equal(*result_pair("http://www.example.com")) 57 | assert_equal(*result_pair("foor for thought")) 58 | assert_equal(*result_pair("2gb63b@%TY25GHawefb3/g3qb")) 59 | end 60 | 61 | def test_has_trailing_separator_with_nil 62 | refute FasterPath.has_trailing_separator?(nil) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/join_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class JoinTest < Minitest::Test 4 | def test_join 5 | r = FasterPath.join(Pathname("a"), Pathname("b"), Pathname("c")) 6 | assert_equal(Pathname("a/b/c"), r) 7 | r = FasterPath.join(Pathname("/a"), Pathname("b"), Pathname("c")) 8 | assert_equal(Pathname("/a/b/c"), r) 9 | r = FasterPath.join(Pathname("/a"), Pathname("/b"), Pathname("c")) 10 | assert_equal(Pathname("/b/c"), r) 11 | r = FasterPath.join(Pathname("/a"), Pathname("/b"), Pathname("/c")) 12 | assert_equal(Pathname("/c"), r) 13 | r = FasterPath.join(Pathname("/a"), "/b", "/c") 14 | assert_equal(Pathname("/c"), r) 15 | r = FasterPath.join(Pathname("/foo/var")) 16 | assert_equal(Pathname("/foo/var"), r) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/monkeypatches/faster_path_test.rb: -------------------------------------------------------------------------------- 1 | if !RUBY_VERSION["2.4.2"] 2 | require 'test_helper' 3 | require 'read_source' 4 | 5 | if ENV['TEST_MONKEYPATCHES'].to_s['true'] 6 | require 'faster_path/optional/monkeypatches' 7 | FasterPath.sledgehammer_everything! 8 | end 9 | 10 | class MonkeyPatchesTest < Minitest::Test 11 | def setup 12 | @path = Pathname.new(".") 13 | end 14 | 15 | if ENV['TEST_MONKEYPATCHES'].to_s['true'] 16 | def test_it_redefines_absolute? 17 | assert @path.method(:absolute?).read_source[/FasterPath/] 18 | end 19 | 20 | def test_it_redefines_directory? 21 | @path.method(:directory?).read_source[/FasterPath/] 22 | end 23 | 24 | def test_it_redefines_chop_basename 25 | assert @path.method(:chop_basename).read_source[/FasterPath/] 26 | end 27 | 28 | def test_it_redefines_relative? 29 | assert @path.method(:relative?).read_source[/FasterPath/] 30 | end 31 | 32 | def test_it_redefines_add_trailing_separator 33 | assert @path.method(:add_trailing_separator).read_source[/FasterPath/] 34 | end 35 | 36 | def test_it_redefines_has_trailing_separator 37 | assert @path.method(:has_trailing_separator?).read_source[/FasterPath/] 38 | end 39 | else 40 | def test_it_redefines_absolute? 41 | refute @path.method(:absolute?).read_source[/FasterPath/] 42 | end 43 | 44 | def test_it_does_not_redefine_directory? 45 | assert_raises { @path.method(:directory?).read_source[/FasterPath/] } 46 | end 47 | 48 | def test_it_redefines_chop_basename 49 | refute @path.method(:chop_basename).read_source[/FasterPath/] 50 | end 51 | 52 | def test_it_redefines_relative? 53 | refute @path.method(:relative?).read_source[/FasterPath/] 54 | end 55 | 56 | def test_it_redefines_add_trailing_separator 57 | refute @path.method(:add_trailing_separator).read_source[/FasterPath/] 58 | end 59 | 60 | def test_it_redefines_has_trailing_separator 61 | refute @path.method(:has_trailing_separator?).read_source[/FasterPath/] 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/output_type_compatibility_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OutputTypeCompatibilityTest < Minitest::Test 4 | ::Minitest::Assertions.module_eval do 5 | # rubocop:disable Metrics/CyclomaticComplexity 6 | # rubocop:disable Metrics/PerceivedComplexity 7 | # rubocop:disable Metrics/MethodLength 8 | def assert_same_output_kind(mthd, opt_mthd = nil, valid=true) 9 | vpath = valid ? "." : "a" 10 | a = Pathname.instance_method(mthd).arity 11 | args = [] 12 | a.abs.times do 13 | args << vpath 14 | end 15 | 16 | b = FasterPath.method(opt_mthd || mthd).arity 17 | bargs = [] 18 | b.abs.times do 19 | bargs << vpath 20 | end 21 | 22 | pathname_raised = false 23 | fasterpath_raised = false 24 | if !valid 25 | begin 26 | Pathname.new(vpath).send(mthd, *args) 27 | rescue 28 | pathname_raised = true 29 | end 30 | begin 31 | FasterPath.send(opt_mthd || mthd, *bargs) 32 | rescue 33 | fasterpath_raised = true 34 | end 35 | 36 | if pathname_raised && fasterpath_raised 37 | return 38 | elsif pathname_raised 39 | raise "Only Pathname raised when invalid path" 40 | elsif fasterpath_raised 41 | raise "Only FasterPath raised when invalid path" 42 | end 43 | else 44 | pth = Pathname.new(vpath).send(mthd, *args) 45 | fpth = FasterPath.send(opt_mthd || mthd, *bargs) 46 | end 47 | 48 | assert_equal pth.is_a?(Array), fpth.is_a?(Array) 49 | if pth.is_a?(Array) && fpth.is_a?(Array) 50 | assert_equal pth.first.class, fpth.first.class, 51 | "Array output kind not equal for method '#{mthd}':\nExpected: #{pth.inspect}\nGot: #{fpth.inspect}" 52 | else 53 | assert_kind_of pth.class, fpth, 54 | "Output kind not equal for method '#{mthd}':\nExpected: #{pth.inspect}\nGot: #{fpth.inspect}" 55 | end 56 | end 57 | 58 | def assert_happy_path(m) 59 | assert_same_output_kind(m) 60 | end 61 | 62 | def assert_unhappy_path(m) 63 | assert_same_output_kind(m, (), false) 64 | end 65 | 66 | def assert_happy_compat_path(m, c) 67 | assert_same_output_kind(m, c) 68 | end 69 | 70 | def assert_unhappy_compat_path(m, c) 71 | assert_same_output_kind(m, c, false) 72 | end 73 | 74 | end 75 | 76 | describe "test for identical output" do 77 | def test_absolute? 78 | assert_happy_path :absolute? 79 | assert_unhappy_path :absolute? 80 | end 81 | 82 | def test_add_trailing_separator 83 | assert_happy_path :add_trailing_separator 84 | assert_unhappy_path :add_trailing_separator 85 | end 86 | 87 | def test_chop_basename 88 | assert_happy_path :chop_basename 89 | assert_unhappy_path :chop_basename 90 | end 91 | 92 | def test_directory? 93 | assert_happy_path :directory? 94 | assert_unhappy_path :directory? 95 | end 96 | 97 | def test_has_trailing_separator? 98 | assert_happy_path :has_trailing_separator? 99 | assert_unhappy_path :has_trailing_separator? 100 | end 101 | 102 | def test_join 103 | assert_happy_path :join 104 | assert_unhappy_path :join 105 | end 106 | 107 | def test_plus 108 | assert_happy_path :plus 109 | assert_unhappy_path :plus 110 | end 111 | 112 | def test_relative? 113 | assert_happy_path :relative? 114 | assert_unhappy_path :relative? 115 | end 116 | 117 | def test_children_compat 118 | assert_happy_compat_path :children, :children_compat 119 | assert_unhappy_compat_path :children, :children_compat 120 | end 121 | 122 | def test_entries_compat 123 | assert_happy_compat_path :entries, :entries_compat 124 | assert_unhappy_compat_path :entries, :entries_compat 125 | end 126 | end 127 | 128 | describe "test output types are not the same" do 129 | def pth 130 | "." 131 | end 132 | 133 | def test_children 134 | refute_equal Pathname.new(pth).children.first.class, 135 | FasterPath.children(pth).first.class 136 | end 137 | 138 | def test_entries 139 | refute_equal Pathname.new(pth).entries.first.class, 140 | FasterPath.entries(pth).first.class 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /test/pbench/pbench.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'minitest/benchmark' 3 | 4 | class Pbench # < Minitest::Benchmark 5 | def initialize(_) # if for whatever reason we inherit from Minitest::Benchmark - they take 1 parameter 6 | end 7 | 8 | def self.io # :nodoc: 9 | @io = $stdout 10 | end 11 | 12 | def io # :nodoc: 13 | self.class.io 14 | end 15 | 16 | def self.bench_range 17 | # Looking for a consistent result 18 | # which seems to require more of the same 19 | amplitude = ENV['LONG_RUN'].to_s[/\d+/].to_i 20 | amplitude = 1 if amplitude < 1 21 | amplitude = 30 if amplitude > 30 22 | [10_000 * amplitude] * 5 23 | end 24 | 25 | def performance(baseline, new_impl) 26 | range = self.class.bench_range 27 | 28 | times_a = [] 29 | range.each do |x| 30 | GC.start 31 | t0 = Minitest.clock_time 32 | baseline.call(x) 33 | t = Minitest.clock_time - t0 34 | times_a << t 35 | end 36 | 37 | # This seems to stabalize the results a bit 38 | sleep 0.02; GC.start 39 | 40 | times_b = [] 41 | range.each do |x| 42 | GC.start 43 | t0 = Minitest.clock_time 44 | new_impl.call(x) 45 | t = Minitest.clock_time - t0 46 | times_b << t 47 | end 48 | 49 | increase(average(times_a), average(times_b)).round(1) 50 | end 51 | 52 | # run(hash) 53 | # the key is the name of the method 54 | # value :old will be a proc to execute original method behavior 55 | # value :new will be a proc to execute newer method behavior 56 | def run(hsh) 57 | io.send :puts, "Pinch-bench (Pbench) by Daniel P. Clark" 58 | io.send :puts, "-"*80 59 | io.send :puts, os_lang_specs 60 | io.send :puts, "-"*80 61 | io.flush 62 | hsh.each_key do |k| 63 | h = hsh[k] 64 | result = performance(h[:old], h[:new]) 65 | io.send :puts, "Performance change for #{k} is %.1f%%" % result 66 | io.flush 67 | end 68 | end 69 | 70 | def os_lang_specs 71 | os_stats = "#{FasterPath.ruby_arch_bits}-bit #{`ruby -v`.chomp}\n" 72 | rust, detail = `rustc -Vv`.split("\n", 2) 73 | os_stats += "#{FasterPath.rust_arch_bits}-bit #{rust}\n" 74 | os_stats + "architecture: #{detail.scan(/(?<=host: ).+(?=\n)/).first}" 75 | end 76 | 77 | def increase(old_num, new_num) 78 | (old_num-new_num)*100/old_num 79 | end 80 | 81 | def average(t) 82 | t.map(&:to_f).inject(:+)./(t.count) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/pbench/pbench_suite.rb: -------------------------------------------------------------------------------- 1 | require 'pbench' 2 | 3 | # PBENCHES[:""] = { 4 | # new: lambda do |x| 5 | # x.times do 6 | # end 7 | # end, 8 | # old: lambda do |x| 9 | # x.times do 10 | # end 11 | # end 12 | # } 13 | 14 | # SOME DEFAULTS 15 | PATHNAME_A = Pathname.new('a').freeze 16 | PATHNAME_DOT = Pathname.new('.').freeze 17 | PATHNAME_PWD = Pathname.new('./').freeze 18 | PATHNAME_SRC = Pathname.new('./src').freeze 19 | PATHNAME_ABC = Pathname.new('a//b/c').freeze 20 | PATHNAME_ABS = Pathname.new('/hello').freeze 21 | PATHNAME_REL = Pathname.new('goodbye').freeze 22 | 23 | PBENCHES = {} 24 | PBENCHES[:"allocate, instead of new,"] = { 25 | # The allocate test is both for demonstration and as a baseline for comparing 26 | # different results from different computer systems. The code for these methods 27 | # won't change. 28 | new: lambda do |x| 29 | x.times do 30 | Pathname.allocate 31 | end 32 | end, 33 | old: lambda do |x| 34 | x.times do 35 | Pathname.new("") 36 | end 37 | end 38 | } 39 | PBENCHES[:"absolute?"] = { 40 | new: lambda do |x| 41 | x.times do 42 | FasterPath.absolute?("/hello") 43 | FasterPath.absolute?("goodbye") 44 | end 45 | end, 46 | old: lambda do |x| 47 | x.times do 48 | PATHNAME_ABC.absolute? 49 | PATHNAME_REL.absolute? 50 | end 51 | end, 52 | } 53 | PBENCHES[:add_trailing_separator] = { 54 | new: lambda do |x| 55 | x.times do 56 | FasterPath.add_trailing_separator('/hello/world') 57 | FasterPath.add_trailing_separator('/hello/world/') 58 | end 59 | end, 60 | old: lambda do |x| 61 | x.times do 62 | PATHNAME_DOT.send(:add_trailing_separator, '/hello/world') 63 | PATHNAME_DOT.send(:add_trailing_separator, '/hello/world/') 64 | end 65 | end 66 | } 67 | PBENCHES[:basename] = { 68 | new: lambda do |x| 69 | x.times do 70 | FasterPath.basename("/hello/world") 71 | FasterPath.basename('/home/gumby/work/ruby.rb', '.rb') 72 | FasterPath.basename('/home/gumby/work/ruby.rb', '.*') 73 | FasterPath.basename('ruby.rbx', 'rbx') 74 | FasterPath.basename('ruby.rbx', 'x') 75 | FasterPath.basename('ruby.rbx', '*') 76 | end 77 | end, 78 | old: lambda do |x| 79 | x.times do 80 | File.basename("/hello/world") 81 | File.basename('/home/gumby/work/ruby.rb', '.rb') 82 | File.basename('/home/gumby/work/ruby.rb', '.*') 83 | File.basename('ruby.rbx', 'rbx') 84 | File.basename('ruby.rbx', 'x') 85 | File.basename('ruby.rbx', '*') 86 | end 87 | end 88 | } 89 | PBENCHES[:children] = { 90 | new: lambda do |x| 91 | (x/5).times do 92 | FasterPath.children(".") 93 | end 94 | end, 95 | old: lambda do |x| 96 | (x/5).times do 97 | PATHNAME_DOT.children 98 | end 99 | end 100 | } 101 | PBENCHES[:children_compat] = { 102 | new: lambda do |x| 103 | (x/5).times do 104 | FasterPath.children_compat('.') 105 | end 106 | end, 107 | old: lambda do |x| 108 | (x/5).times do 109 | PATHNAME_DOT.children 110 | end 111 | end 112 | } 113 | PBENCHES[:chop_basename] = { 114 | new: lambda do |x| 115 | x.times do 116 | FasterPath.chop_basename "/hello/world.txt" 117 | FasterPath.chop_basename "world.txt" 118 | FasterPath.chop_basename "" 119 | end 120 | end, 121 | old: lambda do |x| 122 | x.times do 123 | PATHNAME_DOT.send :chop_basename, "/hello/world.txt" 124 | PATHNAME_DOT.send :chop_basename, "world.txt" 125 | PATHNAME_DOT.send :chop_basename, "" 126 | end 127 | end 128 | } 129 | PATHNAME_CA1 = Pathname.new('/../.././../a').freeze 130 | PATHNAME_CA2 = Pathname.new('a/b/../../../../c/../d').freeze 131 | PBENCHES[:cleanpath_aggressive] = { 132 | new: lambda do |x| 133 | x.times do 134 | Pathname.new(FasterPath.cleanpath_aggressive '/../.././../a') 135 | Pathname.new(FasterPath.cleanpath_aggressive 'a/b/../../../../c/../d') 136 | end 137 | end, 138 | old: lambda do |x| 139 | x.times do 140 | PATHNAME_CA1.send :cleanpath_aggressive 141 | PATHNAME_CA2.send :cleanpath_aggressive 142 | end 143 | end 144 | } 145 | PBENCHES[:cleanpath_conservative] = { 146 | new: lambda do |x| 147 | x.times do 148 | Pathname.new(FasterPath.cleanpath_conservative '/../.././../a') 149 | Pathname.new(FasterPath.cleanpath_conservative 'a/b/../../../../c/../d') 150 | end 151 | end, 152 | old: lambda do |x| 153 | x.times do 154 | PATHNAME_CA1.send :cleanpath_conservative 155 | PATHNAME_CA2.send :cleanpath_conservative 156 | end 157 | end 158 | } 159 | PBENCHES[:del_trailing_separator] = { 160 | new: lambda do |x| 161 | x.times do 162 | FasterPath.del_trailing_separator('/hello/world') 163 | FasterPath.del_trailing_separator('/hello/world/') 164 | end 165 | end, 166 | old: lambda do |x| 167 | x.times do 168 | PATHNAME_DOT.send(:del_trailing_separator, '/hello/world') 169 | PATHNAME_DOT.send(:del_trailing_separator, '/hello/world/') 170 | end 171 | end 172 | } 173 | PBENCHES[:"directory?"] = { 174 | new: lambda do |x| 175 | x.times do 176 | FasterPath.directory?("/hello") 177 | FasterPath.directory?("goodbye") 178 | end 179 | end, 180 | old: lambda do |x| 181 | x.times do 182 | PATHNAME_ABS.directory? 183 | PATHNAME_REL.directory? 184 | end 185 | end 186 | } 187 | PBENCHES[:dirname] = { 188 | new: lambda do |x| 189 | x.times do 190 | FasterPath.dirname "/really/long/path/name/which/ruby/doesnt/like/bar.txt" 191 | FasterPath.dirname "/foo/" 192 | FasterPath.dirname "." 193 | end 194 | end, 195 | old: lambda do |x| 196 | x.times do 197 | File.dirname "/really/long/path/name/which/ruby/doesnt/like/bar.txt" 198 | File.dirname "/foo/" 199 | File.dirname "." 200 | end 201 | end 202 | } 203 | PBENCHES[:entries] = { 204 | new: lambda do |x| 205 | (x/5).times do 206 | FasterPath.entries("./") 207 | FasterPath.entries("./src") 208 | end 209 | end, 210 | old: lambda do |x| 211 | (x/5).times do 212 | PATHNAME_PWD.entries 213 | PATHNAME_SRC.entries 214 | end 215 | end 216 | } 217 | PBENCHES[:entries_compat] = { 218 | new: lambda do |x| 219 | (x/5).times do 220 | FasterPath.entries_compat("./") 221 | FasterPath.entries_compat("./src") 222 | end 223 | end, 224 | old: lambda do |x| 225 | (x/5).times do 226 | PATHNAME_PWD.entries 227 | PATHNAME_SRC.entries 228 | end 229 | end 230 | } 231 | PBENCHES[:extname] = { 232 | new: lambda do |x| 233 | x.times do 234 | FasterPath.extname('verylongfilename_verylongfilename.rb') 235 | FasterPath.extname('/very/long/path/name/very/long/path/name/very/long/path/name/file.rb') 236 | FasterPath.extname('/ext/mail.rb') 237 | end 238 | end, 239 | old: lambda do |x| 240 | x.times do 241 | File.extname('verylongfilename_verylongfilename.rb') 242 | File.extname('/very/long/path/name/very/long/path/name/very/long/path/name/file.rb') 243 | File.extname('/ext/main.rb') 244 | end 245 | end 246 | } 247 | PBENCHES[:"has_trailing_separator?"] = { 248 | new: lambda do |x| 249 | x.times do 250 | FasterPath.has_trailing_separator? '////a//aaa/a//a/aaa////' 251 | FasterPath.has_trailing_separator? 'hello/' 252 | end 253 | end, 254 | old: lambda do |x| 255 | x.times do 256 | PATHNAME_DOT.send :has_trailing_separator?, '////a//aaa/a//a/aaa////' 257 | PATHNAME_DOT.send :has_trailing_separator?, 'hello/' 258 | end 259 | end 260 | } 261 | PBENCHES[:join] = { 262 | new: lambda do |x| 263 | x.times do 264 | FasterPath.join('a', 'b') 265 | FasterPath.join('.', 'b') 266 | FasterPath.join('a//b/c', '../d//e') 267 | end 268 | end, 269 | old: lambda do |x| 270 | x.times do 271 | PATHNAME_A.send(:join, 'b') 272 | PATHNAME_DOT.send(:join, 'b') 273 | PATHNAME_ABC.send(:join, '../d//e') 274 | end 275 | end 276 | } 277 | PBENCHES[:plus] = { 278 | new: lambda do |x| 279 | x.times do 280 | FasterPath.plus('a', 'b') 281 | FasterPath.plus('.', 'b') 282 | FasterPath.plus('a//b/c', '../d//e') 283 | end 284 | end, 285 | old: lambda do |x| 286 | x.times do 287 | PATHNAME_A.send(:plus, 'a', 'b') 288 | PATHNAME_DOT.send(:plus, '.', 'b') 289 | PATHNAME_ABC.send(:plus, 'a//b/c', '../d//e') 290 | end 291 | end 292 | } 293 | PBENCHES[:"relative?"] = { 294 | new: lambda do |x| 295 | x.times do 296 | FasterPath.relative?("/hello") 297 | FasterPath.relative?("goodbye") 298 | end 299 | end, 300 | old: lambda do |x| 301 | x.times do 302 | PATHNAME_ABS.relative? 303 | PATHNAME_REL.relative? 304 | end 305 | end 306 | } 307 | PATHNAME_AB = Pathname("/a/b") 308 | PATHNAME_ABCD = Pathname("/a/b/c/d") 309 | PBENCHES[:relative_path_from] = { 310 | new: lambda do |x| 311 | x.times do 312 | FasterPath.relative_path_from "/a/b/c/d", "/a/b" 313 | FasterPath.relative_path_from "/a/b", "/a/b/c/d" 314 | end 315 | end, 316 | old: lambda do |x| 317 | x.times do 318 | PATHNAME_ABCD.relative_path_from PATHNAME_AB 319 | PATHNAME_AB.relative_path_from PATHNAME_ABCD 320 | end 321 | end 322 | } 323 | Pbench.new(nil).run(PBENCHES) 324 | -------------------------------------------------------------------------------- /test/plus_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PlusTest < Minitest::Test 4 | def test_plus 5 | assert_equal('/', FasterPath.plus('/', '/')) 6 | assert_equal('a/b', FasterPath.plus('a', 'b')) 7 | assert_equal('a', FasterPath.plus('a', '.')) 8 | assert_equal('b', FasterPath.plus('.', 'b')) 9 | assert_equal('.', FasterPath.plus('.', '.')) 10 | assert_equal('/b', FasterPath.plus('a', '/b')) 11 | 12 | assert_equal('/', FasterPath.plus('/', '..')) 13 | assert_equal('.', FasterPath.plus('a', '..')) 14 | assert_equal('a', FasterPath.plus('a/b', '..')) 15 | assert_equal('../..', FasterPath.plus('..', '..')) 16 | assert_equal('/c', FasterPath.plus('/', '../c')) 17 | assert_equal('c', FasterPath.plus('a', '../c')) 18 | assert_equal('a/c', FasterPath.plus('a/b', '../c')) 19 | assert_equal('../../c', FasterPath.plus('..', '../c')) 20 | 21 | assert_equal('a//b/d//e', FasterPath.plus('a//b/c', '../d//e')) 22 | 23 | assert_equal('//foo/var/bar', FasterPath.plus('//foo/var', 'bar')) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/refinements/absoulte_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require "faster_path/optional/refinements" 3 | # At the moment refinements don't allow introspection 4 | 5 | class RefinedPathname 6 | using FasterPath::RefinePathname 7 | def absolute?(v) 8 | Pathname.new(v).absolute? 9 | end 10 | end 11 | 12 | class AbsoluteRefinementTest < Minitest::Test 13 | def test_refines_pathname_absolute? 14 | assert RefinedPathname.new.absolute?("/") 15 | end 16 | 17 | def test_nil_behaves_the_same 18 | assert_raises(TypeError) { RefinedPathname.new.absolute?(nil) } 19 | assert_raises(TypeError) { Pathname.new(nil).absolute? } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/refinements/add_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | def add_trailing_separator(pth) 7 | Pathname.allocate.send(:add_trailing_separator, pth) 8 | end 9 | end 10 | 11 | class AddTrailingSeparatorTest < Minitest::Test 12 | def test_refines_pathname_add_trailing_separator 13 | assert RefinedPathname.allocate.send(:add_trailing_separator, 'hello') 14 | end 15 | 16 | def test_refines_pathname_add_trailing_separator_in_dosish_context 17 | if File.dirname('A:') == 'A:.' 18 | assert RefinedPathname.allocate.send(:add_trailing_separator, 'A:') 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/refinements/basename_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RefinedFile 4 | using FasterPath::RefineFile 5 | def basename(*args) 6 | File.basename(*args) 7 | end 8 | end 9 | 10 | class BasenameTest < Minitest::Test 11 | # Tests copied from https://searchcode.com/codesearch/view/12785140/ 12 | def test_it_creates_basename_correctly 13 | # Tests for basename 14 | assert_equal(RefinedFile.new.basename('/home/gumby/work/ruby.rb'), 'ruby.rb', "Pickaxe A") 15 | assert_equal(RefinedFile.new.basename('/home/gumby/work/ruby.rb', '.rb'), 'ruby', "Pickaxe B") 16 | assert_equal(RefinedFile.new.basename('/home/gumby/work/ruby.rb', '.*'), 'ruby', "Pickaxe C") 17 | assert_equal(RefinedFile.new.basename('ruby.rb', '.*'), 'ruby', "GemStone A") 18 | assert_equal(RefinedFile.new.basename('/ruby.rb', '.*'), 'ruby', "GemStone B") 19 | assert_equal(RefinedFile.new.basename('ruby.rbx', '.*'), 'ruby', "GemStone C") 20 | assert_equal(RefinedFile.new.basename('ruby.rbx', '.rb'), 'ruby.rbx', "GemStone D") 21 | assert_equal(RefinedFile.new.basename('ruby.rb', ''), 'ruby.rb', "GemStone E") 22 | assert_equal(RefinedFile.new.basename('ruby.rbx', '.rb*'), 'ruby.rbx', "GemStone F") 23 | assert_equal(RefinedFile.new.basename('ruby.rbx'), 'ruby.rbx', "GemStone G") 24 | 25 | # Try some extensions w/o a '.' 26 | assert_equal(RefinedFile.new.basename('ruby.rbx', 'rbx'), 'ruby.', "GemStone H") 27 | assert_equal(RefinedFile.new.basename('ruby.rbx', 'x'), 'ruby.rb', "GemStone I") 28 | assert_equal(RefinedFile.new.basename('ruby.rbx', '*'), 'ruby.rbx', "GemStone J") 29 | 30 | # A couple of regressions: 31 | assert_equal(RefinedFile.new.basename('', ''), '', 'File.basename regression 1') 32 | assert_equal(RefinedFile.new.basename('/'), '/', 'File.basename regression 2') 33 | assert_equal(RefinedFile.new.basename('//'), '/', 'File.basename regression 3') 34 | assert_equal(RefinedFile.new.basename('//dir///base//'), 'base', 'File.basename regression 4') 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/refinements/chop_basename_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require "faster_path/optional/refinements" 3 | # At the moment refinements don't allow introspection 4 | 5 | class RefinedPathname 6 | using FasterPath::RefinePathname 7 | def chop_basename(v) 8 | Pathname.new(v).send :chop_basename, v 9 | end 10 | end 11 | 12 | class ChopBasenameRefinementTest < Minitest::Test 13 | def test_refines_pathname_chop_basename 14 | assert RefinedPathname.new.chop_basename("/hello/world") 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/refinements/directory_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require "faster_path/optional/refinements" 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | def directory?(v) 7 | Pathname.new(v).directory? 8 | end 9 | end 10 | 11 | class DirectoryRefinementTest < Minitest::Test 12 | def test_refines_pathname_directory? 13 | assert RefinedPathname.new.directory?("/") 14 | end 15 | 16 | def test_nil_behaves_the_same 17 | assert_raises(TypeError) { RefinedPathname.new.directory?(nil) } 18 | assert_raises(TypeError) { Pathname.new(nil).directory? } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/refinements/dirname_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require "faster_path/optional/refinements" 3 | # At the moment refinements don't allow introspection 4 | 5 | class RefinedFile 6 | using FasterPath::RefineFile 7 | def dirname(name) 8 | File.dirname(name) 9 | end 10 | end 11 | 12 | class DirnameTest < Minitest::Test 13 | def setup 14 | @refined_file = RefinedFile.new 15 | end 16 | 17 | def test_it_gets_dirnames_correctly 18 | assert_equal('/home', @refined_file.dirname('/home/jason')) 19 | assert_equal('/home/jason', @refined_file.dirname('/home/jason/poot.txt')) 20 | assert_equal('.', @refined_file.dirname('poot.txt')) 21 | assert_equal('/holy///schnikies', @refined_file.dirname('/holy///schnikies//w00t.bin')) 22 | assert_equal('.', @refined_file.dirname('')) 23 | assert_equal('/', @refined_file.dirname('/')) 24 | assert_equal('/foo', @refined_file.dirname('/foo/foo')) 25 | 26 | assert_equal('/foo', @refined_file.dirname('/foo/bar/')) 27 | 28 | assert_equal(".", @refined_file.dirname("foo")) 29 | assert_equal("/", @refined_file.dirname("/foo")) 30 | assert_equal("/foo", @refined_file.dirname("/foo/bar")) 31 | assert_equal("/foo", @refined_file.dirname("/foo/bar.txt")) 32 | assert_equal("/foo/bar", @refined_file.dirname("/foo/bar/baz")) 33 | 34 | assert_equal(".", @refined_file.dirname("")) 35 | assert_equal(".", @refined_file.dirname(".")) 36 | assert_equal(".", @refined_file.dirname("./")) 37 | assert_equal("./b", @refined_file.dirname("./b/./")) 38 | assert_equal(".", @refined_file.dirname("..")) 39 | assert_equal(".", @refined_file.dirname("../")) 40 | assert_equal("/", @refined_file.dirname("/")) 41 | assert_equal("/", @refined_file.dirname("/.")) 42 | assert_equal("/", @refined_file.dirname("/foo/")) 43 | assert_equal("/foo", @refined_file.dirname("/foo/.")) 44 | assert_equal("/foo", @refined_file.dirname("/foo/./")) 45 | assert_equal("/foo/..", @refined_file.dirname("/foo/../.")) 46 | assert_equal("foo", @refined_file.dirname("foo/../")) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/refinements/extname_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RefinedFile 4 | using FasterPath::RefineFile 5 | def extname(path) 6 | File.extname(path) 7 | end 8 | end 9 | 10 | class ExtnameTest < Minitest::Test 11 | # Tests copied from https://searchcode.com/codesearch/view/12785140/ 12 | def test_extname 13 | assert_equal ".rb", RefinedFile.new.extname("foo.rb") 14 | assert_equal ".rb", RefinedFile.new.extname("/foo/bar.rb") 15 | assert_equal ".c", RefinedFile.new.extname("/foo.rb/bar.c") 16 | assert_equal "", RefinedFile.new.extname("bar") 17 | assert_equal "", RefinedFile.new.extname(".bashrc") 18 | assert_equal "", RefinedFile.new.extname("./foo.bar/baz") 19 | assert_equal ".conf", RefinedFile.new.extname(".app.conf") 20 | end 21 | 22 | def test_extname_edge_cases 23 | assert_equal "", RefinedFile.new.extname("") 24 | assert_equal "", RefinedFile.new.extname(".") 25 | assert_equal "", RefinedFile.new.extname("/") 26 | assert_equal "", RefinedFile.new.extname("/.") 27 | assert_equal "", RefinedFile.new.extname("..") 28 | assert_equal "", RefinedFile.new.extname("...") 29 | assert_equal "", RefinedFile.new.extname("....") 30 | assert_equal "", RefinedFile.new.extname(".foo.") 31 | assert_equal "", RefinedFile.new.extname("foo.") 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/refinements/has_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | class RefinedPathname 2 | using FasterPath::RefinePathname 3 | def has_trailing_separator?(path) 4 | Pathname.new(path).has_trailing_separator? 5 | end 6 | end 7 | 8 | class HasTrailingSeparatorRefinementTest < Minitest::Test 9 | def test_refines_pathname_has_trailing_separator? 10 | assert RefinedPathname.new.has_trailing_separator? "a/" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/refinements/plus_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RefinedPathname 4 | using FasterPath::RefinePathname 5 | def plus(*args) 6 | Pathname.plus(*args) 7 | end 8 | end 9 | 10 | class BasenameTest < Minitest::Test 11 | # Tests copied from https://searchcode.com/codesearch/view/12785140/ 12 | def test_it_creates_basename_correctly 13 | assert_equal('/', RefinedPathname.allocate.send(:plus, '/', '/')) 14 | assert_equal('a/b', RefinedPathname.allocate.send(:plus, 'a', 'b')) 15 | assert_equal('a', RefinedPathname.allocate.send(:plus, 'a', '.')) 16 | assert_equal('b', RefinedPathname.allocate.send(:plus, '.', 'b')) 17 | assert_equal('.', RefinedPathname.allocate.send(:plus, '.', '.')) 18 | assert_equal('/b', RefinedPathname.allocate.send(:plus, 'a', '/b')) 19 | 20 | assert_equal('/', RefinedPathname.allocate.send(:plus, '/', '..')) 21 | assert_equal('.', RefinedPathname.allocate.send(:plus, 'a', '..')) 22 | assert_equal('a', RefinedPathname.allocate.send(:plus, 'a/b', '..')) 23 | assert_equal('../..', RefinedPathname.allocate.send(:plus, '..', '..')) 24 | assert_equal('/c', RefinedPathname.allocate.send(:plus, '/', '../c')) 25 | assert_equal('c', RefinedPathname.allocate.send(:plus, 'a', '../c')) 26 | assert_equal('a/c', RefinedPathname.allocate.send(:plus, 'a/b', '../c')) 27 | assert_equal('../../c', RefinedPathname.allocate.send(:plus, '..', '../c')) 28 | 29 | assert_equal('a//b/d//e', RefinedPathname.allocate.send(:plus, 'a//b/c', '../d//e')) 30 | 31 | assert_equal('//foo/var/bar', RefinedPathname.allocate.send(:plus, '//foo/var', 'bar')) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/refinements/relative_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require "faster_path/optional/refinements" 3 | # At the moment refinements don't allow introspection 4 | 5 | class RefinedPathname 6 | using FasterPath::RefinePathname 7 | def relative?(v) 8 | Pathname.new(v).relative? 9 | end 10 | end 11 | 12 | class RelativeRefinementTest < Minitest::Test 13 | def test_refines_pathname_relative? 14 | refute RefinedPathname.new.relative?("/") 15 | end 16 | 17 | def test_nil_behaves_the_same 18 | assert_raises(TypeError) { RefinedPathname.new.relative?(nil) } 19 | assert_raises(TypeError) { Pathname.new(nil).relative? } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/relative_path_from_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | class RelativePathFromTest < Minitest::Test 3 | def test_relative_path_from 4 | assert_equal FasterPath.relative_path_from("a", "b").to_s, "../a" 5 | assert_equal FasterPath.relative_path_from("a", "b/").to_s, "../a" 6 | assert_equal FasterPath.relative_path_from("a/", "b").to_s, "../a" 7 | assert_equal FasterPath.relative_path_from("a/", "b/").to_s, "../a" 8 | assert_equal FasterPath.relative_path_from("/a", "/b").to_s, "../a" 9 | assert_equal FasterPath.relative_path_from("/a", "/b/").to_s, "../a" 10 | assert_equal FasterPath.relative_path_from("/a/", "/b").to_s, "../a" 11 | assert_equal FasterPath.relative_path_from("/a/", "/b/").to_s, "../a" 12 | assert_equal FasterPath.relative_path_from("a/b", "a/c").to_s, "../b" 13 | assert_equal FasterPath.relative_path_from("../a", "../b").to_s, "../a" 14 | assert_equal FasterPath.relative_path_from("a", ".").to_s, "a" 15 | assert_equal FasterPath.relative_path_from(".", "a").to_s, ".." 16 | assert_equal FasterPath.relative_path_from(".", ".").to_s, "." 17 | assert_equal FasterPath.relative_path_from("..", "..").to_s, "." 18 | assert_equal FasterPath.relative_path_from("..", ".").to_s, ".." 19 | assert_equal FasterPath.relative_path_from("/a/b/c/d", "/a/b").to_s, "c/d" 20 | assert_equal FasterPath.relative_path_from("/a/b", "/a/b/c/d").to_s, "../.." 21 | assert_equal FasterPath.relative_path_from("/e", "/a/b/c/d").to_s, "../../../../e" 22 | assert_equal FasterPath.relative_path_from("a/b/c", "a/d").to_s, "../b/c" 23 | assert_equal FasterPath.relative_path_from("/../a", "/b").to_s, "../a" 24 | assert_equal FasterPath.relative_path_from("../a", "b").to_s, "../../a" 25 | assert_equal FasterPath.relative_path_from("/a/../../b", "/b").to_s, "." 26 | assert_equal FasterPath.relative_path_from("a/..", "a").to_s, ".." 27 | assert_equal FasterPath.relative_path_from("a/../b", "b").to_s, "." 28 | assert_equal FasterPath.relative_path_from("a", "b/..").to_s, "a" 29 | assert_equal FasterPath.relative_path_from("b/c", "b/..").to_s, "b/c" 30 | assert_raises(ArgumentError) { FasterPath.relative_path_from("/", ".") } 31 | assert_raises(ArgumentError) { FasterPath.relative_path_from(".", "/") } 32 | assert_raises(ArgumentError) { FasterPath.relative_path_from("a", "..") } 33 | assert_raises(ArgumentError) { FasterPath.relative_path_from(".", "..") } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/relative_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RelativeTest < Minitest::Test 4 | def test_it_takes_nil_safely 5 | refute FasterPath.relative? nil 6 | end 7 | 8 | def test_it_knows_its_relativeness 9 | refute FasterPath.relative? '/' 10 | refute FasterPath.relative? '/a' 11 | refute FasterPath.relative? '/..' 12 | assert FasterPath.relative? 'a' 13 | assert FasterPath.relative? 'a/b' 14 | 15 | if DOSISH_UNC 16 | refute FasterPath.relative? '//' 17 | refute FasterPath.relative? '//a' 18 | refute FasterPath.relative? '//a/' 19 | refute FasterPath.relative? '//a/b' 20 | refute FasterPath.relative? '//a/b/' 21 | refute FasterPath.relative? '//a/b/c' 22 | end 23 | end 24 | 25 | def test_it_knows_its_relativeness_in_dos_like_drive_letters 26 | refute FasterPath.relative? 'A:' 27 | refute FasterPath.relative? 'A:/' 28 | refute FasterPath.relative? 'A:/a' 29 | end if DOSISH_DRIVE_LETTER 30 | end 31 | -------------------------------------------------------------------------------- /test/ripple_effects/cleanpath_aggressive_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | using FasterPath::RefineFile 7 | def cleanpath_aggressive(pth) 8 | Pathname.new(pth).send(:cleanpath_aggressive).to_s 9 | end 10 | end 11 | 12 | class CleanpathAggressiveTest < Minitest::Test 13 | def test_clean_aggressive_defaults1 14 | assert_equal RefinedPathname.new.cleanpath_aggressive('/'), '/' 15 | assert_equal RefinedPathname.new.cleanpath_aggressive(''), '.' 16 | assert_equal RefinedPathname.new.cleanpath_aggressive('.'), '.' 17 | assert_equal RefinedPathname.new.cleanpath_aggressive('..'), '..' 18 | assert_equal RefinedPathname.new.cleanpath_aggressive('a'), 'a' 19 | assert_equal RefinedPathname.new.cleanpath_aggressive('/.'), '/' 20 | assert_equal RefinedPathname.new.cleanpath_aggressive('/..'), '/' 21 | assert_equal RefinedPathname.new.cleanpath_aggressive('/a'), '/a' 22 | assert_equal RefinedPathname.new.cleanpath_aggressive('./'), '.' 23 | assert_equal RefinedPathname.new.cleanpath_aggressive('../'), '..' 24 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/'), 'a' 25 | assert_equal RefinedPathname.new.cleanpath_aggressive('a//b'), 'a/b' 26 | end 27 | 28 | def test_clean_aggressive_defaults2 29 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/.'), 'a' 30 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/./'), 'a' 31 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/..'), '.' 32 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/../'), '.' 33 | assert_equal RefinedPathname.new.cleanpath_aggressive('/a/.'), '/a' 34 | assert_equal RefinedPathname.new.cleanpath_aggressive('./..'), '..' 35 | assert_equal RefinedPathname.new.cleanpath_aggressive('../.'), '..' 36 | assert_equal RefinedPathname.new.cleanpath_aggressive('./../'), '..' 37 | assert_equal RefinedPathname.new.cleanpath_aggressive('.././'), '..' 38 | assert_equal RefinedPathname.new.cleanpath_aggressive('/./..'), '/' 39 | end 40 | 41 | def test_clean_aggressive_defaults3 42 | assert_equal RefinedPathname.new.cleanpath_aggressive('/../.'), '/' 43 | assert_equal RefinedPathname.new.cleanpath_aggressive('/./../'), '/' 44 | assert_equal RefinedPathname.new.cleanpath_aggressive('/.././'), '/' 45 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/b/c'), 'a/b/c' 46 | assert_equal RefinedPathname.new.cleanpath_aggressive('./b/c'), 'b/c' 47 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/./c'), 'a/c' 48 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/b/.'), 'a/b' 49 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/../.'), '.' 50 | assert_equal RefinedPathname.new.cleanpath_aggressive('/../.././../a'), '/a' 51 | assert_equal RefinedPathname.new.cleanpath_aggressive('a/b/../../../../c/../d'), '../../d' 52 | end 53 | 54 | def test_clean_aggressive_dosish_stuff 55 | if File.dirname("//") == "//" # DOSISH_UNC 56 | assert_equal RefinedPathname.new.cleanpath_aggressive('//a/b/c/'), '//a/b/c' 57 | else 58 | assert_equal RefinedPathname.new.cleanpath_aggressive('///'), '/' 59 | assert_equal RefinedPathname.new.cleanpath_aggressive('///a'), '/a' 60 | assert_equal RefinedPathname.new.cleanpath_aggressive('///..'), '/' 61 | assert_equal RefinedPathname.new.cleanpath_aggressive('///.'), '/' 62 | assert_equal RefinedPathname.new.cleanpath_aggressive('///a/../..'), '/' 63 | end 64 | 65 | if !File::ALT_SEPARATOR.nil? # DOSISH 66 | assert_equal RefinedPathname.new.cleanpath_aggressive('c:\\foo\\bar'), 'c:/foo/bar' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/ripple_effects/cleanpath_conservative_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | using FasterPath::RefineFile 7 | def cleanpath_conservative(pth) 8 | Pathname.new(pth).send(:cleanpath_conservative).to_s 9 | end 10 | end 11 | 12 | class CleanpathConservativeTest < Minitest::Test 13 | def test_clean_conservative_defaults1 14 | assert_equal RefinedPathname.new.cleanpath_conservative('/'), '/' 15 | assert_equal RefinedPathname.new.cleanpath_conservative(''), '.' 16 | assert_equal RefinedPathname.new.cleanpath_conservative('.'), '.' 17 | assert_equal RefinedPathname.new.cleanpath_conservative('..'), '..' 18 | assert_equal RefinedPathname.new.cleanpath_conservative('a'), 'a' 19 | assert_equal RefinedPathname.new.cleanpath_conservative('/.'), '/' 20 | assert_equal RefinedPathname.new.cleanpath_conservative('/..'), '/' 21 | assert_equal RefinedPathname.new.cleanpath_conservative('/a'), '/a' 22 | assert_equal RefinedPathname.new.cleanpath_conservative('./'), '.' 23 | assert_equal RefinedPathname.new.cleanpath_conservative('../'), '..' 24 | assert_equal RefinedPathname.new.cleanpath_conservative('a/'), 'a/' 25 | assert_equal RefinedPathname.new.cleanpath_conservative('a//b'), 'a/b' 26 | end 27 | 28 | def test_clean_conservative_defaults2 29 | assert_equal RefinedPathname.new.cleanpath_conservative('a/.'), 'a/.' 30 | assert_equal RefinedPathname.new.cleanpath_conservative('a/./'), 'a/.' 31 | assert_equal RefinedPathname.new.cleanpath_conservative('a/../'), 'a/..' 32 | assert_equal RefinedPathname.new.cleanpath_conservative('/a/.'), '/a/.' 33 | assert_equal RefinedPathname.new.cleanpath_conservative('./..'), '..' 34 | assert_equal RefinedPathname.new.cleanpath_conservative('../.'), '..' 35 | assert_equal RefinedPathname.new.cleanpath_conservative('./../'), '..' 36 | assert_equal RefinedPathname.new.cleanpath_conservative('.././'), '..' 37 | assert_equal RefinedPathname.new.cleanpath_conservative('/./..'), '/' 38 | assert_equal RefinedPathname.new.cleanpath_conservative('/../.'), '/' 39 | end 40 | 41 | def test_clean_conservative_defaults3 42 | assert_equal RefinedPathname.new.cleanpath_conservative('/./../'), '/' 43 | assert_equal RefinedPathname.new.cleanpath_conservative('/.././'), '/' 44 | assert_equal RefinedPathname.new.cleanpath_conservative('a/b/c'), 'a/b/c' 45 | assert_equal RefinedPathname.new.cleanpath_conservative('./b/c'), 'b/c' 46 | assert_equal RefinedPathname.new.cleanpath_conservative('a/./c'), 'a/c' 47 | assert_equal RefinedPathname.new.cleanpath_conservative('a/b/.'), 'a/b/.' 48 | assert_equal RefinedPathname.new.cleanpath_conservative('a/../.'), 'a/..' 49 | assert_equal RefinedPathname.new.cleanpath_conservative('/../.././../a'), '/a' 50 | assert_equal RefinedPathname.new.cleanpath_conservative('a/b/../../../../c/../d'), 'a/b/../../../../c/../d' 51 | end 52 | 53 | def test_clean_conservative_dosish_stuff 54 | if !File::ALT_SEPARATOR.nil? 55 | assert_equal RefinedPathname.new.cleanpath_conservative('c:\\foo\\bar'), 'c:/foo/bar' 56 | end 57 | 58 | if File.dirname("//") == "//" 59 | assert_equal RefinedPathname.new.cleanpath_conservative('//'), '//' 60 | else 61 | assert_equal RefinedPathname.new.cleanpath_conservative('//'), '/' 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/ripple_effects/del_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | using FasterPath::RefineFile 7 | def del_trailing_separator(pth) 8 | Pathname.allocate.send(:del_trailing_separator, pth) 9 | end 10 | end 11 | 12 | class DelTrailingSeparatorTest < Minitest::Test 13 | def test_del_trailing_separator 14 | assert_equal RefinedPathname.new.del_trailing_separator("/"), "/" 15 | assert_equal RefinedPathname.new.del_trailing_separator("/a"), "/a" 16 | assert_equal RefinedPathname.new.del_trailing_separator("/a/"), "/a" 17 | assert_equal RefinedPathname.new.del_trailing_separator("/a//"), "/a" 18 | assert_equal RefinedPathname.new.del_trailing_separator("."), "." 19 | assert_equal RefinedPathname.new.del_trailing_separator("./"), "." 20 | assert_equal RefinedPathname.new.del_trailing_separator(".//"), "." 21 | end 22 | 23 | def test_del_trailing_separator_in_dosish_context 24 | if File.dirname("A:") == "A:." # DOSISH_DRIVE_LETTER 25 | assert_equal RefinedPathname.new.del_trailing_separator("A:"), "A:" 26 | assert_equal RefinedPathname.new.del_trailing_separator("A:/"), "A:/" 27 | assert_equal RefinedPathname.new.del_trailing_separator("A://"), "A:/" 28 | assert_equal RefinedPathname.new.del_trailing_separator("A:."), "A:." 29 | assert_equal RefinedPathname.new.del_trailing_separator("A:./"), "A:." 30 | assert_equal RefinedPathname.new.del_trailing_separator("A:.//"), "A:." 31 | end 32 | 33 | if File.dirname("//") == "//" # DOSISH_UNC 34 | assert_equal RefinedPathname.new.del_trailing_separator("//"), "//" 35 | assert_equal RefinedPathname.new.del_trailing_separator("//a"), "//a" 36 | assert_equal RefinedPathname.new.del_trailing_separator("//a/"), "//a" 37 | assert_equal RefinedPathname.new.del_trailing_separator("//a//"), "//a" 38 | assert_equal RefinedPathname.new.del_trailing_separator("//a/b"), "//a/b" 39 | assert_equal RefinedPathname.new.del_trailing_separator("//a/b/"), "//a/b" 40 | assert_equal RefinedPathname.new.del_trailing_separator("//a/b//"), "//a/b" 41 | assert_equal RefinedPathname.new.del_trailing_separator("//a/b/c"), "//a/b/c" 42 | assert_equal RefinedPathname.new.del_trailing_separator("//a/b/c/"), "//a/b/c" 43 | assert_equal RefinedPathname.new.del_trailing_separator("//a/b/c//"), "//a/b/c" 44 | else 45 | assert_equal RefinedPathname.new.del_trailing_separator("///"), "/" 46 | assert_equal RefinedPathname.new.del_trailing_separator("///a/"), "///a" 47 | end 48 | 49 | if !File::ALT_SEPARATOR.nil? # DOSISH 50 | assert_equal RefinedPathname.new.del_trailing_separator("a\\"), "a" 51 | assert_equal RefinedPathname.new.del_trailing_separator("\225\\\\".dup.force_encoding("cp932")), "\225\\".dup.force_encoding("cp932") 52 | assert_equal RefinedPathname.new.del_trailing_separator("\225\\\\".dup.force_encoding("cp437")), "\225".dup.force_encoding("cp437") 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/ripple_effects/has_trailing_separator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | using FasterPath::RefineFile 7 | def has_trailing_separator?(pth) 8 | Pathname.allocate.send(:has_trailing_separator?, pth) 9 | end 10 | end 11 | 12 | class HasTrailingSeparatorTest < Minitest::Test 13 | def test_has_trailing_separator? 14 | refute RefinedPathname.new.has_trailing_separator?("/") 15 | refute RefinedPathname.new.has_trailing_separator?("///") 16 | refute RefinedPathname.new.has_trailing_separator?("a") 17 | assert RefinedPathname.new.has_trailing_separator?("a/") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/ripple_effects/join_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | using FasterPath::RefineFile 7 | def join(*args) 8 | a, *rest = args 9 | Pathname(a).join(*rest) 10 | end 11 | end 12 | 13 | class JoinTest < Minitest::Test 14 | def test_join 15 | r = RefinedPathname.new.join("a", Pathname("b"), Pathname("c")) 16 | assert_equal(Pathname("a/b/c"), r) 17 | r = RefinedPathname.new.join("/a", Pathname("b"), Pathname("c")) 18 | assert_equal(Pathname("/a/b/c"), r) 19 | r = RefinedPathname.new.join("/a", Pathname("/b"), Pathname("c")) 20 | assert_equal(Pathname("/b/c"), r) 21 | r = RefinedPathname.new.join("/a", Pathname("/b"), Pathname("/c")) 22 | assert_equal(Pathname("/c"), r) 23 | r = RefinedPathname.new.join("/a", "/b", "/c") 24 | assert_equal(Pathname("/c"), r) 25 | r = RefinedPathname.new.join("/foo/var") 26 | assert_equal(Pathname("/foo/var"), r) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/ripple_effects/plus_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'faster_path/optional/refinements' 3 | 4 | class RefinedPathname 5 | using FasterPath::RefinePathname 6 | using FasterPath::RefineFile 7 | def plus(a, b) 8 | send(:+, a, b).to_s 9 | end 10 | 11 | def +(pth, other) 12 | Pathname(pth) + Pathname(other) 13 | end 14 | end 15 | 16 | class CleanpathAggressiveTest < Minitest::Test 17 | def test_plus 18 | assert_kind_of(Pathname, RefinedPathname.new.+("a", "b")) 19 | end 20 | 21 | def plus(path1, path2) # -> path 22 | (Pathname.new(path1) + Pathname.new(path2)).to_s 23 | end 24 | 25 | def test_clean_aggresive_defaults 26 | assert_equal RefinedPathname.new.plus('/', '/'), '/' 27 | assert_equal RefinedPathname.new.plus('a', 'b'), 'a/b' 28 | assert_equal RefinedPathname.new.plus('a', '.'), 'a' 29 | assert_equal RefinedPathname.new.plus('.', 'b'), 'b' 30 | assert_equal RefinedPathname.new.plus('.', '.'), '.' 31 | assert_equal RefinedPathname.new.plus('a', '/b'), '/b' 32 | assert_equal RefinedPathname.new.plus('/', '..'), '/' 33 | assert_equal RefinedPathname.new.plus('a', '..'), '.' 34 | assert_equal RefinedPathname.new.plus('a/b', '..'), 'a' 35 | assert_equal RefinedPathname.new.plus('..', '..'), '../..' 36 | assert_equal RefinedPathname.new.plus('/', '../c'), '/c' 37 | assert_equal RefinedPathname.new.plus('a', '../c'), 'c' 38 | assert_equal RefinedPathname.new.plus('a/b', '../c'), 'a/c' 39 | assert_equal RefinedPathname.new.plus('..', '../c'), '../../c' 40 | assert_equal RefinedPathname.new.plus('a//b/c', '../d//e'), 'a//b/d//e' 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/support/bundler/helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'open3' 3 | module Spec 4 | module Helpers 5 | def self.bang(method) 6 | define_method("#{method}!") do |*args, &blk| 7 | send(method, *args, &blk).tap do 8 | if exitstatus && exitstatus != 0 9 | error = out + "\n" + err 10 | error.strip! 11 | raise RuntimeError, 12 | "Invoking #{method}!(#{args.map(&:inspect).join(", ")}) failed:\n#{error}", 13 | caller.drop_while {|bt| bt.start_with?(__FILE__) } 14 | end 15 | end 16 | end 17 | end 18 | 19 | attr_reader :out, :err, :exitstatus 20 | 21 | def lib 22 | File.expand_path("../../../lib", __FILE__) 23 | end 24 | 25 | def bundle(cmd, options = {}) 26 | with_sudo = options.delete(:sudo) 27 | sudo = with_sudo == :preserve_env ? "sudo -E" : "sudo" if with_sudo 28 | 29 | options["no-color"] = true unless options.key?("no-color") || cmd.to_s =~ /\A(e|ex|exe|exec|conf|confi|config)(\s|\z)/ 30 | 31 | bundle_bin = options.delete("bundle_bin") || File.expand_path("../../../exe/bundle", __FILE__) 32 | 33 | if system_bundler = options.delete(:system_bundler) 34 | bundle_bin = "-S bundle" 35 | end 36 | 37 | requires = options.delete(:requires) || [] 38 | # if artifice = options.delete(:artifice) { "fail" unless RSpec.current_example.metadata[:realworld] } 39 | # requires << File.expand_path("../artifice/#{artifice}.rb", __FILE__) 40 | # end 41 | # requires << "support/hax" 42 | requires_str = requires.map {|r| "-r#{r}" }.join(" ") 43 | 44 | load_path = [] 45 | load_path << lib unless system_bundler 46 | load_path << File.expand_path("../../../spec", __FILE__) 47 | load_path_str = "-I#{load_path.join(File::PATH_SEPARATOR)}" 48 | 49 | env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}'" }.join(" ") 50 | env["PATH"].gsub!("#{Path.root}/exe", "") if env["PATH"] && system_bundler 51 | args = options.map do |k, v| 52 | v == true ? " --#{k}" : " --#{k} #{v}" if v 53 | end.join 54 | 55 | cmd = "#{env} #{sudo} #{Gem.ruby} #{load_path_str} #{requires_str} #{bundle_bin} #{cmd}#{args}" 56 | sys_exec(cmd) {|i, o, thr| yield i, o, thr if block_given? } 57 | end 58 | bang :bundle 59 | 60 | def sys_exec(cmd) 61 | Open3.popen3(cmd.to_s) do |stdin, stdout, stderr, wait_thr| 62 | yield stdin, stdout, wait_thr if block_given? 63 | stdin.close 64 | 65 | @exitstatus = wait_thr && wait_thr.value.exitstatus 66 | @out = Thread.new { stdout.read }.value.strip 67 | @err = Thread.new { stderr.read }.value.strip 68 | end 69 | 70 | (@all_output ||= String.new) << [ 71 | "$ #{cmd.to_s.strip}", 72 | out, 73 | err, 74 | @exitstatus ? "# $? => #{@exitstatus}" : "", 75 | "\n", 76 | ].reject(&:empty?).join("\n") 77 | 78 | @out 79 | end 80 | bang :sys_exec 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/support/bundler/path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "pathname" 3 | 4 | module Spec 5 | module Path 6 | def root 7 | @root ||= Pathname.new(File.expand_path("../../..", __FILE__)) 8 | end 9 | 10 | def tmp(*path) 11 | root.join("tmp", *path) 12 | end 13 | 14 | def bundled_app(*path) 15 | root = tmp.join("bundled_app") 16 | FileUtils.mkdir_p(root) 17 | root.join(*path) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | if ENV['TEST_MONKEYPATCHES'] && 3 | ENV['WITH_REGRESSION'] && 4 | !ENV['RUST_BACKTRACE'] && 5 | ENV['TRAVIS_OS_NAME'] == 'linux' && 6 | Gem::Dependency.new('', '~> 2.4', '< 2.5').match?('', RUBY_VERSION) 7 | require 'simplecov' 8 | require 'coveralls' 9 | 10 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 11 | SimpleCov::Formatter::HTMLFormatter, 12 | Coveralls::SimpleCov::Formatter 13 | ] 14 | SimpleCov.start do 15 | add_filter '/test/' 16 | add_filter '/spec/' 17 | end 18 | end 19 | require 'faster_path' 20 | require 'minitest/autorun' 21 | require 'pathname' 22 | require 'minitest/reporters' 23 | require 'color_pound_spec_reporter' 24 | 25 | Minitest::Reporters.use! [ColorPoundSpecReporter.new] 26 | 27 | ::Minitest::Assertions.module_eval do 28 | def assert_incompatible_encoding 29 | d = "\u{3042}\u{3044}".encode("utf-16le") 30 | assert_raises(Encoding::CompatibilityError) {yield d} 31 | m = Class.new {define_method(:to_path) {d}} 32 | assert_raises(Encoding::CompatibilityError) {yield m.new} 33 | end 34 | end 35 | 36 | ::Minitest::Test.class_eval do 37 | DRIVE = Dir.pwd[%r{\A(?:[a-z]:|//[^/]+/[^/]+)}i] 38 | POSIX = /cygwin|mswin|bccwin|mingw|emx/ !~ RUBY_PLATFORM 39 | NTFS = !(/mingw|mswin|bccwin/ !~ RUBY_PLATFORM) 40 | DOSISH = !File::ALT_SEPARATOR.nil? 41 | DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:." 42 | DOSISH_UNC = File.dirname("//") == "//" 43 | 44 | def regular_file 45 | return @file if defined? @file 46 | @file = make_tmp_filename("file") 47 | make_file("foo", @file) 48 | @file 49 | end 50 | 51 | def make_tmp_filename(prefix) 52 | "#{@dir}/#{prefix}.test" 53 | end 54 | 55 | def make_file(content, file) 56 | open(file, "w") {|fh| fh << content } 57 | end 58 | 59 | def utf8_file 60 | return @utf8file if defined? @utf8file 61 | @utf8file = make_tmp_filename("\u3066\u3059\u3068") 62 | make_file("foo", @utf8file) 63 | @utf8file 64 | end 65 | 66 | def nofile 67 | return @nofile if defined? @nofile 68 | @nofile = make_tmp_filename("nofile") 69 | @nofile 70 | end 71 | 72 | def with_tmpchdir(base=nil) 73 | Dir.mktmpdir(base) do |d| 74 | d = Pathname.new(d).realpath.to_s 75 | Dir.chdir(d) do 76 | yield d 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/thermite_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'thermite/tasks' 3 | 4 | class ThermiteTest < Minitest::Test 5 | def setup 6 | project_toplevel_dir = File.dirname(__dir__) 7 | @thermite = Thermite::Tasks.new(cargo_project_path: project_toplevel_dir, 8 | ruby_project_path: project_toplevel_dir) 9 | end 10 | 11 | def test_has_methods_needed_for_monkeypatch 12 | assert Thermite::Config.public_method_defined?(:ruby_version), 'Thermite ruby_version not defined!' 13 | assert Thermite::Tasks.public_method_defined?(:github_download_uri), 'Thermite github_download_uri not defined!' 14 | 15 | assert_match(/ruby\d+\.\d+\.\d+/, Thermite::Config.new.ruby_version) 16 | 17 | gh_uri = @thermite.github_download_uri('lol', '0.0.1') 18 | 19 | assert_match(/faster_path/, gh_uri) 20 | assert_match(/0\.0\.1/, gh_uri) 21 | assert_match(/#{FasterPath::VERSION}/, gh_uri) 22 | assert_match(/github/, gh_uri) 23 | assert_match(/tar\.gz/, gh_uri) 24 | end 25 | end 26 | --------------------------------------------------------------------------------