Slide 1

Slide 1 text

ripgrep をライブラリとして使う Twitter: @Linda_pp, GitHub: @rhysd Rust LT Online #5 (2021/11/23)

Slide 2

Slide 2 text

ripgrep とは 有名な grep 互換のコマンドラインツール.SIMD や マルチスレッドを活用し処理を高速化してい る..gitignore を見てくれたり出力に色を付けてくれ たりなど,ユーザフレンドリーな機能も提供. pcre2 対応を除いて,すべて Rust で実装されてお り,ライブラリとしても使えるようにうまくモジュール化 されている.

Slide 3

Slide 3 text

活用例 hgrep (Human-friendly GREP) というツールでファイ ル検索に利用. 検索結果を良い感じにスニペットにまとめて構文ハイ ライトして出力するコマンドラインツール. - リポジトリ: https://github.com/rhysd/hgrep - 紹介記事: https://rhysd.hatenablog.com/entry/2021/11/2 3/211530

Slide 4

Slide 4 text

TL;DR https://github.com/rhysd/hgrep/bl ob/main/src/ripgrep.rs ↑ここにすべての実装が入っている grep 出力 パーサ grep 実装 chunk の 計算処理 Printer Bat Printer Syntect Printer ↓ ここの話 --no-ignore --ignore-case --smart-case --glob GLOB... --glob-case-insensitive --fixed-strings --word-regexp --follow --multiline --multiline-dotall --crlf --mmap --max-count NUM --max-depth NUM --max-filesize NUM+SUFFIX? --line-regexp --invert-match --pcre2 --type TYPE --type-not TYPE --type-list --one-file-system --no-unicode --regex-size-limit --dfa-size-limit

Slide 5

Slide 5 text

ripgrep のモジュール構造 ripgrep cli core ignore globpath matcher pcre2 regex memmem memchr printer searcher ripgrep 用 CLI パーサ (grep-cli) 本体(main 関数はここ) ディレクトリを辿ってファイルパスを列挙 **/*.rs のような glob のファイルパス列挙 ファイルに対するマッチ処理 (grep-matcher) pcre2 の Rust binding 有名な regex crate libc の memmem の SIMD 実装 libc の memchr の SIMD 実装 ファイルの検索処理 (grep-searcher) マッチ結果の表示処理 (grep-printer)

Slide 6

Slide 6 text

ripgrep のモジュール構造 ripgrep cli core ignore globpath matcher pcre2 regex memmem memchr printer searcher ripgrep 用 CLI パーサ (grep-cli) 本体(main 関数はここ) ディレクトリを辿ってファイルパスを列挙 **/*.rs のような glob のファイルパス列挙 ファイルに対するマッチ処理 (grep-matcher) pcre2 の Rust binding 有名な regex crate libc の memmem の SIMD 実装 libc の memchr の SIMD 実装 ファイルの検索処理 (grep-searcher) マッチ結果の表示処理 (grep-printer)

Slide 7

Slide 7 text

サンプルコード https://github.com/rhysd/misc/tree/master/rust/chibigrep - ripgrep のうち再帰的にファイルを検索してマッチ結果を 取得する処理部分だけを使う - 特定のテキストを含んだファイルを探してくるユースケー スを想定 - ignore, grep-matcher, grep-searcher の3つの crate を 使う

Slide 8

Slide 8 text

ディレクトリの walker を生成 use ignore::{WalkBuilder, WalkState}; // Path を再帰的に辿る walker を生成.今回は WalkParallel でマルチスレッドでパスを 辿る // スレッドプールは自動で生成される(スレッド数は指定もできるが,デフォルトで良い感じに決 めてくれる) let mut builder = WalkBuilder::new(path); for path in rest { builder.add(path); } builder .hidden(false) // 隠しファイルを検索 .ignore(true) // ignore されたファイルを無視 .parents(true); // 親ディレクトリを辿って .gitignore を探す let walker = builder.build_parallel();

Slide 9

Slide 9 text

ディレクトリをマルチスレッドで再帰的に辿る let (tx, rx) = mpsc::channel(); // walker.run はマルチスレッドで呼ばれるので値の受け渡しを channel でやる walker.run(|| { // 初期化関数.ここはスレッドプールのスレッドごとに呼ばれる let tx = tx.clone(); Box::new(move |result| match result { // この内側のコールバックはファイルパスごとに呼ばれる Ok(entry) if entry.file_type().map(|t| t.is_file()).unwrap_or(false) => { // `entry` は `ignore::DirEntry` grep_file(pat, entry.into_path(), &tx); // ファイルの時.ファイル内を検索 ignore::WalkState::Continue // 検索を続ける } Ok(_) => ignore::WalkState::Continue, // ディレクトリの時.検索を続ける Err(err) => { tx.send(Err(format!("{}", err))).unwrap(); ignore::WalkState::Quit // 検索を中止する } }) });

Slide 10

Slide 10 text

マッチ結果を受け取る Sink を実装 use grep_searcher::{Sink, SinkMatch}; struct SearchSink<'a> { tx: &'a Sender>, path: &'a Path, } // 結果を集めるためのコールバックを Sink で実装.マッチ箇所ごとに `matched` が呼ばれる impl<'a> Sink for SearchSink<'a> { type Error = io::Error; // `SinkMatch` にマッチ情報が入っている fn matched(&mut self, _searcher: &Searcher, mat: &SinkMatch<'_>) -> Result { let m = Match { path: self.path.to_owned(), lnum: mat.line_number().unwrap_or(0), line: mat.bytes().to_vec(), }; self.tx.send(Ok(m)).unwrap(); // マッチ結果を返す Ok(true) } }

Slide 11

Slide 11 text

マッチ処理を行う matcher を生成 use grep_regex::RegexMatcherBuilder; let mut builder = RegexMatcherBuilder::new(); builder .case_smart(true) // smart case を有効に .unicode(true); // unicode 対応 // Matcher を生成.今回は regex crate を使った RegexMatcher を使う // これ以外にも pcre2 を使ったものもある let matcher = match builder.build(pat) { Ok(m) => m, Err(err) => { tx.send(Err(format!("{}", err))).unwrap(); return; } };

Slide 12

Slide 12 text

searcher を生成してファイル内を検索 use grep_searcher::{BinaryDetection, MmapChoice, SearcherBuilder}; // Searcher を生成 let mut builder = SearcherBuilder::new(); builder .binary_detection(BinaryDetection::quit(0)) // バイナリファイルだと判明したら検索をやめる .line_number(true) .memory_map(unsafe { MmapChoice::auto() }); // mmap を有効にする let mut searcher = builder.build(); // ここでファイルを検索.マッチごとに sink の matched メソッドが呼ばれる let mut sink = SearchSink { tx, path: &path }; if let Err(err) = searcher.search_path(&matcher, &path, &mut sink) { tx.send(Err(format!("{}", err))).unwrap(); }

Slide 13

Slide 13 text

まとめ ● ripgrep は個々の機能ごと複数の crate に分割して実装されている ○ ファイルパス検索(ignore) ○ マッチ処理(grep-regex, grep-matcher) ○ ファイル検索(grep-searcher) ○ 結果表示(grep-printer) ● 「特定のパターンのテキストを含んだファイルを探してくる」という処理に対してはか なり汎用的・簡単に使うことができ,実行効率も良い(マルチスレッド活用・SIMD に よる検索実装)