faster_path 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e04d827e927a1e405d74ccf0fe49946faaf67650c935ad1e5a5dec744254d27
4
- data.tar.gz: '067897cef2171835830c97c8340397ad2d4a97ad1c72b15a88c0876bf58a1690'
3
+ metadata.gz: 9fb5780bd8ce8ea17d538b540ac69b1c023118d1351e167cde418ee48a4f8085
4
+ data.tar.gz: b12bc7a5ab8e4e26cfd46328bdb9e4b57eea367a9c40e76aeba74e12f06eb0e7
5
5
  SHA512:
6
- metadata.gz: 16298d55d4cd08d92555ed02e995a55520430527e37d057d7372182e7b9f12758bbf730d2f5969740f806e4bc3221b32843e2a90a8aacb96a57d95500995e477
7
- data.tar.gz: c09f83ba8f3687b3dc509536fbf985435528a160161795285baf612edf965f409bbac07f6b6de7075be3b5df29d23263de41176ad478d90ade20a567e1fb2a8d
6
+ metadata.gz: fa615d0278378e74432832369cf56b16147ecc0e5e27bd12b1fd90c2690fab41dc68fd3042e8dd7c92b8ebb7741f7d08e9db25bd0cec61b384b520f9bd8b400a
7
+ data.tar.gz: 905389671e4062ef0824e7fb7af8d05fefb1df0ad2a5919bc8a54a8506309ba77cea577859ea49da4e15616fd2fb4bf1d025e5082baef6e61bf18c0c9799d000
data/Cargo.lock CHANGED
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "faster_path"
7
- version = "0.1.0"
7
+ version = "0.5.0"
8
8
  dependencies = [
9
9
  "memchr",
10
10
  "rutie",
@@ -30,9 +30,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
30
30
 
31
31
  [[package]]
32
32
  name = "rutie"
33
- version = "0.10.2"
33
+ version = "0.13.1"
34
34
  source = "registry+https://github.com/rust-lang/crates.io-index"
35
- checksum = "a97641d6bd3731d263d914141e960a8b515bf5ff4ca6c961b25ca4b2694c6490"
35
+ checksum = "30d1529470ad0b69b81a6d0224632d00c01b46ef671452e24407512165b7ed84"
36
36
  dependencies = [
37
37
  "lazy_static",
38
38
  "libc",
data/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "faster_path"
3
- version = "0.1.0"
3
+ version = "0.5.0"
4
4
  authors = ["Daniel P. Clark <6ftdan@gmail.com>"]
5
5
  description = "Alternative to Pathname"
6
6
  homepage = "https://github.com/danielpclark/faster_path"
@@ -9,13 +9,10 @@ license = "MIT OR Apache-2.0"
9
9
  readme = "README.md"
10
10
  edition = "2021"
11
11
 
12
- [package.metadata.thermite]
13
- github_releases = true
14
-
15
12
  [lib]
16
13
  name = "faster_path"
17
14
  crate-type = ["cdylib"]
18
15
 
19
16
  [dependencies]
20
- rutie = "0.10.2"
17
+ rutie = "0.13.1"
21
18
  memchr = "2"
data/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  [![CI](https://github.com/danielpclark/faster_path/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/danielpclark/faster_path/actions/workflows/ci.yml)
4
4
  [![Latest Tag](https://img.shields.io/github/tag/danielpclark/faster_path.svg)](https://github.com/danielpclark/faster_path/tags)
5
5
  [![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)
6
- [![Binary Release](https://img.shields.io/github/release/danielpclark/faster_path.svg)](https://github.com/danielpclark/faster_path/releases)
7
6
  [![Inline docs](http://inch-ci.org/github/danielpclark/faster_path.svg?branch=master)](http://inch-ci.org/github/danielpclark/faster_path)
8
7
  [![Code Triagers Badge](https://www.codetriage.com/danielpclark/faster_path/badges/users.svg)](https://www.codetriage.com/danielpclark/faster_path)
9
8
 
@@ -114,14 +113,15 @@ As mentioned earlier Sprockets, which handles assets, changed away from using `P
114
113
 
115
114
  ## Requirements
116
115
 
117
- * Ruby 2.5, 2.6 or 2.7 (the Rust extension is built with [Rutie](https://github.com/danielpclark/rutie) 0.10, which supports these)
118
- * Rust 1.71 or later
116
+ * Ruby 3.2, 3.3 or 3.4 (the Rust extension is built with [Rutie](https://github.com/danielpclark/rutie) 0.13, which supports these)
117
+ * Rust 1.71 or later: `gem install` builds the extension with Cargo (through the
118
+ [rutie](https://rubygems.org/gems/rutie) gem's Rake task) and keeps only the built library
119
119
 
120
120
  ## Installation
121
121
 
122
- Ensure Rust is installed:
122
+ Ensure Rust is installed, with `cargo` on your `PATH`:
123
123
 
124
- [Rust Downloads](https://www.rust-lang.org/downloads.html)
124
+ [Install Rust](https://www.rust-lang.org/tools/install)
125
125
 
126
126
  ```
127
127
  curl -sSf https://sh.rustup.rs | sh
@@ -130,7 +130,7 @@ curl -sSf https://sh.rustup.rs | sh
130
130
  Add this line to your application's Gemfile:
131
131
 
132
132
  ```ruby
133
- gem 'faster_path', '~> 0.4.0'
133
+ gem 'faster_path', '~> 0.5.0'
134
134
  ```
135
135
 
136
136
  And then execute:
@@ -141,6 +141,12 @@ Or install it yourself as:
141
141
 
142
142
  $ gem install faster_path
143
143
 
144
+ Installing compiles the Rust extension for the Ruby doing the installing ("Building native
145
+ extensions. This could take a while..."), which takes about a minute the first time while Cargo
146
+ fetches and builds the dependencies. If Rust is missing, the install fails at that step with
147
+ Cargo not being found; install Rust and run it again. Nothing is downloaded from GitHub: there are
148
+ no prebuilt binaries.
149
+
144
150
  ## Visual Benchmarks
145
151
 
146
152
  Benchmarks in Faster Path now produce visual graph charts of performance improvements.
@@ -159,25 +165,25 @@ require "faster_path"
159
165
 
160
166
  Current methods implemented:
161
167
 
162
- |FasterPath Rust Implementation|Ruby Implementation|Time Shaved Off (Ruby 2.7)|Time Shaved Off (Ruby 2.5)|
163
- |---|---|:---:|:---:|
164
- | `FasterPath.absolute?` | `Pathname#absolute?` | 95.7% | 96.7% |
165
- | `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 85.4% | 83.0% |
166
- | `FasterPath.basename` | `File.basename` | 42.4% | 39.1% |
167
- | `FasterPath.children` | `Pathname#children` | 54.7% | 47.8% |
168
- | `FasterPath.chop_basename` | `Pathname#chop_basename` | 70.4% | 75.1% |
169
- | `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` | 90.3% | 90.6% |
170
- | `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` | 89.6% | 91.6% |
171
- | `FasterPath.del_trailing_separator` | `Pathname#del_trailing_separator` | 85.3% | 87.6% |
172
- | `FasterPath.directory?` | `Pathname#directory?` | 34.3% | 37.5% |
173
- | `FasterPath.dirname` | `File.dirname` | 56.8% | 54.7% |
174
- | `FasterPath.entries` | `Pathname#entries` | 38.0% | 33.9% |
175
- | `FasterPath.extname` | `File.extname` | 74.0% | 73.1% |
176
- | `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 88.5% | 89.1% |
177
- | `FasterPath.join` | `Pathname#join` | 88.1% | 90.3% |
178
- | `FasterPath.plus` | `Pathname#plus` | 91.7% | 93.4% |
179
- | `FasterPath.relative?` | `Pathname#relative?` | 92.8% | 95.2% |
180
- | `FasterPath.relative_path_from` | `Pathname#relative_path_from` | 92.5% | 93.6% |
168
+ |FasterPath Rust Implementation|Ruby Implementation|Time Shaved Off (Ruby 3.4)|Time Shaved Off (Ruby 3.3)|Time Shaved Off (Ruby 3.2)|
169
+ |---|---|:---:|:---:|:---:|
170
+ | `FasterPath.absolute?` | `Pathname#absolute?` | 55.5% | 53.2% | 43.1% |
171
+ | `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 87.4% | 85.3% | 85.8% |
172
+ | `FasterPath.basename` | `File.basename` | 30.7% | 22.2% | 27.5% |
173
+ | `FasterPath.children` | `Pathname#children` | 65.0% | 55.1% | 55.4% |
174
+ | `FasterPath.chop_basename` | `Pathname#chop_basename` | 75.3% | 73.5% | 72.8% |
175
+ | `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` | 92.4% | 92.2% | 92.4% |
176
+ | `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` | 91.4% | 91.3% | 91.8% |
177
+ | `FasterPath.del_trailing_separator` | `Pathname#del_trailing_separator` | 89.1% | 87.9% | 87.8% |
178
+ | `FasterPath.directory?` | `Pathname#directory?` | 41.5% | 39.9% | 44.2% |
179
+ | `FasterPath.dirname` | `File.dirname` | 52.0% | 40.3% | 40.8% |
180
+ | `FasterPath.entries` | `Pathname#entries` | 57.8% | 42.4% | 45.9% |
181
+ | `FasterPath.extname` | `File.extname` | 69.4% | 66.7% | 69.2% |
182
+ | `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 93.4% | 92.8% | 91.7% |
183
+ | `FasterPath.join` | `Pathname#join` | 89.1% | 89.0% | 88.4% |
184
+ | `FasterPath.plus` | `Pathname#plus` | 95.5% | 94.7% | 94.5% |
185
+ | `FasterPath.relative?` | `Pathname#relative?` | 66.9% | 66.3% | 60.4% |
186
+ | `FasterPath.relative_path_from` | `Pathname#relative_path_from` | 94.4% | 94.6% | 94.2% |
181
187
 
182
188
  See [Benchmarks](#benchmarks) for how these are measured.
183
189
 
@@ -205,10 +211,10 @@ FasterPath.sledgehammer_everything!
205
211
 
206
212
  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.
207
213
 
208
- |FasterPath Implementation|Ruby Implementation|Time Shaved Off (Ruby 2.7)|Time Shaved Off (Ruby 2.5)|
209
- |---|---|:---:|:---:|
210
- | `FasterPath.entries_compat` | `Pathname.entries` | 16.8% | 9.2% |
211
- | `FasterPath.children_compat` | `Pathname.children` | 32.1% | 26.6% |
214
+ |FasterPath Implementation|Ruby Implementation|Time Shaved Off (Ruby 3.4)|Time Shaved Off (Ruby 3.3)|Time Shaved Off (Ruby 3.2)|
215
+ |---|---|:---:|:---:|:---:|
216
+ | `FasterPath.entries_compat` | `Pathname.entries` | 35.7% | 22.0% | 15.9% |
217
+ | `FasterPath.children_compat` | `Pathname.children` | 46.7% | 33.6% | 32.3% |
212
218
 
213
219
  It's been my observation (and some others) that the Rust implementation of the C code for `File` has similar results but
214
220
  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.
@@ -216,13 +222,19 @@ performance seems to vary based on CPU cache on possibly 64bit/32bit system envi
216
222
  ## Benchmarks
217
223
 
218
224
  The "Time Shaved Off" figures come from the project's Pinch-bench (`rake pbench`, in
219
- `test/pbench`): each method runs against Ruby's own implementation, and the result is how much
220
- less time FasterPath took. They are the median of 3 runs of `LONG_RUN=10 rake pbench` (500,000
221
- calls of each method, 5 times) on Linux x86_64 with Ruby 2.7.8 and 2.5.7, built with Rust 1.97.
222
- Expect a few points of difference from run to run.
225
+ `test/pbench`): each method runs against Ruby's own implementation, alternating with it round by
226
+ round, and the result is how much less time FasterPath's best round took than Ruby's best round.
227
+ They are the median of 3 runs of `LONG_RUN=30 rake pbench` (5 rounds of 300,000 calls of each
228
+ method) on Linux x86_64, each run pinned to one CPU core, with Ruby 3.4.6, 3.3.6 and 3.2.9, built
229
+ with Rust 1.97. The three runs agree within a few points for most methods; the ones that touch
230
+ the filesystem (`children`, `directory?`, `entries`) vary the most.
231
+
232
+ The figures are smaller than they were on Ruby 2 for the cheapest methods, `absolute?` and
233
+ `relative?` above all: Ruby 3's own `Pathname#absolute?` is a single regexp match, where Ruby 2's
234
+ walked the path with `chop_basename`, so there is less left to shave off.
223
235
 
224
236
  `rake pbench` is also the performance regression test: each method has a floor in
225
- `test/pbench/pbench_suite.rb` (about half of the improvement in the table above) and the task
237
+ `test/pbench/pbench_suite.rb` (about half of its lowest figure in the table above) and the task
226
238
  fails when a method measures below it, so a change that makes a method slower shows up in
227
239
  `rake test`. On Windows, where `join` is currently slower than Ruby's
228
240
  ([#185](https://github.com/danielpclark/faster_path/issues/185)), the floors are reported but not
@@ -230,8 +242,10 @@ enforced.
230
242
 
231
243
  ### Before and after the Rutie 0.10 upgrade
232
244
 
233
- Measured the same way, alternating runs of the code before the upgrade (built with Rutie 0.6)
234
- and after it. Higher is better; the change is in percentage points.
245
+ The last figures on Ruby 2, from the Rutie 0.10 upgrade (faster_path 0.4.0): Ruby 2.7.8 and
246
+ 2.5.7, alternating runs of the code before the upgrade (built with Rutie 0.6) and after it, with
247
+ the benchmark then measuring the average of 5 rounds run one implementation after the other.
248
+ Higher is better; the change is in percentage points.
235
249
 
236
250
  |Method|Ruby 2.7 before|Ruby 2.7 after|Change|Ruby 2.5 before|Ruby 2.5 after|Change|
237
251
  |---|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -278,7 +292,9 @@ Rust method like `!absolute?` _(not absolute)_ drops 39% of the performance alre
278
292
  Whenever feasible implement it in Rust.
279
293
 
280
294
  After checking out the repo, make sure you have Rust installed, then run `bundle`.
281
- Run `rake test` to run the tests, and `rake bench` for benchmarks.
295
+ Run `rake test` to run the tests, and `rake bench` for benchmarks. Both build the extension
296
+ first (`rake rutie:build`, into `target/release`, which is where `require 'faster_path'` loads it
297
+ from in a checkout).
282
298
 
283
299
  ### Building and running tests
284
300
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
- require 'thermite/tasks'
4
- require 'faster_path/thermite_initialize'
3
+ require 'faster_path/version'
4
+ require 'faster_path/build_task'
5
5
 
6
6
  desc 'System Details'
7
7
  task :sysinfo do
@@ -22,7 +22,7 @@ task :sysinfo do
22
22
  puts "RAKE\n #{Rake::VERSION}"
23
23
  end
24
24
 
25
- thermite = Thermite::Tasks.new
25
+ FasterPath::BuildTask.define
26
26
 
27
27
  desc "Generate Contriburs.md Manifest"
28
28
  task :contrib do
@@ -31,10 +31,8 @@ task :contrib do
31
31
  ' > Contributors.md;git shortlog -s -n -e | sed 's/^......./- /' >> Contributors.md"
32
32
  end
33
33
 
34
- desc 'Build + clean up Rust extension'
35
- task build_lib: 'thermite:build' do
36
- thermite.run_cargo 'clean'
37
- end
34
+ desc 'Build the Rust extension'
35
+ task build_lib: 'rutie:build'
38
36
 
39
37
  desc 'Code Quality Check'
40
38
  task :lint do
@@ -92,10 +90,8 @@ Rake::TestTask.new(bench: :build_lib) do |t|
92
90
  t.pattern = 'test/**/*_benchmark.rb'
93
91
  end
94
92
 
93
+ # LONG_RUN=<1..30> multiplies the calls per round, see test/pbench/pbench.rb
95
94
  Rake::TestTask.new(pbench: :build_lib) do |t|
96
- if ARGV.last == '--long-run'
97
- ENV['LONG_RUN'] = '10'
98
- end
99
95
  t.libs = %w[lib test test/pbench]
100
96
  t.pattern = 'test/pbench/pbench_suite.rb'
101
97
  end
data/ext/Rakefile CHANGED
@@ -1,8 +1,14 @@
1
- require 'thermite/tasks'
2
- require_relative '../lib/faster_path/thermite_initialize'
1
+ require 'fileutils'
2
+ require_relative '../lib/faster_path/build_task'
3
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)
4
+ FasterPath::BuildTask.define
7
5
 
8
- task default: 'thermite:build'
6
+ # Keeps the built library (in lib/faster_path, where FasterPath loads it from)
7
+ # and removes the rest of Cargo's output from the installed gem.
8
+ task install_library: 'rutie:build' do
9
+ FileUtils.mkdir_p(FasterPath::BuildTask::INSTALLED_LIBRARY_DIR)
10
+ FileUtils.cp(FasterPath::BuildTask.built_library, FasterPath::BuildTask::INSTALLED_LIBRARY_DIR)
11
+ Rake::Task['rutie:clean'].invoke
12
+ end
13
+
14
+ task default: :install_library
data/faster_path.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.files = [
17
17
  'Cargo.lock', 'Cargo.toml', 'Gemfile', 'MIT-LICENSE.txt', 'README.md', 'Rakefile',
18
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',
19
+ 'lib/faster_path/version.rb', 'lib/faster_path/build_task.rb',
20
20
  'lib/faster_path/optional/monkeypatches.rb', 'lib/faster_path/optional/refinements.rb'
21
21
  ]
22
22
  spec.files += Dir['src/**/*']
@@ -24,17 +24,18 @@ Gem::Specification.new do |spec|
24
24
  spec.extensions = ['ext/Rakefile']
25
25
  spec.require_paths = ['lib']
26
26
 
27
- # Rutie 0.10, which the Rust extension is built with, supports Ruby 2.5 to 2.7
28
- spec.required_ruby_version = ['>= 2.5', '< 3.0']
27
+ # Rutie 0.13, which the Rust extension is built with, supports Ruby 3.2 to 3.4
28
+ spec.required_ruby_version = ['>= 3.2', '< 3.5']
29
29
 
30
- spec.add_dependency 'bundler', '>= 1.12'
30
+ # The extension is built at install time with Rutie's Rake task and loaded with Rutie
31
31
  spec.add_dependency 'rake', '>= 12.3'
32
- spec.add_dependency 'thermite', '0.13.0'
32
+ spec.add_dependency 'rutie', '~> 0.0.5'
33
33
  spec.add_development_dependency 'read_source', '~> 0.2.6'
34
34
  spec.add_development_dependency 'minitest', '~> 5.11'
35
35
  spec.add_development_dependency 'minitest-reporters', '~> 1.1'
36
36
  spec.add_development_dependency 'color_pound_spec_reporter', '~> 0.0.9'
37
- spec.add_development_dependency 'rubocop', '0.53'
37
+ spec.add_development_dependency 'rubocop', '~> 1.80'
38
+ spec.add_development_dependency 'rubocop-performance', '~> 1.20'
38
39
  spec.add_development_dependency 'stop_watch', '~> 1.0'
39
40
  if !ENV['CI'] && ENV['GRAPH']
40
41
  spec.add_development_dependency 'gruff', '~> 0.7.0'
@@ -0,0 +1,32 @@
1
+ require 'rbconfig'
2
+ require 'rutie'
3
+ require 'rutie/rake_task'
4
+
5
+ module FasterPath
6
+ # Defines `rutie:build` and `rutie:clean`, which build the Rust extension
7
+ # with Cargo. Used by the Rakefile and by ext/Rakefile when the gem is
8
+ # installed; both may run from another directory, so Cargo is pointed at
9
+ # the gem's Cargo.toml.
10
+ #
11
+ # The Rakefile leaves the library in target/release, where Rutie loads it
12
+ # from; ext/Rakefile moves it to lib/faster_path so that the installed gem
13
+ # keeps nothing else of the build.
14
+ module BuildTask
15
+ PROJECT_DIR = File.expand_path('../..', __dir__)
16
+ # Where `gem install` puts the built library
17
+ INSTALLED_LIBRARY_DIR = File.join(PROJECT_DIR, 'lib', 'faster_path')
18
+
19
+ def self.define
20
+ # Rutie's build script links the extension to the Ruby named by RUBY,
21
+ # or the first `ruby` on the PATH; make it the Ruby running Rake.
22
+ ENV['RUBY'] ||= File.join(RbConfig::CONFIG['bindir'],
23
+ RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT'])
24
+ Rutie::RakeTask.new(cargo_args: ['--manifest-path', File.join(PROJECT_DIR, 'Cargo.toml')])
25
+ end
26
+
27
+ # The library `rutie:build` builds, in the project's target/release
28
+ def self.built_library
29
+ Rutie.new(:faster_path).ffi_library(File.join(PROJECT_DIR, 'lib'))
30
+ end
31
+ end
32
+ end
@@ -24,10 +24,10 @@ module FasterPath
24
24
  end
25
25
 
26
26
  # @private :nodoc:
27
- def self.dirname(pth)
27
+ def self.dirname(pth, level = 1)
28
28
  pth = pth.to_path if pth.respond_to? :to_path
29
29
  raise TypeError unless pth.is_a? String
30
- FasterPath.dirname(pth)
30
+ FasterPath.dirname(pth, level)
31
31
  end
32
32
  end
33
33
  end
@@ -20,10 +20,10 @@ module FasterPath
20
20
  end
21
21
 
22
22
  # @private :nodoc:
23
- def self.dirname(pth)
23
+ def self.dirname(pth, level = 1)
24
24
  pth = pth.to_path if pth.respond_to? :to_path
25
25
  raise TypeError unless pth.is_a? String
26
- FasterPath.dirname(pth)
26
+ FasterPath.dirname(pth, level)
27
27
  end
28
28
  end
29
29
  end
@@ -1,3 +1,3 @@
1
1
  module FasterPath
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/faster_path.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'faster_path/version'
2
2
  require 'pathname'
3
- require 'thermite/config'
4
- require 'faster_path/thermite_initialize'
3
+ require 'rutie'
5
4
  require 'fiddle'
6
5
  require 'fiddle/import'
7
6
 
@@ -14,16 +13,12 @@ require 'fiddle/import'
14
13
  # existing code ecosystem. To do so you will need to
15
14
  # `require 'faster_path/optional/monkeypatches'` beforehand.
16
15
  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_VOID).
26
- call
16
+ # The Rust extension: in the project's target/release after `rake rutie:build`,
17
+ # or in lib/faster_path, where `gem install` puts it (see ext/Rakefile)
18
+ RUTIE = [Rutie.new(:faster_path), Rutie.new(:faster_path, lib_path: 'faster_path')].
19
+ find { |rutie| File.exist?(rutie.ffi_library(__dir__)) } || Rutie.new(:faster_path)
20
+ FFI_LIBRARY = RUTIE.ffi_library(__dir__)
21
+ RUTIE.init('Init_faster_pathname', __dir__)
27
22
 
28
23
  Public.class_eval do
29
24
  private_class_method :basename
data/src/pathname.rs CHANGED
@@ -3,7 +3,7 @@ use std::ffi::OsStr;
3
3
  use std::fs;
4
4
  use std::io;
5
5
 
6
- use rutie::{AnyException, AnyObject, Array, Boolean, Class, NilClass, Object, RString};
6
+ use rutie::{AnyException, AnyObject, Array, Boolean, Class, Integer, NilClass, Object, RString, Symbol};
7
7
 
8
8
  use crate::basename;
9
9
  use crate::chop_basename::{self, is_relative};
@@ -15,7 +15,7 @@ use crate::path_parsing::Rules;
15
15
  use crate::plus;
16
16
  use crate::relative_path_from::{self, RelativePathError};
17
17
  use crate::ruby::{
18
- argument_error, check_max_arguments, error, new_pathname, path_like_to_string, pathname_class,
18
+ argument_error, check_max_arguments, error, inspect, new_pathname, path_like_to_string, pathname_class,
19
19
  truthy_argument, EncodingId, EncodingOf, PathArgument, PathString, RubyResult,
20
20
  };
21
21
 
@@ -152,11 +152,51 @@ pub fn pn_is_directory(arguments: &[AnyObject]) -> RubyResult {
152
152
  Ok(Boolean::new(is_directory).into())
153
153
  }
154
154
 
155
+ // `File.dirname(path, level = 1)`: `level` is the number of trailing
156
+ // components to remove (Ruby 3.1+).
155
157
  pub fn pn_dirname(arguments: &[AnyObject]) -> RubyResult {
156
- check_max_arguments(arguments, 1)?;
158
+ check_max_arguments(arguments, 2)?;
157
159
  let argument = PathArgument::new(arguments, 0)?;
158
160
  let path = argument.path()?;
159
- Ok(path.to_ruby(&dirname::dirname(RULES, path.bytes())).into())
161
+ let level = match arguments.get(1) {
162
+ Some(level) => level_argument(level)?,
163
+ None => 1,
164
+ };
165
+ let mut result: Cow<[u8]> = Cow::Borrowed(path.bytes());
166
+ for _ in 0..level {
167
+ let parent = dirname::dirname(RULES, &result);
168
+ if *parent == *result {
169
+ // The root ("/", "." or "C:/"), which is its own dirname
170
+ break;
171
+ }
172
+ result = Cow::Owned(parent.into_owned());
173
+ }
174
+ if result.is_empty() {
175
+ // `File.dirname("", 0)` is "." like `File.dirname("")`
176
+ result = Cow::Borrowed(b".");
177
+ }
178
+ Ok(path.to_ruby(&result).into())
179
+ }
180
+
181
+ // The `level` argument of `File.dirname`, converted like Ruby's `NUM2INT`.
182
+ fn level_argument(object: &AnyObject) -> Result<usize, AnyException> {
183
+ let integer = match object.try_convert_to::<Integer>() {
184
+ Ok(integer) => integer,
185
+ Err(_) => {
186
+ let responds = object.protect_send("respond_to?", &[Symbol::new("to_int").into()])?;
187
+ if !responds.value().is_true() {
188
+ let class_name = object.protect_send("class", &[]).ok().and_then(|class| inspect(&class).ok()).unwrap_or_default();
189
+ return Err(error(&Class::type_error(), &format!("no implicit conversion of {} into Integer", class_name)));
190
+ }
191
+ let converted = object.protect_send("to_int", &[])?;
192
+ converted.try_convert_to::<Integer>()?
193
+ }
194
+ };
195
+ let level = integer.to_i64();
196
+ if level < 0 {
197
+ return Err(argument_error(&format!("negative level: {}", level)));
198
+ }
199
+ Ok(level as usize)
160
200
  }
161
201
 
162
202
  // Returns an array of `String`s
@@ -315,7 +355,7 @@ fn system_call_error(err: &io::Error, path: &AnyObject, func: &str) -> AnyExcept
315
355
  #[cfg(unix)]
316
356
  {
317
357
  if let Some(errno) = err.raw_os_error() {
318
- let arguments = [path.clone(), rutie::Integer::new(errno.into()).into(), RString::new_utf8(func).into()];
358
+ let arguments = [path.clone(), Integer::new(errno.into()).into(), RString::new_utf8(func).into()];
319
359
  if let Ok(exception) = Class::system_call_error().protect_send("new", &arguments) {
320
360
  if let Ok(exception) = exception.try_convert_to::<AnyException>() {
321
361
  return exception;
metadata CHANGED
@@ -1,29 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faster_path
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel P. Clark
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-07 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '1.12'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '1.12'
27
12
  - !ruby/object:Gem::Dependency
28
13
  name: rake
29
14
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +24,19 @@ dependencies:
39
24
  - !ruby/object:Gem::Version
40
25
  version: '12.3'
41
26
  - !ruby/object:Gem::Dependency
42
- name: thermite
27
+ name: rutie
43
28
  requirement: !ruby/object:Gem::Requirement
44
29
  requirements:
45
- - - '='
30
+ - - "~>"
46
31
  - !ruby/object:Gem::Version
47
- version: 0.13.0
32
+ version: 0.0.5
48
33
  type: :runtime
49
34
  prerelease: false
50
35
  version_requirements: !ruby/object:Gem::Requirement
51
36
  requirements:
52
- - - '='
37
+ - - "~>"
53
38
  - !ruby/object:Gem::Version
54
- version: 0.13.0
39
+ version: 0.0.5
55
40
  - !ruby/object:Gem::Dependency
56
41
  name: read_source
57
42
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +97,30 @@ dependencies:
112
97
  name: rubocop
113
98
  requirement: !ruby/object:Gem::Requirement
114
99
  requirements:
115
- - - '='
100
+ - - "~>"
116
101
  - !ruby/object:Gem::Version
117
- version: '0.53'
102
+ version: '1.80'
118
103
  type: :development
119
104
  prerelease: false
120
105
  version_requirements: !ruby/object:Gem::Requirement
121
106
  requirements:
122
- - - '='
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.80'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubocop-performance
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.20'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
123
122
  - !ruby/object:Gem::Version
124
- version: '0.53'
123
+ version: '1.20'
125
124
  - !ruby/object:Gem::Dependency
126
125
  name: stop_watch
127
126
  requirement: !ruby/object:Gem::Requirement
@@ -155,9 +154,9 @@ files:
155
154
  - ext/Rakefile
156
155
  - faster_path.gemspec
157
156
  - lib/faster_path.rb
157
+ - lib/faster_path/build_task.rb
158
158
  - lib/faster_path/optional/monkeypatches.rb
159
159
  - lib/faster_path/optional/refinements.rb
160
- - lib/faster_path/thermite_initialize.rb
161
160
  - lib/faster_path/version.rb
162
161
  - src/basename.rs
163
162
  - src/chop_basename.rs
@@ -177,7 +176,6 @@ homepage: https://github.com/danielpclark/faster_path
177
176
  licenses:
178
177
  - MIT OR Apache-2.0
179
178
  metadata: {}
180
- post_install_message:
181
179
  rdoc_options: []
182
180
  require_paths:
183
181
  - lib
@@ -185,18 +183,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
185
183
  requirements:
186
184
  - - ">="
187
185
  - !ruby/object:Gem::Version
188
- version: '2.5'
186
+ version: '3.2'
189
187
  - - "<"
190
188
  - !ruby/object:Gem::Version
191
- version: '3.0'
189
+ version: '3.5'
192
190
  required_rubygems_version: !ruby/object:Gem::Requirement
193
191
  requirements:
194
192
  - - ">="
195
193
  - !ruby/object:Gem::Version
196
194
  version: '0'
197
195
  requirements: []
198
- rubygems_version: 3.1.6
199
- signing_key:
196
+ rubygems_version: 3.6.9
200
197
  specification_version: 4
201
198
  summary: Reimplementation of Pathname for better performance
202
199
  test_files: []
@@ -1,22 +0,0 @@
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