grepfruit 3.2.1 → 4.0.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
2
  SHA256:
3
- metadata.gz: fe98ab0d2336cca1ac32274bd34aac1b21599fbf0754e3d282c85bb1f91062f3
4
- data.tar.gz: db3c4492f5aa9d45131dff7c0342dd58e74c96b04bfacb538c6131e4ca2950ee
3
+ metadata.gz: d27a11eff0190c85e32b6081219b6db2b3f79e92170f85443e7e422df011bd5e
4
+ data.tar.gz: 7d1ab993aa73df36b9cfd1653721757d0e6f0473413562900231da351f9dbc89
5
5
  SHA512:
6
- metadata.gz: 30c29450c9e0535f2926de44a66adbcccf1021537c1d00ed3e4a9282cda2862c34f4235f1847740488c67dde79083e9b86b175a729cd8f5ee980cf2ee9a02e1c
7
- data.tar.gz: cb46abd7db199d7305d4a1c5f67b9fa6153a422ff8219213b7ef30dc196632d1fcac78a2c6442602713b79df2bddf1c015eb692631468d1cb3a6e1a8b85c2b6c
6
+ metadata.gz: a8f66005b0f1ddc8a75747fa4c24b16e6401332fad5061b57220f766eba2a64691a46f9686c349dce4f7b7b42b308e5e7141da58177b338a5104386573f5322e
7
+ data.tar.gz: 8ed7dc08c649a3becacc813a9e6f7bfbb0722c14a5b63cf3749e9a00cf2333fa75b8990ac2656d82c47dea5422bcb06e48ebc00eb413aca362fdb6d6e91b754a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v4.0.1
2
+
3
+ - Restored accidentally removed CLI progress indicator
4
+
5
+ ## v4.0.0
6
+
7
+ - Match results now always contain absolute file paths
8
+ - Renamed `directory` key to `path` in programmatic API search results
9
+ - Minor performance improvements
10
+ - Dropped support for Ruby 3.2
11
+
1
12
  ## v3.2.1
2
13
 
3
14
  - Skip unreadable files instead of crashing
data/README.md CHANGED
@@ -1,24 +1,18 @@
1
- # Grepfruit: Text Search Tool
1
+ # Grepfruit: Regex Search in Files
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/grepfruit.svg)](http://badge.fury.io/rb/grepfruit)
4
4
  [![Downloads](https://img.shields.io/gem/dt/grepfruit.svg)](https://rubygems.org/gems/grepfruit)
5
5
  [![Github Actions badge](https://github.com/enjaku4/grepfruit/actions/workflows/ci.yml/badge.svg)](https://github.com/enjaku4/grepfruit/actions/workflows/ci.yml)
6
6
  [![License](https://img.shields.io/github/license/enjaku4/grepfruit.svg)](LICENSE)
7
7
 
8
- Grepfruit is a tool for searching regex patterns in files, with a CLI designed for CI/CD pipelines and a programmatic API for Ruby applications.
9
-
10
- <img width="774" height="155" alt="Screenshot 2026-01-02 at 17 15 48" src="https://github.com/user-attachments/assets/550057e0-7783-4bd2-be9b-ce49f48bdc04" />
11
- <img width="598" height="484" alt="Screenshot 2026-01-02 at 17 16 52" src="https://github.com/user-attachments/assets/6eff05af-b58d-4e28-a496-343e243b79a2" />
8
+ Grepfruit is a Ruby gem for regex pattern searching, offering both a programmatic API and a CI/CD-friendly CLI.
12
9
 
13
10
  ## Table of Contents
14
11
 
15
12
  **Gem Usage:**
16
13
  - [Installation](#installation)
17
- - [Basic Usage](#basic-usage)
18
- - [Command Line Options](#command-line-options)
19
- - [Usage Examples](#usage-examples)
20
- - [Exit Status](#exit-status)
21
14
  - [Programmatic API](#programmatic-api)
15
+ - [CLI](#cli)
22
16
 
23
17
  **Community Resources:**
24
18
  - [Getting Help and Contributing](#getting-help-and-contributing)
@@ -33,7 +27,48 @@ Install the gem:
33
27
  gem install grepfruit
34
28
  ```
35
29
 
36
- ## Basic Usage
30
+ ## Programmatic API
31
+
32
+ Use Grepfruit directly in Ruby applications:
33
+
34
+ ```rb
35
+ result = Grepfruit.search(
36
+ regex: /TODO|FIXME/,
37
+ path: "app",
38
+ exclude: ["tmp", "vendor"],
39
+ include: ["*.rb"],
40
+ truncate: 80,
41
+ search_hidden: false,
42
+ jobs: 4,
43
+ count: false
44
+ )
45
+ ```
46
+
47
+ Returns a hash (when `count: true`, the `matches` key is omitted):
48
+
49
+ ```rb
50
+ {
51
+ search: {
52
+ pattern: /TODO|FIXME/,
53
+ path: "/path/to/app",
54
+ exclusions: ["tmp", "vendor"],
55
+ inclusions: ["*.rb"]
56
+ },
57
+ summary: {
58
+ files_checked: 42,
59
+ files_with_matches: 8,
60
+ total_matches: 23
61
+ },
62
+ matches: [
63
+ { file: "/path/to/app/models/user.rb", line: 15, content: "# TODO: add validation" },
64
+ # ...
65
+ ]
66
+ }
67
+ ```
68
+
69
+ All keyword arguments correspond to their CLI flag counterparts.
70
+
71
+ ## CLI
37
72
 
38
73
  Search for regex patterns within files in a specified directory:
39
74
 
@@ -49,7 +84,7 @@ grepfruit s [options] [PATH]
49
84
 
50
85
  If no PATH is specified, Grepfruit searches the current directory.
51
86
 
52
- ## Command Line Options
87
+ ### Command Line Options
53
88
 
54
89
  | Option | Description |
55
90
  |--------|-------------|
@@ -62,9 +97,9 @@ If no PATH is specified, Grepfruit searches the current directory.
62
97
  | `--search-hidden` | Include hidden files and directories in search |
63
98
  | `--json` | Output results in JSON format |
64
99
 
65
- ## Usage Examples
100
+ ### Usage Examples
66
101
 
67
- ### Basic Pattern Search
102
+ **Basic Pattern Search**
68
103
 
69
104
  Search for `TODO` comments in the current directory:
70
105
 
@@ -72,7 +107,7 @@ Search for `TODO` comments in the current directory:
72
107
  grepfruit search -r 'TODO'
73
108
  ```
74
109
 
75
- ### Excluding Directories
110
+ **Excluding Directories**
76
111
 
77
112
  Search for `TODO` patterns while excluding common build and dependency directories:
78
113
 
@@ -80,7 +115,7 @@ Search for `TODO` patterns while excluding common build and dependency directori
80
115
  grepfruit search -r 'TODO' -e 'log,tmp,vendor,node_modules,assets'
81
116
  ```
82
117
 
83
- ### Multiple Pattern Search Excluding Both Directories and Files
118
+ **Multiple Pattern Search Excluding Both Directories and Files**
84
119
 
85
120
  Search for both `FIXME` and `TODO` comments in a specific directory:
86
121
 
@@ -88,7 +123,7 @@ Search for both `FIXME` and `TODO` comments in a specific directory:
88
123
  grepfruit search -r 'FIXME|TODO' -e 'bin,*.md,tmp/log,Gemfile.lock' dev/my_app
89
124
  ```
90
125
 
91
- ### Line-Specific Exclusion
126
+ **Line-Specific Exclusion**
92
127
 
93
128
  Exclude specific lines from search results:
94
129
 
@@ -96,7 +131,7 @@ Exclude specific lines from search results:
96
131
  grepfruit search -r 'FIXME|TODO' -e 'README.md:18'
97
132
  ```
98
133
 
99
- ### Including Specific File Types
134
+ **Including Specific File Types**
100
135
 
101
136
  Search only in specific file types using patterns:
102
137
 
@@ -105,7 +140,7 @@ grepfruit search -r 'TODO' -i '*.rb,*.js'
105
140
  grepfruit search -r 'FIXME' -i '*.py'
106
141
  ```
107
142
 
108
- ### Output Truncation
143
+ **Output Truncation**
109
144
 
110
145
  Limit output length for cleaner results:
111
146
 
@@ -113,7 +148,7 @@ Limit output length for cleaner results:
113
148
  grepfruit search -r 'FIXME|TODO' -t 50
114
149
  ```
115
150
 
116
- ### Including Hidden Files
151
+ **Including Hidden Files**
117
152
 
118
153
  Search hidden files and directories:
119
154
 
@@ -121,7 +156,7 @@ Search hidden files and directories:
121
156
  grepfruit search -r 'FIXME|TODO' --search-hidden
122
157
  ```
123
158
 
124
- ### JSON Output
159
+ **JSON Output**
125
160
 
126
161
  Get structured JSON output:
127
162
 
@@ -135,7 +170,7 @@ This outputs a JSON response containing search metadata, summary statistics, and
135
170
  {
136
171
  "search": {
137
172
  "pattern": "/TODO/",
138
- "directory": "/path/to/search",
173
+ "path": "/path/to/search",
139
174
  "exclusions": ["node_modules"],
140
175
  "inclusions": ["*.rb", "*.js"],
141
176
  "timestamp": "2025-01-16T10:30:00+00:00"
@@ -147,7 +182,7 @@ This outputs a JSON response containing search metadata, summary statistics, and
147
182
  },
148
183
  "matches": [
149
184
  {
150
- "file": "src/main.js",
185
+ "file": "/path/to/search/src/main.js",
151
186
  "line": 15,
152
187
  "content": "// TODO: Implement error handling"
153
188
  },
@@ -156,7 +191,7 @@ This outputs a JSON response containing search metadata, summary statistics, and
156
191
  }
157
192
  ```
158
193
 
159
- ### Count Only Mode
194
+ **Count Only Mode**
160
195
 
161
196
  Show only match counts without displaying the actual matches:
162
197
 
@@ -165,7 +200,7 @@ grepfruit search -r 'TODO' --count
165
200
  grepfruit search -r 'FIXME|TODO' -c -e 'vendor,node_modules'
166
201
  ```
167
202
 
168
- ### Parallel Processing
203
+ **Parallel Processing**
169
204
 
170
205
  Grepfruit uses ractors for true parallel processing across CPU cores. Control the number of parallel workers:
171
206
 
@@ -174,75 +209,36 @@ grepfruit search -r 'TODO' -j 8 # Use 8 parallel workers
174
209
  grepfruit search -r 'TODO' -j 1 # Sequential processing
175
210
  ```
176
211
 
177
- ## Exit Status
212
+ ### Exit Status
178
213
 
179
214
  Grepfruit returns meaningful exit codes for CI/CD integration:
180
215
 
181
- - **Exit code 0**: No matches found (ideal for quality gates - code is clean)
182
- - **Exit code 1**: Pattern matches were found (CI should fail - issues detected)
183
-
184
- ## Programmatic API
185
-
186
- Use Grepfruit directly in Ruby applications:
187
-
188
- ```rb
189
- result = Grepfruit.search(
190
- regex: /TODO|FIXME/,
191
- path: "app",
192
- exclude: ["tmp", "vendor"],
193
- include: ["*.rb"],
194
- truncate: 80,
195
- search_hidden: false,
196
- jobs: 4,
197
- count: false
198
- )
199
- ```
200
-
201
- Returns a hash (note: when `count: true`, the `matches` key is omitted):
202
-
203
- ```rb
204
- {
205
- search: {
206
- pattern: /TODO|FIXME/,
207
- directory: "/path/to/app",
208
- exclusions: ["tmp", "vendor"],
209
- inclusions: ["*.rb"]
210
- },
211
- summary: {
212
- files_checked: 42,
213
- files_with_matches: 8,
214
- total_matches: 23
215
- },
216
- matches: [
217
- { file: "models/user.rb", line: 15, content: "# TODO: add validation" },
218
- # ...
219
- ]
220
- }
221
- ```
222
-
223
- All keyword arguments correspond to their CLI flag counterparts.
216
+ - **Exit code 0**: No matches found (useful for quality gates)
217
+ - **Exit code 1**: Pattern matches were found (indicates issues)
224
218
 
225
219
  ## Getting Help and Contributing
226
220
 
227
221
  ### Getting Help
228
- Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/enjaku4/grepfruit/discussions) for:
222
+
223
+ Have a question or need assistance? Open a discussion in the [discussions section](https://github.com/enjaku4/grepfruit/discussions) for:
224
+
229
225
  - Usage questions
230
226
  - Implementation guidance
231
- - Feature suggestions
227
+ - Open-ended ideas or suggestions
228
+
229
+ ### Issues
230
+
231
+ [Issues](https://github.com/enjaku4/grepfruit/issues) track bugs, planned features, and other work. When reporting a bug, please include:
232
232
 
233
- ### Reporting Issues
234
- Found a bug? Please [create an issue](https://github.com/enjaku4/grepfruit/issues) with:
235
233
  - A clear description of the problem
236
234
  - Steps to reproduce the issue
237
235
  - Your environment details (Ruby version, OS, etc.)
238
236
 
239
237
  ### Contributing Code
240
- Ready to contribute? You can:
241
- - Fix bugs by submitting pull requests
242
- - Improve documentation
243
- - Add new features (please discuss first in our [discussions section](https://github.com/enjaku4/grepfruit/discussions))
244
238
 
245
- Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/grepfruit/blob/master/CONTRIBUTING.md)
239
+ Any open issue is free to pick up or discuss. Check the [project board](https://github.com/users/enjaku4/projects/12) or [issues tab](https://github.com/enjaku4/grepfruit/issues).
240
+
241
+ Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/grepfruit/blob/master/CONTRIBUTING.md).
246
242
 
247
243
  ## License
248
244
 
data/grepfruit.gemspec CHANGED
@@ -13,10 +13,10 @@ Gem::Specification.new do |spec|
13
13
  spec.metadata["documentation_uri"] = "#{spec.homepage}/blob/master/README.md"
14
14
  spec.metadata["mailing_list_uri"] = "#{spec.homepage}/discussions"
15
15
  spec.metadata["rubygems_mfa_required"] = "true"
16
- spec.summary = "Text search tool"
17
- spec.description = "A tool for searching regex patterns in files with a CI/CD-friendly CLI and a programmatic Ruby API"
16
+ spec.summary = "Tool for searching regex patterns in files"
17
+ spec.description = "A tool for searching regex patterns in files with a programmatic API and a CI/CD-friendly CLI"
18
18
  spec.license = "MIT"
19
- spec.required_ruby_version = ">= 3.2", "< 4.1"
19
+ spec.required_ruby_version = ">= 3.3", "< 4.1"
20
20
 
21
21
  spec.files = [
22
22
  "grepfruit.gemspec", "README.md", "CHANGELOG.md", "LICENSE.txt"
@@ -11,11 +11,18 @@ module Grepfruit
11
11
  def red(text) = colorize(text, :red)
12
12
  def cyan(text) = colorize(text, :cyan)
13
13
 
14
+ def print_file_progress_dot(has_matches)
15
+ print(has_matches ? red(".") : green("."))
16
+ end
17
+
14
18
  def number_of_files(num) = "#{num} file#{'s' if num != 1}"
15
19
  def number_of_matches(num) = "#{num} match#{'es' if num != 1}"
16
20
 
17
21
  def display_results(results)
18
- puts "" if results.total_files.positive?
22
+ if results.total_files.positive?
23
+ puts
24
+ puts
25
+ end
19
26
 
20
27
  if results.match_count.zero?
21
28
  puts "#{number_of_files(results.total_files)} checked, #{green('no matches found')}"
@@ -5,9 +5,12 @@ module Grepfruit
5
5
  include Grepfruit::CliDecorator
6
6
 
7
7
  def execute
8
- puts "Error: Directory '#{path}' does not exist." and exit 1 unless File.exist?(path)
8
+ puts "Error: Path '#{path}' does not exist." and exit 1 unless File.exist?(path)
9
9
 
10
- puts "Searching for #{regex.inspect} in #{path.inspect}..." unless json
10
+ unless json
11
+ puts "Searching for #{regex.inspect} in #{path.inspect}..."
12
+ puts
13
+ end
11
14
 
12
15
  results = execute_search
13
16
 
@@ -19,5 +22,13 @@ module Grepfruit
19
22
 
20
23
  exit(results.match_count.positive? ? 1 : 0)
21
24
  end
25
+
26
+ private
27
+
28
+ def emit_file_progress(has_matches)
29
+ return if json
30
+
31
+ print_file_progress_dot(has_matches)
32
+ end
22
33
  end
23
34
  end
@@ -1,7 +1,7 @@
1
1
  module Grepfruit
2
2
  class ProgrammaticSearch < Search
3
3
  def execute
4
- raise ArgumentError, "directory '#{path}' does not exist." unless File.exist?(path)
4
+ raise ArgumentError, "path '#{path}' does not exist." unless File.exist?(path)
5
5
 
6
6
  build_result_hash(execute_search)
7
7
  end
@@ -6,7 +6,7 @@ Warning[:experimental] = false
6
6
 
7
7
  module Grepfruit
8
8
  class Search
9
- attr_reader :path, :regex, :exclusions, :inclusions, :excluded_paths, :excluded_lines, :included_paths, :truncate, :search_hidden, :jobs, :json, :count
9
+ attr_reader :path, :regex, :exclusions, :inclusions, :excluded_paths, :excluded_lines, :truncate, :search_hidden, :jobs, :json, :count
10
10
 
11
11
  def initialize(path:, regex:, exclude:, include:, truncate:, search_hidden:, jobs:, json: false, count: false)
12
12
  @path = File.expand_path(path)
@@ -14,7 +14,6 @@ module Grepfruit
14
14
  @exclusions = exclude
15
15
  @inclusions = include
16
16
  @excluded_lines, @excluded_paths = exclude.partition { _1.split("/").last.include?(":") }
17
- @included_paths = include
18
17
  @truncate = truncate
19
18
  @search_hidden = search_hidden
20
19
  @jobs = jobs || Etc.nprocessors
@@ -28,9 +27,9 @@ module Grepfruit
28
27
  result_hash = {
29
28
  search: {
30
29
  pattern: regex,
31
- directory: path,
32
- exclusions: exclusions,
33
- inclusions: inclusions
30
+ path:,
31
+ exclusions:,
32
+ inclusions:
34
33
  },
35
34
  summary: {
36
35
  files_checked: results.total_files,
@@ -40,9 +39,9 @@ module Grepfruit
40
39
  }
41
40
 
42
41
  unless count
43
- result_hash[:matches] = results.raw_matches.map do |relative_path, line_num, line_content|
42
+ result_hash[:matches] = results.raw_matches.map do |file_path, line_num, line_content|
44
43
  {
45
- file: relative_path,
44
+ file: file_path,
46
45
  line: line_num,
47
46
  content: processed_line(line_content)
48
47
  }
@@ -66,7 +65,9 @@ module Grepfruit
66
65
  ready_worker, worker_result = RactorCompat.select_ready(active_workers)
67
66
  port = active_workers.delete(ready_worker)
68
67
 
69
- results.increment_files_with_matches if process_worker_result(worker_result, results)
68
+ has_matches = process_worker_result(worker_result, results)
69
+ results.increment_files_with_matches if has_matches
70
+ emit_file_progress(has_matches)
70
71
  assign_file_to_worker(ready_worker, port, file_enumerator, active_workers, results)
71
72
  end
72
73
 
@@ -86,21 +87,23 @@ module Grepfruit
86
87
  true
87
88
  end
88
89
 
90
+ def emit_file_progress(_has_matches); end
91
+
89
92
  def create_persistent_worker
90
93
  RactorCompat.create_worker do |port|
91
94
  loop do
92
95
  work = Ractor.receive
93
96
  break if work == :quit
94
97
 
95
- file_path, relative_path, pattern, exc_lines, count = work
98
+ file_path, pattern, exc_lines, count = work
96
99
  file_results, has_matches, match_count = [], false, 0
97
100
 
98
101
  File.foreach(file_path).with_index do |line, line_num|
99
102
  next unless line.valid_encoding? && line.match?(pattern)
100
103
 
101
- next if exc_lines.any? { "#{relative_path}:#{line_num + 1}".end_with?(_1) }
104
+ next if exc_lines.any? { "#{file_path}:#{line_num + 1}".end_with?(_1) }
102
105
 
103
- file_results << [relative_path, line_num + 1, line] unless count
106
+ file_results << [file_path, line_num + 1, line] unless count
104
107
  has_matches = true
105
108
  match_count += 1
106
109
  end
@@ -111,10 +114,10 @@ module Grepfruit
111
114
  end
112
115
 
113
116
  def assign_file_to_worker(worker, port, file_enumerator, active_workers, results)
114
- file_path, rel_path = get_next_file(file_enumerator)
117
+ file_path = get_next_file(file_enumerator)
115
118
  return unless file_path
116
119
 
117
- RactorCompat.send_work(worker, [file_path, rel_path, regex, excluded_lines, count])
120
+ RactorCompat.send_work(worker, [file_path, regex, excluded_lines, count])
118
121
  active_workers[worker] = port
119
122
  results.total_files += 1
120
123
  end
@@ -132,18 +135,19 @@ module Grepfruit
132
135
  def create_file_enumerator
133
136
  Enumerator.new do |yielder|
134
137
  Find.find(path) do |file_path|
135
- rel_path = file_path.delete_prefix("#{path}/")
136
- Find.prune if excluded_path?(file_path, rel_path)
138
+ is_file = File.file?(file_path)
139
+
140
+ Find.prune if excluded_path?(is_file, file_path, file_path.delete_prefix("#{path}/"))
137
141
 
138
- next unless File.file?(file_path) && File.readable?(file_path)
142
+ next unless is_file && File.readable?(file_path)
139
143
 
140
- yielder << [file_path, rel_path]
144
+ yielder << file_path
141
145
  end
142
146
  end
143
147
  end
144
148
 
145
- def excluded_path?(file_path, rel_path)
146
- (File.file?(file_path) && included_paths.any? && !matches_pattern?(included_paths, rel_path)) ||
149
+ def excluded_path?(is_file, file_path, rel_path)
150
+ (is_file && inclusions.any? && !matches_pattern?(inclusions, rel_path)) ||
147
151
  matches_pattern?(excluded_paths, rel_path) ||
148
152
  (!search_hidden && File.basename(file_path).start_with?("."))
149
153
  end
@@ -1,3 +1,3 @@
1
1
  module Grepfruit
2
- VERSION = "3.2.1".freeze
2
+ VERSION = "4.0.1".freeze
3
3
  end
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.2.1
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-07-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-cli
@@ -23,8 +23,8 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.1'
26
- description: A tool for searching regex patterns in files with a CI/CD-friendly CLI
27
- and a programmatic Ruby API
26
+ description: A tool for searching regex patterns in files with a programmatic API
27
+ and a CI/CD-friendly CLI
28
28
  email:
29
29
  - contact@brownbox.dev
30
30
  executables:
@@ -64,7 +64,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: '3.2'
67
+ version: '3.3'
68
68
  - - "<"
69
69
  - !ruby/object:Gem::Version
70
70
  version: '4.1'
@@ -74,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  requirements: []
77
- rubygems_version: 4.0.0
77
+ rubygems_version: 4.0.8
78
78
  specification_version: 4
79
- summary: Text search tool
79
+ summary: Tool for searching regex patterns in files
80
80
  test_files: []