たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

雑記2024-09-05

エアブラシ

ガレージキットを買った。学生の頃に欲しかったもので、当時は金がなさ過ぎて買えなかった。あと自分で塗装というのもなかなかハードルが高かった。ガレージキットはその時しか買えない場合が多く、それっきりになっていた。

ところがなんと再販されることになった。今は経済的にも余裕があるし、そろそろ挑戦してみたかったのでえいやで買ってみた。ツール一式もガーッと買った。

パーティングラインを磨いて消して、軸打って、エアブラシで塗装してというものを初めてやった。やってみると構えていたほどでもなかった。もちろん結構時間はかかったし、うまい人なんかと比べるとアレ。でも色や濃度を研究したりするのは楽しかったし、キットは個人的に納得いくものになってすごくよかった。同時に沼にも落ちた。気がする。次はワンフェスとか行きたい。

ミニチュア

上で書いたガレージキットを飾るために簡単なジオラマも作った。カフェっぽいやつ。百均で揃えた材料や素材をちょっと加工する程度。こちらも個人的には良いのができた。加工方法は動画やらネットやらを見て真似してみる感じ。参入しやすくなっててありがたい限り。 先にガレージキットの話を書いたが、最近はむしろこっちがメインになりつつある。めちゃくちゃ素敵なミニチュアとかドールハウスの写真集とかを見ていいな~って。 道具はある程度あるから、そのうち作ってみたいなとも思う。

おわり

やりたいことが多いな~。どれかをちゃんと最後までやらないととは思うけど、全部中途半端になっている感じ。でも期限きってやるのは仕事っぽさが出てモチベが続かないよね。

第69回シェル芸勉強会大阪サテライトに参加しました

参加しました。今回は久しぶりの午前の部がありました。配信アーカイブは以下から

午前

午前は「自作シェルとRustの勉強会」と題して、SD誌に連載中の自作シェルRusty Bash(寿司シェル)の話と実装に利用しているRustについてでした。個人的にはRusty Bashって名前好きです。

Rustはsurgeを作ったとき以来書いてないのですべて無知、初見のつもりで聞いてました。 strStringの違い、メモリの配置などぼんやりしか理解してなかった部分がはっきりしたので良かったです。

大阪サテライトでは、解説内容の所々で出た内容に対する疑問を話したりしてました。これも楽しかったです。出た疑問のいくつかはそのまま配信で触れられて「エスパーなのかな?」って思ったりもしました。

またRust書いてみようかな~

午後

午後はいつもの問題を解くやつでした。全6問。今回もBash/Zsh解とは別にPowerShell解も出してみます。ただし今回は「このツールを使って解いた」みたいなのが多いので、差異があんまりない場合はやらないです

準備

今回はいつものShellGeiDataリポジトリではなく、午前にも登場したshellgei/rusty_bashを利用するとのことでした。

Q1

src/以下にあるRustのソースから、メソッドが4つ以上つながっている部分を探すという問題。

$ rg '[^.]+(\.[^.]+\(.*\)){4}'
feeder/terminal/completion.rs
116:                    completion::compgen_h(core, &mut args).to_vec().into_iter().filter(|h| h.len() > 0).collect()

elements/subword.rs
71:        split_str(self.get_text()).iter().map(|s| f(s.to_string())).collect()

utils/directory.rs
35:    fs.iter().filter(|f| compare(f) ).map(|f| make_path(f) ).collect()

elements/command.rs
72:        self.get_redirects().iter_mut().rev().for_each(|r| r.restore());

想定解としてはこの4つだったみたいですね。コマンドはまぁ正規表現頑張って書きました。という感じです。メソッドの引数内に()が登場するので、その入れ子をいい感じにエスケープする必要がありますね。

もうちょっとそれ専用のツールを使った解も出してみます。

$ sg -p '$A.($$$).($$$).($$$).($$$)' -l rust
./elements/word.rs
74│        words.iter_mut()
75│              .map(|w| w.make_unquoted_word())
76│              .filter(|w| *w != None)
77│              .map(|w| w.unwrap())
78│              .collect()
82let sw: Vec<Option<String>> = self.subwords.iter_mut()
83│            .map(|s| s.make_unquoted_string())
84│            .filter(|s| *s != None)
85│            .collect();

<意外と長いので略>

ast-grepは抽象構文木を検索できるツールです。今回は4つ以上連鎖するメソッド呼び出しということなので、それにマッチするクエリを書いて検索したって感じですね。クエリを書くのがむずいんですが、正規表現を学ぶときと同じで慣れたら楽なんだろうなって思いました。改行を跨いだものも検索できるのでいいですね。

面白いのは --json オプションでマッチ箇所の情報をJSONで出力できるところで、あとの問題でも使ってます。

PowerShell解は同じもの使うだけなので省略です

Q2

ShellCoreCore に置き換えてコンパイルが通るようにする問題

$ sed -i 's/ShellCore/Core/g' ./**/*.rs
$ cargo build

sedで置換するだけでした。解答で見かけたやつですが \<ShellCore\> とすることで AShellCore みたいなものを除外できるとのことでした。勉強になる。

Q3

この問題は小問がありました

小問1

nix::sys::utsname::uname のように、パスが3段以上になっている個所をファイル名、行番号付きで出力してください。ただし use 行は除いてください。という問題

$ sg -p '$$$::$$$::$$$' | grep -v ':use'
./utils.rs:64:    if let Ok(info) = nix::sys::utsname::uname() {
./main.rs:33:        nix::unistd::dup2(2, fd).expect("sush(fatal): init error");
./main.rs:43:            nix::unistd::close(fd).expect("sush(fatal): init error");
./main.rs:47:            thread::sleep(time::Duration::from_millis(100)); //0.1秒周期に変更
./main.rs:78:    builtins::set::set_parameters(&mut core, &mut args);
./elements/pipeline.rs:62:        let self_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_SELF).unwrap();
./elements/pipeline.rs:63:        let children_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_CHILDREN).unwrap();
./elements/subword/command.rs:53:            thread::sleep(time::Duration::from_millis(1));
./core.rs:171:            let self_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_SELF).unwrap();
./core.rs:172:            let children_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_CHILDREN).unwrap();
./core.rs:245:        let pid = nix::unistd::getpid();
<省略>

ast-grep を利用しました。このままだとuse行なものが含まれますが、それは grep で除外する感じですね。

小問2

小問1で見つけたパスの内、elements/pipeline.rsのものについて、パスを2段にして、残りをuseにしてくださいという問題。大変そう

$ ADD=0 sg -p '$$$::$$$::$$$' -l rust ./elements/pipeline.rs --json | \
  jq -r '.[]|[.text, .range.start.line]|@csv' | \
  tr -d \" | \
  awk -F, '{if(!($2 in a) || length($1)<length(a[$2])) a[$2]=$1; cnt[$2]++} END{for(x in a) if(cnt[x] > 1) print x, a[x]}' | \
  while read L R; do sed -i "$((L+ADD))s/${R%::*}:://g;" ./elements/pipeline.rs; if ! grep "use $R;" ./elements/pipeline.rs --quiet; then sed -i "4iuse $R;"
./elements/pipeline.rs; ADD=$((ADD+1)); fi; done
$ cargo build

めちゃくちゃになってしまった。ast-grepで対象のパスを切り出した後、そのJSON情報から置き換え行と置き換え文字列を計算してsedを発行する感じですね。ADDuse行を挿入したときにできる検索時との行数のズレをカウントするやつです。こうすることで、対象行だけsedができますが、今思えば use 行を除いて全部 sedしていいのでなくていいですね。

Q4

src/main.rs の中から1つ関数を選んで tmp.rsに書き出し、src/main.rs からはコメントアウトする問題

$ sg -p 'fn show_version() { $$$BODY }' -l rust main.rs --json > a.json; cat a.json | jq -r '.[0].text' > tmp.rs; cat a.json | jq -r '"sed -i \"\(.[0].range.start.line+1),\(.[0].range.end.line+1)s@^@//@ \" main.rs"' | bash

はい。ast-grep で取り出した情報を使って書き出しとコメントの挿入をしてます。便利~

Q5

main.rsにある以下のような行を

use std::{env, process, thread, time};

use std::env;
use std::process;
use std::thread;
use std::time;

にする問題。

$ grep -Pe '^use ' main.rs | tr '{}' ' ' | sed 's/:: /::,/;s/, /,/g' | comb ifs=, ofs="" 2 | grep -Pe '^
use' | tr -d \; | sed -r 's/\s*$/;/'
use std::env;
use std::process;
use std::thread;
use std::time;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::Ordering::Relaxed;
use crate::core::builtins;
use crate::core::ShellCore;
use crate::elements::script::Script;
use crate::feeder::Feeder;
use crate::feeder::InputError;
use signal_hook::consts;
use signal_hook::iterator::Signals;

egzactのcombを使ってuse文を展開する感じですね。

Q6

Q5の逆をやる問題。大変そう。これは時間内に解けませんでした。

方針としては treeのような構造を作ってから畳み込めばいいのかなと思い…

$ grep -Pe '^use ' ./main.rs | cut -d ' ' -f 2- | tr -d \; | stairl ifs=:: | sort -u
crate
crate core
crate core {builtins, ShellCore}
crate elements
crate elements script
crate elements script Script
crate feeder
crate feeder {Feeder, InputError}
signal_hook
signal_hook consts
signal_hook iterator
signal_hook iterator Signals
std
std path
std path Path
std sync
std sync Arc
std sync atomic
std sync atomic Ordering
std sync atomic Ordering Relaxed
std {env, process, thread, time}

という感じの出力を作ってみましたがここからどうにもできず…

おわり

今回はひさびさの午前がありました。誰かに何かを教えてもらうみたいなのが久しぶりでなんか懐かしかったです。講師の上田先生、TLで追加解説してくださった皆様ありがとうございました!

雑記2024-06-23

AWS Summit Tokyo 2024に行った

行きました。いろんなブース回ったりセッションを聞いたりしました(それはそう)。クッションが去年より気持ちフカフカでよかったです。

特に面白かったセッションは 「AWS-40 Amazon Aurora Limitless Database 内部アーキテクチャ詳解 ~ スケーラビリティと高可用性の秘密 ~」でした。AWS Summitのマイページから配信アーカイブが見られるので興味があれば見てみてください。

ブースの方はAWSのエキスパートに質問できるコーナーが良かったですね。やっぱり強い人と話すと勉強になります。

去年は認定を持ってなかったため入れなかった認定者ラウンジにも入ることができて良かったです。ステッカーもらえてうれしい。来年は別のステッカーも貰えるように頑張ります。

まとめるとすごく楽しかったです。ということです。

かはく行った

上の流れで行きました。めちゃ良かった…。実は去年も行ったんですが、クラファンでもらった図鑑を眺めてたらまた行きたくなったんですよね。博物館いいな…。近場も行ってみようかな。

おわり

エディタの話とか書こうと思ったけどまとまらなかったのでやめてしまった

雑記 2024-04-18

途中のままの技術書だけが積み上がる

再販

学生の頃に欲しかったグッズが再販されるらしい。当時は学生でお金がたりなさすぎたので断念したんだけど、今度の再販のタイミングではなんとかお金が用意できるので購入したいなって思ってる。絶対購入したいので毎時販売ページをリロードしている。販売日時はアナウンスされてるのでリロードはほぼ無駄なんだけどね。

VR

VRを体験する機会があった。なんか崖ギリギリを歩くやつ。映像って思っててもめちゃめちゃ恐怖を感じた。でもスタスタ歩けた。めちゃめちゃ楽しかった。 書いてて思ったけど10年ぐらい前の記事じゃない?これ

分割キーボード

分割キーボードを買ってみた。前から気になってたのでやったー!って感じだったんだけど、カーソルキーやInsert/Delete、F1~F12みたいなキーがFnとの組み合わせなやつでそれが難しい。マクロキーがあるのでそこによく使うキーを割り当ててみたけど「どれだっけ…」ってなる。結局右側の分割キーボードはカーソルキーがあるやつを使うことにした。分割キーボードの右側はほぼ押されることなく置いてある。ううむ…。 マクロを割り当ててショートカットキー置き場みたいにしようかな?

おわり

おわりだよ

第67回シェル芸勉強会に参加しました

参加しました。今回も参加記として解きなおしを残しておこうと思います。
PowerShell解も余裕があればやります。

当日の配信はこちらです。

Q1

shogiファイルは以下のような内容です

香桂銀金王金銀桂香
 飛     角 
歩 歩歩歩歩歩歩歩
 歩

歩 歩
 歩 歩歩歩歩歩歩
 角 銀   飛
香桂 金玉金銀桂香

このファイルの2行目の飛が動ける範囲に×を、8行目の角が動ける範囲にを書く問題です。sedのリハビリ問題とのこと。

$ cat shogi | sed '2s/ /×/g;2s/.$/ /;3s/ /×/' | sed '7~2s/ /〇/g;s/$/         /' | awk -F '' '{if(4<=NR&&NR<=6)$(10-NR)="〇"; print}' OFS=""
香桂銀金王金銀桂香         
×飛×××××角          
歩×歩歩歩歩歩歩歩         
 歩   〇     
    〇    
歩 歩〇        
〇歩〇歩歩歩歩歩歩         
 角 銀   飛         
香桂〇金玉金銀桂香         

sedawkで〇になりうるところ決め打ちで書き換えています。ほとんど解答例として解説されたものと同じですね。

PowerShell解もやってみます。

$ cat .\shogi |%{ if($idx -eq 2 -or $idx -eq 3) { $_ -replace ' $','' -replace ' ','×' } else { $_ };$idx++} -Begin {$idx=1} |%{if($idx -eq 7 -or $idx -eq 9) { $_ -replace ' ','〇' } else { $_ }} | %{ $_ -replace '$','                 '} | % { if(4 -le $idx -and $idx -le 6) { $hoge="$_".ToCharArray();$hoge[9-$idx]="〇"; $_=$hoge -join '' }; $_ }

なが~い。方針は同じなので解説はしません!

Q2

32ビット符号付整数の上限値をggらずにどうやって知るか?という問題。いろいろありそうですね。

$ echo 'typemax(Int32)' | julia
2147483647

はい。数値系の問題とかは最近juliaを使うようになりました。便利!

PowerShell解は以下の通りですね

$ [System.Int32]::MaxValue
2147483647

やはり型があると便利な場面はありますね。

Q3

hoge.cは以下のような内容です。

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>

int main(){
    printf("%d", INT_MAX);
    exit(0);
}

このファイルをコピペしただけではコンパイルできないようにしてください。という問題。学校の先生も大変だ。

cat hoge.c | sed 's/$/'"$(echo 💩| zws|tr -d \')"/

zwsを使って💩をゼロ幅スペースにエンコードし、それらを各行の末尾に追加しました。便利なコマンドですねzws

Q4

grep a / -R &が実行されたとたん、コマンドの実行を止めるようにシェルなどに細工をする問題。想定解はstty tostopのようです。知らんかった。

さて自分の解答は以下の通りです。

$ function a() { [[ "$1" == "grep a / -R &" ]] && kill -9 $$ }
$ add-zsh-hook preexec a

該当のコマンドが実行されたとき、そのシェルごとkillするフックをシェルに仕込みました。ヤバイ

Q5

PID=1な topコマンドを作る問題。

$ sudo unshare --fork --pid --mount-proc top

昔やった問題の応用ですね。docker run --rm ubuntu topとかでもよいと思います

Q6

端末のプロンプト部分をファイルに書き出す問題。つまり、$PS1をファイルに書き出すってことのようです。むずかしそう

# bash >= 4.4 
$ echo ${PS1@P}

BashのParameter Expansionの機能で再現できました。なんでもあるもんだなあ

ちなみに zshにもありました

$ echo ${(%)PS1}

そしてPowerShellではpromptで出力できますね

$ prompt

Q7

psコマンドのVSZかRSSで並び替えたとき、COMMANDの列にメッセージが出るように細工をする問題。むずかしそう

$ echo うんこ | grep -o . | nl | shuf | awk '{print "awk -F "$2" n="1000**($1+3)" &"}' > a
$ source a
$ ps u | sort -nk6,6 
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
xztaity+   20149  0.0  0.0   8756  2812 pts/4    TN   15:46   0:00 awk -Fn=1000000000000
xztaity+   20150  0.0  0.0   8756  2816 pts/4    TN   15:46   0:00 awk -Fn=1000000000000000
xztaity+   20151  0.0  0.0   8756  3040 pts/4    TN   15:46   0:00 awk -Fn=1000000000000000000

ほかの環境でも再現性があるかはわからないんですが、大きな数値を代入するawkコードをを実行することでRSSを使わせ、メッセージを仕込むことができました。ほかの文字列でもできるかは謎です

終わり

stty tostopやプロンプトの展開など、全然知らないことを知れたのが嬉しかったです。 今回も企画・開催ありがとうございました!

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|        終        |
|    制作・著作    |
|   ̄ ̄ ̄ ̄ ̄ ̄ ̄  |
|    xztaityozx    |
|_________|
  ∧∧   ||         
  ( ゚д゚)||           
  /    づΦ         

sel の csv/tsv オプションと template オプションの話

selコマンドを知っていますか?カラムを取り出すことに注目したコマンドで、cutawkのちょうど間ぐらいの使い心地を目指して私がちまちま作っているものですね。

github.com

他の似たツールとして selfchoose があります。

基本的な使い方

selはカラムを選択するだけのプログラムです。カラムは1始まりの数値で選択します。0は行全体を表します。

$ cat ./file
1 2 3 4 5 6
a b c d e f

# 1 ~ 3カラム目を取り出す
$ cat ./file | sel 1 2 3
1 2 3
a b c

# 0は行全体
$ cat ./file | sel 0
1 2 3 4 5 6
a b c d e f

負のインデックスを指定することで「後ろから何番目」を表すこともできます。

# 後ろから1番目のカラムを取り出す
$ cat ./file | sel -- -1
6
f

また、単一のカラムだけでなく、範囲指定することができます。(これがこのコマンドで一番やりたかったことでもあります)

# 1 ~ 3カラム目を取り出す
$ cat ./file | sel 1:3
1 2 3
a b c

基本的な使い方は以上です。もっと詳しく使い方を説明したいところですが、それはREADMEを読んでいただくこととし、この記事では最近追加した新機能2種について書こうと思います。

csv/tsv オプション

sel v1.1.9ちゃんとしたCSVとTSVがサポートされました。
今までも入力文字列のデリミターを-d, --input-delimiterオプションを通じて変更できたので、-d,とすれば使えたのですが hoge,"fuga,piyo",bar というようなデータをちゃんと処理できない状態でした。

# CSVの2カラム目としては `fuga,piyo` が正しいが、`-d,`では `"fuga` になってしまう
$ echo 'hoge,"fuga,piyo",bar' | sel -d, 2
"fuga

これに対応するために、awkFPATのようなものを実装しようと考えましたが、面倒だしバグらせる自信があるし、なによりFPATは気軽に書けないので Goのencoding/csvパッケージを使って入力を読み取るオプションを搭載することにしました。それが--csvオプションです。

$ echo 'hoge,"fuga,piyo",bar' | sel --csv 2
fuga,piyo

ついでにTSVもサポートしました。--tsvオプションを通じて利用することができます。

ところで、最近awkでもcsvがサポートされました。便利ですね。

template オプション

sel v1.1.10で、出力文字列のフォーマットを指定できる -t, --templateオプションを追加しました。

いままではシンプルに選択されたカラムが出力されるだけでしたが、--templateオプションの追加で以下のような出力ができるようになりました。

$ echo AAA BBB CCC | sel --template '1: {} 2: {} 3: {}' 1:3 
1: AAA 2: BBB 3: CCC

テンプレート内にプレースホルダ{}を書くと、そこへ選択したカラムを順番に割り当てることができます。それだけです。awkのように数値計算したりはできません。

正直この機能を実装するかどうか結構悩みました。機能過多だと思ったからです。機能が増えるとそれだけメンテするコストが上がってしまうので面倒です。 でも結局実装しました。便利だし、モチベであるcutawkのちょうど間ぐらいの機能として違和感はなかったからです。

まとめ

今回は最近selに追加したcsv/tsvオプションとtemplateオプションについて書きました。

個人的に結構気に入ってるコマンドなので、ぜひ使ってみて貰えると嬉しいです。Goがインストールされていれば go installを通じてインストールできます。Goがない場合でも GitHub Releasesからお好みのバイナリをダウンロードすることができます。

# install
$ go install github.com/xztaityozx/sel@latest

以上です。

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|        終        |
|    制作・著作    |
|   ̄ ̄ ̄ ̄ ̄ ̄ ̄  |
|    xztaityozx    |
|_________|
  ∧∧   ||         
  ( ゚д゚)||           
  /    づΦ         

付録

SourceGeneratorで自作例外クラスのコンストラクタを自動生成してみる

自作の例外クラス書いてますか?自分は時々書いてます。

で、雑に以下のように書きますよね。

public class FailedToHogeException : Exception {
    public FailedToHogeException(string message) : base(message) {}
}

するとSonarLintなどからこんな感じの警告もらいませんか?自分はもらいます。

よくみるやつ

これってちゃんと実装しようね!ってことなだけなんですけど、結構実装量多い…ような気がするので自動生成されると嬉しいですね。というわけでSourceGeneratorの素振りの題材として作ってみます。

実装したやつ

実装したものは以下のリポジトリにあります。

github.com

以下のように自動生成してもらいたい例外クラスに SerializableException属性を付けるだけで

namespace Example;

[SerializableException]
public partial class FailedToHogeException : Exception { }

次のようなファイルを生成してくれます

// <auto-generated>
// THIS FILE IS GENERATED BY SerializableExceptionGenerator. DO NOT EDIT IT.
// </auto-generated>

namespace Example;

[global::System.Serializable]
public partial class FailedToHogeException : global::System.Exception
{
        public FailedToHogeException() { }
        public FailedToHogeException(string message) : base(message) { }
        public FailedToHogeException(string message, global::System.Exception inner) : base(message, inner) { }
        protected FailedToHogeException(
                global::System.Runtime.Serialization.SerializationInfo info,
                global::System.Runtime.Serialization.StreamingContext context
        ) : base(info, context) { }
}

引数にオプション値を渡すことで、生成したいコンストラクタを自由に選ぶこともできます。例えば以下のように GenerateTarget.StringMessageExceptionConstructor だけ指定すると

using SerializableExceptionGenerator;

namespace Example;

[SerializableException(GenerateTarget.StringMessageExceptionConstructor)]
public partial class FailedToException : Exception { }

次のようなものが生成されます。

// <auto-generated>
// THIS FILE IS GENERATED BY SerializableExceptionGenerator. DO NOT EDIT IT.
// </auto-generated>

namespace Example;

[global::System.Serializable]
public partial class FailedToException : global::System.Exception
{
    public FailedToException(string message, global::System.Exception inner) : base(message, inner) { }
}

自前実装した残りは自動実装してくれよな!ってときに便利かもしれません

実現方法

最初に言った通りSourceGeneratorを使っています。SerializableException属性を探して実装を出力するIIncrementalGeneratorを書いたって感じです。

using System.Text;
using Microsoft.CodeAnalysis;

namespace SerializableExceptionGenerator;

[Generator(LanguageNames.CSharp)]
public class SerializableExceptionGenerator : IIncrementalGenerator
{
    // 名前は結構繰り返し使うので変数に入れとくと楽だったり楽じゃなかったりする
    private const string GeneratorAttributeName = "SerializableExceptionAttribute";
    private const string GeneratorNamespace = "SerializableExceptionGenerator";

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var source = context.SyntaxProvider.ForAttributeWithMetadataName(
            $"{GeneratorNamespace}.{GeneratorAttributeName}",
            static (_, _) => true,
            static (context, _) => context // ここでGenerateTargetの値とれそうなきがしなくもない
        );

        // コード生成は別のメソッドにしてる
        context.RegisterSourceOutput(source, Emit);

        context.RegisterPostInitializationOutput(static postInitializationContext =>
        {
            // 引数用のEnumってどこに実装するのがいいんだろう
            // 生成するコードに書くと、二重管理になってめんどくさい気がするんだよな…
            postInitializationContext.AddSource(
                $"{GeneratorAttributeName}.cs",
                $$"""
// <auto-generated>
// THIS FILE IS GENERATED BY SerializableExceptionGenerator. DO NOT EDIT IT.
// </auto-generated>

namespace {{GeneratorNamespace}} {

    [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    internal sealed class {{GeneratorAttributeName}} : global::System.Attribute {
        public GenerateTarget Target { get; set; }

        public {{GeneratorAttributeName}}(GenerateTarget target = GenerateTarget.All) {
            this.Target = target;
        }
    }


    [global::System.Flags]
    internal enum GenerateTarget
    {
        None = 0,
        DefaultConstructor = 1,
        StringMessageConstructor = 1 << 1,
        StringMessageExceptionConstructor = 1 << 2,
        SerializationInfoStreamingContextConstructor = 1 << 3,
        All =
            DefaultConstructor
            | StringMessageConstructor
            | StringMessageExceptionConstructor
            | SerializationInfoStreamingContextConstructor
    }
}
"""
            );
        });
    }

    private static void Emit(SourceProductionContext context, GeneratorAttributeSyntaxContext source)
    {
        <省略だよ>
    }

    // さっきも書いたけど生成コード中に同じものがあるのつらいお気持ちがなくはない
    [Flags]
    internal enum GenerateTarget
    {
        <省略だよ>
    }
}

コメント内にも書いていますが、オプション値として使うenumの定義が2か所に生えてしまうのをどうにかしたいなというお気持ちです。なんかいいアイデアがあれば教えてください。

まとめ

  • 自作例外クラスを書くときによく見るRSPEC-3925に対応できるSourceGeneratorが欲しかった

  • なので指定したコンストラクタを自動生成してくれるSourceGeneratorを書いた

  • オプションで生成するコンストラクタを制御できるようにした

  • フラグのenumをどこに書くべきかはいいアイデアが出なかった

正直テンプレートでもいいので使うかは謎…ですね。Copilotが勝手に埋めてくれたりもしますしね…。
とはいえ良いSourceGeneratorの素振りにはなりました。皆さんも何か書いてみてはいかがでしょうか。

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|        終        |
|    制作・著作    |
|   ̄ ̄ ̄ ̄ ̄ ̄ ̄  |
|    xztaityozx    |
|_________|
  ∧∧   ||
  ( ゚д゚)||
  /    づΦ