grepfruit 2.0.4 → 3.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: 510715dadc6ac5afba47b5a02b122eb6ae53e46dcb4454a3d83adf7374615b1e
4
- data.tar.gz: 4797c7612d6f0ab3e0ec3c15a3c48941984f9eba4837a919ba98f10551d3c4fb
3
+ metadata.gz: 21bbe94a4fe8b462011334fbba7773cf570da96f5497d7b9ad7e125351b92b13
4
+ data.tar.gz: 2ad07b148d00350ed821d973c29675d9b91f3a99dc9a2f3e64f3acbce7018932
5
5
  SHA512:
6
- metadata.gz: a62a669cb08f455f3105ee73b4f70d991216c7b34e68477adf6546d922e43e5994ff9a9cfca38671adf69b90cb89edf1960019b8d5dd2fb3bb8df5602f6ccc2c
7
- data.tar.gz: b85bd902107f90713cd14be4ce8662250975c76dfd95e7757a12f36da4004261098545e7929abe103c9b8204b7c457c011d129ff5daff5cc5dafa5e4aa163b34
6
+ metadata.gz: d8b5a7b272696fc8dd60993fcb10fc3bdf9bf527d51ba0ef36a7a86adb1bb152f834c1a0b9cefadae6d630acd87ce635028b07ae12cdb162caf68bc15a10b1eb
7
+ data.tar.gz: 0f497ea749fc6cb5740c1e8c16b7672970c75333e46cd1e95418aa0d701d8a3db7b08ffb1775b51c32b66297ce202458768276a8b3065e2b3e25fd4d97fe313d
data/CHANGELOG.md CHANGED
@@ -1,4 +1,13 @@
1
+ ## v3.0.0
2
+
3
+ - Dropped support for Ruby 3.1
4
+ - Optimized search algorithm for better performance
5
+ - Changed the interface: now use `grepfruit search` instead of just `grepfruit` to perform searches
6
+ - Added JSON output format for search results
7
+ - Added parallel processing and --jobs option to control worker count
8
+
1
9
  ## v2.0.4
10
+
2
11
  - Fixed path resolution bug where searching in relative directories such as `.`, `./`, or `..` did not work correctly
3
12
 
4
13
  ## v2.0.3
data/README.md CHANGED
@@ -3,17 +3,14 @@
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 intelligent exclusion options and 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 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
 
10
- - Regular expression search within files and directories
11
- - Intelligent file and directory exclusion capabilities
10
+ - Parallel search using Ractors
11
+ - JSON output format for programmatic integration
12
12
  - Colorized output for improved readability
13
- - Hidden file and directory search support
14
- - Configurable output truncation
15
- - CI/CD pipeline friendly with meaningful exit codes
16
- - Line-specific exclusion for precise control
13
+ - CI/CD pipeline friendly exit codes
17
14
 
18
15
  ## Table of Contents
19
16
 
@@ -31,20 +28,8 @@ Grepfruit is a Ruby gem for searching files within a directory for specified reg
31
28
 
32
29
  ## Installation
33
30
 
34
- Add Grepfruit to your Gemfile:
35
-
36
- ```rb
37
- gem "grepfruit"
38
- ```
39
-
40
31
  Install the gem:
41
32
 
42
- ```bash
43
- bundle install
44
- ```
45
-
46
- Or install it directly:
47
-
48
33
  ```bash
49
34
  gem install grepfruit
50
35
  ```
@@ -54,7 +39,13 @@ gem install grepfruit
54
39
  Search for regex patterns within files in a specified directory:
55
40
 
56
41
  ```bash
57
- grepfruit [options] PATH
42
+ grepfruit search [options] [PATH]
43
+ ```
44
+
45
+ Or using shorthand `s` command:
46
+
47
+ ```bash
48
+ grepfruit s [options] [PATH]
58
49
  ```
59
50
 
60
51
  If no PATH is specified, Grepfruit searches the current directory.
@@ -66,7 +57,9 @@ If no PATH is specified, Grepfruit searches the current directory.
66
57
  | `-r, --regex REGEX` | Regex pattern to search for (required) |
67
58
  | `-e, --exclude x,y,z` | Comma-separated list of files, directories, or lines to exclude |
68
59
  | `-t, --truncate N` | Truncate search result output to N characters |
60
+ | `-j, --jobs N` | Number of parallel workers (default: number of CPU cores) |
69
61
  | `--search-hidden` | Include hidden files and directories in search |
62
+ | `--json` | Output results in JSON format |
70
63
 
71
64
  ## Usage Examples
72
65
 
@@ -75,7 +68,7 @@ If no PATH is specified, Grepfruit searches the current directory.
75
68
  Search for `TODO` comments in the current directory:
76
69
 
77
70
  ```bash
78
- grepfruit -r 'TODO'
71
+ grepfruit search -r 'TODO'
79
72
  ```
80
73
 
81
74
  ### Excluding Directories
@@ -83,7 +76,7 @@ grepfruit -r 'TODO'
83
76
  Search for `TODO` patterns while excluding common build and dependency directories:
84
77
 
85
78
  ```bash
86
- grepfruit -r 'TODO' -e 'log,tmp,vendor,node_modules,assets'
79
+ grepfruit search -r 'TODO' -e 'log,tmp,vendor,node_modules,assets'
87
80
  ```
88
81
 
89
82
  ### Multiple Pattern Search Excluding Both Directories and Files
@@ -91,7 +84,7 @@ grepfruit -r 'TODO' -e 'log,tmp,vendor,node_modules,assets'
91
84
  Search for both `FIXME` and `TODO` comments in a specific directory:
92
85
 
93
86
  ```bash
94
- grepfruit -r 'FIXME|TODO' -e 'bin,tmp/log,Gemfile.lock' dev/grepfruit
87
+ grepfruit search -r 'FIXME|TODO' -e 'bin,tmp/log,Gemfile.lock' dev/my_app
95
88
  ```
96
89
 
97
90
  ### Line-Specific Exclusion
@@ -99,7 +92,7 @@ grepfruit -r 'FIXME|TODO' -e 'bin,tmp/log,Gemfile.lock' dev/grepfruit
99
92
  Exclude specific lines from search results:
100
93
 
101
94
  ```bash
102
- grepfruit -r 'FIXME|TODO' -e 'README.md:18'
95
+ grepfruit search -r 'FIXME|TODO' -e 'README.md:18'
103
96
  ```
104
97
 
105
98
  ### Output Truncation
@@ -107,7 +100,7 @@ grepfruit -r 'FIXME|TODO' -e 'README.md:18'
107
100
  Limit output length for cleaner results:
108
101
 
109
102
  ```bash
110
- grepfruit -r 'FIXME|TODO' -t 50
103
+ grepfruit search -r 'FIXME|TODO' -t 50
111
104
  ```
112
105
 
113
106
  ### Including Hidden Files
@@ -115,15 +108,58 @@ grepfruit -r 'FIXME|TODO' -t 50
115
108
  Search hidden files and directories:
116
109
 
117
110
  ```bash
118
- grepfruit -r 'FIXME|TODO' --search-hidden
111
+ grepfruit search -r 'FIXME|TODO' --search-hidden
112
+ ```
113
+
114
+ ### JSON Output for Automation
115
+
116
+ Get structured JSON output for scripts and CI/CD pipelines:
117
+
118
+ ```bash
119
+ grepfruit search -r 'TODO' --json
120
+ ```
121
+
122
+ This outputs a structured JSON response containing search metadata, summary statistics, and detailed match information:
123
+
124
+ ```json
125
+ {
126
+ "search": {
127
+ "pattern": "/TODO/",
128
+ "directory": "/path/to/search",
129
+ "exclusions": ["node_modules"],
130
+ "timestamp": "2025-01-16T10:30:00+00:00"
131
+ },
132
+ "summary": {
133
+ "files_checked": 42,
134
+ "files_with_matches": 8,
135
+ "total_matches": 23
136
+ },
137
+ "matches": [
138
+ {
139
+ "file": "src/main.js",
140
+ "line": 15,
141
+ "content": "// TODO: Implement error handling"
142
+ },
143
+ // ...
144
+ ]
145
+ }
146
+ ```
147
+
148
+ ### Parallel Processing
149
+
150
+ Control the number of parallel workers:
151
+
152
+ ```bash
153
+ grepfruit search -r 'TODO' -j 8 # Use 8 parallel workers
154
+ grepfruit search -r 'TODO' -j 1 # Sequential processing
119
155
  ```
120
156
 
121
157
  ## Exit Status
122
158
 
123
159
  Grepfruit returns meaningful exit codes for CI/CD integration:
124
160
 
125
- - **Exit code 0**: No matches found
126
- - **Exit code 1**: Pattern matches were found
161
+ - **Exit code 0**: No matches found (ideal for quality gates - code is clean)
162
+ - **Exit code 1**: Pattern matches were found (CI should fail - issues detected)
127
163
 
128
164
  ## Contributing
129
165
 
data/exe/grepfruit CHANGED
@@ -1,31 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ RubyVM::YJIT.enable if defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable)
4
+
3
5
  $LOAD_PATH.unshift("#{__dir__}/../lib")
4
6
 
5
- require "optparse"
6
7
  require "grepfruit"
7
8
 
8
- options = {
9
- dir: ".",
10
- regex: nil,
11
- exclude: [],
12
- truncate: nil,
13
- search_hidden: false
14
- }
15
-
16
- OptionParser.new do |opts|
17
- opts.banner = "Usage: grepfruit [options] PATH"
18
- opts.on("-r", "--regex REGEX", Regexp, "Regex pattern to search for") { options[:regex] = _1 }
19
- opts.on("-e", "--exclude x,y,z", Array, "Comma-separated list of files and directories to exclude") { options[:exclude] = _1 }
20
- opts.on("-t", "--truncate N", Integer, "Truncate output to N characters") { options[:truncate] = _1 }
21
- opts.on("--search-hidden", TrueClass, "Search hidden files and directories") { options[:search_hidden] = _1 }
22
- end.parse!
23
-
24
- if options[:regex].nil?
25
- puts "Error: You must specify a regex pattern using the -r or --regex option."
26
- exit 1
27
- end
28
-
29
- options[:dir] = ARGV[0] if ARGV[0]
30
-
31
- Grepfruit::Search.new(**options).run
9
+ Grepfruit::CLI.start(ARGV)
data/grepfruit.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.metadata["rubygems_mfa_required"] = "true"
12
12
  spec.summary = "A Ruby gem for searching text patterns in files with colorized output"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.1", "< 3.5"
14
+ spec.required_ruby_version = ">= 3.2", "< 3.5"
15
15
 
16
16
  spec.files = [
17
17
  "grepfruit.gemspec", "README.md", "CHANGELOG.md", "LICENSE.txt"
@@ -20,4 +20,6 @@ Gem::Specification.new do |spec|
20
20
  spec.bindir = "exe"
21
21
  spec.executables = ["grepfruit"]
22
22
  spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "dry-cli", "~> 1.2"
23
25
  end
@@ -0,0 +1,62 @@
1
+ require "dry/cli"
2
+
3
+ module Grepfruit
4
+ module Commands
5
+ extend Dry::CLI::Registry
6
+
7
+ class Search < Dry::CLI::Command
8
+ desc "Search for regex patterns in files"
9
+
10
+ argument :path, required: false, default: ".", desc: "Directory or file to search in"
11
+
12
+ option :regex, aliases: ["-r"], required: true, desc: "Regex pattern to search for"
13
+ option :exclude, aliases: ["-e"], type: :array, default: [], desc: "Comma-separated list of files and directories to exclude"
14
+ option :truncate, aliases: ["-t"], type: :integer, desc: "Truncate output to N characters"
15
+ option :search_hidden, type: :boolean, default: false, desc: "Search hidden files and directories"
16
+ option :jobs, aliases: ["-j"], type: :integer, desc: "Number of parallel workers (default: all CPU cores, use 1 for sequential)"
17
+ option :json, type: :boolean, default: false, desc: "Output results in JSON format"
18
+
19
+ def call(path: ".", **options)
20
+ validate_options!(options)
21
+
22
+ Grepfruit::Search.new(
23
+ dir: path,
24
+ regex: create_regex(options[:regex]),
25
+ exclude: options[:exclude] || [],
26
+ truncate: options[:truncate]&.to_i,
27
+ search_hidden: !!options[:search_hidden],
28
+ jobs: options[:jobs]&.to_i,
29
+ json_output: !!options[:json]
30
+ ).run
31
+ end
32
+
33
+ private
34
+
35
+ def validate_options!(options)
36
+ error_exit("You must specify a regex pattern using the -r or --regex option.") unless options[:regex]
37
+
38
+ jobs = options[:jobs]&.to_i
39
+ error_exit("Number of jobs must be at least 1") if jobs && jobs < 1
40
+ end
41
+
42
+ def create_regex(pattern)
43
+ Regexp.new(pattern)
44
+ rescue RegexpError => e
45
+ error_exit("Invalid regex pattern - #{e.message}")
46
+ end
47
+
48
+ def error_exit(message)
49
+ puts "Error: #{message}"
50
+ exit 1
51
+ end
52
+ end
53
+
54
+ register "search", Search, aliases: ["s"]
55
+ end
56
+
57
+ class CLI < Dry::CLI
58
+ def self.start(argv = ARGV)
59
+ Dry::CLI.new(Commands).call(arguments: argv)
60
+ end
61
+ end
62
+ end
@@ -1,45 +1,23 @@
1
1
  module Grepfruit
2
2
  module Decorator
3
- COLORS = { cyan: "\e[36m", red: "\e[31m", green: "\e[32m", reset: "\e[0m" }
4
- private_constant :COLORS
3
+ COLORS = { cyan: "\e[36m", red: "\e[31m", green: "\e[32m", reset: "\e[0m" }.freeze
5
4
 
6
5
  private
7
6
 
8
- def green(text)
9
- "#{COLORS[:green]}#{text}#{COLORS[:reset]}"
10
- end
11
-
12
- def red(text)
13
- "#{COLORS[:red]}#{text}#{COLORS[:reset]}"
14
- end
7
+ def green(text) = "#{COLORS[:green]}#{text}#{COLORS[:reset]}"
8
+ def red(text) = "#{COLORS[:red]}#{text}#{COLORS[:reset]}"
9
+ def cyan(text) = "#{COLORS[:cyan]}#{text}#{COLORS[:reset]}"
15
10
 
16
- def cyan(text)
17
- "#{COLORS[:cyan]}#{text}#{COLORS[:reset]}"
18
- end
19
-
20
- def number_of_files(num)
21
- "#{num} file#{'s' if num > 1}"
22
- end
23
-
24
- def number_of_matches(num)
25
- "#{num} match#{'es' if num > 1}"
26
- end
11
+ def number_of_files(num) = "#{num} file#{'s' if num != 1}"
12
+ def number_of_matches(num) = "#{num} match#{'es' if num != 1}"
27
13
 
28
14
  def relative_path(path)
29
- Pathname.new(path).relative_path_from(Pathname.new(dir)).to_s
30
- end
31
-
32
- def relative_path_with_line_num(path, line_num)
33
- "#{relative_path(path)}:#{line_num + 1}"
15
+ path.delete_prefix("#{dir}/")
34
16
  end
35
17
 
36
18
  def processed_line(line)
37
19
  stripped_line = line.strip
38
- truncate && stripped_line.length > truncate ? "#{stripped_line[0..truncate - 1]}..." : stripped_line
39
- end
40
-
41
- def decorated_line(path, line_num, line)
42
- "#{cyan(relative_path_with_line_num(path, line_num))}: #{processed_line(line)}"
20
+ truncate && stripped_line.length > truncate ? "#{stripped_line[0...truncate]}..." : stripped_line
43
21
  end
44
22
 
45
23
  def display_results(lines, files, files_with_matches)
@@ -54,5 +32,40 @@ module Grepfruit
54
32
  exit(1)
55
33
  end
56
34
  end
35
+
36
+ def display_json_results(raw_matches, total_files, files_with_matches)
37
+ require "json"
38
+
39
+ search_info = {
40
+ pattern: regex.inspect,
41
+ directory: dir,
42
+ exclusions: (excluded_paths + excluded_lines).map { |path_parts| path_parts.join("/") },
43
+ timestamp: @start_time.strftime("%Y-%m-%dT%H:%M:%S%z")
44
+ }
45
+
46
+ summary = {
47
+ files_checked: total_files,
48
+ files_with_matches: files_with_matches,
49
+ total_matches: raw_matches.size
50
+ }
51
+
52
+ matches = raw_matches.map do |relative_path, line_num, line_content|
53
+ {
54
+ file: relative_path,
55
+ line: line_num,
56
+ content: line_content.strip
57
+ }
58
+ end
59
+
60
+ result = {
61
+ search: search_info,
62
+ summary: summary,
63
+ matches: matches
64
+ }
65
+
66
+ puts JSON.pretty_generate(result)
67
+
68
+ exit(raw_matches.empty? ? 0 : 1)
69
+ end
57
70
  end
58
71
  end
@@ -1,79 +1,142 @@
1
- require "pathname"
2
1
  require "find"
3
- require "byebug"
2
+ require "etc"
4
3
 
5
4
  require_relative "decorator"
6
5
 
6
+ Warning[:experimental] = false
7
+
7
8
  module Grepfruit
8
9
  class Search
9
10
  include Decorator
10
11
 
11
- attr_reader :dir, :regex, :excluded_paths, :excluded_lines, :truncate, :search_hidden
12
+ attr_reader :dir, :regex, :excluded_paths, :excluded_lines, :truncate, :search_hidden, :jobs, :json_output
12
13
 
13
- def initialize(dir:, regex:, exclude:, truncate:, search_hidden:)
14
+ def initialize(dir:, regex:, exclude:, truncate:, search_hidden:, jobs:, json_output: false)
14
15
  @dir = File.expand_path(dir)
15
16
  @regex = regex
16
17
  @excluded_lines, @excluded_paths = exclude.map { _1.split("/") }.partition { _1.last.include?(":") }
17
18
  @truncate = truncate
18
19
  @search_hidden = search_hidden
20
+ @jobs = jobs || Etc.nprocessors
21
+ @json_output = json_output
22
+ @start_time = Time.now
19
23
  end
20
24
 
21
25
  def run
22
- lines, files, files_with_matches = [], 0, 0
26
+ puts "Searching for #{regex.inspect} in #{dir.inspect}...\n\n" unless json_output
23
27
 
24
- puts "Searching for #{regex.inspect} in #{dir.inspect}...\n\n"
28
+ all_lines, total_files_with_matches, total_files = [], 0, 0
29
+ raw_matches = []
30
+ workers = create_workers
31
+ file_enumerator = create_file_enumerator
32
+ active_workers = {}
25
33
 
26
- Find.find(dir) do |path|
27
- Find.prune if excluded_path?(path)
34
+ workers.each do |worker|
35
+ file_path = get_next_file(file_enumerator)
36
+ next unless file_path
28
37
 
29
- next if not_searchable?(path)
38
+ worker.send([file_path, regex, excluded_lines, dir])
39
+ active_workers[worker] = file_path
40
+ total_files += 1
41
+ end
30
42
 
31
- files += 1
32
- match = process_file(path, lines)
43
+ while active_workers.any?
44
+ ready_worker, (file_results, has_matches) = Ractor.select(*active_workers.keys)
45
+ active_workers.delete(ready_worker)
33
46
 
34
- if match
35
- files_with_matches += 1
36
- print red("M")
37
- else
38
- print green(".")
39
- end
47
+ total_files_with_matches += 1 if process_worker_result(file_results, has_matches, all_lines, raw_matches)
48
+
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
40
55
  end
41
56
 
42
- display_results(lines, files, files_with_matches)
57
+ workers.each(&:close_outgoing)
58
+
59
+ if json_output
60
+ display_json_results(raw_matches, total_files, total_files_with_matches)
61
+ else
62
+ display_results(all_lines, total_files, total_files_with_matches)
63
+ end
43
64
  end
44
65
 
45
66
  private
46
67
 
47
- def not_searchable?(path)
48
- File.directory?(path) || File.symlink?(path)
68
+ def get_next_file(enumerator)
69
+ enumerator.next
70
+ rescue StopIteration
71
+ nil
49
72
  end
50
73
 
51
- def process_file(path, lines)
52
- lines_size = lines.size
74
+ def create_workers
75
+ Array.new(jobs) do
76
+ Ractor.new do
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)
53
83
 
54
- File.foreach(path).with_index do |line, line_num|
55
- next if !line.valid_encoding? || !line.match?(regex) || excluded_line?(path, line_num)
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("/")) }
56
86
 
57
- lines << decorated_line(path, line_num, line)
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
58
94
  end
95
+ end
96
+
97
+ def create_file_enumerator
98
+ Enumerator.new do |yielder|
99
+ Find.find(dir) do |path|
100
+ Find.prune if excluded_path?(path)
101
+
102
+ next unless File.file?(path)
59
103
 
60
- lines.size > lines_size
104
+ yielder << path
105
+ end
106
+ rescue Errno::ENOENT
107
+ puts "Error: Directory '#{dir}' does not exist."
108
+ exit 1
109
+ end
61
110
  end
62
111
 
63
- def excluded_path?(path)
64
- excluded?(excluded_paths, relative_path(path)) || !search_hidden && hidden?(path)
112
+ def process_worker_result(file_results, has_matches, all_lines, raw_matches)
113
+ if has_matches
114
+ raw_matches.concat(file_results) if json_output
115
+
116
+ unless json_output
117
+ colored_lines = file_results.map do |relative_path, line_num, line_content|
118
+ "#{cyan("#{relative_path}:#{line_num}")}: #{processed_line(line_content)}"
119
+ end
120
+ all_lines.concat(colored_lines)
121
+ print red("M")
122
+ end
123
+ true
124
+ else
125
+ print green(".") unless json_output
126
+ false
127
+ end
65
128
  end
66
129
 
67
- def excluded_line?(path, line_num)
68
- excluded?(excluded_lines, relative_path_with_line_num(path, line_num))
130
+ def excluded_path?(path)
131
+ excluded?(excluded_paths, relative_path(path)) || (!search_hidden && File.basename(path).start_with?("."))
69
132
  end
70
133
 
71
134
  def excluded?(list, path)
72
135
  list.any? { path.split("/").last(_1.length) == _1 }
73
136
  end
74
137
 
75
- def hidden?(path)
76
- File.basename(path).start_with?(".")
138
+ def relative_path(path)
139
+ path.delete_prefix("#{dir}/")
77
140
  end
78
141
  end
79
142
  end
@@ -1,3 +1,3 @@
1
1
  module Grepfruit
2
- VERSION = "2.0.4"
2
+ VERSION = "3.0.0".freeze
3
3
  end
data/lib/grepfruit.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require_relative "grepfruit/version"
2
2
  require_relative "grepfruit/search"
3
+ require_relative "grepfruit/cli"
3
4
 
4
5
  module Grepfruit
5
6
  class Error < StandardError; end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grepfruit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-05-30 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2025-06-16 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: dry-cli
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.2'
12
26
  executables:
13
27
  - grepfruit
14
28
  extensions: []
@@ -20,6 +34,7 @@ files:
20
34
  - exe/grepfruit
21
35
  - grepfruit.gemspec
22
36
  - lib/grepfruit.rb
37
+ - lib/grepfruit/cli.rb
23
38
  - lib/grepfruit/decorator.rb
24
39
  - lib/grepfruit/search.rb
25
40
  - lib/grepfruit/version.rb
@@ -38,7 +53,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
53
  requirements:
39
54
  - - ">="
40
55
  - !ruby/object:Gem::Version
41
- version: '3.1'
56
+ version: '3.2'
42
57
  - - "<"
43
58
  - !ruby/object:Gem::Version
44
59
  version: '3.5'
@@ -48,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
63
  - !ruby/object:Gem::Version
49
64
  version: '0'
50
65
  requirements: []
51
- rubygems_version: 3.6.2
66
+ rubygems_version: 3.6.3
52
67
  specification_version: 4
53
68
  summary: A Ruby gem for searching text patterns in files with colorized output
54
69
  test_files: []