Perl と Moo で Markdown の目次ジェネレーターを作りながら、見出しの入れ子を if/else で組もうとして行き詰まり、Composite パターンとスタックのパーサーで組み直す全8回の連載の目次です。困りごとからも選べます。
見出しテキストを小文字にし、空白をハイフンに変えてアンカー ID を作り、[はじめに](#はじめに) の形のリンク付き目次を出す AnchoredRenderer を足します。ツリーのクラスは書き換えず、レンダラーを1つ足すだけで済みます(開放閉鎖原則)。連載の完成回です。
SectionHeading と LeafHeading の render に形式の引数を足し、1つのツリーから Markdown のリスト、入れ子の HTML、JSON を出します。入れ子の ul も、子が自分の子を描く再帰で開け閉めがそろいます。形式の切り替えは各クラスの if/elsif に残ります。
HeadingExtractor が返すフラットな見出しの配列を、スタックで親を決めながら SectionHeading のツリーに組み上げる TOCParser を作ります。レベル0の仮想ルートを置くので、H2 を飛ばした H3 や複数の H1 も同じ手順で処理できます。
共通の Heading Role、子を持たない LeafHeading、子を持つ SectionHeading の3つを Moo で作ります。SectionHeading の render が子の render を呼ぶだけで、条件分岐を書かずに入れ子の目次が出ます(Composite)。この回ではツリーはまだ手で組みます。
前の見出しとのレベル差で ul を開け閉めする素朴な HTML 目次は、サンプルでは正しく出ます。ところが H1 の次に H3 が来ると、li のない ul が2重に開く。分岐を足して塞ごうとすると、閉じる数・複数の H1・レベル別のクラスで条件が増え続ける様子を追います。
見出しレベルに応じてスペースを足すだけで、Markdown のリスト形式の目次は出せます。FlatTOCRenderer で実際に表示したあと、どの見出しがどの見出しの下にあるかを持たない配列では、入れ子の ul と li を組むのが難しい理由を見ます。
行頭の # の数がそのまま見出しレベルになります。/^(#{1,6})\s+(.+)$/ のキャプチャで Markdown の各行からレベルとテキストを取り出し、ハッシュの配列にする HeadingExtractor を作ります。# の後に空白がない行など、拾えない書き方も挙げます。
ファイルパスを受け取り、Markdown の全行を配列に読み込む MarkdownReader クラスを Perl と Moo で作ります。lazy と builder で、行が必要になるまで読み込みを遅らせます。全8回で Markdown の目次ジェネレーターに育てる出発点です。
Perl と Moo で SQL クエリビルダーを作りながら、文字列結合が招く引数の増殖と SQL インジェクションに当たり、Builder パターンとプレースホルダーで組み直す全8回の連載の目次です。困りごとからも選べます。
テーブル未指定・不正な並び順・LIMIT なしの OFFSET などを croak で止め、t/query_builder.t に Test::More と Test::Exception でテストを書きます。全8回の SQL クエリビルダーの完成版です。
ページ送り付きの検索や、ユーザーごとの集計のように何度も書くクエリを、QueryDirector のメソッドにまとめます。Builder を呼ぶ順番を1か所に集めて名前を付け、使う側はハッシュで条件を渡すだけにします(Director)。
QueryBuilder に join・left_join・group_by・having・offset を足し、テーブル結合や集計、ページ送りのクエリを組み立てます。from や where など既存のメソッドには手を入れずに済むことを確かめます。
QueryBuilder の WHERE 句で値を埋め込むのをやめ、SQL には ? を置いて値は bind_values で別に返す。DBI の prepare と execute で実行し、第3回と同じ攻撃文字列がただの値として扱われることを確かめます。
7個の引数を並べたコンストラクタをやめ、from・where・order_by・limit を1つずつ呼んで build で SQL を作る QueryBuilder に組み替えます。各メソッドが $self を返すのでメソッドチェーンで書けます(Builder)。値の埋め込みはまだ残ります。