pathspec 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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