faster_path 0.1.8 → 0.1.10

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
  SHA1:
3
- metadata.gz: 8ad592a885912e4267151be6ee15a9accfbd34ef
4
- data.tar.gz: b2b22d3198284b6dc2bb177c72bd1c99bbcbb3a5
3
+ metadata.gz: 40367b49892754c5b9d83891289cc0ce30e757d6
4
+ data.tar.gz: 8a92f9a7796c946a873cfded321b529f1f8d57bf
5
5
  SHA512:
6
- metadata.gz: 17eecf18612f14fa87de8fc58f2ae4b08cb4e7f98e4d62283f87457d09e1822a3aba7db72df2e14e4d9cddcab60abf7cc2148742d9ae301541454e7a62080201
7
- data.tar.gz: 15ad8f6310ce3b5188533f957bbc5e0ba9280686dd148adca124d455249c6426b2ee1f4b9cff43383e6601daf5682fad78a0d376d343537fe1f81118a20fcb21
6
+ metadata.gz: 8dc854ddd64ba12ac79ff11e792bf5fc6723d3ea547056d474a344572e0608f0e0c0c0d688c39a70c50e5a636d36215d0906a952cd65f58402a7fc05d8f135c4
7
+ data.tar.gz: f0e6009a18eeeb0414836f3b13d4684db44839e466845b745c6b08440da63ad247be78acc7671ef53c07dc8c69237d0e964e81c3bd9bb9dd717ce17a65fd2184
data/Cargo.lock CHANGED
@@ -10,3 +10,5 @@ name = "libc"
10
10
  version = "0.2.11"
11
11
  source = "registry+https://github.com/rust-lang/crates.io-index"
12
12
 
13
+ [metadata]
14
+ "checksum libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c96061f0c8a2dc27482e394d82e23073569de41d73cd736672ccd3e5c7471bfd"
data/Gemfile CHANGED
@@ -1,4 +1,12 @@
1
- source 'https://rubygems.org'
1
+ source 'https://rubygems.org' do
2
+ # Specify your gem's dependencies in faster_path.gemspec
3
+ gemspec
4
+ end
2
5
 
3
- # Specify your gem's dependencies in faster_path.gemspec
4
- gemspec
6
+ begin
7
+ # https://github.com/ruby/spec dependencies
8
+ eval_gemfile File.expand_path('spec/ruby_spec/Gemfile', File.dirname(__FILE__))
9
+ rescue
10
+ `git submodule update --init`
11
+ eval_gemfile File.expand_path('spec/ruby_spec/Gemfile', File.dirname(__FILE__))
12
+ end
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
  [![Gem Version](https://badge.fury.io/rb/faster_path.svg)](https://badge.fury.io/rb/faster_path)
3
3
  [![Build Status](https://travis-ci.org/danielpclark/faster_path.svg?branch=master)](https://travis-ci.org/danielpclark/faster_path)
4
4
  [![Tweet This](https://raw.githubusercontent.com/danielpclark/faster_path/master/assets/tweet.png)](https://twitter.com/share?url=https%3A%2F%2Fgithub.com%2Fdanielpclark%2Ffaster_path&via=6ftdan&hashtags=Ruby&text=You%20could%20save%2015%25%20or%20more%20on%20website%20page%20load%20time%20by%20switching%20to%20the%20FasterPath%20gem.)
5
+ [New Wiki Up!](https://github.com/danielpclark/faster_path/wiki)
5
6
 
6
7
  #### This gem shaves off more than 30% of my Rails application page load time.
7
8
 
@@ -116,25 +117,33 @@ And then execute:
116
117
  Or install it yourself as:
117
118
 
118
119
  $ gem install faster_path
119
-
120
+
120
121
  **MAC USERS:** At the moment Mac users need to install the extension manualy. Go to the gem directory and run `cargo build --release` . There is an issue opened for this and I'm looking for people who have Macs to help on this.
121
122
 
123
+ ## Visual Benchmarks
124
+
125
+ Benchmarks in Faster Path now produce visual graph charts of performance improvements.
126
+ When you run `rake bench` the graph art will be placed in `doc/graph/`. Here's the performance
127
+ improvement result for the `chop_basename` method.
128
+
129
+ ![Visual Benchmark](https://raw.githubusercontent.com/danielpclark/faster_path/master/assets/chop_basename_benchmark.jpg "Visual Benchmark")
130
+
122
131
  ## Usage
123
132
 
124
133
  Current methods implemented:
125
134
 
126
135
  |FasterPath Rust Implementation|Ruby 2.3.1 Implementation|Performance Improvement|
127
136
  |---|---|:---:|
128
- | `FasterPath.absolute?` | `Pathname#absolute?` | 1234.6% |
129
- | `FasterPath.basename` | `File.basename` | 31.3% |
130
- | `FasterPath.chop_basename` | `Pathname#chop_basename` | 66.0% |
131
- | `FasterPath.relative?` | `Pathname#relative?` | 1262.3% |
137
+ | `FasterPath.absolute?` | `Pathname#absolute?` | 93.9% |
138
+ | `FasterPath.chop_basename` | `Pathname#chop_basename` | 50.6% |
139
+ | `FasterPath.relative?` | `Pathname#relative?` | 93.2% |
132
140
  | `FasterPath.blank?` | | |
133
- | `FasterPath.directory?` | `Pathname#directory?` | 20% |
134
- | `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 63.8% |
141
+ | `FasterPath.directory?` | `Pathname#directory?` | 25.5% |
142
+ | `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 46.8% |
143
+ | `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 61.2% |
135
144
 
136
145
  You may choose to use the methods directly, or scope change to rewrite behavior on the
137
- standard library with the included refinements, or even call a method to monkeypatch
146
+ standard library with the included refinements, or even call a method to monkeypatch
138
147
  everything everywhere.
139
148
 
140
149
  **Note:** `Pathname#chop_basename` in Ruby STDLIB has a bug with blank strings, that is the
@@ -154,6 +163,21 @@ require "faster_path/optional/monkeypatches"
154
163
  FasterPath.sledgehammer_everything!
155
164
  ```
156
165
 
166
+ ## Unstable optional bits
167
+
168
+ **Optional methods which ~~have regressions.~~ are unstable.** These will **not** be included by default in monkey-patches. Be cautious when using the `FasterPath::RefineFile` refinement. To try them anyways use the environment flag of `WITH_REGRESSION`. These methods are here to be improved upon.
169
+
170
+ |FasterPath Implementation|Ruby Implementation|
171
+ |---|---|
172
+ | `FasterPath.dirname` | `File.dirname` |
173
+ | `FasterPath.basename` | `File.basename` |
174
+ | `FasterPath.extname` | `File.extname` |
175
+
176
+ It's been my observation (and some others) that the Rust implementation of the C code for `File` has similar results but
177
+ performance seems to vary based on CPU cache on possibly 64bit/32bit system environmnets.
178
+
179
+ **Developers for FasterPath:** Most of these need to be rewritten, please use `WITH_REGRESSION` in your testing. You can see the resulting failures currently on TravisCI under "Allow Failures".
180
+
157
181
  ## Getting Started with Development
158
182
 
159
183
  The primary methods to target are mostly listed in the **Why** section above. You may find the Ruby
@@ -166,8 +190,46 @@ Methods will be written as exclusively in Rust as possible. Even just writing a
166
190
  Rust method like `!absolute?` _(not absolute)_ drops 39% of the performance already gained in Rust.
167
191
  Whenever feasible implement it in Rust.
168
192
 
169
- After checking out the repo, make sure you have Rust installed. Then run `bundle && rake build_lib` .
170
- Then, run `rake test` to run the tests, and `rake bench` for benchmarks.
193
+ After checking out the repo, make sure you have Rust installed, then run `bundle`.
194
+ Run `rake test` to run the tests, and `rake bench` for benchmarks.
195
+
196
+ Learn and share performance tips on the [wiki](https://github.com/danielpclark/faster_path/wiki)!
197
+
198
+ ### Building and running tests
199
+
200
+ First, bundle the gem's development dependencies by running `bundle`.
201
+
202
+ Then, build the rust code:
203
+
204
+ ```sh
205
+ rake build_src
206
+ ```
207
+
208
+ FasterPath is tested with [The Ruby Spec Suite](https://github.com/ruby/spec) to ensure it is compatible with the
209
+ native implementation, and also has its own test suite testing its monkey-patching and refinements functionality.
210
+
211
+ To run all the tests at once, simply run `rake`.
212
+ To run all the ruby spec tests, run `mspec`.
213
+
214
+ To run an individual test or benchmark from FasterPath's own suite:
215
+
216
+ ```sh
217
+ # An individual test file:
218
+ ruby -I lib:test test/benches/absolute_benchmark.rb
219
+ # All tests:
220
+ rake minitest
221
+ ```
222
+
223
+ To run an individual ruby spec test, run `mspec` with a path relative to `spec/ruby_spec`, e.g.:
224
+
225
+ ```sh
226
+ # A path to a file or a directory:
227
+ mspec core/file/basename_spec.rb
228
+ # Tests most relevant to FasterPath:
229
+ mspec core/file library/pathname
230
+ # All tests:
231
+ mspec
232
+ ```
171
233
 
172
234
  ## Contributing
173
235
 
data/Rakefile CHANGED
@@ -2,13 +2,13 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
  require 'fileutils'
4
4
 
5
- desc "Building extension..."
5
+ desc "Build Rust extension"
6
6
  task :build_src do
7
7
  puts "Building extension..."
8
8
  system("cargo build --release")
9
9
  end
10
10
 
11
- desc "Cleaning up build..."
11
+ desc "Clean up Rust build"
12
12
  task :clean_src do
13
13
  puts "Cleaning up build..."
14
14
  # Remove all but library file
@@ -22,20 +22,29 @@ task :clean_src do
22
22
  )
23
23
  end
24
24
 
25
- desc "Compiling Rust extension..."
26
- task :build_lib => [:build_src, :clean_src] do
25
+ desc "Build + clean up Rust extension"
26
+ task build_lib: [:build_src, :clean_src] do
27
27
  puts "Completed build!"
28
28
  end
29
29
 
30
- Rake::TestTask.new(:test) do |t|
30
+ Rake::TestTask.new(minitest: :build_lib) do |t|
31
31
  t.libs << "test"
32
32
  t.libs << "lib"
33
33
  t.test_files = FileList['test/**/*_test.rb']
34
34
  end
35
35
 
36
- Rake::TestTask.new(:bench) do |t|
36
+ task :test => :minitest do |t|
37
+ exec 'mspec --format spec core/file/basename core/file/extname core/file/dirname library/pathname'
38
+ end
39
+
40
+ Rake::TestTask.new(bench: :build_lib) do |t|
37
41
  t.libs = %w(lib test)
38
42
  t.pattern = 'test/**/*_benchmark.rb'
39
43
  end
40
44
 
41
- task :default => :test
45
+ Rake::TestTask.new(pbench: :build_lib) do |t|
46
+ t.libs = %w(lib test test/pbench)
47
+ t.pattern = 'test/pbench/pbench_suite.rb'
48
+ end
49
+
50
+ task default: :test
data/faster_path.gemspec CHANGED
@@ -13,17 +13,26 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/danielpclark/faster_path"
14
14
  spec.license = "MIT OR Apache-2.0"
15
15
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets|benches)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
16
+ spec.files = [
17
+ "Cargo.lock", "Cargo.toml", "Gemfile",
18
+ "MIT-LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup",
19
+ "ext/faster_path/extconf.rb", "faster_path.gemspec"
20
+ ]
21
+ spec.files += Dir['lib/**/*']
22
+ spec.files += Dir['src/**/*']
23
+
19
24
  spec.extensions << "ext/faster_path/extconf.rb"
20
25
  spec.require_paths = ["lib"]
21
26
 
27
+ spec.add_dependency "bundler", "~> 1.12"
28
+ spec.add_dependency "rake", "~> 12.0"
22
29
  spec.add_dependency "ffi", "~> 1.9"
23
- spec.add_development_dependency "bundler", "~> 1.12"
24
30
  spec.add_development_dependency "method_source", "~> 0.8.2"
25
- spec.add_development_dependency "rake", "~> 11.1"
26
- spec.add_development_dependency "minitest", "~> 5.8"
31
+ spec.add_development_dependency "minitest", "~> 5.10"
27
32
  spec.add_development_dependency "minitest-reporters", "~> 1.1"
28
33
  spec.add_development_dependency "color_pound_spec_reporter", "~> 0.0.5"
34
+ unless ENV['TRAVIS']
35
+ spec.add_development_dependency "stop_watch", "~> 0.1.0"
36
+ spec.add_development_dependency "gruff", "~> 0.7.0"
37
+ end
29
38
  end
@@ -1,10 +1,20 @@
1
+ require 'pathname'
2
+
1
3
  module FasterPath
2
4
  def self.sledgehammer_everything!
3
5
  ::File.class_eval do
4
- def basename(pth)
5
- FasterPath.basename(pth)
6
- end
7
- end
6
+ def self.basename(pth, ext = '')
7
+ FasterPath.basename(pth, ext)
8
+ end if ENV['WITH_REGRESSION']
9
+
10
+ def self.extname(pth)
11
+ FasterPath.extname(pth)
12
+ end if ENV['WITH_REGRESSION']
13
+
14
+ def self.dirname(pth)
15
+ FasterPath.dirname(pth)
16
+ end if ENV['WITH_REGRESSION']
17
+ end
8
18
 
9
19
  ::Pathname.class_eval do
10
20
  def absolute?
@@ -24,11 +34,20 @@ module FasterPath
24
34
  FasterPath.relative?(@path)
25
35
  end
26
36
 
27
- def add_trailing_separator(pth)
37
+ def add_trailing_separator(pth)
28
38
  FasterPath.add_trailing_separator(pth)
29
- end
30
- private :add_trailing_separator
39
+ end
40
+ private :add_trailing_separator
41
+
42
+ def has_trailing_separator?(pth)
43
+ FasterPath.has_trailing_separator?(pth)
44
+ end
45
+ private :has_trailing_separator?
46
+
47
+ def entries
48
+ FasterPath.entries(@path)
49
+ end if ENV['WITH_REGRESSION']
31
50
  end
51
+ "CAUTION: Monkey patching effects everything! Be very sure you want this!"
32
52
  end
33
53
  end
34
-
@@ -1,11 +1,21 @@
1
+ require 'pathname'
2
+
1
3
  module FasterPath
2
4
  module RefineFile
3
5
  refine File do
4
- def basename(pth)
5
- FasterPath.basename(pth)
6
+ def self.basename(pth, ext = '')
7
+ FasterPath.basename(pth, ext)
8
+ end
9
+
10
+ def self.extname(pth)
11
+ FasterPath.extname(pth)
12
+ end
13
+
14
+ def self.dirname(pth)
15
+ FasterPath.dirname(pth)
6
16
  end
7
17
  end
8
- end
18
+ end
9
19
 
10
20
  module RefinePathname
11
21
  refine Pathname do
@@ -30,6 +40,14 @@ module FasterPath
30
40
  FasterPath.add_trailing_separator(pth)
31
41
  end
32
42
  private :add_trailing_separator
43
+
44
+ def has_trailing_separator?(pth)
45
+ FasterPath.has_trailing_separator?(pth)
46
+ end
47
+
48
+ def entries
49
+ FasterPath.entries(@path)
50
+ end if ENV['WITH_REGRESSION']
33
51
  end
34
52
  end
35
53
  end
@@ -1,3 +1,3 @@
1
1
  module FasterPath
2
- VERSION = "0.1.8"
2
+ VERSION = "0.1.10"
3
3
  end
data/lib/faster_path.rb CHANGED
@@ -3,6 +3,14 @@ require 'pathname'
3
3
  require "ffi"
4
4
 
5
5
  module FasterPath
6
+ def self.rust_arch_bits
7
+ Rust.rust_arch_bits
8
+ end
9
+
10
+ def self.ruby_arch_bits
11
+ 1.size * 8
12
+ end
13
+
6
14
  # Spec to Pathname#absolute?
7
15
  def self.absolute?(pth)
8
16
  Rust.is_absolute(pth)
@@ -18,6 +26,10 @@ module FasterPath
18
26
  Rust.is_relative(pth)
19
27
  end
20
28
 
29
+ def self.dirname(pth)
30
+ Rust.dirname(pth)
31
+ end
32
+
21
33
  # Spec to Pathname#chop_basename
22
34
  # WARNING! Pathname#chop_basename in STDLIB doesn't handle blank strings correctly!
23
35
  # This implementation correctly handles blank strings just as Pathname had intended
@@ -33,12 +45,24 @@ module FasterPath
33
45
 
34
46
  def self.basename(pth, ext="")
35
47
  Rust.basename(pth, ext)
36
- end
48
+ end
37
49
 
38
50
  def self.add_trailing_separator(pth)
39
51
  Rust.add_trailing_separator(pth)
40
52
  end
41
53
 
54
+ def self.has_trailing_separator?(pth)
55
+ Rust.has_trailing_separator(pth)
56
+ end
57
+
58
+ def self.extname(pth)
59
+ Rust.extname(pth)
60
+ end
61
+
62
+ def self.entries(pth)
63
+ Array(Rust.entries(pth))
64
+ end
65
+
42
66
  # EXAMPLE
43
67
  #def self.one_and_two
44
68
  # Rust.one_and_two.to_a
@@ -61,6 +85,7 @@ module FasterPath
61
85
  end
62
86
  end
63
87
 
88
+ attach_function :rust_arch_bits, [], :int32
64
89
  attach_function :is_absolute, [ :string ], :bool
65
90
  attach_function :is_directory, [ :string ], :bool
66
91
  attach_function :is_relative, [ :string ], :bool
@@ -71,6 +96,9 @@ module FasterPath
71
96
  attach_function :basename_for_chop, [ :string ], :string # decoupling behavior
72
97
  attach_function :dirname_for_chop, [ :string ], :string # decoupling behavior
73
98
  attach_function :add_trailing_separator, [ :string ], :string
99
+ attach_function :has_trailing_separator, [ :string ], :bool
100
+ attach_function :extname, [ :string ], :string
101
+ attach_function :entries, [ :string ], FromRustArray.by_value
74
102
 
75
103
  # EXAMPLE
76
104
  #attach_function :one_and_two, [], FromRustArray.by_value
data/src/basename.rs CHANGED
@@ -1,86 +1,26 @@
1
- use std::path::MAIN_SEPARATOR;
2
1
  use libc::c_char;
3
- use std::ffi::{CStr,CString};
4
- use std::str;
2
+ use std::ffi::{CStr, CString};
3
+ use path_parsing::extract_last_path_segment;
5
4
 
6
- #[allow(dead_code)]
7
- fn rubyish_basename(string: &str, globish_string: &str) -> String {
8
- let result = if globish_string == ".*" {
9
- let base = string.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or("");
10
- let index = base.rfind(".");
11
- let (first, _) = base.split_at(index.unwrap());
12
- first
13
- } else {
14
- if &string[string.len()-globish_string.len()..] == globish_string {
15
- &string[0..string.len()-globish_string.len()]
16
- } else {
17
- string
18
- }.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or("")
19
- };
20
- result.to_string()
21
- }
5
+ #[no_mangle]
6
+ pub extern "C" fn basename(c_pth: *const c_char, c_ext: *const c_char) -> *const c_char {
7
+ // TODO: rb_raise on type or encoding errors
8
+ // TODO: support objects that respond to `to_path`
9
+ if c_pth.is_null() || c_ext.is_null() {
10
+ return c_pth;
11
+ }
12
+ let pth = unsafe { CStr::from_ptr(c_pth) }.to_str().unwrap();
13
+ let ext = unsafe { CStr::from_ptr(c_ext) }.to_str().unwrap();
22
14
 
23
- #[test]
24
- fn it_chomps_strings_correctly(){
25
- assert_eq!(rubyish_basename("","") , "");
26
- assert_eq!(rubyish_basename("ruby","") , "ruby");
27
- assert_eq!(rubyish_basename("ruby.rb",".rb") , "ruby");
28
- assert_eq!(rubyish_basename("ruby.rb",".*") , "ruby");
29
- assert_eq!(rubyish_basename(".ruby/ruby.rb",".rb") , "ruby");
30
- assert_eq!(rubyish_basename(".ruby/ruby.rb.swp",".rb") , "ruby.rb.swp");
31
- assert_eq!(rubyish_basename(".ruby/ruby.rb.swp",".swp") , "ruby.rb");
32
- assert_eq!(rubyish_basename(".ruby/ruby.rb.swp",".*") , "ruby.rb");
33
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp","") , "asdf.rb.swp");
34
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb");
35
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", "*") , "asdf.rb.swp");
36
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".") , "asdf.rb.swp");
37
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb");
38
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb") , "asdf.rb.swp");
39
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".swp") , "asdf.rb");
40
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".sw") , "asdf.rb.swp");
41
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".sw*") , "asdf.rb.swp");
42
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb.s*") , "asdf.rb.swp");
43
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".s*") , "asdf.rb.swp");
44
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".s**") , "asdf.rb.swp");
45
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".**") , "asdf.rb.swp");
46
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb");
47
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*.*") , "asdf.rb.swp");
48
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb.swp") , "asdf");
49
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb.s*p") , "asdf.rb.swp");
50
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".r*.s*p") , "asdf.rb.swp");
51
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".r*.sw*p") , "asdf.rb.swp");
52
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".r*b.sw*p") , "asdf.rb.swp");
53
- assert_eq!(rubyish_basename("asdf/asdf.rb.swp", "rb.swp") , "asdf.");
54
- }
15
+ let mut name = extract_last_path_segment(pth);
55
16
 
56
- #[no_mangle]
57
- pub extern fn basename(str_pth: *const c_char, comp_ext: *const c_char) -> *const c_char {
58
- let c_str1 = unsafe {
59
- if str_pth.is_null(){
60
- return str_pth;
61
- }
62
- CStr::from_ptr(str_pth)
63
- };
64
- let c_str2 = unsafe {
65
- if comp_ext.is_null() {
66
- return str_pth;
17
+ if ext == ".*" {
18
+ if let Some(dot_i) = name.rfind('.') {
19
+ name = &name[0..dot_i];
67
20
  }
68
- CStr::from_ptr(comp_ext)
21
+ } else if name.ends_with(ext) {
22
+ name = &name[..name.len() - ext.len()];
69
23
  };
70
- let string = str::from_utf8(c_str1.to_bytes()).unwrap();
71
- let globish_string = str::from_utf8(c_str2.to_bytes()).unwrap();
72
24
 
73
- let result = if globish_string == ".*" {
74
- let base = string.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or("");
75
- let index = base.rfind(".");
76
- let (first, _) = base.split_at(index.unwrap());
77
- first
78
- } else {
79
- if &string[string.len()-globish_string.len()..] == globish_string {
80
- &string[0..string.len()-globish_string.len()]
81
- } else {
82
- string
83
- }.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or("")
84
- };
85
- CString::new(result).unwrap().into_raw()
25
+ CString::new(name).unwrap().into_raw()
86
26
  }
data/src/dirname.rs CHANGED
@@ -1,30 +1,32 @@
1
- use std::path::{Path,MAIN_SEPARATOR};
1
+ use std::path::MAIN_SEPARATOR;
2
2
  use libc::c_char;
3
- use std::ffi::{CStr,CString};
4
- use std::str;
3
+ use std::ffi::{CStr, CString};
4
+ use path_parsing::{last_sep_i, last_non_sep_i, last_non_sep_i_before};
5
5
 
6
6
  #[no_mangle]
7
- pub extern fn dirname(string: *const c_char) -> *const c_char {
8
- let c_str = unsafe {
9
- assert!(!string.is_null());
10
-
11
- CStr::from_ptr(string)
12
- };
13
-
14
- let r_str = str::from_utf8(c_str.to_bytes()).unwrap();
15
-
7
+ pub extern "C" fn dirname(path: *const c_char) -> *const c_char {
8
+ if path.is_null() {
9
+ return path
10
+ }
11
+ let r_str = unsafe { CStr::from_ptr(path) }.to_str().unwrap();
16
12
  if r_str.is_empty() {
17
- return string
13
+ return CString::new(".").unwrap().into_raw();
18
14
  }
19
-
20
- let path = Path::new(r_str).parent().unwrap_or(Path::new(""));
21
-
22
- let out_str = if !path.to_str().unwrap().is_empty() {
23
- format!("{}{}", path.to_str().unwrap(), MAIN_SEPARATOR)
15
+ let non_sep_i = last_non_sep_i(r_str);
16
+ if non_sep_i == -1 {
17
+ return CString::new(MAIN_SEPARATOR.to_string()).unwrap().into_raw();
18
+ }
19
+ let sep_i = last_sep_i(r_str, non_sep_i);
20
+ if sep_i == -1 {
21
+ return CString::new(".").unwrap().into_raw();
22
+ }
23
+ if sep_i == 0 {
24
+ return CString::new(MAIN_SEPARATOR.to_string()).unwrap().into_raw();
25
+ }
26
+ let non_sep_i2 = last_non_sep_i_before(r_str, sep_i);
27
+ if non_sep_i2 != -1 {
28
+ return CString::new(&r_str[..(non_sep_i2 + 1) as usize]).unwrap().into_raw();
24
29
  } else {
25
- format!("{}", path.to_str().unwrap())
26
- };
27
-
28
- let output = CString::new(out_str).unwrap();
29
- output.into_raw()
30
+ return CString::new(MAIN_SEPARATOR.to_string()).unwrap().into_raw();
31
+ }
30
32
  }
data/src/entries.rs ADDED
@@ -0,0 +1,30 @@
1
+ use libc::c_char;
2
+ use std::ffi::{CStr,CString};
3
+ use std::str;
4
+ use std::fs;
5
+ use ruby_array::RubyArray;
6
+
7
+ #[no_mangle]
8
+ pub extern fn entries(string: *const c_char) -> RubyArray {
9
+ let c_str = unsafe {
10
+ assert!(!string.is_null());
11
+
12
+ CStr::from_ptr(string)
13
+ };
14
+
15
+ let r_str = str::from_utf8(c_str.to_bytes()).unwrap();
16
+
17
+ let files = fs::read_dir(r_str).unwrap();
18
+ let mut files_vec = vec![];
19
+
20
+ files_vec.push(CString::new(".").unwrap().into_raw());
21
+ files_vec.push(CString::new("..").unwrap().into_raw());
22
+
23
+ for file in files {
24
+ let file_name_str = file.unwrap().file_name().into_string().unwrap();
25
+ let file_name_cstr = CString::new(file_name_str).unwrap().into_raw();
26
+ files_vec.push(file_name_cstr);
27
+ }
28
+
29
+ RubyArray::from_vec(files_vec)
30
+ }
data/src/extname.rs ADDED
@@ -0,0 +1,20 @@
1
+ use libc::c_char;
2
+ use std::ffi::{CStr, CString};
3
+ use path_parsing::extract_last_path_segment;
4
+
5
+ #[no_mangle]
6
+ pub extern "C" fn extname(c_pth: *const c_char) -> *const c_char {
7
+ if c_pth.is_null() {
8
+ return c_pth
9
+ }
10
+
11
+ let name = extract_last_path_segment(unsafe { CStr::from_ptr(c_pth) }.to_str().unwrap());
12
+
13
+ if let Some(dot_i) = name.rfind('.') {
14
+ if dot_i > 0 && dot_i < name.len() - 1 && name[..dot_i].chars().rev().next().unwrap() != '.' {
15
+ return CString::new(&name[dot_i..]).unwrap().into_raw()
16
+ }
17
+ }
18
+
19
+ CString::new("").unwrap().into_raw()
20
+ }
@@ -0,0 +1,27 @@
1
+ use std::path::{Path, MAIN_SEPARATOR};
2
+ use libc::c_char;
3
+ use std::ffi::CStr;
4
+ use std::str;
5
+
6
+ #[no_mangle]
7
+ pub extern "C" fn has_trailing_separator(string: *const c_char) -> bool {
8
+ let c_str = unsafe {
9
+ if string.is_null() {
10
+ return false
11
+ }
12
+ CStr::from_ptr(string)
13
+ };
14
+
15
+ let r_str = str::from_utf8(c_str.to_bytes()).unwrap_or("");
16
+ let path = Path::new(r_str);
17
+ let last_component = path.iter().last();
18
+ if last_component.is_none() {
19
+ false
20
+ } else {
21
+ let mut parts: Vec<&str> = r_str.rsplit_terminator(MAIN_SEPARATOR).collect();
22
+ parts.retain(|x| !x.is_empty());
23
+ let last_part = parts.first().unwrap_or(&"").chars().last().unwrap_or(MAIN_SEPARATOR);
24
+ let last_char = r_str.chars().last().unwrap();
25
+ (last_part != last_char) && (last_char == MAIN_SEPARATOR)
26
+ }
27
+ }
data/src/is_absolute.rs CHANGED
@@ -1,19 +1,17 @@
1
1
  use libc::c_char;
2
2
  use std::ffi::{CStr};
3
- use std::str;
4
3
  use std::path::MAIN_SEPARATOR;
5
4
 
6
5
 
7
6
  #[no_mangle]
8
- pub extern fn is_absolute(string: *const c_char) -> bool {
9
- let c_str = unsafe {
10
- if string.is_null() {
11
- return false;
12
- }
13
- CStr::from_ptr(string)
14
- };
7
+ pub extern fn is_absolute(path: *const c_char) -> bool {
8
+ if path.is_null() {
9
+ return false;
10
+ }
11
+ let r_str = unsafe { CStr::from_ptr(path) }.to_str().unwrap();
15
12
 
16
- let r_str = str::from_utf8(c_str.to_bytes()).unwrap_or("");
17
-
18
- r_str.chars().next().unwrap_or("muffins".chars().next().unwrap()) == MAIN_SEPARATOR
13
+ match r_str.chars().next() {
14
+ Some(c) => c == MAIN_SEPARATOR,
15
+ None => false
16
+ }
19
17
  }
data/src/is_relative.rs CHANGED
@@ -1,18 +1,16 @@
1
1
  use std::path::MAIN_SEPARATOR;
2
2
  use libc::c_char;
3
3
  use std::ffi::{CStr};
4
- use std::str;
5
4
 
6
5
  #[no_mangle]
7
- pub extern fn is_relative(string: *const c_char) -> bool {
8
- let c_str = unsafe {
9
- if string.is_null() {
10
- return false;
11
- }
12
- CStr::from_ptr(string)
13
- };
6
+ pub extern fn is_relative(path: *const c_char) -> bool {
7
+ if path.is_null() {
8
+ return false;
9
+ }
10
+ let r_str = unsafe { CStr::from_ptr(path) }.to_str().unwrap();
14
11
 
15
- let r_str = str::from_utf8(c_str.to_bytes()).unwrap_or("");
16
-
17
- r_str.chars().next().unwrap_or("muffins".chars().next().unwrap()) != MAIN_SEPARATOR
12
+ match r_str.chars().next() {
13
+ Some(c) => c != MAIN_SEPARATOR,
14
+ None => true
15
+ }
18
16
  }
data/src/lib.rs CHANGED
@@ -18,6 +18,11 @@ pub mod dirname;
18
18
  pub mod basename_for_chop;
19
19
  pub mod dirname_for_chop;
20
20
  pub mod add_trailing_separator;
21
+ pub mod has_trailing_separator;
22
+ pub mod extname;
23
+ pub mod entries;
24
+ pub mod rust_arch_bits;
25
+ mod path_parsing;
21
26
 
22
27
  // EXAMPLE
23
28
  //
@@ -0,0 +1,55 @@
1
+ use std::path::MAIN_SEPARATOR;
2
+
3
+ static SEP: u8 = MAIN_SEPARATOR as u8;
4
+
5
+ pub fn extract_last_path_segment(path: &str) -> &str {
6
+ // Works with bytes directly because MAIN_SEPARATOR is always in the ASCII 7-bit range so we can
7
+ // avoid the overhead of full UTF-8 processing.
8
+ // See src/benches/path_parsing.rs for benchmarks of different approaches.
9
+ let ptr = path.as_ptr();
10
+ let mut i = path.len() as isize - 1;
11
+ while i >= 0 {
12
+ let c = unsafe { *ptr.offset(i) };
13
+ if c != SEP { break; };
14
+ i -= 1;
15
+ }
16
+ let end = (i + 1) as usize;
17
+ while i >= 0 {
18
+ let c = unsafe { *ptr.offset(i) };
19
+ if c == SEP {
20
+ return &path[(i + 1) as usize..end];
21
+ };
22
+ i -= 1;
23
+ }
24
+ &path[..end]
25
+ }
26
+
27
+
28
+ // Returns the byte offset of the last byte preceding a MAIN_SEPARATOR.
29
+ pub fn last_non_sep_i(path: &str) -> isize {
30
+ last_non_sep_i_before(path, path.len() as isize - 1)
31
+ }
32
+
33
+ // Returns the byte offset of the last byte preceding a MAIN_SEPARATOR before the given end offset.
34
+ pub fn last_non_sep_i_before(path: &str, end: isize) -> isize {
35
+ let ptr = path.as_ptr();
36
+ let mut i = end;
37
+ while i >= 0 {
38
+ if unsafe { *ptr.offset(i) } != SEP { break; };
39
+ i -= 1;
40
+ }
41
+ i
42
+ }
43
+
44
+ // Returns the byte offset of the last MAIN_SEPARATOR before the given end offset.
45
+ pub fn last_sep_i(path: &str, end: isize) -> isize {
46
+ let ptr = path.as_ptr();
47
+ let mut i = end - 1;
48
+ while i >= 0 {
49
+ if unsafe { *ptr.offset(i) } == SEP {
50
+ return i;
51
+ };
52
+ i -= 1;
53
+ }
54
+ -1
55
+ }
data/src/ruby_array.rs CHANGED
@@ -9,10 +9,10 @@ pub struct RubyArray {
9
9
 
10
10
  impl RubyArray {
11
11
  #[allow(dead_code)]
12
- fn from_vec<T>(vec: Vec<T>) -> RubyArray {
13
- let array = RubyArray {
14
- data: vec.as_ptr() as *const libc::c_void,
15
- len: vec.len() as libc::size_t
12
+ pub fn from_vec<T>(vec: Vec<T>) -> RubyArray {
13
+ let array = RubyArray {
14
+ data: vec.as_ptr() as *const libc::c_void,
15
+ len: vec.len() as libc::size_t
16
16
  };
17
17
  mem::forget(vec);
18
18
  array
@@ -0,0 +1,11 @@
1
+ #[no_mangle]
2
+ pub extern fn rust_arch_bits() -> i32 {
3
+ use std::mem::size_of;
4
+ let s = size_of::<usize>() * 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
+ }
metadata CHANGED
@@ -1,85 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faster_path
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel P. Clark
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-20 00:00:00.000000000 Z
11
+ date: 2017-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ffi
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.9'
19
+ version: '1.12'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.9'
26
+ version: '1.12'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.12'
34
- type: :development
33
+ version: '12.0'
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.12'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: method_source
42
+ name: ffi
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.8.2
48
- type: :development
47
+ version: '1.9'
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.8.2
54
+ version: '1.9'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rake
56
+ name: method_source
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '11.1'
61
+ version: 0.8.2
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '11.1'
68
+ version: 0.8.2
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.8'
75
+ version: '5.10'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '5.8'
82
+ version: '5.10'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: minitest-reporters
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,34 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: 0.0.5
111
+ - !ruby/object:Gem::Dependency
112
+ name: stop_watch
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.1.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.1.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: gruff
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.7.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.7.0
111
139
  description: FasterPath is a reimplementation of Pathname for better performance.
112
140
  email:
113
141
  - 6ftdan@gmail.com
@@ -116,8 +144,6 @@ extensions:
116
144
  - ext/faster_path/extconf.rb
117
145
  extra_rdoc_files: []
118
146
  files:
119
- - ".gitignore"
120
- - ".travis.yml"
121
147
  - Cargo.lock
122
148
  - Cargo.toml
123
149
  - Gemfile
@@ -138,13 +164,18 @@ files:
138
164
  - src/both_are_blank.rs
139
165
  - src/dirname.rs
140
166
  - src/dirname_for_chop.rs
167
+ - src/entries.rs
168
+ - src/extname.rs
169
+ - src/has_trailing_separator.rs
141
170
  - src/is_absolute.rs
142
171
  - src/is_blank.rs
143
172
  - src/is_directory.rs
144
173
  - src/is_relative.rs
145
174
  - src/lib.rs
175
+ - src/path_parsing.rs
146
176
  - src/ruby_array.rs
147
177
  - src/ruby_string.rs
178
+ - src/rust_arch_bits.rs
148
179
  homepage: https://github.com/danielpclark/faster_path
149
180
  licenses:
150
181
  - MIT OR Apache-2.0
@@ -165,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
196
  version: '0'
166
197
  requirements: []
167
198
  rubyforge_project:
168
- rubygems_version: 2.6.4
199
+ rubygems_version: 2.6.10
169
200
  signing_key:
170
201
  specification_version: 4
171
202
  summary: Reimplementation of Pathname for better performance
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /target/
3
- /.yardoc
4
- /Gemfile.lock
5
- /_yardoc/
6
- /coverage/
7
- /doc/
8
- /pkg/
9
- /spec/reports/
10
- /tmp/
11
- mkmf.log
12
- **/*.swp
13
- **/*.gem
data/.travis.yml DELETED
@@ -1,13 +0,0 @@
1
- sudo: true
2
- language: ruby
3
- rvm:
4
- - 2.2.5
5
- - 2.3.1
6
- env:
7
- - TEST_MONKEYPATCHES=true
8
- - TEST_MONKEYPATCHES=false
9
- before_install:
10
- - gem install bundler -v 1.12.5
11
- - curl -sSf https://static.rust-lang.org/rustup.sh | sudo sh -s -- --channel=nightly
12
- - bundle && bundle exec rake build_src && rake clean_src
13
- script: bundle exec rake test