Skip to content

Latest commit

 

History

History
1146 lines (822 loc) · 48.3 KB

README_ja.md

File metadata and controls

1146 lines (822 loc) · 48.3 KB

MarkTheRipper

MarkTheRipper

MarkTheRipper - マークダウンで書く事が出来る、静的サイトの高速生成ツール。

Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.

NuGet

Package NuGet
MarkTheRipper NuGet MarkTheRipper
MarkTheRipper.Core NuGet MarkTheRipper.Core
MarkTheRipper.Engine NuGet MarkTheRipper.Engine

CI

main develop
MarkTheRipper CI build (main) MarkTheRipper CI build (develop)

English is here

これは何?

TODO: 最終的にこのドキュメントは、MarkTheRipper自身で変換する予定です。

MarkTheRipperは、非常にシンプルかつ高速な静的サイトジェネレータで、コンテンツをマークダウンで書くことができます。 主に想定される用途はブログサイトですが、まるでGitHub Gistで記事を書いているかのように、とにかく複雑な構造やツールの要求、というものを排除しました。

.NET 6.0をインストールしている環境なら、

dotnet tool install -g MarkTheRipper

とするだけでインストールできます。または、.NET Framework 4.71以上に対応した、ビルド済みのバイナリをダウンロードする事もできます。

  • 0.4.0現在、dotnet toolingでのインストールは、正しくないバージョンがインストールされる問題があり、修正中です。

初めて使う場合は、

$ mtr init mininum

とすると、現在のディレクトリの配下に、以下のようにサンプルのひな形が生成されます。 (恐れる必要はありません!たった2つ、しかも中身は余計な定義が殆ど存在しない、数行のファイルです!)

  • contentsディレクトリ: index.mdファイルが置かれます。中身は一般的に想定されるマークダウンによる記事テキストです。
  • layoutsディレクトリ: page.htmlファイルが置かれます。サイト生成するときに、マークダウンがHTMLに変換され、このレイアウトファイルの中に挿入されます。

これだけです!念のために、中身も見せましょう:

contents/index.md

---
title: Hello MarkTheRipper!
tags: foo,bar
---

This is sample post.

## H2

H2 body.

### H3

H3 body.

layouts/page.html

<!DOCTYPE html>
<html lang="{lang}">
<head>
    <meta charset="utf-8" />
    <meta name="keywords" content="{tags}" />
    <title>{title}</title>
</head>
<body>
    <header>
        <h1>{title}</h1>
        <p>Category:{foreach category.breadcrumbs} {item.name}{end}</p>
        <p>Tags:{foreach tags} {item.name}{end}</p>
        <p>
            {foreach (take (older self) 1)}<a href="{relative item.path}">Older</a>{end}
            {foreach (take (newer self) 1)}<a href="{relative item.path}">Newer</a>{end}
        </p>
    </header>
    <hr />
    <article>
        {contentBody}
    </article>
</body>
</html>

中身を見れば、何がどうなるのか想像も付くと思います。MarkTheRipperは、キーワードと本文をHTMLに変換して、レイアウトに挿入しているだけです。 なので、レイアウトをカスタマイズする時には、一般的なHTML/CSS/JavaScriptの技法をそのまま適用でき、制約は殆どありません。

(MarkTheRipperは.NETで書かれていますが、使用者は.NETの事を知らなくても問題ありません)

それでは、そのままサイトを生成してみましょう。サイト生成も非常に簡単です:

$ mtr

ディレクトリ構成がサンプルと同じであれば、mtrを実行するだけでサイトを生成します。 サイト生成はマルチスレッド・マルチ非同期I/Oで行うので、大量のコンテンツがあっても高速です。 デフォルトでは、docsディレクトリ配下に出力されます。 この例では、contents/index.mdファイルが、docs/index.htmlファイルに変換されて配置されます。

その後、すぐにデフォルトのブラウザでプレビューが表示されます:

minimum image

サイト生成は、毎回docsディレクトリ内のファイルをすべて削除して、生成し直します。 ディレクトリ全体をGitで管理している場合は、docsディレクトを含めてコミットしてOKです。 そうすれば、実際に生成されたファイルの差分を確認することが出来ます。 また、github.ioにそのままpushして、簡単にあなたのサイトを公開出来ます!

マークダウンファイルのファイル名や、配置するサブディレクトリにも制約はありません。 contentsディレクトリ配下で拡張子が.mdのファイルがあれば、どのようなサブディレクトリにどのようなファイル名で配置されていても、また、多数存在していても構いません。 サブディレクトリの構造を保ったまま、全ての.mdファイルが.htmlファイルに変換されます。

.mdではない拡張子のファイルは、単純に同じ場所にコピーされます。 例えば、写真などの追加ファイルは、あなたが管理したいように配置して、そこを指すように相対パスでマークダウンを書けば良いのです。

普段の操作

MarkTheRipperは、contentsディレクトリ以下に配置された、全てのマークダウンファイルを自動的に認識するため、 何か記事を書く場合は、単純に好きなディレクトリに好きなようにマークダウンファイルを作って記述すればOKです。

この操作すら面倒ですか? ええ、もちろん知っています。なので、もっと簡単にマークダウンファイルを作って配置して、記事を書くことに集中できる方法があります。

$ mtr new

これで、contentsディレクトリの直下に、現在の日付を元にした、ひな形のマークダウンファイルが生成され、かつデフォルトのマークダウンエディタが自動的に開きます。 カテゴリについては後で詳しく解説しますが、記事をカテゴリに配置したい場合は:

$ mtr new foo/bar/baz

とすれば、カテゴリ「foo」の「bar」の更に「baz」に、記事が配置されます。


もう少し実用的なサンプル

mtr init minimumが生成するサンプルはあまりにシンプルであり(minimumは伊達じゃありません!)、もう少しカスタマイズの例が見たいという場合のために、 標準でサンプルをいくつか内蔵しています。

$ mtr init sidebar
$ mtr init standard
$ mtr init rich

sidebarstandardまたはrichというサンプルを指定できます。以下のような機能があります:

名称 内容
sidebar minimumサンプルに、CSS flexによるサイドバーナビゲーションを追加したものです。全体的に一からデザインしたい場合は、余計な定義が一切含まれていないので、都合がよいでしょう。
standard GitHubのコードブロックのデザインに似た見た目になります。シンタックスハイライトはありません。bootstrap.js 5.0を使用しています。
rich シンタックスハイライトに prism.js を使います。これも、bootstrap.js 5.0 を使っています。

standard image

rich image

心配無用です。これらのサンプルも、最小のレイアウトコードとなるよう、最新の注意を払って実装しました。 理解も容易で、HTMLのビギナーでも、これらのサンプルでカスタマイズを始める事が出来ます。


レイアウトの詳細

MarkTheRipperのレイアウトは、非常にシンプルでありながら、必要十分な柔軟性と応用性を備えています。 レイアウトは、「メタデータ辞書」を参照することで、すべてのキーワード置換を実現します。

レイアウトの置換例を示します。


キーワードの置き換え

もっとも簡単な例は、単純なキーワードの置き換えです。次のようなレイアウトを定義したとします:

<title>{title}</title>

これは、メタデータ辞書のtitleというキーワードに対応する値に置き換えます。 titleの値はどこにあるかというと、対応するマークダウン文書の先頭で定義します:

---
title: Hello MarkTheRipper!
---

(... 本文 ...)

他のサイトジェネレーターを試したことがある場合は、マークダウンにこのような特別な「ヘッダ行」を追加して、タイトルや日時などを挿入する方法を知っているかもしれません。 MarkTheRipperも、構文上はこの慣例に従っていますが、もっと柔軟です。例えば、以下の例は全く同じ結果を生成します:

<title>{foobar}</title>
---
foobar: Hello MarkTheRipper!
---

(... 本文 ...)
  • 補足: このヘッダの事を、他のサイトジェネレーターでは「FrontMatter」と呼び、YAML構文で記述することになっていますが、 MarkTheRipperは厳密にはYAMLではなく、記述の柔軟性を高めるための構文を使用しています。 例えば、titleは、ダブルクオートで括る事は必須ではありませんし、tagsは角括弧で括らなくても正しく認識されます。 念のため、MarkTheRipperでは、FrontMatterという用語を使っていません。

何となく、メタデータ辞書をどう活用すれば良いか、見えてきましたか? つまりMarkTheRipperは、マークダウンのヘッダに書いた「キーワードと値」の組をメタデータ辞書として扱うことが出来て、レイアウト上にいくつでも反映することが出来ます。

任意のキーワードを、レイアウト上の任意の箇所で置き換えできるので、例えば以下のような応用が可能です:

<link rel="stylesheet" href="{stylesheet}.css">
---
title: Hello MarkTheRipper!
stylesheet: darcula
---

(... 本文 ...)

恐らく、この機能だけでも、大部分の問題は解決すると思います。


特殊なキーワードとフォールバック

このメタデータ辞書には、特殊な、しかし重要と思われるキーワードがいくつか存在します。以下に示します:

キーワード 内容
generated MarkTheRipperでサイトを生成した日時
layout 適用するレイアウト名
lang ロケール(en-usja-jpなど)
date 記事の日時
timezone MarkTheRipperでサイトを生成した環境のタイムゾーン。IANA表記、または時間
published 明示的にfalseと指定する事で、このマークダウンを無視する
  • この他にもいくつか特殊なキーワードがありますが、後で解説します。

これらのキーワードは、マークダウンのヘッダに書いて、上書きする事が出来ます。 generatedを上書きする事に意味は無いかも知れませんが、 MarkTheRipperがメタデータ辞書の定義を特別扱いしない、と言う事だけ知っておけば問題ありません。

上記のキーワードのうち、langlayouttimezoneのデフォルト値は何なのかが気になった人もいると思います。 メタデータ辞書は、サイト生成時のベースとなる定義を、metadata.jsonに配置することが出来ます。 (無くても構いません。実際、minimumサンプルには存在しません) 例えば、以下のような定義です:

{
  "title": "(Draft)",
  "author": "Mark the Ripper",
  "layout": "page",
  "lang": "ja-jp",
  "timezone": "Asia/Tokyo",
}

これを見ると面白いことがわかります。titleキーワードの値が"(Draft)"となっています。例えば、以下のようなレイアウトを考えます:

<meta name="author" content="{author}" />
<title>{title}</title>

もし、マークダウンにtitleを指定した場合は、そのタイトルが挿入されますが、指定しなかった場合は"(Draft)"というタイトルが挿入されます。 同様に、authorを指定した場合は、その投稿者名が挿入されますが、指定しなかった場合は"Mark the Ripper"という投稿者名が挿入されます。

ブログに使うことを考えた場合、殆どの投稿文書はあなた自身が書いたものとなるので、いちいちマークダウンのヘッダに自分の名前を書きたいとは思わないでしょう。 しかし、タイトルはもちろん、その投稿毎に異なるはずです。 そのような場合分けに、このメタデータ辞書の「フォールバック」機能を使うことが出来ます。

そして、layoutlangのフォールバックですが:

  • layoutがフォールバックにも見つからない場合に限り、pageというレイアウト名が使われます。
  • langがフォールバックにも見つからない場合に限り、システムのデフォルト言語が適用されます。
  • timezoneがフォールバックにも見つからない場合に限り、システムのタイムゾーン設定が適用されます。

レイアウト名には、少し補足が必要でしょう。レイアウト名は、変換元のレイアウトファイルの特定に使用されます。 例えば、レイアウト名がpageの場合は、layouts/page.htmlファイルが使用されます。もし:

---
title: Hello MarkTheRipper!
layout: fancy
---

(... 本文 ...)

のように指定した場合は、layouts/fancy.htmlが使用されます。

dateは記事の日時を表していて、普通のキーワードと同様に扱われますが、 マークダウンヘッダに定義されていなかった場合は、自動的に生成時の日時が挿入されます。

timezoneは、dateのような日時を扱う場合に参照され、タイムゾーン補正を行う基準となります。 MarkTheRipperを動作させる環境の、システムのタイムゾーン設定が、日常的に記事に埋め込む日時と異なる場合は、 metadata.jsonファイルに含めておくと良いでしょう (このような代表的な環境として、GitHub Actionsなどのクラウドサービス上で動作させる場合が考えられます)。

langは、単に普通のキーワードの一つのようにしか感じられないかも知れません。 これについては、後の節で解説します。


再帰キーワード検索

メタデータ辞書で引いた結果をキーワードとして、再度メタデータ辞書から引きたいと思うことがあります。例えば:

---
title: Hello MarkTheRipper!
category: blog
---

(... 本文 ...)

categoryとは、記事のカテゴリです。ここでは、blogと名付けていますが、以下のようにキーワード参照した場合:

<p>Category: {category}</p>

HTMLには Category: blog のように表示されます。これで問題ない場合もありますが、もっと丁寧な文に置き換えたいかもしれません。 そこで、blogをキーワードとして、再度メタデータ辞書から値を検索させることが出来ます。 MarkTheRipperに組み込まれている、lookup 関数キーワードを使用します:

<p>Category: {lookup category}</p>
  • 関数キーワードの詳細については、後の章で説明します。

こうしておいて、メタデータ辞書に、blog私的な日記 を対にして登録しておけば、HTMLには Category: 私的な日記 と表示されます。

このようなキーワードと値のペアは、前節で示したmetadata.jsonに書いておけば参照出来るようになります。 加えて、実はメタデータ辞書のファイルは、metadata/*.jsonでマッチするすべてのJSONファイルが対象です。 ファイルが分かれていても、MarkTheRipperが起動する時に、すべて読み込まれて内容がマージされます。

例えば、記事カテゴリだけを管理するファイルとして、metadata/category.jsonのように別のファイルにしておけば、管理が容易になるでしょう。


列挙とネスト

タグやカテゴリのような分類は、メニューから選択させてそのページに遷移させたいと思うでしょう。 例えば、サイト全体でタグが5個あったとします。これをページのメニューに自動的に加えて、 メニューからタグに分類されたページに遷移できるようにするには、「列挙機能」を使います。

例によって、小さい例から始めましょう。これはminimumに含まれているレイアウトです:

<p>Tags:{foreach tags} '{item}'{end}</p>
  • tagsキーワードは、タグのリストを示します(後述)

これは、{foreach tags}{end}の間にある文書が、tagsの個数だけ繰り返し出力されるというものです。 「間にある文書」とは、ここでは '{item}' の事です。スペースが含まれてることに注意してください。 同様に、改行やHTMLのタグなど、この間には何を含んでいても構いません。

では、以下のようなマークダウンを変換したとします:

---
title: Hello MarkTheRipper
tags: foo,bar,baz
---

(... 本文 ...)

すると、<p>Tags: 'foo' 'bar' 'baz'</p> と出力されます。 tagsfoo,bar,bazが、スペースで区切られて展開されて、クオートされて出力されました。

{foreach tags}{end}の間にある文書が繰り返し出力されるので、以下のように使う事も出来ます:

<ul>
  {foreach tags}
  <li>{item.index}/{item.count} {item}</li>
  {end}
</ul>

結果:

<ul>
  <li>0/3 foo</li>
  <li>1/3 bar</li>
  <li>2/3 baz</li>
</ul>

間に挿入されている{item}は、繰り返される一つ一つの値を参照できるキーワードです。 また、{item.index} と指定すると、0から始まって1,2,3... とカウントする数値が得られます。 {item.count} は、繰り返しの個数です。上記ではタグが3個あるため、この値は常に3となります。

さらに、複数のキーワードをネストさせる事も出来ます。以下の例は、タグを2重に繰り返します:

<ul>
  {foreach tags}
  {foreach tags}
  <li>{item.index} {item}</li>
  {end}
  {end}
</ul>

結果:

<ul>
  <li>0 foo</li>
  <li>1 bar</li>
  <li>0 foo</li>
  <li>1 bar</li>
</ul>

ところで、この場合のitemは、2重にネストした内側のtagsの繰り返しを指している事に注意して下さい。 場合によっては、外側の繰り返しの値を使いたいと思うかもしれません。 その場合は、foreachに「束縛名」を指定します:

<ul>
  {foreach tags tag1}
  {foreach tags tag2}
  <li>{tag1.index}-{tag2.index} {tag1}/{tag2}</li>
  {end}
  {end}
</ul>

結果:

<ul>
  <li>0-0 foo/foo</li>
  <li>0-1 foo/bar</li>
  <li>1-0 bar/foo</li>
  <li>1-1 bar/bar</li>
</ul>

束縛名を省略した場合は、itemが使用されます。 これで、foreachによる繰り返しの使い方が把握できたと思います。


タグの集約

繰り返しの使い方さえ理解できれば、タグやカテゴリの操作は出来たも同然です。 MarkTheRipperは、コンテンツのすべてのタグとカテゴリの分類を、自動的に集約します。 タグについては、以下の特殊なキーワードで参照できます:

キーワード 内容
tags タグのリスト。各マークダウンのヘッダ部分に記述する
tagList すべてのタグを集約したリスト

まずはタグの一覧を作ってみましょう:

<ul>
  {foreach tagList tag}
  <li>{tag}</li>
  {end}
</ul>

結果:

<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
       :
</ul>

前節では、個々のマークダウンに定義されたtagsを使って繰り返しましたが、ここではtagListを使っています。 この違いは、1つのマークダウンファイルではなく、MarkTheRipperが処理する全てのマークダウンのタグを集計した結果を扱っている事です。

つまり、tagListを使えば、タグによるメニュー項目やリンクリストを追加出来るようになります。 各タグの項目に、リンクを加えるにはどうすれば良いでしょうか? タグだけでは、タグに紐づいたコンテンツ群は分かりませんが、実はタグはforeachで列挙することが出来ます:

{foreach tagList tag}
<h1>{tag}</h1>
{foreach tag.entries entry}
<h2><a href="{entry.path}">{entry.title}</a></h2>
{end}
{end}

束縛名を指定して、何を列挙しようとしているのか分かりやすくしていることに注意してください。

entriesプロパティを列挙すると、対応するマークダウン群の情報にアクセスできます。 この例のようにpathというプロパティを使用すると、コンテンツに対応するファイルへのパスが得られ、 titleを使用すれば、そのタイトル(マークダウンのヘッダに記述されたtitle)が得られます。

  • pathは、マークダウンへのパスではなく、変換されたHTMLファイルへのパスが得られます。

ところで、このパスは、出力ディレクトリを基準とした相対パスです。 HTMLにパスを埋め込む場合、HTMLファイルが存在するディレクトリからの相対パスである必要があります。 この計算を行うには、MarkTheRipper内蔵の関数キーワード relative を使います:

<h2><a href="{relative entry.path}">{entry.title}</a></h2>

relativeを使ってパスを計算すると、MarkTheRipperが出力したHTMLがどのサーバーにどのようにデプロイされたとしても、正しくリンクが機能するようになります。 ハードコーディングされたサイトの基準パスを使用するよりも、安全となるでしょう。


カテゴリの集約

カテゴリについては、以下の特殊なキーワードで参照できます:

キーワード 内容
category カテゴリの階層リスト。各マークダウンのヘッダ部分に記述する
rootCategory ルート(分類なし)となるカテゴリ

タグとカテゴリが決定的に異なるのは、タグは並行して定義されるものであり、カテゴリは階層化を伴って定義されるものである事です。例えば:

(root) --+-- foo --+-- bar --+-- baz --+-- foobarbaz1.md
         |         |         |         +-- foobarbaz2.md
         |         |         |
         |         |         +-- foobar1.md
         |         |
         |         +--- foo1.md
         |         +--- foo2.md
         |         +--- foo3.md
         |
         +--- blog1.md
         +--- blog2.md

上の例では、foobarbaz1.mdは、カテゴリfoo/bar/bazに属しています。 また、blog1.mdは、どのカテゴリにも属していません。 MarkTheRipper内部では、無名の(root)カテゴリに属することになっています。 これが、rootCategoryキーワードです。

タグの場合はtagsキーワードを使用して定義しましたが、カテゴリの場合はcategoryというキーワードを使用します。 上記のfoobarbaz1.mdに相当する定義は:

---
title: Hello MarkTheRipper
category: foo,bar,baz
---

(... 本文 ...)

のように、階層をリストで指定します。タグと異なり、このリストは階層を表していることに注意してください。 CMSやサイトジェネレーターではこのような階層構造を、しばしば「パンくずリスト(breadcrumb)」と呼ぶことがあります。

ところで、MarkTheRipperは、このようにいちいちcategoryキーワードでカテゴリを明示しなくても、コンテンツをカテゴリ分けされたサブディレクトリに配置するだけで、ディレクトリ名からカテゴリを決定出来ます。 従って、カテゴリによるコンテンツの分類は、サブディレクトリで区分けするだけで良いのです。

カテゴリの基本的な構造を把握出来たと思うので、実際にレイアウトを書いてみましょう。まずはルートカテゴリを列挙します:

<h1>{rootCategory.name}</h1>
<ul>
  {foreach rootCategory.entries entry}
  <li>{entry.path}</li>
  {end}
</ul>

結果:

<h1>(root)</h1>
<ul>
  <li>blog1.html</li>
  <li>blog2.html</li>
</ul>

rootCategoryはルートカテゴリを表しているため、そのプロパティname(root)になります。 この名称が表示にふさわしくない場合は、キーワードの再帰検索を使って置き換えるか、またはこの例ではルートなので、直接書いてしまっても良いと思います。

そして、タグの時と同様にentriesで列挙したそれぞれの要素から、各マークダウンのヘッダ情報を引き出すことが出来ます。 ここではpathを指定して、コンテンツのパスを出力していますが、titleとすればタイトルが出力出来て、relative item.pathとすれば、現在のコンテンツからの相対パスが得られるので、これをそのままリンクのURLにすることで、リンクを実現できます。

カテゴリを列挙するには、childrenプロパティを使います:

<h1>{rootCategory.name}</h1>
{foreach rootCategory.children child1}
<h2>{child1.name}</h2>
{foreach child1.children child2}
<h3>{child2.name}</h3>
{end}
{end}

列挙のネストを増やしていけば、深いカテゴリ構造を全て列挙することが出来ます。 残念ながら、カテゴリ構造を動的に列挙する、つまり存在する子孫カテゴリまでを自動的に再帰的に列挙させる事は出来ません。 これは設計上の制約で、MarkTheRipperには、関数と再帰関数を定義する能力が無いためです。 (そのような要求は、恐らくサイト全体の構造リストを出力する場合だけであり、必要性を感じなかったためです)

カテゴリ操作の最後に、パンくずリストを出力する例を示します。これは非常に簡単です:

<ul>
  {foreach category.breadcrumbs}
  <li>{item.name}</li>
  {end}
</ul>

breadcrumbsプロパティは、対象のカテゴリに至るカテゴリを、ルートから列挙出来る値を返します。 (但し、対象のカテゴリがルートの場合は、ルートカテゴリを含み、それ以外の場合は含みません)

列挙した個々の要素は、今まで説明してきたカテゴリと同様です。上記例ではnameプロパティでカテゴリ名を出力しています。


マークダウン中のキーワードの置き換え

これまでに説明してきたキーワードの置き換えは、レイアウトファイルに対して行うというものでした。 このキーワード置き換え機能は、マークダウンファイルにも同様に適用されます。例えば:

---
title: hoehoe
tags: foo,bar,baz
---

Title: {title}

このようなマークダウンを記述すると、{title}が同じようにキーワード置換されます。 もちろんこれまでに説明してきた、関数キーワードによる計算も可能です。

マークダウン上のキーワード置き換えは、コードブロックに対しては機能しません:

---
title: hoehoe
tags: foo,bar,baz
---

Title: `{title}`

```
{title}
```

上記のように、コードブロック内に配置された{...}は、MarkTheRipperで解釈されずに、そのまま出力されます。


関数キーワード

これまでに出てきた関数を含めた、組み込み関数の一覧を示します:

関数 内容
format 引数を文字列に整形します。
relative 引数のパスを、相対パスに変換します。
lookup 引数が示す結果を元に、メタデータ辞書を引きます。
older 引数が示す記事よりも古い記事を列挙します。
newer 引数が示す記事よりも新しい記事を列挙します。
take 列挙可能数を制限します。
add 引数を加算します。
sub 引数を減算します。
mul 引数を乗算します。
div 引数を除算します。
mod 引数の剰余を取得します。
embed oEmbedプロトコルなどを使用して、埋め込みコンテンツを生成します。
card OGPメタデータなどを使用して、カード形式のコンテンツを生成します。

format

引数を文字列に整形と言っても、formatを使わずとも、これまでも特に問題なく整形出来ていました。 この関数が特に有用な場合は、日時を扱う場合です。

まず、書式がどのように決まるのかを示します。このようなHTMLで、dateキーワードやgeneratedキーワードを使う場合:

<p>Date: {date}</p>

以下の書式で日時が出力されます:

---
date: 2022/1/23 12:34:56
lang: ja-jp
---

(... 本文 ...)
<p>Date: 2022/01/02 12:34:56 +09:00</p>

この書式は、langが示す言語によって変化します。ja-jpではなくen-usの場合は:

<p>Date: 1/2/2022 12:34:56 PM +09:00</p>

のように、その言語の標準的な書式に従います。 また、文字列に整形する際に timezone キーワードの値が参照され、タイムゾーン補正された日時が出力されます。

書式を固定したい場合は、以下のようにformat関数を使用します:

<p>Date: {format date 'yyyy-MM-dd HH.mm.ss'}</p>
<p>Date: 2022-01-02 12.34.56</p>

format関数の第一引数は、整形したい値を示す式、第二引数は書式指定文字列です。 書式指定文字列は、シングルクオートかダブルクオートで括ります。

以前に、「MarkTheRipperは.NETで書かれていますが、使用者は.NETの事を知らなくても問題ありません」と書きましたが、 この書式指定文字列だけは、.NETの慣例に従っています。 日時の正確な書式指定文字列の形式は、このドキュメントを参照してください

実際には、日時に限らず、あらゆる値を書式に従って整形出来ます。 例えば、列挙中のindexは数値ですが、以下のようにすれば:

<p>{format item.index 'D3'}</p>

数値を3桁にすることが出来ます。

<p>007</p>

様々な書式指定の形式も、上に挙げた.NETドキュメントの周辺に詳しく掲載されています。

relative

既に解説した通り、relative関数は現在の記事のパスからの相対パスを計算します。

<p>{relative item.path}</p>

関数の引数には、文字列を渡すこともできます。例えば:

<link rel="stylesheet" href="{relative 'github.css'}">

このようにすれば、スタイルシートが存在する正しい相対パスを指定出来ます。 引数に指定するパスは、サイトのルートディレクトリ、docsからの相対位置です。 上記例では、docs/github.cssへの相対パスとなります。

通常このような場合は、絶対パスを指定します。 しかし絶対パスを使った場合、サイトのデプロイ環境によっては、無効なパスとなってしまいます。 relative関数を使用すれば、生成されたコンテンツのポータビリティが向上します。

lookup

lookup関数は、引数に指定された値と同じ名称のキーワードを、再度メタデータ辞書から検索します。 典型的な使用例は、タグやカテゴリの名称を、メタデータ辞書から引き直すというものです:

<p>Tag: {lookup tag}</p>

{tag}の場合は、タグの真の名称が出力されますが、lookup関数を使った場合は、 メタデータ辞書からタグ名と同じキーワードの値を取得して出力します。 従って、metadata.jsonに:

{
  "diary": "今日起きたこと"
}

のようなキーワードを登録しておけば、実際に出力される文字列を入れ替えることが出来ます。 これまで見てきたように、関数の引数に直接値を指定することも出来るため、固定の文字列を出力したい場合でも:

<p>Tag: {lookup 'blog'}</p>

のようにすれば、置き換えが可能です。

older, newer (記事のナビゲーション)

oldernewerを使用すると、指定された記事よりも古い記事や、新しい記事を列挙することが出来ます。 例えば、現在の記事よりも新しい記事を列挙するには、以下の式を使います:

{foreach (newer self)}<p>{item.title}</p>{end}

ここで、selfは現在の記事(このレイアウトを使用中のマークダウン記事)を示し、foreachが列挙する個々の要素が、新しい記事を示します。 従って、以下のようにプロパティを参照して、relative関数を応用して、リンクを作ることが出来ます:

{foreach (newer self)}<p><a href="{relative item.path}">{item.title}</a></p>{end}

括弧の位置に注意しましょう。上記では、newer関数にselfを引数として指定するために、括弧を使用しています。 括弧を指定しないと、foreachnewerselfを引数として指定したことになり、正しく動作しません。

列挙される記事は、その記事が属するカテゴリで、日付順に得られます。 そのため、MarkTheRipperでは、日付順を意識する「ブログ」のような記事を、 blogのようなカテゴリに分類して、そのカテゴリ内でこの関数を使用してナビゲーションする事をお勧めします。

oldernewerの引数には、self以外にも、記事を示す式を指定できます。 entriesプロパティを列挙した値が相当します。

take (列挙操作)

takeは、列挙可能な値の列挙数を制限します。 例えば、tag.entriesは、そのタグを持つ全ての記事を列挙します:

{foreach tags tag}
<h2>{tag}</h2>
{foreach tag.entries}<p>{item.title}</p>{end}
{end}

しかし、これを数個に制限したい事もあるでしょう:

{foreach tags tag}
<h2>{tag}</h2>
{foreach (take tag.entries 3)}<p>{item.title}</p>{end}
{end}

take関数は2つの引数を指定します。1個目は列挙対象、2個目は制限する列挙数です。上記の式では、最大3個に制限しています。

このtake関数と、foreachを使用した、ちょっとしたテクニックがあります。前節のnewer関数と組み合わせて応用します:

{foreach (take (newer self) 1)}<a href="{relative item.path}">Newer: {item.title}</a>{end}

列挙する個数を1個に制限するという事は、もし0個の場合は全く列挙されないと言う事です。 つまり、列挙する記事が無い場合は、foreachの中が出力されないため、表示させない事が出来ます。 それにより、現在の記事よりも新しい記事が存在しない場合は、リンクが表示されなくなります。

この式は、ほぼ同じものがサンプルのレイアウトにあります。

add, sub, mul, div, mod (計算全般)

これらは計算を行う関数です。引数は1個以上必要で、3個以上の場合は、連続して計算を行います。例えば:

<p>1 + 2 + 3 = {add 1 2 3}</p>

のように、引数の数値を全て加算します。 複雑な計算を行う場合は、括弧を使います:

<p>(1 + 2) * 4 = {mul (add 1 2) 4}</p>

括弧はいくつでもネスト出来ます。括弧を応用して、format関数を使って望ましい形に整形出来ます:

<p>1 + 3 = {format (add 1 3) 'D3'}</p>

結果:

<p>1 + 3 = 004</p>

引数が数値ではなくても、文字列が数値として見なすことが出来ればOKです:

<p>1 + 2 + 3 = {add 1 '2' 3}</p>

引数の中に、小数を含む数値があっても構いません。その場合は小数を含む計算として処理されます(浮動小数点演算と呼びます):

<p>1 + 2.1 + 3 = {add 1 2.1 3}</p>

小数を含む結果は、意図したとおりの表示にならない事があります。 そのような可能性を考えて、常にformat関数を使った方が良いかもしれません。 小数を含む書式の指定方法は、ここを参照して下さい

計算を使用する簡単な例を示します。列挙中のitem.indexは、0から開始される数値です。 一方、item.countは列挙できる数ですが、これを並べると一般的な表記となりません:

<p>index/count = {item.index}/{item.count}</p>

結果:

<p>index/count = 0/3</p>
<p>index/count = 1/3</p>
<p>index/count = 2/3</p>

このような場合に、addを使って:

<p>index/count = {add item.index 1}/{item.count}</p>

とすれば、1からcountまでの数値となり、自然な表現に近づける事が出来ます。

結果:

<p>index/count = 1/3</p>
<p>index/count = 2/3</p>
<p>index/count = 3/3</p>

embed (埋め込みコンテンツを生成)

embed (と後述のcard) 関数は特殊で、単純な計算を行うのではなく、 外部データを参照してコンテンツを生成する、強力な関数です。

あなたは、自分のブログにYouTubeの動画を埋め込みたいと考えたことはありませんか? あるいは、単なるリンクではなく、外部コンテンツへのカード形式のコンテンツを表示したいと考えたこともあるかも知れません。

embedcard関数は、このような複雑なコンテンツ埋め込みを容易に実現します。 例えば、文書中に以下のように書きます:

## すばらしい動画を発見

いつか、自由に旅行できるようになったら、訪れてみたい...

{embed https://youtu.be/1La4QzGeaaQ}

すると、以下のように表示されます:

embed-sample1-ja

この画像は、単なるサムネイルではありません。実際にページ内で動画を再生することもできます。 マジック!ですか? これは、業界標準のoEmbedプロトコルを使って、 HTMLに埋め込むべきコンテンツを自動的に収集して実現しています。

embed関数の引数には、そのコンテンツの「パーマリンク」、つまり共有すべきURLを指定するだけです。 YouTubeの他にも、数多くの有名コンテンツサイトが対応しているため:

## Today's DIVISION

{embed https://twitter.com/kekyo2/status/1467073038667882497}

embed-sample2-ja

このように、Twitterなど、他のコンテンツも簡単に埋め込めます。 どのコンテンツサイトが対応しているのかは、直接oEmbedのjsonメタデータを参照して確かめてみてください。 かなり多くのサイトが対応済みであることが分かります。

しかし、私たちが知りうる有用なサイトの一つであるAmazonは、何とoEmbedに対応していません! そのため、MarkTheRipperでは、Amazonの商品リンクを特別に認識して、同様に埋め込めるようにしています:

## USBホストを行う実験

{embed https://amzn.to/3V6lYlQ}

embed-sample3-ja

  • このリンクは、Amazon associatesを有効化する事で、取得出来るようになります。 Amazon associatesは、Amazonのアカウントがあれば誰でも有効化出来ますが、ここでは詳細を省きます。

さて、この便利な関数を使用するには、少しだけ準備が必要です。 この埋め込みコンテンツを表示させるための、専用のレイアウトファイルlayouts/embed.htmlを用意します。 内容は以下の通りです:

<div style="max-width:800px;margin:10px;">
    {contentBody}
</div>

これまでのレイアウト解説と同様に、contentBodyには、実際に埋め込むべきoEmbedのコンテンツが埋め込まれます。 外側のdivタグは、この埋め込みコンテンツの領域を決めるものです。 上記では外周に若干空白を持たせて、本体は横が800pxとなるようにしています。 あなたのサイトのデザインに合わせて、調整すると良いでしょう。

もし、コンテンツサイトごとに異なるレイアウトが必要な場合は、layouts/embed-YouTube.htmlのように、 oEmbedプロバイダ名を指定したファイル名とする事で、サイト固有のカスタマイズが出来ます。

ところで、oEmbedプロトコルで得られる情報には、埋め込みコンテンツが含まれていない場合があります。 そのような場合は、一緒に取得出来たoEmbedメタデータを使用して、次に紹介するcard関数と同様のコンテンツを生成します。

card (カードコンテンツを生成)

embed関数は、コンテンツプロバイダーが用意した埋め込みコンテンツを直接表示させるものでした。 このcard関数は、コンテンツのメタデータを収集して、MarkTheRipper側で用意したビューで表示させます。

メタデータは、以下の方法で収集します:

  • oEmbed: 付随するメタデータを使用(embed関数で埋め込みコンテンツが提供されなかった場合を含む)
  • OGP (Open Graph protocol): 対象のページをスクレイピングし、ページに含まれるOGPメタデータを収集。
  • Amazon: Amazon associatesページから収集。

使い方はembed関数と全く同じです:

## すばらしい動画を発見

いつか、自由に旅行できるようになったら、訪れてみたい...

{card https://youtu.be/1La4QzGeaaQ}

すると、以下のように表示されます:

card-sample1-ja

embed関数と異なり、付加情報をカード状にまとめた、コンテンツとリンクとして表示します。 同様に:

## USBホストを行う実験

{card https://amzn.to/3V6lYlQ}

card-sample3-ja

様々なコンテンツを、同じカード形式で表示できます。 埋め込み形式とカード形式のどちらを使うかは、好みで使い分ければ良いでしょう。

card関数も、embed関数と同様に、専用のレイアウトファイルを用意する必要があります。 レイアウトファイルlayouts/card.htmlも、以下のひな形を元に、自分のサイトに合わせていけば良いでしょう:

<div style="max-width:640px;margin:10px;">
    <ul style="display:flex;padding:0;border:1px solid #e0e0e0;border-radius:5px;">
        <li style="min-width:180px;max-width:180px;padding:0;list-style:none;">
            <a href="{permaLink}" target="_blank" style="display:block;width:100%;height:auto;color:inherit;text-decoration:inherit;">
                <img style="margin:10px;width:100%;height:auto;" src="{imageUrl}" alt="{title}">
            </a>
        </li>
        <li style="flex-grow:1;margin:10px;list-style:none; ">
            <a href="{permaLink}" target="_blank" style="display:block;width:100%;height:auto;color:inherit;text-decoration:inherit;">
                <h5 style="font-weight:bold;">{title}</h5>
                <p>{author}</p>
                <p>{description}</p>
                <p><small class="text-muted">{siteName}</small></p>
            </a>
        </li>
    </ul>
</div>

このひな形は、完全に独立したHTMLになっています。もしBootstrapと併用したいのであれば、 mtr init standardmtr init richで生成されるサンプルレイアウトに含まれるファイルを参照して下さい。

card関数でも、layouts/card-YouTube.htmlのようなファイル名にする事で、サイト固有のカスタマイズが出来ます。


Install develop branch package

$ dotnet tool install -g MarkTheRipper --nuget-source http://nuget.kekyo.online:59103/repository/nuget/index.json

License

Apache-v2.


履歴

  • 0.4.0:
    • ページングナビゲーションができるようになりました。#20
  • 0.3.0:
    • oEmbed を使用できるようになりました。#18
  • 0.2.0:
    • キーワード展開がマークダウン文書そのものに適用できるようになりました。#3
  • 0.1.0:
    • マークダウン・ファイルに日付のメタデータを自動的に挿入するようにしました。#13
    • 関数呼び出しができるようになりました。#2
    • カテゴリーとタグをマークダウンされたコンテンツ全体から集計する事が出来るようになりました。#14
    • ブラケット文字に対応しました。#6
    • ジェネレータキーワードを使用できるようになりました。#5
    • カテゴリの自動収集が可能になりました。#1