Module: Ace::Search::Atoms::FdExecutor

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

Overview

FdExecutor provides a safe wrapper around fd command This is an atom - pure function for executing fd commands

Class Method Summary collapse

Class Method Details

.available?Boolean

Check if fd is available

Returns:

  • (Boolean)

    True if fd is installed



65
66
67
68
69
70
# File 'lib/ace/search/atoms/fd_executor.rb', line 65

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

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

Build fd command with proper escaping

Parameters:

  • pattern (String, nil) (defaults to: nil)

    Search pattern (nil for all files)

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

    Search options

Returns:

  • (String)

    Complete fd command



76
77
78
79
80
81
82
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
143
144
145
146
147
148
149
150
# File 'lib/ace/search/atoms/fd_executor.rb', line 76

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

  # Basic options
  args << "--color=never" unless options[:color]
  args << "--absolute-path" if options[:absolute_path]
  args << "--follow" if options[:follow_symlinks]
  args << "--hidden" if options[:include_hidden] || options[:hidden]
  args << "--no-ignore" if options[:no_ignore]
  args << "--no-ignore-vcs" if options[:no_ignore_vcs]

  # Type filtering
  case options[:fd_type]
  when "f", "file"
    args << "--type=file"
  when "d", "directory"
    args << "--type=directory"
  when "l", "symlink"
    args << "--type=symlink"
  when "s", "socket"
    args << "--type=socket"
  when "p", "pipe"
    args << "--type=pipe"
  end

  # Extension filtering
  if options[:extension]
    Array(options[:extension]).each { |ext| args << "--extension=#{ext}" }
  end

  # Size filtering
  args << "--size=#{options[:size]}" if options[:size]

  # Depth limiting
  args << "--max-depth=#{options[:max_depth]}" if options[:max_depth]
  args << "--min-depth=#{options[:min_depth]}" if options[:min_depth]

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

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

  # Exclude patterns
  if options[:exclude]
    Array(options[:exclude]).each { |pattern| args << "--exclude=#{pattern}" }
  end

  # 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

  # Add pattern with appropriate flag
  if pattern
    # Check if pattern looks like a glob
    if pattern.include?("*") || pattern.include?("?") || pattern.include?("[")
      command_parts << "--glob"
    end
    command_parts << Shellwords.escape(pattern)
  end

  command_parts.concat(paths.map { |p| Shellwords.escape(p) })

  command_parts.join(" ")
end

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

Execute fd with given pattern and options

Parameters:

  • pattern (String) (defaults to: nil)

    Search pattern (can be glob or 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
# File 'lib/ace/search/atoms/fd_executor.rb', line 20

def execute(pattern = nil, options = {})
  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("FdExecutor") do
    DebugLogger.log("Command: #{command}")
    DebugLogger.log("Will chdir to: #{options[:search_path] || "(current directory)"}")
  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 fd version

Returns:

  • (String, nil)

    Version string or nil if not available



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ace/search/atoms/fd_executor.rb', line 154

def version
  return nil unless available?

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