Инструмент, похожий на grep
, который понимает синтаксис исходного кода и позволяет выполнять манипуляции в дополнение к поиску.
Как и grep
, регулярные выражения являются основным примитивом. В отличие от grep
, дополнительные возможности обеспечивают более высокую точность и возможность манипулирования . Это позволяет srgn
работать с регулярными выражениями измерений, а инструменты IDE ( Rename all , Find all references ,...) сами по себе не могут, дополняя их.
srgn
организован вокруг действий, которые необходимо предпринять (если таковые имеются), действующих только в пределах точных, опционально учитывающих грамматику языка областей . Что касается существующих инструментов, думайте об этом как о смеси tr
, sed
, ripgrep и tree-sitter
с целью простоты проектирования: если вы знаете регулярное выражение и основы языка, с которым работаете, все готово. .
Кончик
Все представленные здесь фрагменты кода проверены в рамках модульных тестов с использованием фактического двоичного файла srgn
. То, что здесь продемонстрировано, гарантированно работает.
Самое простое использование srgn
работает аналогично tr
:
$ echo ' Hello World! ' | srgn ' [wW]orld ' ' there ' # replacement
Hello there !
Совпадения с шаблоном регулярного выражения '[wW]orld'
( область действия ) заменяются ( действие ) вторым позиционным аргументом. Можно указать ноль или более действий:
$ echo ' Hello World! ' | srgn ' [wW]orld ' # zero actions: input returned unchanged
Hello World !
$ echo ' Hello World! ' | srgn --upper ' [wW]orld ' ' you ' # two actions: replacement, afterwards uppercasing
Hello YOU !
Замена всегда выполняется в первую очередь и указывается позиционно. Любые другие действия применяются после и задаются как флаги командной строки.
Аналогичным образом можно указать более одной области: в дополнение к шаблону регулярного выражения может быть задана область видимости с учетом грамматики языка , охватывающая синтаксические элементы исходного кода (например, «все тела определений class
в Python»). ). Если заданы оба параметра, шаблон регулярного выражения применяется только в первой области языка . Это позволяет выполнять поиск и манипулирование с точностью, которая обычно невозможна с использованием простых регулярных выражений, и использовать измерение, отличное от таких инструментов, как «Переименовать все» в IDE.
Например, рассмотрим этот (бессмысленный) исходный файл Python:
"""Module for watching birds and their age."""
from dataclasses import dataclass
@ dataclass
class Bird :
"""A bird!"""
name : str
age : int
def celebrate_birthday ( self ):
print ( "?" )
self . age += 1
@ classmethod
def from_egg ( egg ):
"""Create a bird from an egg."""
pass # No bird here yet!
def register_bird ( bird : Bird , db : Db ) -> None :
assert bird . age >= 0
with db . tx () as tx :
tx . insert ( bird )
который можно найти с помощью:
$ cat birds.py | srgn --python ' class ' ' age '
11: age: int
15: self.age += 1
Строка age
искалась и была найдена только в определениях class
Python (а не, например, в телах функций, таких как register_bird
, где age
также встречается и его было бы почти невозможно исключить из рассмотрения в vanilla grep
). По умолчанию этот «режим поиска» также печатает номера строк. Режим поиска включается, если не указаны никакие действия , а такому языку, как --python
присвоено значение 1 — думайте об этом как о «ripgrep, но с синтаксическими элементами языка».
Поиск также можно выполнять по строкам, например, для поиска методов (также известных как def
внутри class
), у которых нет строк документации:
$ cat birds.py | srgn --python ' class ' ' def .+:ns+[^"s]{3} ' # do not try this pattern at home
13: def celebrate_birthday(self):
14: print("?")
Обратите внимание, что это не проявляется ни from_egg
(имеет строку документации), ни register_bird
(не метод, def
вне class
).
Сами области языка также могут быть указаны несколько раз. Например, во фрагменте Rust
pub enum Genre {
Rock ( Subgenre ) ,
Jazz ,
}
const MOST_POPULAR_SUBGENRE : Subgenre = Subgenre :: Something ;
pub struct Musician {
name : String ,
genres : Vec < Subgenre > ,
}
несколько предметов могут быть хирургически рассверлены как
$ cat music.rs | srgn --rust ' pub-enum ' --rust ' type-identifier ' ' Subgenre ' # AND'ed together
2: Rock(Subgenre),
где возвращаются только строки, соответствующие всем критериям, действуя как логические и между всеми условиями. Обратите внимание, что условия оцениваются слева направо, что лишает смысла некоторые комбинации: например, поиск тела class
Python внутри doc-strings
Python обычно ничего не возвращает. Однако обратное работает, как и ожидалось:
$ cat birds.py | srgn --py ' class ' --py ' doc-strings '
8: """A bird!"""
19: """Create a bird from an egg."""
Никакие строки документации вне тела class
не отображаются!
Флаг -j
меняет это поведение: от пересечения слева направо к независимому выполнению всех запросов и объединению их результатов, что позволяет выполнять поиск несколькими способами одновременно:
$ cat birds.py | srgn -j --python ' comments ' --python ' doc-strings ' ' bird[^s] '
8: """A bird!"""
19: """Create a bird from an egg."""
20: pass # No bird here yet!
Шаблон bird[^s]
также встречался внутри комментариев или строк документации, а не только в «строках документации внутри комментариев».
Если стандартный ввод не указан, srgn
знает, как автоматически найти соответствующие исходные файлы, например, в этом репозитории:
$ srgn --python ' class ' ' age '
docs/samples/birds
11: age: int
15: self.age += 1
docs/samples/birds.py
9: age: int
13: self.age += 1
Он рекурсивно просматривает текущий каталог, находя файлы на основе расширений файлов и строк shebang, обрабатывая их на очень высокой скорости. Например, srgn --go strings 'd+'
находит и печатает все ~140 000 серий цифр в литеральных строках Go внутри кодовой базы Kubernetes, состоящей из ~3 000 000 строк кода Go, за 3 секунды на 12 ядрах M3. Подробнее о работе со многими файлами см. ниже.
Области действия и действия можно комбинировать практически произвольно (хотя многие комбинации не будут иметь смысла или даже смысла). Например, рассмотрим этот фрагмент Python (примеры использования других поддерживаемых языков см. ниже):
"""GNU module."""
def GNU_says_moo ():
"""The GNU function -> say moo -> ✅"""
GNU = """
GNU
""" # the GNU...
print ( GNU + " says moo" ) # ...says moo
для которого выполняется следующая команда:
cat gnu.py | srgn --titlecase --python ' doc-strings ' ' (?' ' $1: GNU ? is not Unix '
Анатомия этого призыва такова:
--titlecase
(действие) будет заголовком всего найденного в области видимости
--python 'doc-strings'
(область действия) будет охватывать (т.е. принимать во внимание только) строки документации в соответствии с грамматикой языка Python
'(? (область видимости) видит только то, что уже входило в область действия предыдущей опции, и еще больше сужает ее. Он никогда не сможет расширить предыдущую область действия. Область регулярного выражения применяется после любой языковой области.
(? — синтаксис отрицательного просмотра назад, демонстрирующий доступность этой расширенной функции. Строки
GNU
с префиксом The
не будут учитываться.
'$1: GNU ? is not Unix'
(действие) заменит каждое соответствующее вхождение (т. е. каждый входной раздел, находящийся в области видимости) этой строкой. Соответствующие вхождения — это шаблоны '(? только в строках документации Python. Примечательно, что эта строка замены демонстрирует:
$1
, которая содержит содержимое, захваченное первой группой регулярных выражений. Это ([az]+)
, поскольку (? не захватывает.
Команда использует несколько областей действия (язык и шаблон регулярного выражения) и несколько действий (замена и регистр заголовков). Результат тогда читается
"""Module: GNU ? Is Not Unix."""
def GNU_says_moo ():
"""The GNU function -> say moo -> ✅"""
GNU = """
GNU
""" # the GNU...
print ( GNU + " says moo" ) # ...says moo
где изменения ограничиваются:
- """GNU module."""
+ """Module: GNU ? Is Not Unix."""
def GNU_says_moo():
"""The GNU -> say moo -> ✅"""
Предупреждение
Пока srgn
находится в бета-версии (основная версия 0), обязательно обрабатывайте (рекурсивно) только те файлы, которые можно безопасно восстановить.
Режим поиска не перезаписывает файлы, поэтому он всегда безопасен.
Ниже приведена полная справочная информация об инструменте.
Примечание
Поддерживаемые языки:
Загрузите готовый двоичный файл из выпусков.
Этот крейт предоставляет свои двоичные файлы в формате, совместимом с cargo-binstall
:
cargo install cargo-binstall
(это может занять некоторое время)cargo binstall srgn
(пара секунд, поскольку он загружает готовые двоичные файлы с GitHub).Эти шаги гарантированно сработают™, поскольку они проверены в CI. Они также работают, если для вашей платформы нет готовых двоичных файлов, поскольку инструмент вернется к компиляции из исходного кода.
Формула доступна через:
brew install srgn
Доступно через нестабильную версию:
nix-shell -p srgn
Доступно через AUR.
Доступен порт:
sudo port install srgn
Все образы бегунов GitHub Actions поставляются с предустановленным cargo
, а cargo-binstall
предоставляет удобное действие GitHub:
jobs :
srgn :
name : Install srgn in CI
# All three major OSes work
runs-on : ubuntu-latest
steps :
- uses : cargo-bins/cargo-binstall@main
- name : Install binary
run : >
cargo binstall
--no-confirm
srgn
- name : Use binary
run : srgn --version
Вышеуказанное завершается всего за 5 секунд, поскольку компиляция не требуется. Дополнительную информацию см. в рекомендациях cargo-binstall
по CI.
В Linux gcc
работает.
В macOS используйте clang
.
В Windows MSVC работает.
При установке выберите «Разработка для настольных компьютеров с помощью C++».
cargo install srgn
cargo add srgn
Подробнее см. здесь.
Для сценариев завершения оболочки поддерживаются различные оболочки. Например, добавьте eval "$(srgn --completions zsh)"
к ~/.zshrc
для завершения в ZSH. Интерактивный сеанс может выглядеть так:
Инструмент разработан с учетом областей действия и действий . Области действия сужают части входных данных для обработки. Затем действия выполняют обработку. Как правило, обе области и действия являются компонуемыми, поэтому можно передать более одного из них. И то, и другое необязательно (но не предпринимать никаких действий бессмысленно); указание отсутствия области действия означает, что весь ввод находится в области действия.
В то же время существует значительное совпадение с простым tr
: инструмент разработан так, чтобы иметь близкое соответствие в наиболее распространенных случаях использования и выходить за рамки только при необходимости.
Самое простое действие – замена. Доступ к нему осуществляется специально (в качестве аргумента, а не опции) для совместимости с tr
и общей эргономики. Все остальные действия задаются как флаги или опции, если они принимают значение.
Например, простые односимвольные замены работают как в tr
:
$ echo ' Hello, World! ' | srgn ' H ' ' J '
Jello, World!
Первый аргумент — это область действия (в данном случае буква H
). Все, что соответствует ему, подлежит обработке (в данном случае замене на J
, второй аргумент). Однако прямого понятия классов символов, как в tr
, не существует. Вместо этого по умолчанию областью действия является шаблон регулярного выражения, поэтому его классы можно использовать для аналогичного эффекта:
$ echo ' Hello, World! ' | srgn ' [a-z] ' ' _ '
H____, W____!
По умолчанию замена происходит жадно по всему совпадению (обратите внимание на класс символов UTS, напоминающий tr
[:alnum:]
):
$ echo ' ghp_oHn0As3cr3T!! ' | srgn ' ghp_[[:alnum:]]+ ' ' * ' # A GitHub token
*!!
Поддерживаются расширенные функции регулярных выражений, например обходные пути:
$ echo ' ghp_oHn0As3cr3T ' | srgn ' (?<=ghp_)[[:alnum:]]+ ' ' * '
ghp_*
Будьте осторожны при их безопасном использовании, поскольку передовые модели не имеют определенных гарантий безопасности и производительности. Если они не используются, производительность не пострадает.
Замена не ограничивается одним символом. Это может быть любая строка, например, чтобы исправить эту цитату:
$ echo ' "Using regex, I now have no issues." ' | srgn ' no issues ' ' 2 problems '
"Using regex, I now have 2 problems."
Инструмент полностью поддерживает Unicode и имеет полезную поддержку некоторых расширенных классов символов:
$ echo ' Mood: ? ' | srgn ' ? ' ' ? '
Mood: ?
$ echo ' Mood: ???? :( ' | srgn ' p{Emoji_Presentation} ' ' ? '
Mood: ???? :(
Замены учитывают переменные, которые становятся доступными для использования через группы захвата регулярных выражений. Группы захвата могут быть пронумерованы или, при необходимости, названы. Нулевая группа захвата соответствует всему совпадению.
$ echo ' Swap It ' | srgn ' (w+) (w+) ' ' $2 $1 ' # Regular, numbered
It Swap
$ echo ' Swap It ' | srgn ' (w+) (w+) ' ' $2 $1$1$1 ' # Use as many times as you'd like
It SwapSwapSwap
$ echo ' Call +1-206-555-0100! ' | srgn ' Call (+?d-d{3}-d{3}-d{4}).+ ' ' The phone number in "$0" is: $1. ' # Variable `0` is the entire match
The phone number in "Call +1-206-555-0100!" is: +1-206-555-0100.
Более продвинутый вариант использования — это, например, рефакторинг кода с использованием именованных групп захвата (возможно, вы сможете придумать более полезный вариант...):
$ echo ' let x = 3; ' | srgn ' let (?[a-z]+) = (?.+); ' ' const $var$var = $expr + $expr; '
const xx = 3 + 3;
Как и в bash, используйте фигурные скобки, чтобы устранить неоднозначность переменных из непосредственно соседнего содержимого:
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2${1}1 '
211
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2$11 ' # will fail (`11` is unknown)
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2${11 ' # will fail (brace was not closed)
Поскольку замена представляет собой просто статическую строку, ее полезность ограничена. Именно здесь обычно вступает в игру секретный соус tr
: использование его классов символов, которые действительны также и во второй позиции, аккуратное преобразование членов первой во вторую. Здесь эти классы являются регулярными выражениями и действительны только в первой позиции (область действия). Поскольку регулярное выражение является конечным автоматом, его невозможно сопоставить со «списком символов», который в tr
является вторым (необязательным) аргументом. Эта концепция утеряна, и ее гибкость потеряна.
Вместо этого используются предложенные действия, все они фиксированные . Взглянув на наиболее распространенные варианты использования tr
вы поймете, что предоставленный набор действий охватывает практически все из них! Не стесняйтесь сообщать о проблеме, если ваш вариант использования не охвачен.
Переходим к следующему действию.
Удаляет все, что найдено на входе. То же имя флага, что и в tr
.
$ echo ' Hello, World! ' | srgn -d ' (H|W|!) '
ello, orld
Примечание
Поскольку область по умолчанию соответствует всему вводу, указывать удаление без области является ошибкой.
Сжимает повторы символов, соответствующих области действия, в отдельные экземпляры. То же имя флага, что и в tr
.
$ echo ' Helloooo Woooorld!!! ' | srgn -s ' (o|!) '
Hello World!
Если передается класс символов, все члены этого класса помещаются в тот член класса, который был обнаружен первым:
$ echo ' The number is: 3490834 ' | srgn -s ' d '
The number is: 3
Жадность при сопоставлении не изменяется, поэтому будьте осторожны:
$ echo ' Winter is coming... ??? ' | srgn -s ' ?+ '
Winter is coming... ???
Примечание
Узор совпал со всей серией солнышек, так что сжимать нечего. Лето преобладает.
Инвертируйте жадность, если этого требует вариант использования:
$ echo ' Winter is coming... ??? ' | srgn -s ' ?+? ' ' ☃️ '
Winter is coming... ☃️
Примечание
Опять же, как и в случае с удалением, указание сжатия без явной области действия является ошибкой. В противном случае весь ввод сжимается.
Значительная часть использования tr
попадает в эту категорию. Это очень просто.
$ echo ' Hello, World! ' | srgn --lower
hello, world!
$ echo ' Hello, World! ' | srgn --upper
HELLO, WORLD!
$ echo ' hello, world! ' | srgn --titlecase
Hello, World!
Разлагает входные данные в соответствии с формой нормализации D, а затем отбрасывает кодовые точки категории «Марк» (см. примеры). Грубо говоря, это означает: возьмите причудливый персонаж, оторвите болтающиеся детали и выбросьте их.
$ echo ' Naïve jalapeño ärgert mgła ' | srgn -d ' P{ASCII} ' # Naive approach
Nave jalapeo rgert mga
$ echo ' Naïve jalapeño ärgert mgła ' | srgn --normalize # Normalize is smarter
Naive jalapeno argert mgła
Обратите внимание, что mgła
выходит за рамки NFD, поскольку он «атомарный» и, следовательно, неразлагаемый (по крайней мере, так шепчет мне на ухо ChatGPT).
Это действие заменяет многосимвольные символы ASCII соответствующими однокодовыми символами в собственном коде Unicode.
$ echo ' (A --> B) != C --- obviously ' | srgn --symbols
(A ⟶ B) ≠ C — obviously
В качестве альтернативы, если вас интересует только математика, используйте область видимости:
$ echo ' A <= B --- More is--obviously--possible ' | srgn --symbols ' <= '
A ≤ B --- More is--obviously--possible
Поскольку между символом ASCII и его заменой существует соответствие 1:1, эффект обратим2 :
$ echo ' A ⇒ B ' | srgn --symbols --invert
A => B
На данный момент поддерживается только ограниченный набор символов, но можно добавить больше.
Это действие заменяет альтернативные варианты написания немецких специальных символов (ae, oe, ue, ss) их родными версиями (ä, ö, ü, ß) 3 .
$ echo ' Gruess Gott, Neueroeffnungen, Poeten und Abenteuergruetze! ' | srgn --german
Grüß Gott, Neueröffnungen, Poeten und Abenteuergrütze!
Это действие основано на списке слов (компилируйте без функции german
если это слишком сильно раздувает ваш двоичный файл). Обратите внимание на следующие особенности приведенного выше примера:
Poeten
остался как есть, вместо того, чтобы быть по наивности и ошибке преобразованным в Pöten
Abenteuergrütze
нельзя найти ни в одном разумном списке слов, но, тем не менее, с ним обращались правильно.Abenteuer
также остался как есть, вместо того, чтобы быть ошибочно преобразованным в Abenteür
Neueroeffnungen
незаметно образует элемент ue
, которым не обладает ни одно из составляющих слов ( neu
, Eröffnungen
), но все равно обрабатывается правильно (несмотря на несовпадающие регистры)По запросу замены могут быть произведены принудительно, что потенциально полезно для имен:
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ '
Frau Lötter steht ueber der Mauer.
Благодаря положительному просмотру вперед ничего, кроме приветствия, не было охвачено и, следовательно, изменено. Mauer
правильно остался как есть, но ueber
не обработался. Второй проход исправляет это:
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ ' | srgn --german
Frau Lötter steht über der Mauer.
Примечание
Опции и флаги, относящиеся к некоторому «родителю», имеют префикс с именем своего родителя и, если они заданы, подразумевают его родителя, так что последний не нужно передавать явно. Вот почему --german-naive
называется так, как есть, и --german
не нужно передавать.
Это поведение может измениться, как только clap
будет поддерживать цепочку подкоманд.
Некоторые ответвления неразрешимы для этого скромного инструмента, поскольку он работает без языкового контекста. Например, и Busse
(автобусы), и Buße
(покаяние) являются законными словами. По умолчанию замены выполняются жадно, если это разрешено (в конце концов, в этом весь смысл srgn
), но есть флаг для переключения этого поведения:
$ echo ' Busse und Geluebte ' | srgn --german
Buße und Gelübte
$ echo ' Busse ? und Fussgaenger ?♀️ ' | srgn --german-prefer-original
Busse ? und Fußgänger ?♀️
Большинство действий можно компоновать, если только это не бессмысленно (например, удаление). Порядок их применения фиксирован, поэтому порядок заданных флагов не имеет никакого влияния (при необходимости можно использовать несколько прогонов). Замены всегда происходят в первую очередь. Как правило, CLI предназначен для предотвращения неправильного использования и неожиданностей: он предпочитает сбой, чем что-то неожиданное (что, конечно, субъективно). Обратите внимание, что технически возможно множество комбинаций, но они могут привести к бессмысленным результатам.
Объединение действий может выглядеть так:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu
KOEFFIZIENTEN ≠ BRÜCKEN...
Можно указать более узкую область применения, которая будет одинаково применяться ко всем действиям:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu ' bw{1,8}b '
Koeffizienten != BRÜCKEN...
Границы слов являются обязательными, иначе Koeffizienten
сопоставляется как Koeffizi
и enten
. Обратите внимание, что скользящие периоды нельзя, например, сжать. Требуемый объем .
будет мешать данному. Обычные трубопроводы решают эту проблему:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu ' bw{1,8}b ' | srgn -s ' . '
Koeffizienten != BRÜCKEN.
Примечание. Экранирование регулярных выражений ( .
) можно обойти, используя литеральную область видимости. Специально обработанное действие замены также является компонуемым:
$ echo ' Mooood: ????!!! ' | srgn -s ' p{Emoji} ' ' ? '
Mooood: ?!!!
Эмоджи сначала все заменяются, потом сжимаются. Обратите внимание, что больше ничего не сжимается.
Области действия — это вторая движущая концепция srgn
. По умолчанию основной областью действия является регулярное выражение. В разделе действий этот вариант использования описан довольно подробно, поэтому здесь он не повторяется. Он задается как первый позиционный аргумент.
srgn
расширяет это за счет подготовленных областей, учитывающих грамматику языка, что стало возможным благодаря превосходной библиотеке tree-sitter
. Он предлагает функцию запросов, которая очень похожа на сопоставление шаблонов с древовидной структурой данных.
srgn
поставляется с несколькими наиболее полезными из этих запросов. Через его обнаруживаемый API (в виде библиотеки или через CLI, srgn --help
) можно узнать о поддерживаемых языках и доступных подготовленных запросах. Каждый поддерживаемый язык имеет аварийный выход, позволяющий запускать собственные специальные запросы. Люк имеет вид --lang-query
, где lang
— это такой язык, как python
. Дополнительную информацию по этой сложной теме смотрите ниже.
Примечание
Языковые области применяются в первую очередь , поэтому какое бы регулярное выражение или основную область вы ни передали, оно работает с каждой соответствующей языковой конструкцией индивидуально.
В этом разделе показаны примеры некоторых подготовленных запросов .
unsafe
кода (Rust) Одним из преимуществ ключевого слова unsafe
в Rust является его «grepability». Однако rg 'unsafe'
, конечно, выявляет все совпадения строк (в некоторой степени помогает rg 'bunsafeb'
), а не только те, которые содержатся в фактическом ключевом слове языка Rust. srgn
помогает сделать это более точным. Например:
// Oh no, an unsafe module!
mod scary_unsafe_operations {
pub unsafe fn unsafe_array_access ( arr : & [ i32 ] , index : usize ) -> i32 {
// UNSAFE: This function performs unsafe array access without bounds checking
* arr . get_unchecked ( index )
}
pub fn call_unsafe_function ( ) {
let unsafe_numbers = vec ! [ 1 , 2 , 3 , 4 , 5 ] ;
println ! ( "About to perform an unsafe operation!" ) ;
let result = unsafe {
// Calling an unsafe function
unsafe_array_access ( & unsafe_numbers , 10 )
} ;
println ! ( "Result of unsafe operation: {}" , result ) ;
}
}
можно искать как