grepfruit 3.2.1 → 4.0.0

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: 5756a4241133424de6c099a46fcd450f8144cbbbf72951af6312ceffedc565a3
4
+ data.tar.gz: 9e248f116cf6a8d1742a63a33b1330c7008fd03cc9c11c393cbf4e2bebbb8734
5
5
  SHA512:
6
- metadata.gz: 30c29450c9e0535f2926de44a66adbcccf1021537c1d00ed3e4a9282cda2862c34f4235f1847740488c67dde79083e9b86b175a729cd8f5ee980cf2ee9a02e1c
7
- data.tar.gz: cb46abd7db199d7305d4a1c5f67b9fa6153a422ff8219213b7ef30dc196632d1fcac78a2c6442602713b79df2bddf1c015eb692631468d1cb3a6e1a8b85c2b6c
6
+ metadata.gz: d41210632eb24008ac4f14742aed37e44da75f19540b8d26c973a1bda8d473b3d6648a4fe37691423c87c3e7ba1fa03e60530d19109e8cc0fd6108809ebc101e
7
+ data.tar.gz: 9b787719f4bb9d63fe2641f5b15a4214a7dd95921df63a4f010ce59dce71f87c17e62a2fd888dbc2d5c3206c303d98e5497a6b838fac4e8396b6ca5809989f89
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## v4.0.0
2
+
3
+ - Match results now always contain absolute file paths
4
+ - Renamed `directory` key to `path` in programmatic API search results
5
+ - Minor performance improvements
6
+ - Dropped support for Ruby 3.2
7
+
1
8
  ## v3.2.1
2
9
 
3
10
  - 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,58 +209,17 @@ 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
+ Have a question or need assistance? Open a discussion in the [discussions section](https://github.com/enjaku4/grepfruit/discussions) for:
229
223
  - Usage questions
230
224
  - Implementation guidance
231
225
  - Feature suggestions
@@ -240,7 +234,7 @@ Found a bug? Please [create an issue](https://github.com/enjaku4/grepfruit/issue
240
234
  Ready to contribute? You can:
241
235
  - Fix bugs by submitting pull requests
242
236
  - Improve documentation
243
- - Add new features (please discuss first in our [discussions section](https://github.com/enjaku4/grepfruit/discussions))
237
+ - Add new features (please discuss first in the [discussions section](https://github.com/enjaku4/grepfruit/discussions))
244
238
 
245
239
  Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/grepfruit/blob/master/CONTRIBUTING.md)
246
240
 
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"
@@ -5,7 +5,7 @@ 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
10
  puts "Searching for #{regex.inspect} in #{path.inspect}..." unless json
11
11
 
@@ -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
  }
@@ -92,15 +91,15 @@ module Grepfruit
92
91
  work = Ractor.receive
93
92
  break if work == :quit
94
93
 
95
- file_path, relative_path, pattern, exc_lines, count = work
94
+ file_path, pattern, exc_lines, count = work
96
95
  file_results, has_matches, match_count = [], false, 0
97
96
 
98
97
  File.foreach(file_path).with_index do |line, line_num|
99
98
  next unless line.valid_encoding? && line.match?(pattern)
100
99
 
101
- next if exc_lines.any? { "#{relative_path}:#{line_num + 1}".end_with?(_1) }
100
+ next if exc_lines.any? { "#{file_path}:#{line_num + 1}".end_with?(_1) }
102
101
 
103
- file_results << [relative_path, line_num + 1, line] unless count
102
+ file_results << [file_path, line_num + 1, line] unless count
104
103
  has_matches = true
105
104
  match_count += 1
106
105
  end
@@ -111,10 +110,10 @@ module Grepfruit
111
110
  end
112
111
 
113
112
  def assign_file_to_worker(worker, port, file_enumerator, active_workers, results)
114
- file_path, rel_path = get_next_file(file_enumerator)
113
+ file_path = get_next_file(file_enumerator)
115
114
  return unless file_path
116
115
 
117
- RactorCompat.send_work(worker, [file_path, rel_path, regex, excluded_lines, count])
116
+ RactorCompat.send_work(worker, [file_path, regex, excluded_lines, count])
118
117
  active_workers[worker] = port
119
118
  results.total_files += 1
120
119
  end
@@ -132,18 +131,19 @@ module Grepfruit
132
131
  def create_file_enumerator
133
132
  Enumerator.new do |yielder|
134
133
  Find.find(path) do |file_path|
135
- rel_path = file_path.delete_prefix("#{path}/")
136
- Find.prune if excluded_path?(file_path, rel_path)
134
+ is_file = File.file?(file_path)
137
135
 
138
- next unless File.file?(file_path) && File.readable?(file_path)
136
+ Find.prune if excluded_path?(is_file, file_path, file_path.delete_prefix("#{path}/"))
139
137
 
140
- yielder << [file_path, rel_path]
138
+ next unless is_file && File.readable?(file_path)
139
+
140
+ yielder << file_path
141
141
  end
142
142
  end
143
143
  end
144
144
 
145
- def excluded_path?(file_path, rel_path)
146
- (File.file?(file_path) && included_paths.any? && !matches_pattern?(included_paths, rel_path)) ||
145
+ def excluded_path?(is_file, file_path, rel_path)
146
+ (is_file && inclusions.any? && !matches_pattern?(inclusions, rel_path)) ||
147
147
  matches_pattern?(excluded_paths, rel_path) ||
148
148
  (!search_hidden && File.basename(file_path).start_with?("."))
149
149
  end
@@ -1,3 +1,3 @@
1
1
  module Grepfruit
2
- VERSION = "3.2.1".freeze
2
+ VERSION = "4.0.0".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.0
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-04-01 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: []