Dokumentation

grep- und ripgrep-Passthrough

greppy nimmt gewöhnliche grep-Aufrufe an. Sie starten das echte System-grep und geben stdout, stderr und Exit-Code Byte für Byte weiter. Sie öffnen keinen Index, laden kein Modell und ändern keinen greppy-Cache.

greppy -rn deny_unknown_fields serde_derive/src
greppy -rin "todo" serde_derive/src
greppy grep -rn deny_unknown_fields serde_derive/src

Jeder Aufruf, der keiner von greppys Befehlen ist, ist ein grep-Aufruf. Ein führendes Wort grep geht ebenso, greppy grep -rn foo . und greppy -rn foo . sind gleichwertig.

Byte für Byte

cmp vergleicht die Ausgabe von greppy mit der des System-grep auf serde 1.0.229:

$ cmp <(greppy -rn deny_unknown_fields serde_derive/src) <(grep -rn deny_unknown_fields serde_derive/src) && echo identisch
identisch
$ greppy -rn deny_unknown_fields serde_derive/src
serde_derive/src/internals/symbol.rs:13:pub const DENY_UNKNOWN_FIELDS: Symbol = Symbol("deny_unknown_fields");
serde_derive/src/internals/attr.rs:158:    deny_unknown_fields: bool,
serde_derive/src/internals/attr.rs:241:        let mut deny_unknown_fields = BoolAttr::none(cx, DENY_UNKNOWN_FIELDS);
…

Dasselbe gilt für stderr und den Exit-Code. Diese zehn Aufrufe auf serde wurden jeweils mit cmp gegen das Original geprüft:

Aufruf Exit-Code stdout stdout und stderr
greppy -rn deny_unknown_fields serde_derive/src 0 1.700 Bytes identisch
greppy grep -rn deny_unknown_fields serde_derive/src 0 1.700 Bytes identisch
greppy -c -r Content:: serde/src 0 128 Bytes identisch
greppy -rin 'todo' serde_derive/src 0 118 Bytes identisch
greppy -rn no_such_identifier_xyz serde/src 1 0 Bytes identisch
greppy -n deny_unknown_fields does/not/exist.rs 2 0 Bytes, 51 Bytes stderr identisch
greppy -n Serialize < serde_derive/src/ser.rs 0 7514 Bytes identisch
greppy rg -n --sort path deny_unknown_fields serde_derive/src 0 1.700 Bytes identisch mit rg
greppy --smart-case -t rust --sort path -n deny_unknown_fields serde_derive 0 1800 Bytes identisch mit rg
greppy rg -g '!test_suite' --sort path -n 'fn deserialize_struct' . 0 253 Bytes identisch mit rg

Ein führendes Wort egrep, fgrep oder rgrep nimmt greppy ebenfalls an und startet damit das normale grep, ohne -E, -F oder -r hinzuzufügen. Diese Flags gibst du selbst mit:

$ greppy egrep 'RENAME_ALL|RENAME_ALL_FIELDS' serde_derive/src/internals/symbol.rs; echo "exit $?"
exit 1
$ greppy egrep -E 'RENAME_ALL|RENAME_ALL_FIELDS' serde_derive/src/internals/symbol.rs
pub const RENAME_ALL: Symbol = Symbol("rename_all");
pub const RENAME_ALL_FIELDS: Symbol = Symbol("rename_all_fields");

ripgrep-Syntax

greppy erkennt ripgrep-artige Aufrufe am Namen rg oder an Flags, die zu ripgrep gehören und grep fehlen, etwa --smart-case, -t rust, -g '!target' oder --json:

greppy rg -S deny_unknown_fields serde_derive/src
greppy --smart-case -t rust -n deny_unknown_fields serde_derive
greppy rg -g '!test_suite' -n 'fn deserialize_struct' .
$ greppy rg -n --sort path deny_unknown_fields serde_derive/src
serde_derive/src/de/enum_adjacently.rs:52:    let deny_unknown_fields = cattrs.deny_unknown_fields();
serde_derive/src/de/enum_adjacently.rs:56:    let field_visitor_ty = if deny_unknown_fields {
serde_derive/src/de/enum_adjacently.rs:122:    let next_relevant_key = if deny_unknown_fields {
…

Die Weiterleitung geht in dieser Reihenfolge:

  1. Ist ein echtes rg installiert, delegiert greppy Byte für Byte daran.
  2. Sonst bildet greppy die gängigen Flags auf den grep-Passthrough ab.
  3. Was grep nicht ausdrücken kann, scheitert laut, mit der nächstliegenden Alternative. Es wird nie stillschweigend zu einer anderen Suche.

ripgrep durchsucht Dateien parallel, die Reihenfolge seiner Ausgabe kann sich also von Lauf zu Lauf ändern. Für Vergleiche setzt du --sort path.

Befehle und Muster

greppy unterscheidet einen Befehl an seiner Form von einem Muster. Ein vertippter Befehl hat keinen Dateioperanden, und genau das trennt einen Tippfehler von einem Muster:

$ greppy renames serde_derive/src/internals/attr.rs
                    let (ser, de) = get_renames(cx, RENAME, &meta)?;
                    let (ser, de) = get_renames(cx, RENAME_ALL, &meta)?;
…
$ greppy who-call visit_content_map
status: invalid_invocation
command: `who-call`
message: unknown greppy command; nothing was passed to grep
next: did you mean `greppy who-calls ...`?

greppy renames DATEI bleibt grep und druckt die passenden Zeilen, wie das echte grep. greppy who-call visit_content_map endet mit Exit-Code 64 und einem did you mean-Hinweis.

Ein führendes -p ist für den eingebauten Agent reserviert. Um nach dem wörtlichen Muster -p zu suchen, nimmst du -e:

$ greppy -e -p .github/workflows/ci.yml
      - run: cargo docs-rs -p serde
      - run: cargo docs-rs -p serde_core
…

greppys eigene Flags sind keine grep-Flags. Trägt ein grep-Aufruf ein Flag, das zu greppy gehört, etwa --code, lehnt greppy ihn mit Exit-Code 64 ab, sagt, wohin das Flag gehört, und gibt nichts an grep weiter:

$ greppy -rn deny_unknown_fields serde_derive/src --code
`--code` is a greppy flag, not a grep flag - it belongs to greppy's navigation commands. Drop it, or name the command it goes with.

Nach einem ausdrücklichen rg gehört jede folgende Option zu ripgrep, greppy rg --json … erreicht ripgrep also unverändert.

Standardeingabe

Pipelines funktionieren wie mit grep:

$ ls serde_derive/src/de | greppy -n enum
1:enum_.rs
2:enum_adjacently.rs
3:enum_externally.rs
4:enum_internally.rs
5:enum_untagged.rs

Ein ausdrücklicher Operand - ist der unbegrenzte, byte-genaue Weg für langsame Erzeuger. Ist der einzige Operand ein Wort, das wie ein vertippter greppy-Befehl aussieht, etwa brif, gibt greppy einer impliziten stdin fünf Sekunden, um zu starten. Kommt nichts, meldet es den Tippfehler, statt zu warten, Exit-Code 64:

$ greppy brif
status: invalid_invocation
command: `brif`
message: unknown greppy command; nothing was passed to grep
next: did you mean `greppy brief ...`?

Andere Muster warten auf ihren Erzeuger, wie grep.

Kein grep-Ersatz

greppy installiert sich als greppy, nie als globaler grep-Alias und nie unter dem Namen grep. Dein System-grep bleibt dein grep.

In greppy ist der Passthrough das Werkzeug für Sicherheit auf Textebene: Brauchst du jedes textuelle Vorkommen eines Namens, auch in Kommentaren, Strings und Dateien, die der Graph nicht modelliert, ist grep die richtige Antwort. Für Texttreffer, die zusätzlich die umschließende Definition nennen, nimmst du search-pattern.

Für Agents

Der Agent-Prompt sagt Agents, auch für die Textsuche in greppy zu bleiben: greppy PATTERN [FILE] ist byte-identisches grep und greppy rg … ist ripgrep, jede Suche läuft also über ein Werkzeug. Siehe Agent einrichten.