diff --git a/.gitignore b/.gitignore index 4e0819e..a2fe427 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ mkmf.log **/*.swp **/*.gem +/rubyspec_temp/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..072bbc6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spec/ruby_spec"] + path = spec/ruby_spec + url = https://github.com/ruby/spec diff --git a/Gemfile b/Gemfile index b806447..a9dfb0a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,6 @@ source 'https://rubygems.org' # Specify your gem's dependencies in faster_path.gemspec gemspec + +# https://github.com/ruby/spec dependencies +eval_gemfile File.expand_path('spec/ruby_spec/Gemfile', File.dirname(__FILE__)) diff --git a/README.md b/README.md index f492bb9..3be48dd 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ And then execute: Or install it yourself as: $ gem install faster_path - + **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. ## Usage @@ -134,7 +134,7 @@ Current methods implemented: | `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 46.8% | You may choose to use the methods directly, or scope change to rewrite behavior on the -standard library with the included refinements, or even call a method to monkeypatch +standard library with the included refinements, or even call a method to monkeypatch everything everywhere. **Note:** `Pathname#chop_basename` in Ruby STDLIB has a bug with blank strings, that is the @@ -169,6 +169,42 @@ Whenever feasible implement it in Rust. After checking out the repo, make sure you have Rust installed. Then run `bundle && rake build_lib` . Then, run `rake test` to run the tests, and `rake bench` for benchmarks. +### Building and running tests + +First, bundle the gem's development dependencies by running `bundle`. + +Then, build the rust code: + +```sh +rake build_src +``` + +FasterPath is tested with [The Ruby Spec Suite](https://github.com/ruby/spec) to ensure it is compatible with the +native implementation, and also has its own test suite testing its monkey-patching and refinements functionality. + +To run all the tests at once, simply run `rake`. +To run all the ruby spec tests, run `mspec`. + +To run an individual test or benchmark from FasterPath's own suite: + +```sh +# An individual test file: +ruby -I lib:test test/benches/absolute_benchmark.rb +# All tests: +rake minitest +``` + +To run an individual ruby spec test, run `mspec` with a path relative to `spec/ruby_spec`, e.g.: + +```sh +# A path to a file or a directory: +mspec core/file/basename_spec.rb +# Tests most relevant to FasterPath: +mspec core/file library/pathname +# All tests: +mspec +``` + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/danielpclark/faster_path. diff --git a/Rakefile b/Rakefile index 304c1a1..d6cafce 100644 --- a/Rakefile +++ b/Rakefile @@ -27,12 +27,16 @@ task :build_lib => [:build_src, :clean_src] do puts "Completed build!" end -Rake::TestTask.new(:test) do |t| +Rake::TestTask.new(:minitest) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList['test/**/*_test.rb'] end +task :test => :minitest do |t| + exec 'mspec core/file library/pathname' +end + Rake::TestTask.new(:bench) do |t| t.libs = %w(lib test) t.pattern = 'test/**/*_benchmark.rb' diff --git a/lib/faster_path/optional/monkeypatches.rb b/lib/faster_path/optional/monkeypatches.rb index 58373fa..a210ec7 100644 --- a/lib/faster_path/optional/monkeypatches.rb +++ b/lib/faster_path/optional/monkeypatches.rb @@ -1,10 +1,12 @@ +require 'pathname' + module FasterPath def self.sledgehammer_everything! - ::File.class_eval do - def basename(pth) - FasterPath.basename(pth) - end - end + ::File.module_eval do + def self.basename(pth, ext = '') + FasterPath.basename(pth, ext) + end + end ::Pathname.class_eval do def absolute? diff --git a/lib/faster_path/optional/refinements.rb b/lib/faster_path/optional/refinements.rb index d9056a3..a4bb3af 100644 --- a/lib/faster_path/optional/refinements.rb +++ b/lib/faster_path/optional/refinements.rb @@ -1,11 +1,11 @@ module FasterPath module RefineFile refine File do - def basename(pth) - FasterPath.basename(pth) + def self.basename(pth, ext = '') + FasterPath.basename(pth, ext) end end - end + end module RefinePathname refine Pathname do diff --git a/spec/default.mspec b/spec/default.mspec new file mode 100644 index 0000000..b8e3c6d --- /dev/null +++ b/spec/default.mspec @@ -0,0 +1,8 @@ +load 'spec/ruby_spec/default.mspec' + +class MSpecScript + set :requires, ["-r#{File.expand_path('init', File.dirname(__FILE__))}"] + set :prefix, 'spec/ruby_spec' +end + +MSpec.disable_feature :encoding diff --git a/spec/init.rb b/spec/init.rb new file mode 100644 index 0000000..afe0b11 --- /dev/null +++ b/spec/init.rb @@ -0,0 +1,5 @@ +ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__)) +$LOAD_PATH.unshift File.expand_path('../lib', File.dirname(__FILE__)) +require 'faster_path' +require 'faster_path/optional/monkeypatches' +FasterPath.sledgehammer_everything! diff --git a/spec/ruby_spec b/spec/ruby_spec new file mode 160000 index 0000000..8190a7c --- /dev/null +++ b/spec/ruby_spec @@ -0,0 +1 @@ +Subproject commit 8190a7c915fbcf4776b99390418fe63fc6086c02 diff --git a/src/basename.rs b/src/basename.rs index 7d69f89..8685219 100644 --- a/src/basename.rs +++ b/src/basename.rs @@ -30,15 +30,15 @@ fn it_chomps_strings_correctly(){ assert_eq!(rubyish_basename(".ruby/ruby.rb.swp",".rb") , "ruby.rb.swp"); assert_eq!(rubyish_basename(".ruby/ruby.rb.swp",".swp") , "ruby.rb"); assert_eq!(rubyish_basename(".ruby/ruby.rb.swp",".*") , "ruby.rb"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp","") , "asdf.rb.swp"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", "*") , "asdf.rb.swp"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".") , "asdf.rb.swp"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb") , "asdf.rb.swp"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".swp") , "asdf.rb"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".sw") , "asdf.rb.swp"); - assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".sw*") , "asdf.rb.swp"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp","") , "asdf.rb.swp"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", "*") , "asdf.rb.swp"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".") , "asdf.rb.swp"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".*") , "asdf.rb"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb") , "asdf.rb.swp"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".swp") , "asdf.rb"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".sw") , "asdf.rb.swp"); + assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".sw*") , "asdf.rb.swp"); assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".rb.s*") , "asdf.rb.swp"); assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".s*") , "asdf.rb.swp"); assert_eq!(rubyish_basename("asdf/asdf.rb.swp", ".s**") , "asdf.rb.swp"); @@ -55,32 +55,42 @@ fn it_chomps_strings_correctly(){ #[no_mangle] pub extern fn basename(str_pth: *const c_char, comp_ext: *const c_char) -> *const c_char { + if str_pth.is_null() || comp_ext.is_null() { + return str_pth; + } let c_str1 = unsafe { - if str_pth.is_null(){ - return str_pth; - } CStr::from_ptr(str_pth) }; let c_str2 = unsafe { - if comp_ext.is_null() { - return str_pth; - } CStr::from_ptr(comp_ext) }; + // TODO: rb_raise on utf-8 errors let string = str::from_utf8(c_str1.to_bytes()).unwrap(); let globish_string = str::from_utf8(c_str2.to_bytes()).unwrap(); - let result = if globish_string == ".*" { - let base = string.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or(""); - let index = base.rfind("."); - let (first, _) = base.split_at(index.unwrap()); - first + let result = if string.is_empty() { + string + } else if globish_string == ".*" { + let base = extract_last_segment(string); + match base.rfind(".") { + Some(index) => &base[0..index], + _ => base, + } } else { - if &string[string.len()-globish_string.len()..] == globish_string { - &string[0..string.len()-globish_string.len()] + extract_last_segment(if &string[string.len() - globish_string.len()..] == globish_string { + &string[0..string.len() - globish_string.len()] } else { string - }.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or("") + }) }; CString::new(result).unwrap().into_raw() } + +fn extract_last_segment(str: &str) -> &str { + let trimmed_str = str.trim_matches(MAIN_SEPARATOR); + if trimmed_str.is_empty() { + "/" + } else { + trimmed_str.rsplit_terminator(MAIN_SEPARATOR).nth(0).unwrap_or("") + } +}