pathspec 0.1.2 → 0.2.1

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
- SHA1:
3
- metadata.gz: f259b8f0d32109d62b9bbc1a0f6a96c34f607cf9
4
- data.tar.gz: 551a347443c3e6017eccee1b3ce7ca9cf1e51339
2
+ SHA256:
3
+ metadata.gz: f31eacbf6d689413b94c4607b34f617d2ebee3a9458920ff5435a9e66232d976
4
+ data.tar.gz: 5eab0a8a120d842f5153d186a60e15a632d7fda9f3d67c418297ff755d68f014
5
5
  SHA512:
6
- metadata.gz: df440335455b0deea4f7ca6f8b3566868d7d58f62be038217413628931b68acc3feb8899b38bd83b219676fa70aa8ad25d2e7e57d8ba65a6e56cc1bb908a0986
7
- data.tar.gz: 1f20ff703cf2b7f725cf744139ec26c73e60a496014a929b8ff06dceb8794dea03a73e3884036be57c4dd0d04fc122f23701c7545a160a9756d9e89c7858ced0
6
+ metadata.gz: 220bfd684bc5b278bf1c4c40fbf85dbb515336ad561a334544fa257f8da810efb2bf4c2a3533610dade922d296cd003ede1125ee84e1e1231667c344c805f81d
7
+ data.tar.gz: b042eeb99b7f38c663cb0df25cc6e50490e640eb6644648765ed35df312ce8efd086548602d064b5c928399cd4a19f19d7967cbe7b19645f09c24b2e66c457ee
@@ -1,16 +1,24 @@
1
1
  # pathspec-ruby CHANGELOG
2
2
 
3
- ## 0.1.2
3
+ ## 0.2.0 (Minor Release)
4
+ - (Feature) A CLI tool, pathspec-rb, is now provided with the gem.
5
+ - (API Change) New namespace for gem: `PathSpec`: Everything is now namespaced under `PathSpec`, to prevent naming collisions with other libraries. Thanks @tenderlove!
6
+ - (License) License version updated to Apache 2. Thanks @kytrinyx!
7
+ - (Maint) Pruned Supported Ruby Versions. We now test: 2.2.9, 2.3.6 and 2.4.3.
8
+ - (Maint) Ruby 2.5.0 testing is blocked on Travis, but should work locally. Thanks @SumLare!
9
+ - (Maint) Added Rubocop and made some corrections
10
+
11
+ ## 0.1.2 (Patch/Bug Fix Release)
4
12
  - Fix for regexp matching Thanks @incase! #16
5
13
  - File handling cleanup Thanks @martinandert! #13
6
14
  - `from_filename` actually works now! Thanks @martinandert! #12
7
15
 
8
- ## 0.1.0
16
+ ## 0.1.0 (Minor Release)
9
17
  - Port new edgecase handling from [python-path-specification](https://github.com/cpburnz/python-path-specification/pull/8). Many thanks to @jdpace! :)
10
18
  - Removed EOL Ruby support
11
19
  - Added current Ruby stable to Travis testing
12
20
 
13
- ## 0.0.2
21
+ ## 0.0.2 (Patch/Bug Fix Release)
14
22
  - Fixed issues with Ruby 1.8.7/2.1.1
15
23
  - Added more testing scripts
16
24
  - Fixed Windows path related issues
data/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  pathspec-ruby
2
2
  =============
3
3
 
4
- [![Build Status](https://travis-ci.org/highb/pathspec-ruby.svg?branch=master)](https://travis-ci.org/highb/pathspec-ruby) [![codecov](https://codecov.io/gh/highb/pathspec-ruby/branch/master/graph/badge.svg)](https://codecov.io/gh/highb/pathspec-ruby)
4
+ [![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)
5
5
 
6
- Supported Rubies:
7
- - 2.2.7 (Maintenance)
8
- - 2.3.4 (Stable)
9
- - 2.4.1 (Stable)
6
+ [Supported Rubies](https://www.ruby-lang.org/en/downloads/):
7
+ - 2.2.9 (Maintenance)
8
+ - 2.3.6 (Stable)
9
+ - 2.4.3 (Stable)
10
+ - 2.5.0 (Stable) [Disabled in Travis](https://github.com/travis-ci/travis-ci/issues/8969)
10
11
 
11
12
  Match Path Specifications, such as .gitignore, in Ruby!
12
13
 
@@ -19,6 +20,26 @@ Follows .gitignore syntax defined on [gitscm](http://git-scm.com/docs/gitignore)
19
20
  gem install pathspec
20
21
  ```
21
22
 
23
+ ## CLI Usage
24
+ ```bash
25
+ ➜ test-pathspec cat .gitignore
26
+ *.swp
27
+ /coverage/
28
+ ➜ test-pathspec be pathspec-rb specs_match "coverage/foo"
29
+ /coverage/
30
+ ➜ test-pathspec be pathspec-rb specs_match "file.swp"
31
+ *.swp
32
+ ➜ test-pathspec be pathspec-rb match "file.swp"
33
+ ➜ test-pathspec echo $?
34
+ 0
35
+ ➜ test-pathspec ls
36
+ Gemfile Gemfile.lock coverage file.swp source.rb
37
+ ➜ test-pathspec be pathspec-rb tree .
38
+ ./coverage
39
+ ./coverage/index.html
40
+ ./file.swp
41
+ ```
42
+
22
43
  ## Usage
23
44
  ```ruby
24
45
  require 'pathspec'
@@ -26,7 +47,7 @@ require 'pathspec'
26
47
  # Create a .gitignore-style Pathspec by giving it newline separated gitignore
27
48
  # lines, an array of gitignore lines, or any other enumable object that will
28
49
  # give strings matching the .gitignore-style (File, etc.)
29
- gitignore = Pathspec.new File.read('.gitignore', 'r')
50
+ gitignore = Pathspec.from_filename('spec/files/gitignore_readme')
30
51
 
31
52
  # Our .gitignore in this example contains:
32
53
  # !**/important.txt
@@ -34,18 +55,22 @@ gitignore = Pathspec.new File.read('.gitignore', 'r')
34
55
 
35
56
  # true, matches "abc/**"
36
57
  gitignore.match 'abc/def.rb'
58
+ # CLI equivalent: pathspec.rb -f spec/files/gitignore_readme match 'abc/def.rb'
37
59
 
38
60
  # false, because it has been negated using the line "!**/important.txt"
39
61
  gitignore.match 'abc/important.txt'
62
+ # CLI equivalent: pathspec.rb -f spec/files/gitignore_readme match 'abc/important.txt'
40
63
 
41
64
  # Give a path somewhere in the filesystem, and the Pathspec will return all
42
65
  # matching files underneath.
43
66
  # Returns ['/src/repo/abc/', '/src/repo/abc/123']
44
67
  gitignore.match_tree '/src/repo'
68
+ # CLI equivalent: pathspec.rb -f spec/files/gitignore_readme tree /src/repo
45
69
 
46
70
  # Give an enumerable of paths, and Pathspec will return the ones that match.
47
71
  # Returns ['/abc/123', '/abc/']
48
72
  gitignore.match_paths ['/abc/123', '/abc/important.txt', '/abc/']
73
+ # There is no CLI equivalent to this.
49
74
  ```
50
75
 
51
76
  ## Building/Installing from Source
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optionparser'
4
+ require 'pathspec'
5
+ options = {
6
+ spec_type: :git,
7
+ spec_filename: '.gitignore'
8
+ }
9
+
10
+ optparser = OptionParser.new do |opts|
11
+ opts.banner = %q{Usage: pathspec-rb [options] [subcommand] [path]
12
+ Subcommands:
13
+ specs_match: Finds all specs matching path.
14
+ tree: Finds all files under path matching the spec.
15
+ match: Checks if the path matches any spec.
16
+
17
+ EXIT STATUS:
18
+ 0 Matches found.
19
+ 1 No matches found.
20
+ >1 An error occured.
21
+
22
+ }
23
+ opts.on('-f', '--file FILENAME', String,
24
+ 'A spec file to load. Default: .gitignore') do |filename|
25
+ unless File.readable?(filename)
26
+ puts "Error: I couldn't read #{filename}"
27
+ exit 2
28
+ end
29
+
30
+ options[:spec_filename] = filename
31
+ end
32
+ opts.on('-t', '--type [git|regex]', %i[git regex],
33
+ 'Spec file type in FILENAME. Default: git. Available: git and regex.') do |type|
34
+ options[:spec_type] = type
35
+ end
36
+ opts.on('-v', '--verbose', 'Only output if there are matches.') do |verbose|
37
+ options[:verbose] = true
38
+ end
39
+ end
40
+
41
+ optsparsed = optparser.parse!
42
+
43
+ command = ARGV[0]
44
+ path = ARGV[1]
45
+ if path
46
+ spec = PathSpec.from_filename(options[:spec_filename], options[:spec_type])
47
+ else
48
+ puts optparser.help
49
+ exit 2
50
+ end
51
+
52
+ case command
53
+ when 'specs_match'
54
+ if spec.match(path)
55
+ if options[:verbose]
56
+ puts "#{path} matches the following specs from #{options[:spec_filename]}:"
57
+ end
58
+ puts spec.specs_matching(path)
59
+ else
60
+ if options[:verbose]
61
+ puts "#{path} does not match any specs from #{options[:spec_filename]}"
62
+ end
63
+ exit 1
64
+ end
65
+ when 'tree'
66
+ tree_matches = spec.match_tree(path)
67
+ if tree_matches.size > 0
68
+ if options[:verbose]
69
+ puts "Files in #{path} that match #{options[:spec_filename]}"
70
+ end
71
+ puts tree_matches
72
+ else
73
+ if options[:verbose]
74
+ puts "No file in #{path} matched #{options[:spec_filename]}"
75
+ end
76
+ exit 1
77
+ end
78
+ when 'match', ''
79
+ if spec.match(path)
80
+ if options[:verbose]
81
+ puts "#{path} matches a spec in #{options[:spec_filename]}"
82
+ end
83
+ else
84
+ if options[:verbose]
85
+ puts "#{path} does not match any specs in #{options[:spec_filename]}"
86
+ end
87
+ exit 1
88
+ end
89
+ else
90
+ puts "Unknown sub-command #{command}."
91
+ puts optparser.help
92
+ exit 2
93
+ end
@@ -3,15 +3,14 @@ require 'pathspec/regexspec'
3
3
  require 'find'
4
4
  require 'pathname'
5
5
 
6
+ # Main PathSpec class, provides interfaces to various spec implementations
6
7
  class PathSpec
7
8
  attr_reader :specs
8
9
 
9
- def initialize(lines=nil, type=:git)
10
+ def initialize(lines = nil, type = :git)
10
11
  @specs = []
11
12
 
12
- if lines
13
- add(lines, type)
14
- end
13
+ add(lines, type) if lines
15
14
 
16
15
  self
17
16
  end
@@ -26,9 +25,7 @@ class PathSpec
26
25
 
27
26
  def specs_matching(path)
28
27
  @specs.select do |spec|
29
- if spec.match(path)
30
- spec
31
- end
28
+ spec if spec.match(path)
32
29
  end
33
30
  end
34
31
 
@@ -41,58 +38,52 @@ class PathSpec
41
38
  Find.find(root) do |path|
42
39
  relpath = Pathname.new(path).relative_path_from(rootpath).to_s
43
40
  relpath += '/' if File.directory? path
44
- if match(relpath)
45
- matching << path
46
- end
41
+ matching << path if match(relpath)
47
42
  end
48
43
 
49
44
  matching
50
45
  end
51
46
 
52
- def match_path(path, root='/')
47
+ def match_path(path, root = '/')
53
48
  rootpath = Pathname.new(drive_letter_to_path(root))
54
49
  relpath = Pathname.new(drive_letter_to_path(path)).relative_path_from(rootpath).to_s
55
- relpath = relpath + '/' if path[-1].chr == '/'
50
+ relpath += '/' if path[-1].chr == '/'
56
51
 
57
52
  match(relpath)
58
53
  end
59
54
 
60
- def match_paths(paths, root='/')
55
+ def match_paths(paths, root = '/')
61
56
  matching = []
62
57
 
63
58
  paths.each do |path|
64
- if match_path(path, root)
65
- matching << path
66
- end
59
+ matching << path if match_path(path, root)
67
60
  end
68
61
 
69
62
  matching
70
63
  end
71
64
 
72
65
  def drive_letter_to_path(path)
73
- path.gsub(/^([a-zA-Z]):\//, '/\1/')
66
+ path.gsub(%r{^([a-zA-Z]):\/}, '/\1/')
74
67
  end
75
68
 
76
69
  # Generate specs from a filename, such as a .gitignore
77
- def self.from_filename(filename, type=:git)
78
- File.open(filename, 'r') { |io| self.from_lines(io, type) }
70
+ def self.from_filename(filename, type = :git)
71
+ File.open(filename, 'r') { |io| from_lines(io, type) }
79
72
  end
80
73
 
81
- def self.from_lines(lines, type=:git)
82
- self.new lines, type
74
+ def self.from_lines(lines, type = :git)
75
+ new lines, type
83
76
  end
84
77
 
85
78
  # Generate specs from lines of text
86
- def add(obj, type=:git)
79
+ def add(obj, type = :git)
87
80
  spec_class = spec_type(type)
88
81
 
89
82
  if obj.respond_to?(:each_line)
90
83
  obj.each_line do |l|
91
84
  spec = spec_class.new(l.rstrip)
92
85
 
93
- if !spec.regex.nil? && !spec.inclusive?.nil?
94
- @specs << spec
95
- end
86
+ @specs << spec if !spec.regex.nil? && !spec.inclusive?.nil?
96
87
  end
97
88
  elsif obj.respond_to?(:each)
98
89
  obj.each do |l|
@@ -1,170 +1,163 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathspec/regexspec'
4
2
 
5
- class GitIgnoreSpec < RegexSpec
6
- attr_reader :regex
7
-
8
- def initialize(pattern)
9
- pattern = pattern.strip unless pattern.nil?
10
-
11
- # A pattern starting with a hash ('#') serves as a comment
12
- # (neither includes nor excludes files). Escape the hash with a
13
- # back-slash to match a literal hash (i.e., '\#').
14
- if pattern.start_with?('#')
15
- @regex = nil
16
- @inclusive = nil
17
-
18
- # A blank pattern is a null-operation (neither includes nor
19
- # excludes files).
20
- elsif pattern.empty?
21
- @regex = nil
22
- @inclusive = nil
23
-
24
- # Patterns containing three or more consecutive stars are invalid and
25
- # will be ignored.
26
- elsif pattern =~ /\*\*\*+/
27
- @regex = nil
28
- @inclusive = nil
29
-
30
- # EDGE CASE: According to git check-ignore (v2.4.1)), a single '/'
31
- # does not match any file
32
- elsif pattern == '/'
33
- @regex = nil
34
- @inclusive = nil
35
-
36
- # We have a valid pattern!
37
- else
38
- # A pattern starting with an exclamation mark ('!') negates the
39
- # pattern (exclude instead of include). Escape the exclamation
40
- # mark with a back-slash to match a literal exclamation mark
41
- # (i.e., '\!').
42
- if pattern.start_with?('!')
43
- @inclusive = false
44
- # Remove leading exclamation mark.
45
- pattern = pattern[1..-1]
3
+ class PathSpec
4
+ # Class for parsing a .gitignore spec
5
+ class GitIgnoreSpec < RegexSpec
6
+ attr_reader :regex
7
+ attr_reader :pattern
8
+
9
+ def initialize(original_pattern)
10
+ pattern = original_pattern.strip unless original_pattern.nil?
11
+
12
+ # A pattern starting with a hash ('#') serves as a comment
13
+ # (neither includes nor excludes files). Escape the hash with a
14
+ # back-slash to match a literal hash (i.e., '\#').
15
+ if pattern.start_with?('#')
16
+ @regex = nil
17
+ @inclusive = nil
18
+
19
+ # A blank pattern is a null-operation (neither includes nor
20
+ # excludes files).
21
+ elsif pattern.empty?
22
+ @regex = nil
23
+ @inclusive = nil
24
+
25
+ # Patterns containing three or more consecutive stars are invalid and
26
+ # will be ignored.
27
+ elsif pattern =~ /\*\*\*+/
28
+ @regex = nil
29
+ @inclusive = nil
30
+
31
+ # EDGE CASE: According to git check-ignore (v2.4.1)), a single '/'
32
+ # does not match any file
33
+ elsif pattern == '/'
34
+ @regex = nil
35
+ @inclusive = nil
36
+
37
+ # We have a valid pattern!
46
38
  else
47
- @inclusive = true
48
- end
49
-
50
- # Remove leading back-slash escape for escaped hash ('#') or
51
- # exclamation mark ('!').
52
- if pattern.start_with?('\\')
53
- pattern = pattern[1..-1]
54
- end
55
-
56
- # Split pattern into segments. -1 to allow trailing slashes.
57
- pattern_segs = pattern.split('/', -1)
58
-
59
- # Normalize pattern to make processing easier.
60
-
61
- # A pattern beginning with a slash ('/') will only match paths
62
- # directly on the root directory instead of any descendant
63
- # paths. So, remove empty first segment to make pattern relative
64
- # to root.
65
- if pattern_segs[0].empty?
66
- pattern_segs.shift
67
- elsif pattern_segs.length == 1 ||
68
- pattern_segs.length == 2 && pattern_segs[-1].empty?
69
- # A pattern without a beginning slash ('/') will match any
70
- # descendant path. This is equivilent to "**/{pattern}". So,
71
- # prepend with double-asterisks to make pattern relative to
72
- # root.
73
- # EDGE CASE: This also holds for a single pattern with a
74
- # trailing slash (e.g. dir/).
75
- if pattern_segs[0] != '**'
76
- pattern_segs.insert(0, '**')
39
+ # A pattern starting with an exclamation mark ('!') negates the
40
+ # pattern (exclude instead of include). Escape the exclamation
41
+ # mark with a back-slash to match a literal exclamation mark
42
+ # (i.e., '\!').
43
+ if pattern.start_with?('!')
44
+ @inclusive = false
45
+ # Remove leading exclamation mark.
46
+ pattern = pattern[1..-1]
47
+ else
48
+ @inclusive = true
77
49
  end
78
- end
79
-
80
- # A pattern ending with a slash ('/') will match all descendant
81
- # paths of if it is a directory but not if it is a regular file.
82
- # This is equivilent to "{pattern}/**". So, set last segment to
83
- # double asterisks to include all descendants.
84
- if pattern_segs[-1].empty? && pattern_segs.length > 1
85
- pattern_segs[-1] = '**'
86
- end
87
-
88
- # Handle platforms with backslash separated paths
89
- if File::SEPARATOR == '\\'
90
- path_sep = '\\\\'
91
- else
92
- path_sep = '/'
93
- end
94
50
 
51
+ # Remove leading back-slash escape for escaped hash ('#') or
52
+ # exclamation mark ('!').
53
+ pattern = pattern[1..-1] if pattern.start_with?('\\')
54
+
55
+ # Split pattern into segments. -1 to allow trailing slashes.
56
+ pattern_segs = pattern.split('/', -1)
57
+
58
+ # Normalize pattern to make processing easier.
59
+
60
+ # A pattern beginning with a slash ('/') will only match paths
61
+ # directly on the root directory instead of any descendant
62
+ # paths. So, remove empty first segment to make pattern relative
63
+ # to root.
64
+ if pattern_segs[0].empty?
65
+ pattern_segs.shift
66
+ elsif pattern_segs.length == 1 ||
67
+ pattern_segs.length == 2 && pattern_segs[-1].empty?
68
+ # A pattern without a beginning slash ('/') will match any
69
+ # descendant path. This is equivilent to "**/{pattern}". So,
70
+ # prepend with double-asterisks to make pattern relative to
71
+ # root.
72
+ # EDGE CASE: This also holds for a single pattern with a
73
+ # trailing slash (e.g. dir/).
74
+ pattern_segs.insert(0, '**') if pattern_segs[0] != '**'
75
+ end
95
76
 
96
- # Build regular expression from pattern.
97
- regex = '^'
98
- need_slash = false
99
- regex_end = pattern_segs.size - 1
100
- pattern_segs.each_index do |i|
101
- seg = pattern_segs[i]
102
-
103
- if seg == '**'
104
- # A pattern consisting solely of double-asterisks ('**')
105
- # will match every path.
106
- if i == 0 && i == regex_end
107
- regex.concat('.+')
108
-
109
- # A normalized pattern beginning with double-asterisks
110
- # ('**') will match any leading path segments.
111
- elsif i == 0
112
- regex.concat("(?:.+#{path_sep})?")
113
- need_slash = false
114
-
115
- # A normalized pattern ending with double-asterisks ('**')
116
- # will match any trailing path segments.
117
- elsif i == regex_end
118
- regex.concat("#{path_sep}.*")
119
-
120
- # A pattern with inner double-asterisks ('**') will match
121
- # multiple (or zero) inner path segments.
122
- else
123
- regex.concat("(?:#{path_sep}.+)?")
77
+ # A pattern ending with a slash ('/') will match all descendant
78
+ # paths of if it is a directory but not if it is a regular file.
79
+ # This is equivilent to "{pattern}/**". So, set last segment to
80
+ # double asterisks to include all descendants.
81
+ pattern_segs[-1] = '**' if pattern_segs[-1].empty? && pattern_segs.length > 1
82
+
83
+ # Handle platforms with backslash separated paths
84
+ path_sep = if File::SEPARATOR == '\\'
85
+ '\\\\'
86
+ else
87
+ '/'
88
+ end
89
+
90
+ # Build regular expression from pattern.
91
+ regex = '^'
92
+ need_slash = false
93
+ regex_end = pattern_segs.size - 1
94
+ pattern_segs.each_index do |i|
95
+ seg = pattern_segs[i]
96
+
97
+ if seg == '**'
98
+ # A pattern consisting solely of double-asterisks ('**')
99
+ # will match every path.
100
+ if i == 0 && i == regex_end
101
+ regex.concat('.+')
102
+
103
+ # A normalized pattern beginning with double-asterisks
104
+ # ('**') will match any leading path segments.
105
+ elsif i == 0
106
+ regex.concat("(?:.+#{path_sep})?")
107
+ need_slash = false
108
+
109
+ # A normalized pattern ending with double-asterisks ('**')
110
+ # will match any trailing path segments.
111
+ elsif i == regex_end
112
+ regex.concat("#{path_sep}.*")
113
+
114
+ # A pattern with inner double-asterisks ('**') will match
115
+ # multiple (or zero) inner path segments.
116
+ else
117
+ regex.concat("(?:#{path_sep}.+)?")
118
+ need_slash = true
119
+ end
120
+
121
+ # Match single path segment.
122
+ elsif seg == '*'
123
+ regex.concat(path_sep) if need_slash
124
+
125
+ regex.concat("[^#{path_sep}]+")
124
126
  need_slash = true
125
- end
126
-
127
- # Match single path segment.
128
- elsif seg == '*'
129
- if need_slash
130
- regex.concat(path_sep)
131
- end
132
127
 
133
- regex.concat("[^#{path_sep}]+")
134
- need_slash = true
128
+ else
129
+ # Match segment glob pattern.
130
+ regex.concat(path_sep) if need_slash
135
131
 
136
- else
137
- # Match segment glob pattern.
138
- if need_slash
139
- regex.concat(path_sep)
140
- end
132
+ regex.concat(translate_segment_glob(seg))
141
133
 
142
- regex.concat(translate_segment_glob(seg))
134
+ if i == regex_end && @inclusive
135
+ # A pattern ending without a slash ('/') will match a file
136
+ # or a directory (with paths underneath it).
137
+ # e.g. foo matches: foo, foo/bar, foo/bar/baz, etc.
138
+ # EDGE CASE: However, this does not hold for exclusion cases
139
+ # according to `git check-ignore` (v2.4.1).
140
+ regex.concat("(?:#{path_sep}.*)?")
141
+ end
143
142
 
144
- if i == regex_end && @inclusive
145
- # A pattern ending without a slash ('/') will match a file
146
- # or a directory (with paths underneath it).
147
- # e.g. foo matches: foo, foo/bar, foo/bar/baz, etc.
148
- # EDGE CASE: However, this does not hold for exclusion cases
149
- # according to `git check-ignore` (v2.4.1).
150
- regex.concat("(?:#{path_sep}.*)?")
143
+ need_slash = true
151
144
  end
152
-
153
- need_slash = true
154
145
  end
155
- end
156
146
 
157
- regex.concat('$')
158
- super(regex)
147
+ regex.concat('$')
148
+ super(regex)
149
+
150
+ # Copy original pattern
151
+ @pattern = original_pattern.dup
152
+ end
159
153
  end
160
- end
161
154
 
162
- def match(path)
163
- super(path)
164
- end
155
+ def match(path)
156
+ super(path)
157
+ end
165
158
 
166
- def translate_segment_glob(pattern)
167
- """
159
+ def translate_segment_glob(pattern)
160
+ ''"
168
161
  Translates the glob pattern to a regular expression. This is used in
169
162
  the constructor to translate a path segment glob pattern to its
170
163
  corresponding regular expression.
@@ -172,123 +165,116 @@ class GitIgnoreSpec < RegexSpec
172
165
  *pattern* (``str``) is the glob pattern.
173
166
 
174
167
  Returns the regular expression (``str``).
175
- """
176
- # NOTE: This is derived from `fnmatch.translate()` and is similar to
177
- # the POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set.
178
-
179
- escape = false
180
- regex = ''
181
- i = 0
182
-
183
- while i < pattern.size
184
- # Get next character.
185
- char = pattern[i].chr
186
- i += 1
187
-
188
- # Escape the character.
189
- if escape
190
- escape = false
191
- regex += Regexp.escape(char)
192
-
193
- # Escape character, escape next character.
194
- elsif char == '\\'
195
- escape = true
196
-
197
- # Multi-character wildcard. Match any string (except slashes),
198
- # including an empty string.
199
- elsif char == '*'
200
- regex += '[^/]*'
201
-
202
- # Single-character wildcard. Match any single character (except
203
- # a slash).
204
- elsif char == '?'
205
- regex += '[^/]'
206
-
207
- # Braket expression wildcard. Except for the beginning
208
- # exclamation mark, the whole braket expression can be used
209
- # directly as regex but we have to find where the expression
210
- # ends.
211
- # - "[][!]" matchs ']', '[' and '!'.
212
- # - "[]-]" matchs ']' and '-'.
213
- # - "[!]a-]" matchs any character except ']', 'a' and '-'.
214
- elsif char == '['
215
- j = i
216
- # Pass brack expression negation.
217
- if j < pattern.size && pattern[j].chr == '!'
218
- j += 1
219
- end
220
-
221
- # Pass first closing braket if it is at the beginning of the
222
- # expression.
223
- if j < pattern.size && pattern[j].chr == ']'
224
- j += 1
225
- end
226
-
227
- # Find closing braket. Stop once we reach the end or find it.
228
- while j < pattern.size && pattern[j].chr != ']'
229
- j += 1
230
- end
231
-
232
-
233
- if j < pattern.size
234
- expr = '['
235
-
236
- # Braket expression needs to be negated.
237
- if pattern[i].chr == '!'
238
- expr += '^'
239
- i += 1
240
-
241
- # POSIX declares that the regex braket expression negation
242
- # "[^...]" is undefined in a glob pattern. Python's
243
- # `fnmatch.translate()` escapes the caret ('^') as a
244
- # literal. To maintain consistency with undefined behavior,
245
- # I am escaping the '^' as well.
246
- elsif pattern[i].chr == '^'
247
- expr += '\\^'
248
- i += 1
249
- end
250
-
251
- # Escape brackets contained within pattern
252
- if pattern[i].chr == ']' && i != j
253
- expr += '\]'
254
- i += 1
168
+ "''
169
+ # NOTE: This is derived from `fnmatch.translate()` and is similar to
170
+ # the POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set.
171
+
172
+ escape = false
173
+ regex = ''
174
+ i = 0
175
+
176
+ while i < pattern.size
177
+ # Get next character.
178
+ char = pattern[i].chr
179
+ i += 1
180
+
181
+ # Escape the character.
182
+ if escape
183
+ escape = false
184
+ regex += Regexp.escape(char)
185
+
186
+ # Escape character, escape next character.
187
+ elsif char == '\\'
188
+ escape = true
189
+
190
+ # Multi-character wildcard. Match any string (except slashes),
191
+ # including an empty string.
192
+ elsif char == '*'
193
+ regex += '[^/]*'
194
+
195
+ # Single-character wildcard. Match any single character (except
196
+ # a slash).
197
+ elsif char == '?'
198
+ regex += '[^/]'
199
+
200
+ # Braket expression wildcard. Except for the beginning
201
+ # exclamation mark, the whole braket expression can be used
202
+ # directly as regex but we have to find where the expression
203
+ # ends.
204
+ # - "[][!]" matchs ']', '[' and '!'.
205
+ # - "[]-]" matchs ']' and '-'.
206
+ # - "[!]a-]" matchs any character except ']', 'a' and '-'.
207
+ elsif char == '['
208
+ j = i
209
+ # Pass brack expression negation.
210
+ j += 1 if j < pattern.size && pattern[j].chr == '!'
211
+
212
+ # Pass first closing braket if it is at the beginning of the
213
+ # expression.
214
+ j += 1 if j < pattern.size && pattern[j].chr == ']'
215
+
216
+ # Find closing braket. Stop once we reach the end or find it.
217
+ j += 1 while j < pattern.size && pattern[j].chr != ']'
218
+
219
+ if j < pattern.size
220
+ expr = '['
221
+
222
+ # Braket expression needs to be negated.
223
+ if pattern[i].chr == '!'
224
+ expr += '^'
225
+ i += 1
226
+
227
+ # POSIX declares that the regex braket expression negation
228
+ # "[^...]" is undefined in a glob pattern. Python's
229
+ # `fnmatch.translate()` escapes the caret ('^') as a
230
+ # literal. To maintain consistency with undefined behavior,
231
+ # I am escaping the '^' as well.
232
+ elsif pattern[i].chr == '^'
233
+ expr += '\\^'
234
+ i += 1
235
+ end
236
+
237
+ # Escape brackets contained within pattern
238
+ if pattern[i].chr == ']' && i != j
239
+ expr += '\]'
240
+ i += 1
241
+ end
242
+
243
+ # Build regex braket expression. Escape slashes so they are
244
+ # treated as literal slashes by regex as defined by POSIX.
245
+ expr += pattern[i..j].sub('\\', '\\\\')
246
+
247
+ # Add regex braket expression to regex result.
248
+ regex += expr
249
+
250
+ # Found end of braket expression. Increment j to be one past
251
+ # the closing braket:
252
+ #
253
+ # [...]
254
+ # ^ ^
255
+ # i j
256
+ #
257
+ j += 1
258
+ # Set i to one past the closing braket.
259
+ i = j
260
+
261
+ # Failed to find closing braket, treat opening braket as a
262
+ # braket literal instead of as an expression.
263
+ else
264
+ regex += '\['
255
265
  end
256
266
 
257
-
258
- # Build regex braket expression. Escape slashes so they are
259
- # treated as literal slashes by regex as defined by POSIX.
260
- expr += pattern[i..j].sub('\\', '\\\\')
261
-
262
- # Add regex braket expression to regex result.
263
- regex += expr
264
-
265
- # Found end of braket expression. Increment j to be one past
266
- # the closing braket:
267
- #
268
- # [...]
269
- # ^ ^
270
- # i j
271
- #
272
- j += 1
273
- # Set i to one past the closing braket.
274
- i = j
275
-
276
- # Failed to find closing braket, treat opening braket as a
277
- # braket literal instead of as an expression.
267
+ # Regular character, escape it for regex.
278
268
  else
279
- regex += '\['
269
+ regex << Regexp.escape(char)
280
270
  end
281
-
282
- # Regular character, escape it for regex.
283
- else
284
- regex << Regexp.escape(char)
285
271
  end
286
- end
287
272
 
288
- regex
289
- end
273
+ regex
274
+ end
290
275
 
291
- def inclusive?
292
- @inclusive
276
+ def inclusive?
277
+ @inclusive
278
+ end
293
279
  end
294
280
  end