Genau ein Symbol lesen, genau ein Symbol ändern
Was du davon hast
Dein Agent liest eine Funktion, eine Methode oder einen Typ beim Namen, ohne Zeilennummern zu suchen. Für eine Datei bekommt er ihre Gliederung. Geändert wird genauso gezielt: ein Symbol ersetzen, überall umbenennen, mehrere Dateien in einem Patch. Jede Änderung meldet sich mit einer Quittung, und greppy undo nimmt sie in einem Schritt zurück.
Vorher und nachher
Erste Aufgabe an serde, zum Lesen: Zeig mir ContentDeserializer::deserialize_any.
Ohne greppy
In serde/src/private/de.rs gibt es zwölf Funktionen namens deserialize_any. Der Agent sucht die richtige über ihren impl-Block und schneidet sie dann mit sed aus.
$ grep -n 'impl.*for ContentDeserializer\|fn deserialize_any' serde/src/private/de.rs
38: fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, E>
1120: impl<'de, E> Deserializer<'de> for ContentDeserializer<'de, E>
1126: fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
1546: fn deserialize_any<V>(mut self, visitor: V) -> Result<V::Value, Self::Error>
…
$ sed -n '1126,/^ }$/p' serde/src/private/de.rs
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.content {
Content::Bool(v) => visitor.visit_bool(v),
…
}
Mit greppy
$ greppy read ContentDeserializer::deserialize_any
serde/src/private/de.rs:1126-1154 ContentDeserializer::deserialize_any
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.content {
Content::Bool(v) => visitor.visit_bool(v),
…
Content::Map(v) => visit_content_map(v, visitor),
}
}
| ohne greppy | mit greppy | |
|---|---|---|
| Aufrufe | 2 | 1 |
| Zeichen Ausgabe | 2.737 | 1.639 |
| Token | 696 | 364 |
Zweite Aufgabe, zum Ändern: Benenne get_lit_str überall in get_str_lit um. Die Änderung lief in einer privaten Kopie von serde.
Ohne greppy
Der Agent prüft jede Fundstelle darauf, dass sie wirklich die Funktion meint, und ersetzt dann per Textersetzung.
$ grep -rnw get_lit_str --include='*.rs' .
./serde_derive/src/internals/attr.rs:416: if let Some(s) = get_lit_str(cx, TAG, &meta)? {
./serde_derive/src/internals/attr.rs:438: if let Some(s) = get_lit_str(cx, CONTENT, &meta)? {
…
./serde_derive/src/internals/attr.rs:1411:fn get_lit_str(
…
./serde_derive/src/internals/attr.rs:1545: let Some(string) = get_lit_str(cx, BORROW, meta)? else {
$ perl -pi -e 's/\bget_lit_str\b/get_str_lit/g' serde_derive/src/internals/attr.rs
Mit greppy
$ greppy rename get_lit_str get_str_lit
applied serde_derive/src/internals/attr.rs:416,438,490,787,1086,1411,1460,1481,1523,1545 a6f0ae
| ohne greppy | mit greppy | |
|---|---|---|
| Aufrufe | 2 | 1 |
| Zeichen Ausgabe | 1.039 | 97 |
| Token | 316 | 44 |
Beide Wege ergeben eine byte-identische Datei: die Definition und neun Aufrufe in serde_derive/src/internals/attr.rs heißen jetzt get_str_lit. Die Quittung nennt jede geänderte Zeile und die Edit-ID. Ein Befehl nimmt die ganze Umbenennung zurück:
$ greppy undo
reversed a6f0ae serde_derive/src/internals/attr.rs
So funktioniert es
read holt die Definition über den Index und druckt ihre aktuellen Bytes samt Doc-Kommentaren und Attributen. Jeder Lesebefehl gibt auf Wunsch (--handle) einen Handle aus, der Datei, Byte-Bereich und Inhalts-Hash festhält. replace-span lehnt ab, sobald sich die Datei seitdem geändert hat. rename ändert die Definition und jede Referenz, die der Graph auflöst, und lehnt ab, wenn der alte Name noch als Bezeichner ohne aufgelöste Referenz vorkommt. patch landet einen Unified Diff atomar über alle Dateien: Entweder ändern sich alle, oder keine. --verify prüft die berührten Dateien mit dem passenden lokalen Checker und meldet dessen Diagnosen. Jede Änderung bekommt eine Edit-ID, undo macht sie als Ganzes rückgängig.
Die Befehle
| Befehl | was er tut |
|---|---|
greppy read S |
die Definition eines Symbols, byte-genau mit Doc-Kommentaren und Attributen; für eine Datei ihre Gliederung |
greppy read-file F --lines A:B |
genau den Zeilenbereich A bis B einer Datei, inklusiv und ab 1 gezählt |
greppy replace S |
eine Definition ersetzen, mit --body ihren Rumpf; der neue Text kommt als Argument oder über stdin |
greppy patch --verify |
einen Unified Diff als eine Transaktion über alle Dateien anwenden und die berührten Dateien prüfen |
greppy rename S NAME |
eine Definition und jede vom Graph aufgelöste Referenz umbenennen |
greppy undo |
die letzte Änderung zurücknehmen, mit ID eine bestimmte |
Mehr in der Doku
- Quelltext lesen:
read,read-smart,read-fileund Handles - Transaktional ändern: alle Edit-Befehle, Quittungen,
--verifyundundo