fast_ignore 0.12.1 → 0.15.2
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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +157 -80
- data/lib/fast_ignore.rb +27 -26
- data/lib/fast_ignore/backports.rb +2 -2
- data/lib/fast_ignore/file_root.rb +39 -0
- data/lib/fast_ignore/gitignore_include_rule_builder.rb +102 -0
- data/lib/fast_ignore/gitignore_rule_builder.rb +182 -0
- data/lib/fast_ignore/gitignore_rule_regexp_builder.rb +76 -0
- data/lib/fast_ignore/gitignore_rule_scanner.rb +73 -0
- data/lib/fast_ignore/global_gitignore.rb +50 -0
- data/lib/fast_ignore/rule.rb +29 -13
- data/lib/fast_ignore/rule_builder.rb +18 -80
- data/lib/fast_ignore/rule_set.rb +23 -16
- data/lib/fast_ignore/rule_sets.rb +113 -0
- data/lib/fast_ignore/shebang_rule.rb +34 -14
- data/lib/fast_ignore/unmatchable_rule.rb +41 -0
- data/lib/fast_ignore/version.rb +1 -1
- metadata +21 -33
- data/.gitignore +0 -6
- data/.leftovers.yml +0 -5
- data/.rspec +0 -3
- data/.rubocop.yml +0 -216
- data/.simplecov +0 -12
- data/.spellr.yml +0 -5
- data/.spellr_wordlists/english.txt +0 -62
- data/.spellr_wordlists/ruby.txt +0 -1
- data/.spellr_wordlists/shell.txt +0 -3
- data/.travis.yml +0 -11
- data/Gemfile +0 -9
- data/Rakefile +0 -18
- data/bin/console +0 -8
- data/bin/ls +0 -6
- data/bin/ls_seconds +0 -21
- data/bin/setup +0 -8
- data/bin/time +0 -21
- data/fast_ignore.gemspec +0 -41
- data/lib/fast_ignore/fn_match_to_re.rb +0 -96
- data/lib/fast_ignore/rule_set_builder.rb +0 -141
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastIgnore
|
4
|
+
module GlobalGitignore
|
5
|
+
class << self
|
6
|
+
def path(root:)
|
7
|
+
gitconfig_gitignore_path(::File.expand_path('.git/config', root)) ||
|
8
|
+
gitconfig_gitignore_path(::File.expand_path('~/.gitconfig')) ||
|
9
|
+
gitconfig_gitignore_path(xdg_config_path) ||
|
10
|
+
gitconfig_gitignore_path('/etc/gitconfig') ||
|
11
|
+
default_global_gitignore_path
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def gitconfig_gitignore_path(config_path)
|
17
|
+
return unless config_path
|
18
|
+
return unless ::File.exist?(config_path)
|
19
|
+
|
20
|
+
ignore_path = ::File.readlines(config_path).find { |l| l.sub!(/\A\s*excludesfile\s*=/, '') }
|
21
|
+
return unless ignore_path
|
22
|
+
|
23
|
+
ignore_path.strip!
|
24
|
+
return ignore_path if ignore_path.empty? # don't expand path in this case
|
25
|
+
|
26
|
+
::File.expand_path(ignore_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def xdg_config_path
|
30
|
+
xdg_config_home? && ::File.expand_path('git/config', xdg_config_home)
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_global_gitignore_path
|
34
|
+
if xdg_config_home?
|
35
|
+
::File.expand_path('git/ignore', xdg_config_home)
|
36
|
+
else
|
37
|
+
::File.expand_path('~/.config/git/ignore')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def xdg_config_home
|
42
|
+
::ENV['XDG_CONFIG_HOME']
|
43
|
+
end
|
44
|
+
|
45
|
+
def xdg_config_home?
|
46
|
+
xdg_config_home && (not xdg_config_home.empty?)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/fast_ignore/rule.rb
CHANGED
@@ -6,24 +6,40 @@ class FastIgnore
|
|
6
6
|
alias_method :negation?, :negation
|
7
7
|
undef :negation
|
8
8
|
|
9
|
+
attr_reader :component_rules
|
10
|
+
attr_reader :component_rules_count
|
11
|
+
|
9
12
|
attr_reader :dir_only
|
10
13
|
alias_method :dir_only?, :dir_only
|
11
14
|
undef :dir_only
|
12
15
|
|
13
|
-
attr_reader :
|
14
|
-
alias_method :unanchored?, :unanchored
|
15
|
-
undef :unanchored
|
16
|
-
|
17
|
-
attr_reader :type
|
16
|
+
attr_reader :squashable_type
|
18
17
|
attr_reader :rule
|
19
18
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
19
|
+
def squash(rules)
|
20
|
+
# component rules is to improve the performance of repos with many .gitignore files. e.g. linux.
|
21
|
+
component_rules = rules.flat_map(&:component_rules)
|
22
|
+
::FastIgnore::Rule.new(
|
23
|
+
::Regexp.union(component_rules.map(&:rule)).freeze,
|
24
|
+
@negation, @anchored, @dir_only, component_rules
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(rule, negation, anchored, dir_only, component_rules = self) # rubocop:disable Metrics/MethodLength
|
29
|
+
@rule = rule
|
30
|
+
@anchored = anchored
|
23
31
|
@dir_only = dir_only
|
24
32
|
@negation = negation
|
33
|
+
@component_rules = component_rules
|
34
|
+
@component_rules_count = component_rules == self ? 1 : component_rules.length
|
25
35
|
|
26
|
-
@
|
36
|
+
@squashable_type = if anchored && negation
|
37
|
+
1
|
38
|
+
elsif anchored
|
39
|
+
0
|
40
|
+
else
|
41
|
+
::Float::NAN # because it doesn't equal itself
|
42
|
+
end
|
27
43
|
|
28
44
|
freeze
|
29
45
|
end
|
@@ -32,17 +48,17 @@ class FastIgnore
|
|
32
48
|
false
|
33
49
|
end
|
34
50
|
|
35
|
-
def shebang
|
36
|
-
|
51
|
+
def shebang?
|
52
|
+
false
|
37
53
|
end
|
38
54
|
|
39
55
|
# :nocov:
|
40
56
|
def inspect
|
41
|
-
"#<Rule #{'!' if @negation}#{@rule}#{'/' if @dir_only}>"
|
57
|
+
"#<Rule #{'!' if @negation}#{'/' if @anchored}#{@rule}#{'/' if @dir_only}>"
|
42
58
|
end
|
43
59
|
# :nocov:
|
44
60
|
|
45
|
-
def match?(relative_path,
|
61
|
+
def match?(relative_path, _full_path, _filename, _content)
|
46
62
|
@rule.match?(relative_path)
|
47
63
|
end
|
48
64
|
end
|
@@ -7,96 +7,34 @@ class FastIgnore
|
|
7
7
|
using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
|
8
8
|
# :nocov:
|
9
9
|
|
10
|
-
def build(rule, allow,
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
gitignore_rules(rule, allow, expand_path, file_root)
|
10
|
+
def build(rule, allow, expand_path_with, file_root)
|
11
|
+
if rule.delete_prefix!('#!:')
|
12
|
+
shebang_rules(rule, allow, file_root)
|
13
|
+
else
|
14
|
+
gitignore_rules(rule, allow, file_root, expand_path_with)
|
15
|
+
end
|
17
16
|
end
|
18
17
|
|
19
18
|
private
|
20
19
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def remove_shebang(rule)
|
27
|
-
return unless rule.delete_prefix!('#!:')
|
28
|
-
|
29
|
-
rule.strip!
|
30
|
-
|
31
|
-
true
|
32
|
-
end
|
33
|
-
|
34
|
-
def shebang_rules(rule, allow)
|
35
|
-
rules = [::FastIgnore::ShebangRule.new(/\A#!.*\b#{Regexp.escape(rule)}\b/.freeze, allow)]
|
36
|
-
return rules unless allow
|
20
|
+
def shebang_rules(shebang, allow, file_root)
|
21
|
+
shebang.strip!
|
22
|
+
pattern = /\A#!.*\b#{::Regexp.escape(shebang)}\b/i
|
23
|
+
rule = ::FastIgnore::ShebangRule.new(pattern, allow, file_root&.shebang_path_pattern)
|
24
|
+
return rule unless allow
|
37
25
|
|
38
|
-
rules
|
26
|
+
rules = gitignore_rules('*/'.dup, allow, file_root)
|
27
|
+
rules.pop # don't want the include all children one.
|
28
|
+
rules << rule
|
39
29
|
rules
|
40
30
|
end
|
41
31
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
def gitignore_rules(rule, allow, expand_path, file_root)
|
47
|
-
dir_only = extract_dir_only(rule)
|
48
|
-
negation = extract_negation(rule, allow)
|
49
|
-
|
50
|
-
unanchored = if expand_path
|
51
|
-
expand_rule_path(rule, expand_path)
|
32
|
+
def gitignore_rules(rule, allow, file_root, expand_path_with = nil)
|
33
|
+
if allow
|
34
|
+
::FastIgnore::GitignoreIncludeRuleBuilder.new(rule, file_root, expand_path_with).build
|
52
35
|
else
|
53
|
-
|
54
|
-
end
|
55
|
-
rule.delete_prefix!('/')
|
56
|
-
|
57
|
-
rule.prepend("#{file_root}#{'**/' if unanchored}") if file_root || unanchored
|
58
|
-
|
59
|
-
build_gitignore_rules(rule, unanchored, allow, dir_only, negation)
|
60
|
-
end
|
61
|
-
|
62
|
-
def extract_dir_only(rule)
|
63
|
-
rule.delete_suffix!('/')
|
64
|
-
end
|
65
|
-
|
66
|
-
def extract_negation(rule, allow)
|
67
|
-
return allow unless rule.delete_prefix!('!')
|
68
|
-
|
69
|
-
not allow
|
70
|
-
end
|
71
|
-
|
72
|
-
EXPAND_PATH_RE = %r{^(?:[~/]|\.{1,2}/)}.freeze
|
73
|
-
def expand_rule_path(rule, root)
|
74
|
-
rule.replace(::File.expand_path(rule)) if rule.match?(EXPAND_PATH_RE)
|
75
|
-
rule.delete_prefix!(root)
|
76
|
-
rule.start_with?('*')
|
77
|
-
end
|
78
|
-
|
79
|
-
def unanchored?(rule)
|
80
|
-
not rule.include?('/') # we've already removed the trailing '/' with extract_dir_only
|
81
|
-
end
|
82
|
-
|
83
|
-
def build_gitignore_rules(rule, unanchored, allow, dir_only, negation)
|
84
|
-
rules = [::FastIgnore::Rule.new(rule, negation, unanchored, dir_only)]
|
85
|
-
return rules unless allow
|
86
|
-
|
87
|
-
rules << ::FastIgnore::Rule.new("#{rule}/**/*", negation, unanchored, false)
|
88
|
-
rules + ancestor_rules(rule, unanchored)
|
89
|
-
end
|
90
|
-
|
91
|
-
def ancestor_rules(parent, unanchored)
|
92
|
-
ancestor_rules = []
|
93
|
-
|
94
|
-
while (parent = ::File.dirname(parent)) != '.'
|
95
|
-
rule = ::File.basename(parent) == '**' ? "#{parent}/*" : parent.freeze
|
96
|
-
ancestor_rules << ::FastIgnore::Rule.new(rule, true, unanchored, true)
|
36
|
+
::FastIgnore::GitignoreRuleBuilder.new(rule, file_root).build
|
97
37
|
end
|
98
|
-
|
99
|
-
ancestor_rules
|
100
38
|
end
|
101
39
|
end
|
102
40
|
end
|
data/lib/fast_ignore/rule_set.rb
CHANGED
@@ -6,11 +6,10 @@ class FastIgnore
|
|
6
6
|
alias_method :gitignore?, :gitignore
|
7
7
|
undef :gitignore
|
8
8
|
|
9
|
-
def initialize(rules, allow, gitignore)
|
10
|
-
@dir_rules = squash_rules(rules.reject(&:file_only?)).freeze
|
11
|
-
@file_rules = squash_rules(rules.reject(&:dir_only?)).freeze
|
12
|
-
@
|
13
|
-
@has_shebang_rules = rules.any?(&:shebang)
|
9
|
+
def initialize(rules, allow, gitignore, squash = true)
|
10
|
+
@dir_rules = (squash ? squash_rules(rules.reject(&:file_only?)) : rules.reject(&:file_only?)).freeze
|
11
|
+
@file_rules = (squash ? squash_rules(rules.reject(&:dir_only?)) : rules.reject(&:dir_only?)).freeze
|
12
|
+
@has_shebang_rules = rules.any?(&:shebang?)
|
14
13
|
|
15
14
|
@allowed_recursive = { '.' => true }
|
16
15
|
@allow = allow
|
@@ -22,10 +21,9 @@ class FastIgnore
|
|
22
21
|
def <<(other)
|
23
22
|
return unless other
|
24
23
|
|
25
|
-
@any_not_anchored ||= other.any_not_anchored
|
26
24
|
@has_shebang_rules ||= other.has_shebang_rules
|
27
|
-
@dir_rules
|
28
|
-
@file_rules
|
25
|
+
@dir_rules = squash_rules(@dir_rules + other.dir_rules)
|
26
|
+
@file_rules = squash_rules(@file_rules + other.file_rules)
|
29
27
|
end
|
30
28
|
|
31
29
|
def allowed_recursive?(relative_path, dir, full_path, filename, content = nil)
|
@@ -41,17 +39,24 @@ class FastIgnore
|
|
41
39
|
return rule.negation? if rule.match?(relative_path, full_path, filename, content)
|
42
40
|
end
|
43
41
|
|
44
|
-
|
42
|
+
not @allow
|
45
43
|
end
|
46
44
|
|
47
|
-
def squash_rules(rules)
|
48
|
-
|
49
|
-
|
45
|
+
def squash_rules(rules) # rubocop:disable Metrics/MethodLength
|
46
|
+
running_component_rule_size = rules.first&.component_rules_count || 0
|
47
|
+
rules.chunk_while do |a, b|
|
48
|
+
# a.squashable_type == b.squashable_type
|
49
|
+
next true if a.squashable_type == b.squashable_type &&
|
50
|
+
(running_component_rule_size + b.component_rules_count <= 40)
|
50
51
|
|
51
|
-
|
52
|
-
|
52
|
+
running_component_rule_size = b.component_rules_count
|
53
|
+
false
|
54
|
+
end.map do |chunk| # rubocop:disable Style/MultilineBlockChain
|
55
|
+
first = chunk.first
|
56
|
+
next first if chunk.length == 1
|
53
57
|
|
54
|
-
|
58
|
+
first.squash(chunk)
|
59
|
+
end
|
55
60
|
end
|
56
61
|
|
57
62
|
def weight
|
@@ -64,6 +69,8 @@ class FastIgnore
|
|
64
69
|
|
65
70
|
protected
|
66
71
|
|
67
|
-
attr_reader :dir_rules
|
72
|
+
attr_reader :dir_rules
|
73
|
+
attr_reader :file_rules
|
74
|
+
attr_reader :has_shebang_rules
|
68
75
|
end
|
69
76
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FastIgnore
|
4
|
+
class RuleSets
|
5
|
+
# :nocov:
|
6
|
+
using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
|
7
|
+
# :nocov:
|
8
|
+
|
9
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
10
|
+
root:,
|
11
|
+
ignore_rules: nil,
|
12
|
+
ignore_files: nil,
|
13
|
+
gitignore: true,
|
14
|
+
include_rules: nil,
|
15
|
+
include_files: nil,
|
16
|
+
argv_rules: nil
|
17
|
+
)
|
18
|
+
@array = []
|
19
|
+
@project_root = root
|
20
|
+
append_root_gitignore(gitignore)
|
21
|
+
append_set_from_array(ignore_rules)
|
22
|
+
append_set_from_array(include_rules, allow: true)
|
23
|
+
append_set_from_array(argv_rules, allow: true, expand_path_with: @project_root)
|
24
|
+
append_sets_from_files(ignore_files)
|
25
|
+
append_sets_from_files(include_files, allow: true)
|
26
|
+
@array.sort_by!(&:weight)
|
27
|
+
@array.freeze if @gitignore_rule_set
|
28
|
+
end
|
29
|
+
|
30
|
+
def allowed_recursive?(relative_path, full_path, filename, content)
|
31
|
+
@array.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename, content) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def allowed_unrecursive?(relative_path, dir, full_path, filename)
|
35
|
+
@array.all? { |r| r.allowed_unrecursive?(relative_path, dir, full_path, filename, nil) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def append_subdir_gitignore(relative_path:, check_exists: true) # rubocop:disable Metrics/MethodLength
|
39
|
+
if @gitignore_rule_set
|
40
|
+
new_gitignore = build_set_from_file(relative_path, gitignore: true, check_exists: check_exists, squash: false)
|
41
|
+
return if !new_gitignore || new_gitignore.empty?
|
42
|
+
|
43
|
+
@gitignore_rule_set << new_gitignore
|
44
|
+
else
|
45
|
+
new_gitignore = build_set_from_file(relative_path, gitignore: true, check_exists: check_exists)
|
46
|
+
return if !new_gitignore || new_gitignore.empty?
|
47
|
+
|
48
|
+
@array << new_gitignore
|
49
|
+
@gitignore_rule_set = new_gitignore
|
50
|
+
@array.sort_by!(&:weight) && @array.freeze
|
51
|
+
end
|
52
|
+
new_gitignore
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def append_and_return_if_present(value)
|
58
|
+
return unless value && !value.empty?
|
59
|
+
|
60
|
+
@array << value
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def append_root_gitignore(gitignore)
|
65
|
+
return @gitignore_rule_set = nil unless gitignore
|
66
|
+
|
67
|
+
append_set_from_array('.git')
|
68
|
+
gi = ::FastIgnore::RuleSet.new([], false, true)
|
69
|
+
gi << build_from_root_gitignore_file(::FastIgnore::GlobalGitignore.path(root: @project_root))
|
70
|
+
gi << build_from_root_gitignore_file("#{@project_root}.git/info/exclude")
|
71
|
+
gi << build_from_root_gitignore_file("#{@project_root}.gitignore")
|
72
|
+
@gitignore_rule_set = append_and_return_if_present(gi)
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_from_root_gitignore_file(path)
|
76
|
+
return unless ::File.exist?(path)
|
77
|
+
|
78
|
+
build_rule_set(::File.readlines(path), false, gitignore: true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_rule_set(rules, allow, expand_path_with: nil, file_root: nil, gitignore: false, squash: true) # rubocop:disable Metrics/ParameterLists
|
82
|
+
rules = rules.flat_map do |rule|
|
83
|
+
::FastIgnore::RuleBuilder.build(rule, allow, expand_path_with, file_root)
|
84
|
+
end
|
85
|
+
|
86
|
+
::FastIgnore::RuleSet.new(rules, allow, gitignore, squash)
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_set_from_file(filename, allow: false, gitignore: false, check_exists: false, squash: true)
|
90
|
+
filename = ::File.expand_path(filename, @project_root)
|
91
|
+
return if check_exists && !::File.exist?(filename)
|
92
|
+
raise ::FastIgnore::Error, "#{filename} is not within #{@project_root}" unless filename.start_with?(@project_root)
|
93
|
+
|
94
|
+
file_root = ::FastIgnore::FileRoot.build(filename, @project_root)
|
95
|
+
build_rule_set(::File.readlines(filename), allow, file_root: file_root, gitignore: gitignore, squash: squash)
|
96
|
+
end
|
97
|
+
|
98
|
+
def append_sets_from_files(files, allow: false)
|
99
|
+
Array(files).each do |file|
|
100
|
+
append_and_return_if_present(build_set_from_file(file, allow: allow))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def append_set_from_array(rules, allow: false, expand_path_with: nil)
|
105
|
+
return unless rules
|
106
|
+
|
107
|
+
rules = Array(rules).flat_map { |string| string.to_s.lines }
|
108
|
+
return if rules.empty?
|
109
|
+
|
110
|
+
append_and_return_if_present(build_rule_set(rules, allow, expand_path_with: expand_path_with))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -7,16 +7,25 @@ class FastIgnore
|
|
7
7
|
undef :negation
|
8
8
|
|
9
9
|
attr_reader :rule
|
10
|
-
alias_method :shebang, :rule
|
11
10
|
|
12
|
-
attr_reader :
|
11
|
+
attr_reader :file_path_pattern
|
13
12
|
|
14
|
-
|
13
|
+
attr_reader :squashable_type
|
14
|
+
|
15
|
+
def squash(rules)
|
16
|
+
::FastIgnore::ShebangRule.new(::Regexp.union(rules.map(&:rule)).freeze, negation?, file_path_pattern)
|
17
|
+
end
|
18
|
+
|
19
|
+
def component_rules_count
|
20
|
+
1
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(rule, negation, file_path_pattern)
|
15
24
|
@rule = rule
|
16
25
|
@negation = negation
|
26
|
+
@file_path_pattern = file_path_pattern
|
17
27
|
|
18
|
-
@
|
19
|
-
@type += 1 if negation
|
28
|
+
@squashable_type = (negation ? 13 : 12) + file_path_pattern.object_id
|
20
29
|
|
21
30
|
freeze
|
22
31
|
end
|
@@ -29,28 +38,39 @@ class FastIgnore
|
|
29
38
|
false
|
30
39
|
end
|
31
40
|
|
32
|
-
def unanchored?
|
33
|
-
true
|
34
|
-
end
|
35
|
-
|
36
41
|
# :nocov:
|
37
42
|
def inspect
|
38
|
-
|
43
|
+
allow_fragment = 'allow ' if @negation
|
44
|
+
in_fragment = " in #{@file_path_pattern}" if @file_path_pattern
|
45
|
+
"#<ShebangRule #{allow_fragment}#!:#{@rule.to_s[15..-4]}#{in_fragment}>"
|
39
46
|
end
|
40
47
|
# :nocov:
|
41
48
|
|
42
|
-
def match?(
|
49
|
+
def match?(relative_path, full_path, filename, content)
|
43
50
|
return false if filename.include?('.')
|
51
|
+
return false unless (not @file_path_pattern) || @file_path_pattern.match?(relative_path)
|
44
52
|
|
45
53
|
(content || first_line(full_path))&.match?(@rule)
|
46
54
|
end
|
47
55
|
|
56
|
+
def shebang?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
48
60
|
private
|
49
61
|
|
50
|
-
def first_line(path)
|
62
|
+
def first_line(path) # rubocop:disable Metrics/MethodLength
|
51
63
|
file = ::File.new(path)
|
52
|
-
first_line = file.sysread(
|
53
|
-
|
64
|
+
first_line = new_fragment = file.sysread(64)
|
65
|
+
if first_line.start_with?('#!')
|
66
|
+
until new_fragment.include?("\n")
|
67
|
+
new_fragment = file.sysread(64)
|
68
|
+
first_line += new_fragment
|
69
|
+
end
|
70
|
+
else
|
71
|
+
file.close
|
72
|
+
return
|
73
|
+
end
|
54
74
|
file.close
|
55
75
|
first_line
|
56
76
|
rescue ::EOFError, ::SystemCallError
|