grepfruit 3.1.0 → 3.1.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 +4 -4
- data/CHANGELOG.md +6 -1
- data/lib/grepfruit/cli.rb +3 -5
- data/lib/grepfruit/decorator.rb +7 -6
- data/lib/grepfruit/search.rb +58 -58
- data/lib/grepfruit/search_results.rb +25 -0
- data/lib/grepfruit/version.rb +1 -1
- data/lib/grepfruit.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51c0381a8449c749e2017cf4feac9cf35fb2c58e38f19a6a00fe994997d38b17
|
4
|
+
data.tar.gz: 61bca9861ce7d63331285d7b089e8521eac58f19465c293a50b07c5e4eb3cf01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e16bd1bc1c78d56b9253e2be32788f7dc5a03584de099e6ff6e7b78f581ea7ba9af018dc6c7b17dda06a7f55ab61488181237320b16fc3cd5768422a16d0cf8f
|
7
|
+
data.tar.gz: 948e8fadc208c058cfa943eeabb79a8b17d499fb78277e757b0655357dd028d8f312d008c52098c14bdcc2d1e94279fcefc1a821467e54020a26b209a72000d1
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
## v3.1.1
|
2
|
+
|
3
|
+
- Fixed JSON timestamp to reflect result generation time
|
4
|
+
- Minor optimization
|
5
|
+
|
1
6
|
## v3.1.0
|
2
7
|
|
3
8
|
- Added --include option to specify files to include in the search
|
4
|
-
- Both --exclude and --include options can now accept
|
9
|
+
- Both --exclude and --include options can now accept wildcard patterns
|
5
10
|
|
6
11
|
## v3.0.0
|
7
12
|
|
data/lib/grepfruit/cli.rb
CHANGED
@@ -26,9 +26,9 @@ module Grepfruit
|
|
26
26
|
exclude: options[:exclude] || [],
|
27
27
|
include: options[:include] || [],
|
28
28
|
truncate: options[:truncate]&.to_i,
|
29
|
-
search_hidden:
|
29
|
+
search_hidden: options[:search_hidden],
|
30
30
|
jobs: options[:jobs]&.to_i,
|
31
|
-
json_output:
|
31
|
+
json_output: options[:json]
|
32
32
|
).run
|
33
33
|
end
|
34
34
|
|
@@ -36,9 +36,7 @@ module Grepfruit
|
|
36
36
|
|
37
37
|
def validate_options!(options)
|
38
38
|
error_exit("You must specify a regex pattern using the -r or --regex option.") unless options[:regex]
|
39
|
-
|
40
|
-
jobs = options[:jobs]&.to_i
|
41
|
-
error_exit("Number of jobs must be at least 1") if jobs && jobs < 1
|
39
|
+
error_exit("Number of jobs must be at least 1") if (jobs = options[:jobs]&.to_i) && jobs < 1
|
42
40
|
end
|
43
41
|
|
44
42
|
def create_regex(pattern)
|
data/lib/grepfruit/decorator.rb
CHANGED
@@ -4,9 +4,10 @@ module Grepfruit
|
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def
|
8
|
-
def
|
9
|
-
def
|
7
|
+
def colorize(text, color) = "#{COLORS[color]}#{text}#{COLORS[:reset]}"
|
8
|
+
def green(text) = colorize(text, :green)
|
9
|
+
def red(text) = colorize(text, :red)
|
10
|
+
def cyan(text) = colorize(text, :cyan)
|
10
11
|
|
11
12
|
def number_of_files(num) = "#{num} file#{'s' if num != 1}"
|
12
13
|
def number_of_matches(num) = "#{num} match#{'es' if num != 1}"
|
@@ -16,8 +17,8 @@ module Grepfruit
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def processed_line(line)
|
19
|
-
|
20
|
-
truncate &&
|
20
|
+
stripped = line.strip
|
21
|
+
truncate && stripped.length > truncate ? "#{stripped[0...truncate]}..." : stripped
|
21
22
|
end
|
22
23
|
|
23
24
|
def display_results(lines, files, files_with_matches)
|
@@ -41,7 +42,7 @@ module Grepfruit
|
|
41
42
|
directory: dir,
|
42
43
|
exclusions: (excluded_paths + excluded_lines).map { |path_parts| path_parts.join("/") },
|
43
44
|
inclusions: included_paths.map { |path_parts| path_parts.join("/") },
|
44
|
-
timestamp:
|
45
|
+
timestamp: Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")
|
45
46
|
}
|
46
47
|
|
47
48
|
summary = {
|
data/lib/grepfruit/search.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require "find"
|
2
2
|
require "etc"
|
3
3
|
|
4
|
-
require_relative "decorator"
|
5
|
-
|
6
4
|
Warning[:experimental] = false
|
7
5
|
|
8
6
|
module Grepfruit
|
@@ -20,49 +18,77 @@ module Grepfruit
|
|
20
18
|
@search_hidden = search_hidden
|
21
19
|
@jobs = jobs || Etc.nprocessors
|
22
20
|
@json_output = json_output
|
23
|
-
@start_time = Time.now
|
24
21
|
end
|
25
22
|
|
26
23
|
def run
|
27
24
|
puts "Searching for #{regex.inspect} in #{dir.inspect}...\n\n" unless json_output
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
display_final_results(execute_search)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def execute_search
|
32
|
+
results = SearchResults.new
|
33
|
+
workers = Array.new(jobs) { create_persistent_worker }
|
32
34
|
file_enumerator = create_file_enumerator
|
33
35
|
active_workers = {}
|
34
36
|
|
35
37
|
workers.each do |worker|
|
36
|
-
assign_file_to_worker(worker, file_enumerator, active_workers)
|
38
|
+
assign_file_to_worker(worker, file_enumerator, active_workers, results)
|
37
39
|
end
|
38
40
|
|
39
41
|
while active_workers.any?
|
40
|
-
ready_worker,
|
42
|
+
ready_worker, worker_result = Ractor.select(*active_workers.keys)
|
41
43
|
active_workers.delete(ready_worker)
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
assign_file_to_worker(ready_worker, file_enumerator, active_workers) && total_files += 1
|
45
|
+
results.increment_files_with_matches if process_worker_result(worker_result, results)
|
46
|
+
assign_file_to_worker(ready_worker, file_enumerator, active_workers, results)
|
46
47
|
end
|
47
48
|
|
48
|
-
workers
|
49
|
+
shutdown_workers(workers)
|
50
|
+
results
|
51
|
+
end
|
49
52
|
|
53
|
+
def display_final_results(results)
|
50
54
|
if json_output
|
51
|
-
display_json_results(raw_matches, total_files, total_files_with_matches)
|
55
|
+
display_json_results(results.raw_matches, results.total_files, results.total_files_with_matches)
|
52
56
|
else
|
53
|
-
display_results(all_lines, total_files, total_files_with_matches)
|
57
|
+
display_results(results.all_lines, results.total_files, results.total_files_with_matches)
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
57
|
-
|
61
|
+
def create_persistent_worker
|
62
|
+
Ractor.new do
|
63
|
+
loop do
|
64
|
+
work = Ractor.receive
|
65
|
+
break if work == :quit
|
66
|
+
|
67
|
+
file_path, pattern, exc_lines, base_dir = work
|
68
|
+
file_results, has_matches = [], false
|
69
|
+
|
70
|
+
File.foreach(file_path).with_index do |line, line_num|
|
71
|
+
next unless line.valid_encoding? && line.match?(pattern)
|
72
|
+
|
73
|
+
relative_path = file_path.delete_prefix("#{base_dir}/")
|
74
|
+
next if exc_lines.any? { "#{relative_path}:#{line_num + 1}".end_with?(_1.join("/")) }
|
75
|
+
|
76
|
+
file_results << [relative_path, line_num + 1, line]
|
77
|
+
has_matches = true
|
78
|
+
end
|
79
|
+
|
80
|
+
Ractor.yield([file_results, has_matches])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
58
84
|
|
59
|
-
def assign_file_to_worker(worker, file_enumerator, active_workers)
|
85
|
+
def assign_file_to_worker(worker, file_enumerator, active_workers, results)
|
60
86
|
file_path = get_next_file(file_enumerator)
|
61
|
-
return
|
87
|
+
return unless file_path
|
62
88
|
|
63
89
|
worker.send([file_path, regex, excluded_lines, dir])
|
64
90
|
active_workers[worker] = file_path
|
65
|
-
|
91
|
+
results.total_files += 1
|
66
92
|
end
|
67
93
|
|
68
94
|
def get_next_file(enumerator)
|
@@ -71,27 +97,9 @@ module Grepfruit
|
|
71
97
|
nil
|
72
98
|
end
|
73
99
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
loop do
|
78
|
-
file_path, pattern, exc_lines, base_dir = Ractor.receive
|
79
|
-
results, has_matches = [], false
|
80
|
-
|
81
|
-
File.foreach(file_path).with_index do |line, line_num|
|
82
|
-
next unless line.valid_encoding? && line.match?(pattern)
|
83
|
-
|
84
|
-
relative_path = file_path.delete_prefix("#{base_dir}/")
|
85
|
-
next if exc_lines.any? { "#{relative_path}:#{line_num + 1}".end_with?(_1.join("/")) }
|
86
|
-
|
87
|
-
results << [relative_path, line_num + 1, line]
|
88
|
-
has_matches = true
|
89
|
-
end
|
90
|
-
|
91
|
-
Ractor.yield([results, has_matches])
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
100
|
+
def shutdown_workers(workers)
|
101
|
+
workers.each { |worker| worker.send(:quit) }
|
102
|
+
workers.each(&:close_outgoing)
|
95
103
|
end
|
96
104
|
|
97
105
|
def create_file_enumerator
|
@@ -109,36 +117,32 @@ module Grepfruit
|
|
109
117
|
end
|
110
118
|
end
|
111
119
|
|
112
|
-
def process_worker_result(
|
120
|
+
def process_worker_result(worker_result, results)
|
121
|
+
file_results, has_matches = worker_result
|
122
|
+
|
113
123
|
if has_matches
|
114
|
-
|
124
|
+
results.add_raw_matches(file_results) if json_output
|
115
125
|
|
116
126
|
unless json_output
|
117
127
|
colored_lines = file_results.map do |relative_path, line_num, line_content|
|
118
128
|
"#{cyan("#{relative_path}:#{line_num}")}: #{processed_line(line_content)}"
|
119
129
|
end
|
120
|
-
|
130
|
+
results.add_lines(colored_lines)
|
121
131
|
print red("M")
|
122
132
|
end
|
123
|
-
true
|
124
133
|
else
|
125
134
|
print green(".") unless json_output
|
126
|
-
false
|
127
135
|
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def excluded_path?(path)
|
131
|
-
rel_path = relative_path(path)
|
132
136
|
|
133
|
-
|
137
|
+
has_matches
|
134
138
|
end
|
135
139
|
|
136
|
-
def
|
137
|
-
|
138
|
-
end
|
140
|
+
def excluded_path?(path)
|
141
|
+
rel_path = path.delete_prefix("#{dir}/")
|
139
142
|
|
140
|
-
|
141
|
-
|
143
|
+
(File.file?(path) && included_paths.any? && !matches_pattern?(included_paths, rel_path)) ||
|
144
|
+
matches_pattern?(excluded_paths, rel_path) ||
|
145
|
+
(!search_hidden && File.basename(path).start_with?("."))
|
142
146
|
end
|
143
147
|
|
144
148
|
def matches_pattern?(pattern_list, path)
|
@@ -147,9 +151,5 @@ module Grepfruit
|
|
147
151
|
File.fnmatch?(pattern, path, File::FNM_PATHNAME) || File.fnmatch?(pattern, File.basename(path))
|
148
152
|
end
|
149
153
|
end
|
150
|
-
|
151
|
-
def relative_path(path)
|
152
|
-
path.delete_prefix("#{dir}/")
|
153
|
-
end
|
154
154
|
end
|
155
155
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Grepfruit
|
2
|
+
class SearchResults
|
3
|
+
attr_reader :all_lines, :raw_matches, :total_files_with_matches
|
4
|
+
attr_accessor :total_files
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@all_lines = []
|
8
|
+
@raw_matches = []
|
9
|
+
@total_files = 0
|
10
|
+
@total_files_with_matches = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def increment_files_with_matches
|
14
|
+
@total_files_with_matches += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_lines(lines)
|
18
|
+
@all_lines.concat(lines)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_raw_matches(matches)
|
22
|
+
@raw_matches.concat(matches)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/grepfruit/version.rb
CHANGED
data/lib/grepfruit.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grepfruit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- enjaku4
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-07-
|
10
|
+
date: 2025-07-16 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: dry-cli
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- lib/grepfruit/cli.rb
|
38
38
|
- lib/grepfruit/decorator.rb
|
39
39
|
- lib/grepfruit/search.rb
|
40
|
+
- lib/grepfruit/search_results.rb
|
40
41
|
- lib/grepfruit/version.rb
|
41
42
|
homepage: https://github.com/brownboxdev/grepfruit
|
42
43
|
licenses:
|