Module: Ace::Search::Atoms::RipgrepExecutor

Defined in:
lib/ace/search/atoms/ripgrep_executor.rb

Overview

RipgrepExecutor provides a safe wrapper around ripgrep (rg) command This is an atom - pure function for executing ripgrep commands

Class Method Summary collapse

Class Method Details

.available?Boolean

Check if ripgrep is available

Returns:

  • (Boolean)

    True if ripgrep is installed



72
73
74
75
76
77
# File 'lib/ace/search/atoms/ripgrep_executor.rb', line 72

def available?
  stdout, _stderr, status = Open3.capture3("which rg")
  status.success? && !stdout.strip.empty?
rescue
  false
end

.build_command(pattern, options = {}) ⇒ String

Build ripgrep command with proper escaping

Parameters:

  • pattern (String)

    Search pattern

  • options (Hash) (defaults to: {})

    Search options

Returns:

  • (String)

    Complete ripgrep command



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ace/search/atoms/ripgrep_executor.rb', line 83

def build_command(pattern, options = {})
  args = ["rg"]

  # Basic options
  args << "--color=never" unless options[:color]
  args << "--json" if options[:json_output]
  args << "--line-number" unless options[:no_line_numbers]
  args << "--no-heading" unless options[:heading]
  args << "--with-filename" unless options[:no_filename]
  args << "--count" if options[:count]
  args << "--files-with-matches" if options[:files_with_matches]
  args << "--invert-match" if options[:invert_match]
  args << "--hidden" if options[:hidden]

  # Context options
  if options[:context]
    args << "--context=#{options[:context]}"
  elsif options[:before_context] || options[:after_context]
    args << "--before-context=#{options[:before_context]}" if options[:before_context]
    args << "--after-context=#{options[:after_context]}" if options[:after_context]
  end

  # File type filtering
  if options[:file_type]
    Array(options[:file_type]).each { |t| args << "--type=#{t}" }
  end

  # Glob patterns
  if options[:glob]
    Array(options[:glob]).each { |g| args << "--glob=#{Shellwords.escape(g)}" }
  end

  # Case sensitivity
  args << "--ignore-case" if options[:ignore_case]
  args << "--case-sensitive" if options[:case_sensitive]

  # Max results
  args << "--max-count=#{options[:max_count]}" if options[:max_count]

  # Multiline mode
  args << "--multiline" if options[:multiline]
  args << "--multiline-dotall" if options[:multiline_dotall]

  # Whole word matching
  args << "--word-regexp" if options[:word_regexp]

  # Search paths
  # When search_path is set, we use chdir in execute(), so search current dir
  paths = if options[:search_path]
    ["."]  # Will chdir to search_path, then search current dir
  elsif options[:paths]
    options[:paths]
  else
    ["."]
  end

  # Build the complete command
  command_parts = args + [Shellwords.escape(pattern)] + paths.map { |p| Shellwords.escape(p) }
  command_parts.join(" ")
end

.execute(pattern, options = {}) ⇒ Hash

Execute ripgrep with given options and pattern

Parameters:

  • pattern (String)

    Search pattern (regex)

  • options (Hash) (defaults to: {})

    Search options

Returns:

  • (Hash)

    Command result with success status and output



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ace/search/atoms/ripgrep_executor.rb', line 20

def execute(pattern, options = {})
  return {success: false, error: "Pattern cannot be nil or empty"} if pattern.nil? || pattern.empty?

  command = build_command(pattern, options)
  # Read timeout from options (runtime override), then config, then fallback to 120 seconds
  default_timeout = Ace::Search.config["timeout"] || 120
  timeout_seconds = options.fetch(:timeout, default_timeout)

  # Debug output
  DebugLogger.section("RipgrepExecutor") do
    DebugLogger.log("options[:search_path] = #{options[:search_path].inspect}")
    DebugLogger.log("Current Dir.pwd = #{Dir.pwd}")
    DebugLogger.log("Command: #{command}")

    search_dir_debug = options[:search_path] || "."
    DebugLogger.log("Will chdir to: #{search_dir_debug}")
    DebugLogger.log("Absolute chdir: #{File.expand_path(search_dir_debug)}")
  end

  begin
    # Change to search directory if specified, otherwise use current dir
    # This ensures .gitignore, excludes, and includes are processed correctly
    search_dir = options[:search_path] || "."

    stdout, stderr, status = Timeout.timeout(timeout_seconds) do
      Open3.capture3(command, chdir: search_dir)
    end

    {
      success: status.success?,
      stdout: stdout,
      stderr: stderr,
      exit_code: status.exitstatus,
      command: command
    }
  rescue Timeout::Error
    {
      success: false,
      error: "Command timed out after #{timeout_seconds} seconds",
      exit_code: -1
    }
  rescue => e
    {
      success: false,
      error: e.message,
      exit_code: -1
    }
  end
end

.versionString?

Get ripgrep version

Returns:

  • (String, nil)

    Version string or nil if not available



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/ace/search/atoms/ripgrep_executor.rb', line 146

def version
  return nil unless available?

  stdout, _stderr, status = Open3.capture3("rg --version")
  if status.success?
    version_match = stdout.match(/ripgrep ([\d.]+)/)
    version_match ? version_match[1] : nil
  end
rescue
  nil
end