grepfruit 3.0.0 → 3.1.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: 21bbe94a4fe8b462011334fbba7773cf570da96f5497d7b9ad7e125351b92b13
4
- data.tar.gz: 2ad07b148d00350ed821d973c29675d9b91f3a99dc9a2f3e64f3acbce7018932
3
+ metadata.gz: 50c38c621aa4a7c34b262cd61c45ced836cca10158b33566d709fd76bdb18f9f
4
+ data.tar.gz: f0a93887ab397d50a0a22e0600bb06925013632beab1040adf768c831bb6e5ac
5
5
  SHA512:
6
- metadata.gz: d8b5a7b272696fc8dd60993fcb10fc3bdf9bf527d51ba0ef36a7a86adb1bb152f834c1a0b9cefadae6d630acd87ce635028b07ae12cdb162caf68bc15a10b1eb
7
- data.tar.gz: 0f497ea749fc6cb5740c1e8c16b7672970c75333e46cd1e95418aa0d701d8a3db7b08ffb1775b51c32b66297ce202458768276a8b3065e2b3e25fd4d97fe313d
6
+ metadata.gz: 50838340c7e1b6168675ee64bf0f0608beaa708237d12b40efc0fb8099afbde20ae5f18860747984e37e60cf090d4ff4b5e96667cd728dd51299bc5b44ebcdf2
7
+ data.tar.gz: 66e4d2c8cda063641243eaad779c43bd5fba37ecaad1bda19c81de996986688ff2eb825d86260da1f6a2bda0b522de73c5e5af304b3fe166f2d0438d15fa270f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v3.1.0
2
+
3
+ - Added --include option to specify files to include in the search
4
+ - Both --exclude and --include options can now accept multiple patterns
5
+
1
6
  ## v3.0.0
2
7
 
3
8
  - Dropped support for Ruby 3.1
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/grepfruit.svg)](http://badge.fury.io/rb/grepfruit)
4
4
  [![Github Actions badge](https://github.com/brownboxdev/grepfruit/actions/workflows/ci.yml/badge.svg)](https://github.com/brownboxdev/grepfruit/actions/workflows/ci.yml)
5
5
 
6
- Grepfruit is a Ruby gem for searching files within a directory for specified regular expression patterns, with exclusion options and JSON-formatted or colorized output for enhanced readability. Originally designed for CI/CD pipelines to search for `TODO` comments in Ruby on Rails applications, Grepfruit provides more user-friendly output than the standard `grep` command while maintaining the flexibility for diverse search scenarios.
6
+ Grepfruit is a Ruby gem for searching files within a directory for specified regular expression patterns, with exclusion and inclusion options and JSON-formatted or colorized output for enhanced readability. Originally designed for CI/CD pipelines to search for `TODO` comments in Ruby on Rails applications, Grepfruit provides more user-friendly output than the standard `grep` command while maintaining the flexibility for diverse search scenarios.
7
7
 
8
8
  **Key Features:**
9
9
 
@@ -22,7 +22,7 @@ Grepfruit is a Ruby gem for searching files within a directory for specified reg
22
22
  - [Exit Status](#exit-status)
23
23
 
24
24
  **Community Resources:**
25
- - [Contributing](#contributing)
25
+ - [Getting Help and Contributing](#getting-help-and-contributing)
26
26
  - [License](#license)
27
27
  - [Code of Conduct](#code-of-conduct)
28
28
 
@@ -56,6 +56,7 @@ If no PATH is specified, Grepfruit searches the current directory.
56
56
  |--------|-------------|
57
57
  | `-r, --regex REGEX` | Regex pattern to search for (required) |
58
58
  | `-e, --exclude x,y,z` | Comma-separated list of files, directories, or lines to exclude |
59
+ | `-i, --include x,y,z` | Comma-separated list of file patterns to include (only these files will be searched) |
59
60
  | `-t, --truncate N` | Truncate search result output to N characters |
60
61
  | `-j, --jobs N` | Number of parallel workers (default: number of CPU cores) |
61
62
  | `--search-hidden` | Include hidden files and directories in search |
@@ -84,7 +85,7 @@ grepfruit search -r 'TODO' -e 'log,tmp,vendor,node_modules,assets'
84
85
  Search for both `FIXME` and `TODO` comments in a specific directory:
85
86
 
86
87
  ```bash
87
- grepfruit search -r 'FIXME|TODO' -e 'bin,tmp/log,Gemfile.lock' dev/my_app
88
+ grepfruit search -r 'FIXME|TODO' -e 'bin,*.md,tmp/log,Gemfile.lock' dev/my_app
88
89
  ```
89
90
 
90
91
  ### Line-Specific Exclusion
@@ -95,6 +96,15 @@ Exclude specific lines from search results:
95
96
  grepfruit search -r 'FIXME|TODO' -e 'README.md:18'
96
97
  ```
97
98
 
99
+ ### Including Specific File Types
100
+
101
+ Search only in specific file types using patterns:
102
+
103
+ ```bash
104
+ grepfruit search -r 'TODO' -i '*.rb,*.js'
105
+ grepfruit search -r 'FIXME' -i '*.py'
106
+ ```
107
+
98
108
  ### Output Truncation
99
109
 
100
110
  Limit output length for cleaner results:
@@ -111,22 +121,23 @@ Search hidden files and directories:
111
121
  grepfruit search -r 'FIXME|TODO' --search-hidden
112
122
  ```
113
123
 
114
- ### JSON Output for Automation
124
+ ### JSON Output
115
125
 
116
- Get structured JSON output for scripts and CI/CD pipelines:
126
+ Get structured JSON output:
117
127
 
118
128
  ```bash
119
- grepfruit search -r 'TODO' --json
129
+ grepfruit search -r 'TODO' -e 'node_modules' -i '*.rb,*.js' --json /path/to/search
120
130
  ```
121
131
 
122
- This outputs a structured JSON response containing search metadata, summary statistics, and detailed match information:
132
+ This outputs a JSON response containing search metadata, summary statistics, and detailed match information:
123
133
 
124
- ```json
134
+ ```jsonc
125
135
  {
126
136
  "search": {
127
137
  "pattern": "/TODO/",
128
138
  "directory": "/path/to/search",
129
139
  "exclusions": ["node_modules"],
140
+ "inclusions": ["*.rb", "*.js"],
130
141
  "timestamp": "2025-01-16T10:30:00+00:00"
131
142
  },
132
143
  "summary": {
@@ -161,7 +172,7 @@ Grepfruit returns meaningful exit codes for CI/CD integration:
161
172
  - **Exit code 0**: No matches found (ideal for quality gates - code is clean)
162
173
  - **Exit code 1**: Pattern matches were found (CI should fail - issues detected)
163
174
 
164
- ## Contributing
175
+ ## Getting Help and Contributing
165
176
 
166
177
  ### Getting Help
167
178
  Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/brownboxdev/grepfruit/discussions) for:
data/lib/grepfruit/cli.rb CHANGED
@@ -11,6 +11,7 @@ module Grepfruit
11
11
 
12
12
  option :regex, aliases: ["-r"], required: true, desc: "Regex pattern to search for"
13
13
  option :exclude, aliases: ["-e"], type: :array, default: [], desc: "Comma-separated list of files and directories to exclude"
14
+ option :include, aliases: ["-i"], type: :array, default: [], desc: "Comma-separated list of file patterns to include (only these files will be searched)"
14
15
  option :truncate, aliases: ["-t"], type: :integer, desc: "Truncate output to N characters"
15
16
  option :search_hidden, type: :boolean, default: false, desc: "Search hidden files and directories"
16
17
  option :jobs, aliases: ["-j"], type: :integer, desc: "Number of parallel workers (default: all CPU cores, use 1 for sequential)"
@@ -23,6 +24,7 @@ module Grepfruit
23
24
  dir: path,
24
25
  regex: create_regex(options[:regex]),
25
26
  exclude: options[:exclude] || [],
27
+ include: options[:include] || [],
26
28
  truncate: options[:truncate]&.to_i,
27
29
  search_hidden: !!options[:search_hidden],
28
30
  jobs: options[:jobs]&.to_i,
@@ -40,6 +40,7 @@ module Grepfruit
40
40
  pattern: regex.inspect,
41
41
  directory: dir,
42
42
  exclusions: (excluded_paths + excluded_lines).map { |path_parts| path_parts.join("/") },
43
+ inclusions: included_paths.map { |path_parts| path_parts.join("/") },
43
44
  timestamp: @start_time.strftime("%Y-%m-%dT%H:%M:%S%z")
44
45
  }
45
46
 
@@ -9,12 +9,13 @@ module Grepfruit
9
9
  class Search
10
10
  include Decorator
11
11
 
12
- attr_reader :dir, :regex, :excluded_paths, :excluded_lines, :truncate, :search_hidden, :jobs, :json_output
12
+ attr_reader :dir, :regex, :excluded_paths, :excluded_lines, :included_paths, :truncate, :search_hidden, :jobs, :json_output
13
13
 
14
- def initialize(dir:, regex:, exclude:, truncate:, search_hidden:, jobs:, json_output: false)
14
+ def initialize(dir:, regex:, exclude:, include:, truncate:, search_hidden:, jobs:, json_output: false)
15
15
  @dir = File.expand_path(dir)
16
16
  @regex = regex
17
17
  @excluded_lines, @excluded_paths = exclude.map { _1.split("/") }.partition { _1.last.include?(":") }
18
+ @included_paths = include.map { _1.split("/") }
18
19
  @truncate = truncate
19
20
  @search_hidden = search_hidden
20
21
  @jobs = jobs || Etc.nprocessors
@@ -32,12 +33,7 @@ module Grepfruit
32
33
  active_workers = {}
33
34
 
34
35
  workers.each do |worker|
35
- file_path = get_next_file(file_enumerator)
36
- next unless file_path
37
-
38
- worker.send([file_path, regex, excluded_lines, dir])
39
- active_workers[worker] = file_path
40
- total_files += 1
36
+ assign_file_to_worker(worker, file_enumerator, active_workers) && total_files += 1
41
37
  end
42
38
 
43
39
  while active_workers.any?
@@ -46,12 +42,7 @@ module Grepfruit
46
42
 
47
43
  total_files_with_matches += 1 if process_worker_result(file_results, has_matches, all_lines, raw_matches)
48
44
 
49
- next_file = get_next_file(file_enumerator)
50
- next unless next_file
51
-
52
- ready_worker.send([next_file, regex, excluded_lines, dir])
53
- active_workers[ready_worker] = next_file
54
- total_files += 1
45
+ assign_file_to_worker(ready_worker, file_enumerator, active_workers) && total_files += 1
55
46
  end
56
47
 
57
48
  workers.each(&:close_outgoing)
@@ -65,6 +56,15 @@ module Grepfruit
65
56
 
66
57
  private
67
58
 
59
+ def assign_file_to_worker(worker, file_enumerator, active_workers)
60
+ file_path = get_next_file(file_enumerator)
61
+ return false unless file_path
62
+
63
+ worker.send([file_path, regex, excluded_lines, dir])
64
+ active_workers[worker] = file_path
65
+ true
66
+ end
67
+
68
68
  def get_next_file(enumerator)
69
69
  enumerator.next
70
70
  rescue StopIteration
@@ -82,7 +82,7 @@ module Grepfruit
82
82
  next unless line.valid_encoding? && line.match?(pattern)
83
83
 
84
84
  relative_path = file_path.delete_prefix("#{base_dir}/")
85
- next if exc_lines.any? { |exc| "#{relative_path}:#{line_num + 1}".end_with?(exc.join("/")) }
85
+ next if exc_lines.any? { "#{relative_path}:#{line_num + 1}".end_with?(_1.join("/")) }
86
86
 
87
87
  results << [relative_path, line_num + 1, line]
88
88
  has_matches = true
@@ -128,11 +128,24 @@ module Grepfruit
128
128
  end
129
129
 
130
130
  def excluded_path?(path)
131
- excluded?(excluded_paths, relative_path(path)) || (!search_hidden && File.basename(path).start_with?("."))
131
+ rel_path = relative_path(path)
132
+
133
+ not_included_path?(path, rel_path) || matches_pattern?(excluded_paths, rel_path) || excluded_hidden?(path)
132
134
  end
133
135
 
134
- def excluded?(list, path)
135
- list.any? { path.split("/").last(_1.length) == _1 }
136
+ def not_included_path?(path, rel_path)
137
+ File.file?(path) && included_paths.any? && !matches_pattern?(included_paths, rel_path)
138
+ end
139
+
140
+ def excluded_hidden?(path)
141
+ !search_hidden && File.basename(path).start_with?(".")
142
+ end
143
+
144
+ def matches_pattern?(pattern_list, path)
145
+ pattern_list.any? do |pattern_parts|
146
+ pattern = pattern_parts.join("/")
147
+ File.fnmatch?(pattern, path, File::FNM_PATHNAME) || File.fnmatch?(pattern, File.basename(path))
148
+ end
136
149
  end
137
150
 
138
151
  def relative_path(path)
@@ -1,3 +1,3 @@
1
1
  module Grepfruit
2
- VERSION = "3.0.0".freeze
2
+ VERSION = "3.1.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.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-16 00:00:00.000000000 Z
10
+ date: 2025-07-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-cli