pax_global_header00006660000000000000000000000064137747512000014520gustar00rootroot0000000000000052 comment=32f017b976671758224b63ce76e36e0c68a813bf pathspec-ruby-1.0.0/000077500000000000000000000000001377475120000143045ustar00rootroot00000000000000pathspec-ruby-1.0.0/.gitignore000066400000000000000000000012741377475120000163000ustar00rootroot00000000000000*.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /test/tmp/ /test/version_tmp/ /tmp/ ## Specific to RubyMotion: .dat* .repl_history build/ ## Documentation cache and generated files: /.yardoc/ /_yardoc/ /doc/ /rdoc/ /docs/man/*.1 ## Environment normalisation: /.bundle/ /lib/bundler/man/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: Gemfile.lock .ruby-version .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc # OS generated files # ###################### .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db pathspec-ruby-1.0.0/.rubocop.yml000066400000000000000000000002631377475120000165570ustar00rootroot00000000000000inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.6 NewCops: enable Style/NumericPredicate: Enabled: false Layout/ClosingHeredocIndentation: Enabled: false pathspec-ruby-1.0.0/.rubocop_todo.yml000066400000000000000000000067341377475120000176150ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2018-01-11 16:42:16 -0800 using RuboCop version 0.52.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 2 # Cop supports --auto-correct. Layout/BlockEndNewline: Exclude: - 'spec/unit/pathspec_spec.rb' # Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent Layout/HeredocIndentation: Exclude: - 'spec/unit/pathspec_spec.rb' # Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: - 'lib/pathspec.rb' - 'spec/unit/pathspec_spec.rb' # Offense count: 2 Lint/ImplicitStringConcatenation: Exclude: - 'lib/pathspec/gitignorespec.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. Lint/UnusedMethodArgument: Exclude: - 'lib/pathspec/spec.rb' # Offense count: 3 Lint/UselessAssignment: Exclude: - 'spec/unit/pathspec_spec.rb' # Offense count: 2 Lint/Void: Exclude: - 'lib/pathspec.rb' - 'lib/pathspec/gitignorespec.rb' # Offense count: 3 Metrics/AbcSize: Enabled: false Max: 62 # Offense count: 7 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: Enabled: false Max: 270 # Offense count: 2 # Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 4 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 146 # Offense count: 2 Metrics/CyclomaticComplexity: Max: 26 # Offense count: 3 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 77 # Offense count: 2 Metrics/PerceivedComplexity: Max: 32 # Offense count: 25 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object # FunctionalMethods: let, let!, subject, watch # IgnoredMethods: lambda, proc, it Style/BlockDelimiters: Exclude: - 'spec/unit/pathspec_spec.rb' # Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: when_needed, always, never Style/FrozenStringLiteralComment: Exclude: - 'Gemfile' - 'Rakefile' - 'lib/pathspec.rb' - 'lib/pathspec/gitignorespec.rb' - 'lib/pathspec/regexspec.rb' - 'lib/pathspec/spec.rb' - 'pathspec.gemspec' - 'spec/spec_helper.rb' - 'spec/unit/pathspec/gitignorespec_spec.rb' - 'spec/unit/pathspec/spec_spec.rb' - 'spec/unit/pathspec_spec.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Exclude: - 'lib/pathspec.rb' # Offense count: 7 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Layout/LineLength: Max: 108 pathspec-ruby-1.0.0/.tool-versions000066400000000000000000000000131377475120000171220ustar00rootroot00000000000000ruby 2.7.2 pathspec-ruby-1.0.0/.travis.yml000066400000000000000000000001651377475120000164170ustar00rootroot00000000000000language: ruby before_install: - gem install bundler script: 'bundle exec rake' rvm: - 2.6.5 - 2.7.2 - 3.0.0 pathspec-ruby-1.0.0/CHANGELOG.md000066400000000000000000000023051377475120000161150ustar00rootroot00000000000000# pathspec-ruby CHANGELOG ## 0.2.0 (Minor Release) - (Feature) A CLI tool, pathspec-rb, is now provided with the gem. - (API Change) New namespace for gem: `PathSpec`: Everything is now namespaced under `PathSpec`, to prevent naming collisions with other libraries. Thanks @tenderlove! - (License) License version updated to Apache 2. Thanks @kytrinyx! - (Maint) Pruned Supported Ruby Versions. We now test: 2.2.9, 2.3.6 and 2.4.3. - (Maint) Ruby 2.5.0 testing is blocked on Travis, but should work locally. Thanks @SumLare! - (Maint) Added Rubocop and made some corrections ## 0.1.2 (Patch/Bug Fix Release) - Fix for regexp matching Thanks @incase! #16 - File handling cleanup Thanks @martinandert! #13 - `from_filename` actually works now! Thanks @martinandert! #12 ## 0.1.0 (Minor Release) - Port new edgecase handling from [python-path-specification](https://github.com/cpburnz/python-path-specification/pull/8). Many thanks to @jdpace! :) - Removed EOL Ruby support - Added current Ruby stable to Travis testing ## 0.0.2 (Patch/Bug Fix Release) - Fixed issues with Ruby 1.8.7/2.1.1 - Added more testing scripts - Fixed Windows path related issues - Cleanup unnecessary things in gem ## 0.0.1 - Initial version. pathspec-ruby-1.0.0/Gemfile000066400000000000000000000000471377475120000156000ustar00rootroot00000000000000source 'https://rubygems.org' gemspec pathspec-ruby-1.0.0/LICENSE000066400000000000000000000260731377475120000153210ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.pathspec-ruby-1.0.0/README.md000066400000000000000000000054541377475120000155730ustar00rootroot00000000000000# pathspec-ruby [![Gem Version](https://badge.fury.io/rb/pathspec.svg)](https://badge.fury.io/rb/pathspec) [![Build Status](https://travis-ci.org/highb/pathspec-ruby.svg?branch=master)](https://travis-ci.org/highb/pathspec-ruby) [![Maintainability](https://api.codeclimate.com/v1/badges/4f3b5917e01fb34f790d/maintainability)](https://codeclimate.com/github/highb/pathspec-ruby/maintainability) [Supported Rubies](https://www.ruby-lang.org/en/downloads/): - 2.4.6 (Maintenance) - 2.5.6 (Stable) - 2.6.4 (Stable) Match Path Specifications, such as .gitignore, in Ruby! Follows .gitignore syntax defined on [gitscm](http://git-scm.com/docs/gitignore) .gitignore functionality ported from [Python pathspec](https://pypi.python.org/pypi/pathspec/0.2.2) by [@cpburnz](https://github.com/cpburnz/python-path-specification) ## Build/Install from Rubygems ```shell gem install pathspec ``` ## CLI Usage ```bash ➜ test-pathspec cat .gitignore *.swp /coverage/ ➜ test-pathspec be pathspec-rb specs_match "coverage/foo" /coverage/ ➜ test-pathspec be pathspec-rb specs_match "file.swp" *.swp ➜ test-pathspec be pathspec-rb match "file.swp" ➜ test-pathspec echo $? 0 ➜ test-pathspec ls Gemfile Gemfile.lock coverage file.swp source.rb ➜ test-pathspec be pathspec-rb tree . ./coverage ./coverage/index.html ./file.swp ``` ## Usage ```ruby require 'pathspec' # Create a .gitignore-style Pathspec by giving it newline separated gitignore # lines, an array of gitignore lines, or any other enumable object that will # give strings matching the .gitignore-style (File, etc.) gitignore = Pathspec.from_filename('spec/files/gitignore_readme') # Our .gitignore in this example contains: # !**/important.txt # abc/** # true, matches "abc/**" gitignore.match 'abc/def.rb' # CLI equivalent: pathspec.rb -f spec/files/gitignore_readme match 'abc/def.rb' # false, because it has been negated using the line "!**/important.txt" gitignore.match 'abc/important.txt' # CLI equivalent: pathspec.rb -f spec/files/gitignore_readme match 'abc/important.txt' # Give a path somewhere in the filesystem, and the Pathspec will return all # matching files underneath. # Returns ['/src/repo/abc/', '/src/repo/abc/123'] gitignore.match_tree '/src/repo' # CLI equivalent: pathspec.rb -f spec/files/gitignore_readme tree /src/repo # Give an enumerable of paths, and Pathspec will return the ones that match. # Returns ['/abc/123', '/abc/'] gitignore.match_paths ['/abc/123', '/abc/important.txt', '/abc/'] # There is no CLI equivalent to this. ``` ## Building/Installing from Source ```shell git clone git@github.com:highb/pathspec-ruby.git cd pathspec-ruby && bash ./build_from_source.sh ``` ## Contributing Pull requests, bug reports, and feature requests welcome! :smile: I've tried to write exhaustive tests but who knows what cases I've missed. pathspec-ruby-1.0.0/Rakefile000066400000000000000000000012761377475120000157570ustar00rootroot00000000000000begin require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) rescue LoadError puts 'rspec rake task failed to load' end require 'rubocop/rake_task' require 'kramdown' require 'fileutils' RuboCop::RakeTask.new(:rubocop) do |t| t.options = ['--display-cop-names'] end task default: %i[rubocop spec docs] desc 'Generate man page for executable script' task :docs do kramdown = Kramdown::Document.new(File.read('docs/pathspec-rb.md')) FileUtils.mkdir_p 'docs/man' File.open('docs/man/pathspec-rb.man.1', 'w') do |f| f.write(kramdown.to_man) end FileUtils.mkdir_p 'docs/html' File.open('docs/html/pathspec-rb.html', 'w') do |f| f.write(kramdown.to_html) end end pathspec-ruby-1.0.0/bin/000077500000000000000000000000001377475120000150545ustar00rootroot00000000000000pathspec-ruby-1.0.0/bin/pathspec-rb000077500000000000000000000042001377475120000172060ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'optionparser' require 'pathspec' options = { spec_type: :git, spec_filename: '.gitignore' } optparser = OptionParser.new do |opts| opts.banner = 'Usage: pathspec-rb [options] [subcommand] [path] Subcommands: specs_match: Finds all specs matching path. tree: Finds all files under path matching the spec. match: Checks if the path matches any spec. EXIT STATUS: 0 Matches found. 1 No matches found. >1 An error occured. ' opts.on('-f', '--file FILENAME', String, 'A spec file to load. Default: .gitignore') do |filename| unless File.readable?(filename) puts "Error: I couldn't read #{filename}" exit 2 end options[:spec_filename] = filename end opts.on('-t', '--type [git|regex]', %i[git regex], 'Spec file type in FILENAME. Default: git. Available: git and regex.') do |type| options[:spec_type] = type end opts.on('-v', '--verbose', 'Only output if there are matches.') do |_verbose| options[:verbose] = true end end optparser.parse! command = ARGV[0] path = ARGV[1] if path spec = PathSpec.from_filename(options[:spec_filename], options[:spec_type]) else puts optparser.help exit 2 end case command when 'specs_match' if spec.match?(path) puts "#{path} matches the following specs from #{options[:spec_filename]}:" if options[:verbose] puts spec.specs_matching(path) else puts "#{path} does not match any specs from #{options[:spec_filename]}" if options[:verbose] exit 1 end when 'tree' tree_matches = spec.match_tree(path) if tree_matches.any? puts "Files in #{path} that match #{options[:spec_filename]}" if options[:verbose] puts tree_matches else puts "No file in #{path} matched #{options[:spec_filename]}" if options[:verbose] exit 1 end when 'match', '' if spec.match?(path) puts "#{path} matches a spec in #{options[:spec_filename]}" if options[:verbose] else puts "#{path} does not match any specs in #{options[:spec_filename]}" if options[:verbose] exit 1 end else puts "Unknown sub-command #{command}." puts optparser.help exit 2 end pathspec-ruby-1.0.0/docs/000077500000000000000000000000001377475120000152345ustar00rootroot00000000000000pathspec-ruby-1.0.0/docs/html/000077500000000000000000000000001377475120000162005ustar00rootroot00000000000000pathspec-ruby-1.0.0/docs/html/pathspec-rb.html000066400000000000000000000044161377475120000213030ustar00rootroot00000000000000

pathspec-rb(1)

NAME

pathspec - Test pathspecs against a specific path

SYNOPSIS

pathspec-rb [OPTIONS] [SUBCOMMAND] [PATH] NAME PATH

DESCRIPTION

pathspc-rb is a tool that accompanies the pathspec-ruby library to help you test what match results the library would find using path specs. You can either find all specs matching a path, find all files matching specs, or verify that a path would match any spec.

https://github.com/highb/pathspec-ruby

SUB-COMMANDS

Name Description
specs_match Find all specs matching path
tree Find all files under path matching the spec
match Check if the path matches any spec

OPTIONS

-f <FILENAME>, --file <FILENAME>
Load path specs from the file passed in as argument. If this option is not specified, pathspec-rb defaults to loading .gitignore.
-t [git|regex], --type [git|regex]
Type of spec expected in the loaded specs file (see -f option). Defaults to git.
-v, --verbose
Only output if there are matches.

EXAMPLE

Find all files ignored by git under your source directory:

  $ pathspec-rb tree src/

List all spec rules that would match for the specified path:

  $ pathspec-rb specs_match build/

Check that a path matches at least one of the specs in a new version of a gitignore file:

  $ pathspec-rb match -f .gitignore.new spec/fixtures/

AUTHOR

Brandon High highb@users.noreply.github.com

Gabriel Filion

pathspec-ruby-1.0.0/docs/pathspec-rb.md000066400000000000000000000027331377475120000177730ustar00rootroot00000000000000# pathspec-rb(1) {:data-date="2020/01/04"} ## NAME pathspec - Test pathspecs against a specific path ## SYNOPSIS `pathspec-rb` [`OPTIONS`] [`SUBCOMMAND`] [`PATH`] NAME PATH ## DESCRIPTION `pathspc-rb` is a tool that accompanies the pathspec-ruby library to help you test what match results the library would find using path specs. You can either find all specs matching a path, find all files matching specs, or verify that a path would match any spec. https://github.com/highb/pathspec-ruby ## SUB-COMMANDS |- | Name | Description |- | *specs_match* | Find all specs matching path |- | *tree* | Find all files under path matching the spec |- | *match* | Check if the path matches any spec |- ## OPTIONS `-f `, `--file ` : Load path specs from the file passed in as argument. If this option is not specified, `pathspec-rb` defaults to loading `.gitignore`. `-t [git|regex]`, `--type [git|regex]` : Type of spec expected in the loaded specs file (see `-f` option). Defaults to `git`. `-v`, `--verbose` : Only output if there are matches. ## EXAMPLE Find all files ignored by git under your source directory: $ pathspec-rb tree src/ List all spec rules that would match for the specified path: $ pathspec-rb specs_match build/ Check that a path matches at least one of the specs in a new version of a gitignore file: $ pathspec-rb match -f .gitignore.new spec/fixtures/ ## AUTHOR Brandon High highb@users.noreply.github.com Gabriel Filion pathspec-ruby-1.0.0/install_from_source.sh000077500000000000000000000004051377475120000207130ustar00rootroot00000000000000#!/bin/bash bundle install # Ensure this build is sane bundle exec rspec spec # Ensure there is no existing version bundle exec gem uninstall pathspec # Build and install! bundle exec gem build pathspec.gemspec && bundle exec gem install pathspec-0.2.0.gem pathspec-ruby-1.0.0/lib/000077500000000000000000000000001377475120000150525ustar00rootroot00000000000000pathspec-ruby-1.0.0/lib/pathspec.rb000066400000000000000000000047601377475120000172150ustar00rootroot00000000000000require 'pathspec/gitignorespec' require 'pathspec/regexspec' require 'find' require 'pathname' # Main PathSpec class, provides interfaces to various spec implementations class PathSpec attr_reader :specs def initialize(lines = nil, type = :git) @specs = [] add(lines, type) if lines self end # Check if a path matches the pathspecs described # Returns true if there are matches and none are excluded # Returns false if there aren't matches or none are included def match(path) matches = specs_matching(path.to_s) !matches.empty? && matches.all? {|m| m.inclusive?} end def specs_matching(path) @specs.select do |spec| spec if spec.match(path) end end # Check if any files in a given directory or subdirectories match the specs # Returns matched paths or nil if no paths matched def match_tree(root) rootpath = Pathname.new(root) matching = [] Find.find(root) do |path| relpath = Pathname.new(path).relative_path_from(rootpath).to_s relpath += '/' if File.directory? path matching << path if match(relpath) end matching end def match_path(path, root = '/') rootpath = Pathname.new(drive_letter_to_path(root)) relpath = Pathname.new(drive_letter_to_path(path)).relative_path_from(rootpath).to_s relpath += '/' if path[-1].chr == '/' match(relpath) end def match_paths(paths, root = '/') matching = [] paths.each do |path| matching << path if match_path(path, root) end matching end def drive_letter_to_path(path) path.gsub(%r{^([a-zA-Z]):/}, '/\1/') end # Generate specs from a filename, such as a .gitignore def self.from_filename(filename, type = :git) File.open(filename, 'r') { |io| from_lines(io, type) } end def self.from_lines(lines, type = :git) new lines, type end # Generate specs from lines of text def add(obj, type = :git) spec_class = spec_type(type) if obj.respond_to?(:each_line) obj.each_line do |l| spec = spec_class.new(l.rstrip) @specs << spec if !spec.regex.nil? && !spec.inclusive?.nil? end elsif obj.respond_to?(:each) obj.each do |l| add(l, type) end else raise 'Cannot make Pathspec from non-string/non-enumerable object.' end self end def empty? @specs.empty? end def spec_type(type) case type when :git GitIgnoreSpec when :regex RegexSpec else raise "Unknown spec type #{type}" end end end pathspec-ruby-1.0.0/lib/pathspec/000077500000000000000000000000001377475120000166615ustar00rootroot00000000000000pathspec-ruby-1.0.0/lib/pathspec/gitignorespec.rb000066400000000000000000000220761377475120000220570ustar00rootroot00000000000000require 'pathspec/regexspec' class PathSpec # Class for parsing a .gitignore spec class GitIgnoreSpec < RegexSpec attr_reader :regex, :pattern def initialize(original_pattern) # rubocop:disable Metrics/CyclomaticComplexity pattern = original_pattern.strip unless original_pattern.nil? # A pattern starting with a hash ('#') serves as a comment # (neither includes nor excludes files). Escape the hash with a # back-slash to match a literal hash (i.e., '\#'). if pattern.start_with?('#') @regex = nil @inclusive = nil # A blank pattern is a null-operation (neither includes nor # excludes files). elsif pattern.empty? # rubocop:disable Lint/DuplicateBranch @regex = nil @inclusive = nil # Patterns containing three or more consecutive stars are invalid and # will be ignored. elsif /\*\*\*+/.match?(pattern) # rubocop:disable Lint/DuplicateBranch @regex = nil @inclusive = nil # EDGE CASE: According to git check-ignore (v2.4.1)), a single '/' # does not match any file elsif pattern == '/' # rubocop:disable Lint/DuplicateBranch @regex = nil @inclusive = nil # We have a valid pattern! else # A pattern starting with an exclamation mark ('!') negates the # pattern (exclude instead of include). Escape the exclamation # mark with a back-slash to match a literal exclamation mark # (i.e., '\!'). if pattern.start_with?('!') @inclusive = false # Remove leading exclamation mark. pattern = pattern[1..] else @inclusive = true end # Remove leading back-slash escape for escaped hash ('#') or # exclamation mark ('!'). pattern = pattern[1..] if pattern.start_with?('\\') # Split pattern into segments. -1 to allow trailing slashes. pattern_segs = pattern.split('/', -1) # Normalize pattern to make processing easier. # A pattern beginning with a slash ('/') will only match paths # directly on the root directory instead of any descendant # paths. So, remove empty first segment to make pattern relative # to root. if pattern_segs[0].empty? pattern_segs.shift elsif pattern_segs.length == 1 || pattern_segs.length == 2 && pattern_segs[-1].empty? # A pattern without a beginning slash ('/') will match any # descendant path. This is equivilent to "**/{pattern}". So, # prepend with double-asterisks to make pattern relative to # root. # EDGE CASE: This also holds for a single pattern with a # trailing slash (e.g. dir/). pattern_segs.insert(0, '**') if pattern_segs[0] != '**' end # A pattern ending with a slash ('/') will match all descendant # paths of if it is a directory but not if it is a regular file. # This is equivilent to "{pattern}/**". So, set last segment to # double asterisks to include all descendants. pattern_segs[-1] = '**' if pattern_segs[-1].empty? && pattern_segs.length > 1 # Handle platforms with backslash separated paths path_sep = if File::SEPARATOR == '\\' '\\\\' else '/' end # Build regular expression from pattern. regex = '^' need_slash = false regex_end = pattern_segs.size - 1 pattern_segs.each_index do |i| seg = pattern_segs[i] case seg when '**' # A pattern consisting solely of double-asterisks ('**') # will match every path. if i == 0 && i == regex_end regex.concat('.+') # A normalized pattern beginning with double-asterisks # ('**') will match any leading path segments. elsif i == 0 regex.concat("(?:.+#{path_sep})?") need_slash = false # A normalized pattern ending with double-asterisks ('**') # will match any trailing path segments. elsif i == regex_end regex.concat("#{path_sep}.*") # A pattern with inner double-asterisks ('**') will match # multiple (or zero) inner path segments. else regex.concat("(?:#{path_sep}.+)?") need_slash = true end # Match single path segment. when '*' regex.concat(path_sep) if need_slash regex.concat("[^#{path_sep}]+") need_slash = true else # Match segment glob pattern. regex.concat(path_sep) if need_slash regex.concat(translate_segment_glob(seg)) if i == regex_end && @inclusive # A pattern ending without a slash ('/') will match a file # or a directory (with paths underneath it). # e.g. foo matches: foo, foo/bar, foo/bar/baz, etc. # EDGE CASE: However, this does not hold for exclusion cases # according to `git check-ignore` (v2.4.1). regex.concat("(?:#{path_sep}.*)?") end need_slash = true end end regex.concat('$') super(regex) # Copy original pattern @pattern = original_pattern.dup end end def translate_segment_glob(pattern) ''" Translates the glob pattern to a regular expression. This is used in the constructor to translate a path segment glob pattern to its corresponding regular expression. *pattern* (``str``) is the glob pattern. Returns the regular expression (``str``). "'' # NOTE: This is derived from `fnmatch.translate()` and is similar to # the POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set. escape = false regex = '' i = 0 while i < pattern.size # Get next character. char = pattern[i].chr i += 1 # Escape the character. if escape escape = false regex += Regexp.escape(char) # Escape character, escape next character. elsif char == '\\' escape = true # Multi-character wildcard. Match any string (except slashes), # including an empty string. elsif char == '*' regex += '[^/]*' # Single-character wildcard. Match any single character (except # a slash). elsif char == '?' regex += '[^/]' # Braket expression wildcard. Except for the beginning # exclamation mark, the whole braket expression can be used # directly as regex but we have to find where the expression # ends. # - "[][!]" matchs ']', '[' and '!'. # - "[]-]" matchs ']' and '-'. # - "[!]a-]" matchs any character except ']', 'a' and '-'. elsif char == '[' j = i # Pass brack expression negation. j += 1 if j < pattern.size && pattern[j].chr == '!' # Pass first closing braket if it is at the beginning of the # expression. j += 1 if j < pattern.size && pattern[j].chr == ']' # Find closing braket. Stop once we reach the end or find it. j += 1 while j < pattern.size && pattern[j].chr != ']' if j < pattern.size expr = '[' # Braket expression needs to be negated. case pattern[i].chr when '!' expr += '^' i += 1 # POSIX declares that the regex braket expression negation # "[^...]" is undefined in a glob pattern. Python's # `fnmatch.translate()` escapes the caret ('^') as a # literal. To maintain consistency with undefined behavior, # I am escaping the '^' as well. when '^' expr += '\\^' i += 1 end # Escape brackets contained within pattern if pattern[i].chr == ']' && i != j expr += '\]' i += 1 end # Build regex braket expression. Escape slashes so they are # treated as literal slashes by regex as defined by POSIX. expr += pattern[i..j].sub('\\', '\\\\') # Add regex braket expression to regex result. regex += expr # Found end of braket expression. Increment j to be one past # the closing braket: # # [...] # ^ ^ # i j # j += 1 # Set i to one past the closing braket. i = j # Failed to find closing braket, treat opening braket as a # braket literal instead of as an expression. else regex += '\[' end # Regular character, escape it for regex. else regex << Regexp.escape(char) end end regex end def inclusive? @inclusive end end end pathspec-ruby-1.0.0/lib/pathspec/regexspec.rb000066400000000000000000000004711377475120000211750ustar00rootroot00000000000000require 'pathspec/spec' class PathSpec # Simple regex-based spec class RegexSpec < Spec def initialize(pattern) @pattern = pattern.dup @regex = Regexp.compile pattern super end def inclusive? true end def match(path) @regex&.match(path) end end end pathspec-ruby-1.0.0/lib/pathspec/spec.rb000066400000000000000000000003741377475120000201440ustar00rootroot00000000000000class PathSpec # Abstract spec class Spec attr_reader :regex, :pattern def initialize(*_); end def match(files) raise 'Unimplemented' end def inclusive? true end def to_s @pattern end end end pathspec-ruby-1.0.0/pathspec.gemspec000066400000000000000000000020631377475120000174610ustar00rootroot00000000000000lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |s| s.name = 'pathspec' s.version = '1.0.0' s.date = '2018-01-11' s.summary = 'PathSpec: for matching path patterns' s.description = 'Use to match path patterns such as gitignore' s.authors = ['Brandon High'] s.email = 'highb@users.noreply.github.com' s.files = Dir.glob('{lib,spec,docs}/**/*') + %w[LICENSE README.md CHANGELOG.md] s.bindir = 'bin' s.executables << 'pathspec-rb' s.test_files = s.files.grep(%r{^spec/}) s.require_paths = ['lib'] s.homepage = 'https://github.com/highb/pathspec-ruby' s.license = 'Apache-2.0' s.required_ruby_version = '>= 2.6.0' s.add_development_dependency 'bundler', '~> 2.2' s.add_development_dependency 'fakefs', '~> 1.3' s.add_development_dependency 'kramdown', '~> 2.3' s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'rspec', '~> 3.10' s.add_development_dependency 'rubocop', '~> 1.7' s.add_development_dependency 'simplecov', '~> 0.21' end pathspec-ruby-1.0.0/rspec_all_versions.sh000077500000000000000000000006341377475120000205420ustar00rootroot00000000000000#!/bin/bash function testversion { echo Testing Ruby $1 rbenv install -s $1 rbenv local $1 gem install bundler --quiet bundle install --quiet bundle exec rspec if [ $? -eq 0 ]; then echo -e "\033[32mSuccess testing Ruby $1\033[0m" else echo -e "\033[31mFailed testing Ruby $1 Exit code was $?\033[0m" fi echo } for VERSION in 2.2.9 2.3.6 2.4.3 2.5.0; do testversion $VERSION done pathspec-ruby-1.0.0/spec/000077500000000000000000000000001377475120000152365ustar00rootroot00000000000000pathspec-ruby-1.0.0/spec/files/000077500000000000000000000000001377475120000163405ustar00rootroot00000000000000pathspec-ruby-1.0.0/spec/files/gitignore_readme000066400000000000000000000000301377475120000215600ustar00rootroot00000000000000!**/important.txt abc/**pathspec-ruby-1.0.0/spec/files/gitignore_ruby000066400000000000000000000021431377475120000213130ustar00rootroot00000000000000# Source: https://github.com/github/gitignore/blob/master/Ruby.gitignore *.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /spec/examples.txt /test/tmp/ /test/version_tmp/ /tmp/ # Used by dotenv library to load environment variables. # .env ## Specific to RubyMotion: .dat* .repl_history build/ *.bridgesupport build-iPhoneOS/ build-iPhoneSimulator/ ## Specific to RubyMotion (use of CocoaPods): # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # vendor/Pods/ ## Documentation cache and generated files: /.yardoc/ /_yardoc/ /doc/ /rdoc/ ## Environment normalization: /.bundle/ /vendor/bundle /lib/bundler/man/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # Gemfile.lock # .ruby-version # .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc pathspec-ruby-1.0.0/spec/files/gitignore_simple000066400000000000000000000000051377475120000216160ustar00rootroot00000000000000*.md pathspec-ruby-1.0.0/spec/files/regex_simple000066400000000000000000000000071377475120000207430ustar00rootroot00000000000000.*\.md pathspec-ruby-1.0.0/spec/spec_helper.rb000066400000000000000000000002261377475120000200540ustar00rootroot00000000000000begin require 'simplecov' SimpleCov.start rescue StandardError puts 'SimpleCov failed to start, most likely this due to running Ruby 1.8.7' end pathspec-ruby-1.0.0/spec/unit/000077500000000000000000000000001377475120000162155ustar00rootroot00000000000000pathspec-ruby-1.0.0/spec/unit/pathspec/000077500000000000000000000000001377475120000200245ustar00rootroot00000000000000pathspec-ruby-1.0.0/spec/unit/pathspec/gitignorespec_spec.rb000066400000000000000000000276541377475120000242430ustar00rootroot00000000000000require 'spec_helper' require 'pathspec/gitignorespec' describe PathSpec::GitIgnoreSpec do # Original specification by http://git-scm.com/docs/gitignore # A blank line matches no files, so it can serve as a separator for # readability. describe 'does nothing for newlines' do subject { PathSpec::GitIgnoreSpec.new "\n" } it { is_expected.to_not match('foo.tmp') } it { is_expected.to_not match(' ') } it { is_expected.to_not be_inclusive } end describe 'does nothing for blank strings' do subject { PathSpec::GitIgnoreSpec.new '' } it { is_expected.to_not match 'foo.tmp' } it { is_expected.to_not match ' ' } it { is_expected.to_not be_inclusive } end # A line starting with # serves as a comment. Put a backslash ("\") in front # of the first hash for patterns that begin with a hash. describe 'does nothing for comments' do subject { PathSpec::GitIgnoreSpec.new '# this is a gitignore style comment' } it { is_expected.to_not match('foo.tmp') } it { is_expected.to_not match(' ') } it { is_expected.to_not be_inclusive } end describe 'ignores comment char with a slash' do subject { PathSpec::GitIgnoreSpec.new '\#averystrangefile' } it { is_expected.to match('#averystrangefile') } it { is_expected.to_not match('foobar') } it { is_expected.to be_inclusive } end describe 'escapes characters with slashes' do subject { PathSpec::GitIgnoreSpec.new 'twinkletwinkle\*' } it { is_expected.to match('twinkletwinkle*') } it { is_expected.to_not match('twinkletwinkletwinkle') } it { is_expected.to be_inclusive } end # Trailing spaces are ignored unless they are quoted with backlash ("\"). describe 'ignores trailing spaces' do subject { PathSpec::GitIgnoreSpec.new 'foo ' } it { is_expected.to match('foo') } it { is_expected.to_not match('foo ') } it { is_expected.to be_inclusive } end # This is not handled properly yet describe 'does not ignore escaped trailing spaces' # An optional prefix "!" which negates the pattern; any matching file excluded # by a previous pattern will become included again. It is not possible to # re-include a file if a parent directory of that file is excluded. Git # doesn't list excluded directories for performance reasons, so any patterns # on contained files have no effect, no matter where they are defined. Put a # backslash ("\") in front of the first "!" for patterns that begin with a # literal "!", for example, "\!important!.txt". describe 'is exclusive of !' do subject { PathSpec::GitIgnoreSpec.new '!important.txt' } it { is_expected.to match('important.txt') } it { is_expected.to_not be_inclusive } it { is_expected.to_not match('!important.txt') } end # If the pattern ends with a slash, it is removed for the purpose of the # following description, but it would only find a match with a directory. In # other words, foo/ will match a directory foo and paths underneath it, but # will not match a regular file or a symbolic link foo (this is consistent # with the way how pathspec works in general in Git). describe 'trailing slashes match directories and their contents but not regular files or symlinks' do subject { PathSpec::GitIgnoreSpec.new 'foo/' } it { is_expected.to match('foo/') } it { is_expected.to match('foo/bar') } it { is_expected.to match('baz/foo/bar') } it { is_expected.to_not match('foo') } it { is_expected.to be_inclusive } end # If the pattern does not contain a slash '/', Git treats it as a shell glob # pattern and checks for a match against the pathname relative to the location # of the .gitignore file (relative to the toplevel of the work tree if not # from a .gitignore file). describe 'handles basic globbing' do subject { PathSpec::GitIgnoreSpec.new '*.tmp' } it { is_expected.to match('foo.tmp') } it { is_expected.to match('foo/bar.tmp') } it { is_expected.to match('foo/bar.tmp/baz') } it { is_expected.to_not match('foo.rb') } it { is_expected.to be_inclusive } end describe 'handles inner globs' do subject { PathSpec::GitIgnoreSpec.new 'foo-*-bar' } it { is_expected.to match('foo--bar') } it { is_expected.to match('foo-hello-bar') } it { is_expected.to match('a/foo-hello-bar') } it { is_expected.to match('foo-hello-bar/b') } it { is_expected.to match('a/foo-hello-bar/b') } it { is_expected.to_not match('foo.tmp') } end describe 'handles postfix globs' do subject { PathSpec::GitIgnoreSpec.new '~temp-*' } it { is_expected.to match('~temp-') } it { is_expected.to match('~temp-foo') } it { is_expected.to match('foo/~temp-bar') } it { is_expected.to match('foo/~temp-bar/baz') } it { is_expected.to_not match('~temp') } end describe 'handles multiple globs' do subject { PathSpec::GitIgnoreSpec.new '*.middle.*' } it { is_expected.to match('hello.middle.rb') } it { is_expected.to_not match('foo.rb') } it { is_expected.to be_inclusive } end describe 'handles dir globs' do subject { PathSpec::GitIgnoreSpec.new 'dir/*' } it { is_expected.to match('dir/foo') } it { is_expected.to_not match('foo/') } it { is_expected.to be_inclusive } end # Otherwise, Git treats the pattern as a shell glob suitable for consumption # by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not # match a / in the pathname. For example, "Documentation/*.html" matches # "Documentation/git.html" but not "Documentation/ppc/ppc.html" or # "tools/perf/Documentation/perf.html". describe 'handles dir globs' do subject { PathSpec::GitIgnoreSpec.new 'dir/*' } it { is_expected.to match('dir/foo') } it { is_expected.to_not match('foo/') } it { is_expected.to be_inclusive } end describe 'handles globs inside of dirs' do subject { PathSpec::GitIgnoreSpec.new 'Documentation/*.html' } it { is_expected.to match('Documentation/git.html') } it { is_expected.to_not match('Documentation/ppc/ppc.html') } it { is_expected.to_not match('tools/perf/Documentation/perf.html') } # TODO: Or is it? Git 2 weirdness? it { is_expected.to be_inclusive } end describe 'handles wildcards' do subject { PathSpec::GitIgnoreSpec.new 'jokeris????' } it { is_expected.to match('jokeriswild') } it { is_expected.to_not match('jokerisfat') } it { is_expected.to be_inclusive } end describe 'handles brackets' do subject { PathSpec::GitIgnoreSpec.new '*[eu][xl]*' } it { is_expected.to match('youknowregex') } it { is_expected.to match('youknowregularexpressions') } it { is_expected.to_not match('youknownothing') } it { is_expected.to be_inclusive } end describe 'handles unmatched brackets' do subject { PathSpec::GitIgnoreSpec.new '*[*[*' } it { is_expected.to match('bracket[oh[wow') } it { is_expected.to be_inclusive } end describe 'handles brackets with carats' do subject { PathSpec::GitIgnoreSpec.new '*[^]' } it { is_expected.to match('myfavorite^') } it { is_expected.to be_inclusive } end describe 'handles brackets for brackets' do subject { PathSpec::GitIgnoreSpec.new '*[]]' } it { is_expected.to match('yodawg[]]') } it { is_expected.to be_inclusive } end describe 'handles brackets with escaped characters' do # subject { GitIgnoreSpec.new 'back[\\]slash' } # it { is_expected.to match('back\\slash') } # it { is_expected.to_not match('back\\\\slash') } # it { is_expected.to be_inclusive } end describe 'handles negated brackets' do subject { PathSpec::GitIgnoreSpec.new 'ab[!cd]ef' } it { is_expected.to_not match('abcef') } it { is_expected.to match('abzef') } it { is_expected.to be_inclusive } end # A leading slash matches the beginning of the pathname. For example, "/*.c" # matches "cat-file.c" but not "mozilla-sha1/sha1.c". describe 'handles leading / as relative to base directory' do subject { PathSpec::GitIgnoreSpec.new '/*.c' } it { is_expected.to match('cat-file.c') } it { is_expected.to_not match('mozilla-sha1/sha1.c') } it { is_expected.to be_inclusive } end describe 'handles simple single paths' do subject { PathSpec::GitIgnoreSpec.new 'spam' } it { is_expected.to match('spam') } it { is_expected.to match('spam/') } it { is_expected.to match('foo/spam') } it { is_expected.to match('spam/foo') } it { is_expected.to match('foo/spam/bar') } it { is_expected.to_not match('foo') } end # Two consecutive asterisks ("**") in patterns matched against full pathname # may have special meaning: # A leading "**" followed by a slash means match in all directories. For # example, "**/foo" matches file or directory "foo" anywhere, the same as # pattern "foo". "**/foo/bar" matches file or directory "bar" anywhere that is # directly under directory "foo". describe 'handles prefixed ** as searching any location' do subject { PathSpec::GitIgnoreSpec.new '**/foo' } it { is_expected.to match('foo') } it { is_expected.to match('bar/foo') } it { is_expected.to match('baz/bar/foo') } it { is_expected.to_not match('baz/bar/foo.rb') } it { is_expected.to be_inclusive } end describe 'handles prefixed ** with a directory as searching a file under a directory in any location' do subject { PathSpec::GitIgnoreSpec.new '**/foo/bar' } it { is_expected.to_not match('foo') } it { is_expected.to match('foo/bar') } it { is_expected.to match('baz/foo/bar') } it { is_expected.to match('baz/foo/bar/sub') } it { is_expected.to_not match('baz/foo/bar.rb') } it { is_expected.to_not match('baz/bananafoo/bar') } it { is_expected.to be_inclusive } end # A trailing "/**" matches everything inside. For example, "abc/**" matches # all files inside directory "abc", relative to the location of the .gitignore # file, with infinite depth. describe 'handles leading /** as all files inside a directory' do subject { PathSpec::GitIgnoreSpec.new 'abc/**' } it { is_expected.to match('abc/') } it { is_expected.to match('abc/def') } it { is_expected.to_not match('123/abc/def') } it { is_expected.to_not match('123/456/abc/') } it { is_expected.to be_inclusive } end # A slash followed by two consecutive asterisks then a slash matches zero or # more directories. For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" # and so on. describe 'handles /** in the middle of a path' do subject { PathSpec::GitIgnoreSpec.new 'a/**/b' } it { is_expected.to match('a/b') } it { is_expected.to match('a/x/b') } it { is_expected.to match('a/x/y/b') } it { is_expected.to_not match('123/a/b') } it { is_expected.to_not match('123/a/x/b') } it { is_expected.to be_inclusive } end describe 'matches all paths when given **' do subject { PathSpec::GitIgnoreSpec.new '**' } it { is_expected.to match('a/b') } it { is_expected.to match('a/x/b') } it { is_expected.to match('a/x/y/b') } it { is_expected.to match('123/a/b') } it { is_expected.to match('123/a/x/b') } end # Other consecutive asterisks are considered invalid. describe 'considers other consecutive asterisks invalid' do subject { PathSpec::GitIgnoreSpec.new 'a/***/b' } it { is_expected.to_not match('a/b') } it { is_expected.to_not match('a/x/b') } it { is_expected.to_not match('a/x/y/b') } it { is_expected.to_not match('123/a/b') } it { is_expected.to_not match('123/a/x/b') } it { is_expected.to_not be_inclusive } end describe 'does not match single absolute paths' do subject { PathSpec::GitIgnoreSpec.new '/' } it { is_expected.to_not match('foo.tmp') } it { is_expected.to_not match(' ') } it { is_expected.to_not match('a/b') } end describe 'nested paths are relative to the file' do subject { PathSpec::GitIgnoreSpec.new 'foo/spam' } it { is_expected.to match('foo/spam') } it { is_expected.to match('foo/spam/bar') } it { is_expected.to_not match('bar/foo/spam') } end end pathspec-ruby-1.0.0/spec/unit/pathspec/spec_spec.rb000066400000000000000000000004061377475120000223150ustar00rootroot00000000000000require 'spec_helper' require 'pathspec/spec' describe PathSpec::Spec do subject { PathSpec::Spec.new } it 'does not allow matching' do expect { subject.match 'anything' }.to raise_error(/Unimplemented/) end it { is_expected.to be_inclusive } end pathspec-ruby-1.0.0/spec/unit/pathspec_spec.rb000066400000000000000000000233051377475120000213660ustar00rootroot00000000000000require 'spec_helper' require 'fakefs/safe' require 'pathspec' require 'fakefs/spec_helpers' describe PathSpec do shared_examples 'standard gitignore negation' do it { is_expected.not_to match('important.txt') } it { is_expected.not_to match('abc/important.txt') } it { is_expected.to match('bar/baz/') } it { is_expected.to match('foo/file') } it { is_expected.not_to match('foo/important.txt') } it { is_expected.to match('foo/subdir/file') } end # Specs that should be kept up to date with the README context 'README.md' do subject { PathSpec.from_filename 'spec/files/gitignore_readme' } it { is_expected.to match('abc/def.rb') } it { is_expected.not_to match('abc/important.txt') } it do expect(subject.match_paths(['/abc/123', '/abc/important.txt', '/abc/'])).to contain_exactly( '/abc/123', '/abc/' ) end end context 'initialization' do context 'from multilines' do context '#new' do subject { PathSpec.new <<-IGNORELINES !important.txt foo/** /bar/baz IGNORELINES } it_behaves_like 'standard gitignore negation' end end context 'from a string with no newlines' do let(:str) { 'foo/**' } context '#new' do subject { PathSpec.new str } it { is_expected.to match('foo/important.txt') } it { is_expected.to match('foo/bar/') } end end context 'from a non-string/non-enumerable' do it 'throws an exception' do expect { PathSpec.new Object.new }.to raise_error(/Cannot make Pathspec/) end end context 'from array of gitignore strings' do let(:arr) { ['!important.txt', 'foo/**', '/bar/baz'] } context '#new' do subject { PathSpec.new arr } it_behaves_like 'standard gitignore negation' end context '#from_lines' do subject { PathSpec.from_lines(arr) } it_behaves_like 'standard gitignore negation' end context '#add array' do subject { ps = PathSpec.new [] ps.add arr } it_behaves_like 'standard gitignore negation' end end context 'from linedelimited gitignore string' do let(:line) { "!important.txt\nfoo/**\n/bar/baz\n" } context '#new' do subject { PathSpec.new line } it_behaves_like 'standard gitignore negation' end context '#from_lines' do subject { PathSpec.from_lines(line) } it_behaves_like 'standard gitignore negation' end context '#add' do subject { ps = PathSpec.new ps.add line } it_behaves_like 'standard gitignore negation' end end context 'from a gitignore file' do include FakeFS::SpecHelpers let(:filename) { '.gitignore' } before(:each) do file = File.open(filename, 'w') { |f| f << "!important.txt\n" f << "foo/**\n" f << "/bar/baz\n" } end context '#new' do subject { PathSpec.new File.open(filename, 'r') } it_behaves_like 'standard gitignore negation' end context '#from_filename' do subject { PathSpec.from_filename(filename) } it_behaves_like 'standard gitignore negation' end end context 'from multiple gitignore files' do include FakeFS::SpecHelpers let(:filenames) { ['.gitignore', '.otherignore'] } before(:each) do file = File.open('.gitignore', 'w') { |f| f << "!important.txt\n" f << "foo/**\n" f << "/bar/baz\n" } file = File.open('.otherignore', 'w') { |f| f << "ban*na\n" f << "!banana\n" } end context '#new' do subject { arr = filenames.collect { |f| File.open(f, 'r') } PathSpec.new arr } it_behaves_like 'standard gitignore negation' it { is_expected.to_not match('banana') } it { is_expected.to match('banananananana') } end context '#add' do subject { arr = filenames.collect { |f| File.open(f, 'r') } ps = PathSpec.new ps.add arr } it_behaves_like 'standard gitignore negation' it { is_expected.to_not match('banana') } it { is_expected.to match('banananananana') } end end end context '#match_tree' do include FakeFS::SpecHelpers context 'unix' do let(:root) {'/tmp/project'} let(:gitignore) { <<-GITIGNORE !**/important.txt abc/** GITIGNORE } before(:each) { FileUtils.mkdir_p root FileUtils.mkdir_p "#{root}/abc" FileUtils.touch "#{root}/abc/1" FileUtils.touch "#{root}/abc/2" FileUtils.touch "#{root}/abc/important.txt" } subject { PathSpec.new(gitignore).match_tree(root) } it { is_expected.to include "#{root}/abc".to_s } it { is_expected.to include "#{root}/abc/1".to_s } it { is_expected.not_to include "#{root}/abc/important.txt".to_s } it { is_expected.not_to include root.to_s.to_s } end context 'windows' do let(:root) {'C:/project'} let(:gitignore) { <<-GITIGNORE !**/important.txt abc/** GITIGNORE } before(:each) { FileUtils.mkdir_p root FileUtils.mkdir_p "#{root}/abc" FileUtils.touch "#{root}/abc/1" FileUtils.touch "#{root}/abc/2" FileUtils.touch "#{root}/abc/important.txt" } subject { PathSpec.new(gitignore).match_tree(root) } it { is_expected.to include "#{root}/abc".to_s } it { is_expected.to include "#{root}/abc/1".to_s } it { is_expected.not_to include "#{root}/abc/important.txt".to_s } it { is_expected.not_to include root.to_s.to_s } end end context '#match_paths' do let(:gitignore) { <<-GITIGNORE !**/important.txt /abc/** GITIGNORE } context 'with no root arg' do subject { PathSpec.new(gitignore).match_paths(['/abc/important.txt', '/abc/', '/abc/1']) } it { is_expected.to include '/abc/' } it { is_expected.to include '/abc/1' } it { is_expected.not_to include '/abc/important.txt' } end context 'relative to non-root dir' do subject { PathSpec.new(gitignore).match_paths([ '/def/abc/important.txt', '/def/abc/', '/def/abc/1' ], '/def') } it { is_expected.to include '/def/abc/' } it { is_expected.to include '/def/abc/1' } it { is_expected.not_to include '/def/abc/important.txt' } end context 'relative to windows drive letter' do subject { PathSpec.new(gitignore).match_paths([ 'C:/def/abc/important.txt', 'C:/def/abc/', 'C:/def/abc/1' ], 'C:/def/') } it { is_expected.to include 'C:/def/abc/' } it { is_expected.to include 'C:/def/abc/1' } it { is_expected.not_to include 'C:/def/abc/important.txt' } end end # Example to exclude everything except a specific directory foo/bar (note # the /* - without the slash, the wildcard would also exclude everything # within foo/bar): (from git-scm.com) context 'very specific gitignore' do let(:gitignore) { <<-GITIGNORE # exclude everything except directory foo/bar /* !/foo /foo/* !/foo/bar GITIGNORE } subject { PathSpec.new(gitignore) } it { is_expected.not_to match('foo/bar') } it { is_expected.to match('anything') } it { is_expected.to match('foo/otherthing') } end context '#empty' do let(:gitignore) { <<-GITIGNORE # A comment GITIGNORE } subject { PathSpec.new gitignore } it 'is empty when there are no valid lines' do expect(subject.empty?).to be true end end context 'regex file' do let(:regexfile) { <<-REGEX ab*a REGEX } subject { PathSpec.new regexfile, :regex} it 'matches the regex' do expect(subject.match('anna')).to be false expect(subject.match('abba')).to be true end context '#from_filename' do it 'forwards the type argument' do io = double expect(File).to receive(:open).and_yield(io) expect(PathSpec).to receive(:from_lines).with(io, :regex) PathSpec.from_filename '/some/file', :regex end it 'reads a simple regex file' do spec = PathSpec.from_filename 'spec/files/regex_simple', :regex expect(spec.match('artifact.md')).to be true expect(spec.match('code.rb')).to be false end it 'reads a simple gitignore file' do spec = PathSpec.from_filename 'spec/files/gitignore_simple', :git expect(spec.match('artifact.md')).to be true expect(spec.match('code.rb')).to be false end it 'reads an example ruby gitignore file' do spec = PathSpec.from_filename 'spec/files/gitignore_ruby', :git expect(spec.match('coverage/')).to be true expect(spec.match('coverage/index.html')).to be true expect(spec.match('pathspec-0.0.1.gem')).to be true expect(spec.match('lib/pathspec')).to be false expect(spec.match('Gemfile')).to be false end end end context 'unsuppored spec type' do let(:file) { <<-REGEX This is some kind of nonsense. REGEX } it 'does not allow an unknown spec type' do expect { PathSpec.new file, :foo}.to raise_error(/Unknown/) end end end