ソース コードの構文を理解し、検索に加えて操作を可能にするgrep
に似たツール。
grep
と同様、正規表現はコアプリミティブです。 grep
とは異なり、追加機能により操作オプションを使用して精度を高めることができます。これにより、 srgn
正規表現と IDE ツール (すべての名前を変更、すべての参照の検索など) だけでは不可能なディメンションに沿って操作できるようになり、それらを補完します。
srgn
は、実行するアクション(存在する場合) を中心に編成されており、正確な、オプションで言語文法を意識したスコープ内でのみ動作します。既存のツールに関しては、簡素化という設計目標を備えたtr
、 sed
、 ripgrep 、 tree-sitter
を組み合わせたものと考えてください。正規表現と、使用している言語の基本を知っていれば、問題なく使用できます。 。
ヒント
ここに表示されるすべてのコード スニペットは、実際のsrgn
バイナリを使用した単体テストの一部として検証されています。ここで紹介するものは動作することが保証されています。
最も単純なsrgn
使用法はtr
と同様に機能します。
$ echo ' Hello World! ' | srgn ' [wW]orld ' ' there ' # replacement
Hello there !
正規表現パターン'[wW]orld'
(スコープ) の一致は、2 番目の位置引数によって置き換えられます (アクション)。 0 個以上のアクションを指定できます。
$ 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 !
置換は常に最初に実行され、位置が指定されます。他のアクションはコマンド ライン フラグの後に適用され、コマンド ライン フラグとして指定されます。
同様に、複数のスコープを指定できます。正規表現パターンに加えて、ソース コードの構文要素(たとえば、「Python のclass
定義のすべての本体」など) をスコープとする、言語文法を意識したスコープを指定できます。 )。両方が指定された場合、正規表現パターンは最初の言語スコープ内でのみ適用されます。これにより、単純な正規表現を使用して通常は不可能な精度での検索と操作が可能になり、IDE のRename allなどのツールとは異なる次元で機能します。
たとえば、次の (無意味な) 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
、Python class
定義内でのみ検索され、見つかりました (たとえば、 age
も発生し、バニラのgrep
で考慮から除外するのはほぼ不可能であるregister_bird
などの関数本体ではありません)。デフォルトでは、この「検索モード」では行番号も出力されます。アクションが指定されていない場合、検索モードに入り、 --python
などの言語には1 が与えられます。これは、「ripgrep ですが構文言語要素が含まれる」ようなものだと考えてください。
検索は行をまたがって実行することもできます。たとえば、docstring が欠落しているメソッド ( class
内のdef
とも呼ばれます) を見つける場合です。
$ 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
(docstring を持つ) または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),
ここでは、すべての条件の間の論理andのように機能して、すべての条件に一致する行のみが返されます。条件は左から右に評価されるため、一部の組み合わせは意味をなさないことに注意してください。たとえば、Python doc-strings
内の Python class
本体を検索しても、通常は何も返されません。ただし、その逆は期待どおりに機能します。
$ cat birds.py | srgn --py ' class ' --py ' doc-strings '
8: """A bird!"""
19: """Create a bird from an egg."""
class
本体の外にある docstring は表示されません。
-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]
「コメント内のdocstring」だけでなく、コメントまたはdocstringの内部でも同様に見つかりました。
標準入力が指定されない場合、 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
現在のディレクトリを再帰的に探索し、ファイル拡張子とシバン行に基づいてファイルを検索し、非常に高速に処理します。たとえば、 srgn --go strings 'd+'
は、M3 の 12 コア上で、約 3,000,000 行の Go コードの Kubernetes コードベース内のリテラル Go 文字列の約 140,000 行のすべての数字を 3 秒以内に検索して出力します。多くのファイルの操作の詳細については、以下を参照してください。
スコープとアクションは、ほぼ任意に組み合わせることができます (ただし、多くの組み合わせは役に立たず、意味さえありません)。たとえば、次の 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 言語の文法に従って docstring をスコープとします (つまり、考慮するだけです)。
'(? (スコープ) は、前のオプションですでにスコープされているものだけを参照し、さらに絞り込みます。以前の範囲を拡張することはできません。正規表現スコープは、言語スコープの後に適用されます。
(?は負の後読み構文で、この高度な機能がどのように利用できるかを示しています。
The
という接頭辞が付いたGNU
の文字列は考慮されません。
'$1: GNU ? is not Unix'
(アクション) は、一致した各出現箇所 (つまり、スコープ内にあることが判明した各入力セクション) をこの文字列で置き換えます。一致するのは、Python docstring内でのみ'(?のパターンです。特に、この置換文字列は次のことを示しています。
$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 Action を提供します。
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 秒で完了します。詳しいコンテキストについては、CI に関するcargo-binstall
のアドバイスを参照してください。
Linux ではgcc
機能します。
macOS では、 clang
を使用します。
Windows では、MSVC が機能します。
インストール時に「C++によるデスクトップ開発」を選択します。
cargo install srgn
実行します cargo add srgn
詳細については、ここを参照してください。
シェル完了スクリプトでは、さまざまなシェルがサポートされています。たとえば、ZSH で補完を行うには、 eval "$(srgn --completions zsh)"
~/.zshrc
に追加します。インタラクティブなセッションは次のようになります。
このツールはスコープとアクションを中心に設計されています。スコープは、処理する入力の部分を絞り込みます。その後、アクションによって処理が実行されます。一般に、スコープとアクションは両方とも構成可能であるため、それぞれ複数を渡すことができます。どちらもオプションです (ただし、何もアクションを起こさないのは無意味です)。スコープを指定しないことは、入力全体がスコープ内にあることを意味します。
同時に、プレーンtr
とかなりの重複があります。このツールは、最も一般的なユースケースで密接に対応し、必要な場合にのみそれを超えるように設計されています。
最も簡単なアクションは置き換えです。 tr
との互換性および一般的な人間工学のために、(オプションではなく引数として) 特別にアクセスされます。他のすべてのアクションはフラグとして指定されるか、値を取る場合はオプションとして指定されます。
たとえば、単純な単一文字の置換はtr
のように機能します。
$ echo ' Hello, World! ' | srgn ' H ' ' J '
Jello, World!
最初の引数はスコープ (この場合はリテラルH
) です。これに一致するものはすべて処理の対象になります (この場合は 2 番目の引数J
による置換)。ただし、 tr
のような文字クラスの直接的な概念はありません。代わりに、デフォルトではスコープは正規表現パターンであるため、そのクラスを使用して同様の効果を得ることができます。
$ echo ' Hello, World! ' | srgn ' [a-z] ' ' _ '
H____, W____!
デフォルトでは、置換は一致全体にわたって貪欲に行われます ( tr
の[:alnum:]
を彷彿とさせる UTS 文字クラスに注意してください)。
$ echo ' ghp_oHn0As3cr3T!! ' | srgn ' ghp_[[:alnum:]]+ ' ' * ' # A GitHub token
*!!
ルックアラウンドなどの高度な正規表現機能がサポートされています。
$ echo ' ghp_oHn0As3cr3T ' | srgn ' (?<=ghp_)[[:alnum:]]+ ' ' * '
ghp_*
高度なパターンには安全性とパフォーマンスが保証されていないため、安全に使用するように注意してください。これらを使用しなくても、パフォーマンスには影響しません。
置換される文字は 1 文字に限定されません。たとえば、この引用符を修正するには、任意の文字列を指定できます。
$ 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: ???? :(
置換は変数を認識し、正規表現キャプチャ グループを通じてアクセスして使用できるようになります。キャプチャ グループには番号を付けることも、オプションで名前を付けることもできます。 0 番目のキャプチャ グループは、一致全体に対応します。
$ 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
の秘密のソースが機能します。つまり、2 番目の位置でも有効な文字クラスを使用して、最初のメンバーから 2 番目のメンバーにきちんと変換します。ここで、これらのクラスは正規表現であり、最初の位置 (スコープ) でのみ有効です。正規表現は状態マシンであるため、 tr
の 2 番目 (オプション) 引数である「文字のリスト」と照合することはできません。その概念は枠外となり、柔軟性が失われています。
代わりに、提供されたアクション (すべて固定) が使用されます。 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
構成単語 ( neu
、 Eröffnungen
) のどちらも持っていないue
要素をこっそり形成しますが、(大文字小文字の不一致にも関わらず) 正しく処理されます。リクエストに応じて、名前に役立つ可能性があるため、置換を強制することができます。
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ '
Frau Lötter steht ueber der Mauer.
肯定的な先読みにより、挨拶以外の何も範囲が設定されず、変更されました。 Mauer
正しくそのままでしたが、 ueber
処理されませんでした。 2 回目のパスでこれを修正します。
$ 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
に続く 2 番目の推進コンセプトです。デフォルトの場合、メインスコープは正規表現です。この使用例についてはアクションのセクションで詳しく紹介したため、ここでは繰り返しません。これは最初の位置引数として指定されます。
srgn
優れたtree-sitter
ライブラリを通じて可能になる、準備された言語文法を意識したスコープを通じてこれを拡張します。ツリー データ構造に対するパターン マッチングとよく似たクエリ機能を提供します。
srgn
これらのクエリのうち最も有用なものがいくつかバンドルされています。検出可能な API (ライブラリとして、または CLI、 srgn --help
経由) を通じて、サポートされている言語と利用可能な準備されたクエリを知ることができます。サポートされている各言語にはエスケープ ハッチが付属しており、独自のカスタム アドホック クエリを実行できます。ハッチングは--lang-query
の形式で提供されます。ここで、 lang
はpython
などの言語です。この高度なトピックの詳細については、以下を参照してください。
注記
言語スコープが最初に適用されるため、どの正規表現、別名メイン スコープを渡しても、一致した各言語構造に対して個別に動作します。
このセクションでは、準備されたクエリのいくつかの例を示します。
unsafe
コードの検索 (Rust) Rust におけるunsafe
キーワードの利点の 1 つは、その「grepability」です。ただし、 rg 'unsafe'
は、実際の Rust 言語キーワードの文字列だけでなく、すべての文字列の一致を明らかにします ( rg 'bunsafeb'
ある程度役立ちます)。 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 ) ;
}
}
次のように検索できます