1package tools
2
3import (
4 "context"
5 "log/slog"
6 "os/exec"
7 "path/filepath"
8 "strings"
9 "sync"
10 "testing"
11
12 "git.secluded.site/crush/internal/log"
13)
14
15var getRg = sync.OnceValue(func() string {
16 if testing.Testing() {
17 return ""
18 }
19 path, err := exec.LookPath("rg")
20 if err != nil {
21 if log.Initialized() {
22 slog.Warn("Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower.")
23 }
24 return ""
25 }
26 return path
27})
28
29func getRgCmd(ctx context.Context, globPattern string) *exec.Cmd {
30 name := getRg()
31 if name == "" {
32 return nil
33 }
34 args := []string{"--files", "-L", "--null"}
35 if globPattern != "" {
36 if !filepath.IsAbs(globPattern) && !strings.HasPrefix(globPattern, "/") {
37 globPattern = "/" + globPattern
38 }
39 args = append(args, "--glob", globPattern)
40 }
41 return exec.CommandContext(ctx, name, args...)
42}
43
44func getRgSearchCmd(ctx context.Context, pattern, path, include string) *exec.Cmd {
45 name := getRg()
46 if name == "" {
47 return nil
48 }
49 // Use -n to show line numbers, -0 for null separation to handle Windows paths
50 args := []string{"--json", "-H", "-n", "-0", pattern}
51 if include != "" {
52 args = append(args, "--glob", include)
53 }
54 args = append(args, path)
55
56 return exec.CommandContext(ctx, name, args...)
57}