はじめに
「Mooで覚えるオブジェクト指向プログラミング」を修了された皆さん、おめでとうございます!
このシリーズでは、Compositeパターンというデザインパターンを学びながら、Markdown目次ジェネレーターを作っていきます。Markdownファイルを読み込み、見出し(#、##、###など)を抽出して、自動的に目次を生成するツールです。
全8回を通じて、以下のことを学びます:
- ファイル読み込みとテキスト処理
- 正規表現による見出し抽出
- 階層構造(ツリー構造)の表現方法
- Compositeパターンによるツリー構造の統一的扱い
完成すると、技術ブログやGitHubのREADME作成で実際に使えるツールになります。
前提知識
本シリーズは、Mooで覚えるオブジェクト指向プログラミング修了を前提としています。特に、以下の概念を使用します:
- クラス定義(
package、use Moo) - 属性(
has) - メソッド定義
- Role(後半の回で使用)
今回のゴール
今回は、Markdownファイルを読み込んで、全行を配列で取得するMarkdownReaderクラスを作成します。シンプルですが、これがシリーズ全体の土台となります。
MarkdownReaderクラスの実装
まずは、ファイルパスを受け取って内容を読み込むクラスを作りましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package MarkdownReader;
use v5.36;
use Moo;
has 'filepath' => (
is => 'ro',
required => 1,
);
has 'lines' => (
is => 'lazy',
builder => '_build_lines',
);
sub _build_lines ($self) {
my @lines;
open my $fh, '<:utf8', $self->filepath
or die "Cannot open file: $!";
while (my $line = <$fh>) {
chomp $line;
push @lines, $line;
}
close $fh;
return \@lines;
}
sub line_count ($self) {
return scalar $self->lines->@*;
}
1;
|
コードの解説
filepath属性
1
2
3
4
| has 'filepath' => (
is => 'ro',
required => 1,
);
|
is => 'ro': 読み取り専用(read-only)required => 1: インスタンス生成時に必須
lines属性とlazy
1
2
3
4
| has 'lines' => (
is => 'lazy',
builder => '_build_lines',
);
|
is => 'lazy': 最初にアクセスされたときに値を構築するbuilder => '_build_lines': 値を構築するメソッドを指定
lazyを使うことで、インスタンス生成時にはまだファイルを読み込まず、$reader->linesにアクセスした時点で初めて読み込みが行われます。これにより、ファイルを使わない場合に無駄な読み込みを避けられます。
_build_linesメソッド
1
2
3
4
5
6
7
8
9
10
11
| sub _build_lines ($self) {
my @lines;
open my $fh, '<:utf8', $self->filepath
or die "Cannot open file: $!";
while (my $line = <$fh>) {
chomp $line;
push @lines, $line;
}
close $fh;
return \@lines;
}
|
open my $fh, '<:utf8', $self->filepath: UTF-8エンコーディングでファイルを開くchomp $line: 行末の改行を除去push @lines, $line: 各行を配列に追加return \@lines: 配列のリファレンスを返す
line_countメソッド
1
2
3
| sub line_count ($self) {
return scalar $self->lines->@*;
}
|
$self->lines->@*: 配列リファレンスを展開(postfix dereference)scalar ...: 配列の要素数を返す
使用例
では、実際にMarkdownReaderを使ってみましょう。まず、テスト用のMarkdownファイルを用意します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # はじめに
これはテスト用のMarkdownファイルです。
## 第1章
内容がここに入ります。
### セクション1.1
詳細な説明です。
## 第2章
別の話題です。
|
このファイルをsample.mdとして保存し、以下のスクリプトで読み込みます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #!/usr/bin/env perl
use v5.36;
use lib '.';
use MarkdownReader;
my $reader = MarkdownReader->new(
filepath => 'sample.md',
);
say "ファイル: " . $reader->filepath;
say "行数: " . $reader->line_count;
say "";
say "内容:";
for my $line ($reader->lines->@*) {
say " $line";
}
|
実行結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| ファイル: sample.md
行数: 15
内容:
# はじめに
これはテスト用のMarkdownファイルです。
## 第1章
内容がここに入ります。
### セクション1.1
詳細な説明です。
## 第2章
別の話題です.
|
完成コード
今回作成したMarkdownReaderクラスの完成コードです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package MarkdownReader;
use v5.36;
use Moo;
has 'filepath' => (
is => 'ro',
required => 1,
);
has 'lines' => (
is => 'lazy',
builder => '_build_lines',
);
sub _build_lines ($self) {
my @lines;
open my $fh, '<:utf8', $self->filepath
or die "Cannot open file: $!";
while (my $line = <$fh>) {
chomp $line;
push @lines, $line;
}
close $fh;
return \@lines;
}
sub line_count ($self) {
return scalar $self->lines->@*;
}
1;
|
まとめ
今回は、Markdown目次ジェネレーターの第一歩として、ファイル読み込みを担当するMarkdownReaderクラスを作成しました。
学んだこと:
hasのlazyとbuilderオプションで遅延初期化を実現openによるファイル読み込みとUTF-8エンコーディング指定- postfix dereference(
->@*)による配列リファレンスの展開
次回は、読み込んだファイルから正規表現を使って見出しを抽出していきます。# タイトルや## 章といったMarkdownの見出しを検出し、レベル(H1、H2、H3…)とテキストを取り出す処理を実装します。