Hexo

Hexoのローカルファイルを吹っ飛ばしたので粛々と復旧した件

Hexoのローカルファイルを吹っ飛ばした

ghq管理下にハードリンクとかやってたら、なんか変になったハードリンクを削除した際に、なぜか大元まで消えてしまいました。

一瞬だけ頭を抱えた後、まあデプロイ済みのデータをcloneすればよかろうと思い立ち実行。

Herokuのリモートリポジトリを確認

コンテンツはHerokuにデプロイしているので、まずHerokuにログインしてリモートリポジトリを確認します。

https://dashboard-next.heroku.com/apps/wbtmiu/settingsにアクセスしてGit URLを確認。このブログの場合、git@heroku.com:wbtmiu.gitになっています。

Herokuだと、GithubのようなGitホスティングサービスと違ってリモートリポジトリの内容が見えませんが、前git ls-filesしたとき,publicディレクトリ以外もリストされていたので完全に復旧できる……はず。

clone

1
git clone git@heroku.com:wbtmiu.git wbtmiu

とやってクローン。source/_draftsの内容まで復旧しました。

Hexoのdeployコマンドでやってることがわりと謎ですが、どうもソースを全部deployしてアクセスをpublicディレクトリに誘導しているっぽい。の、かな。

git branch -rするとorigin/masterしか出てこないから、Octopressみたくソース専用のブランチ切ってコンテンツだけをorigin/masterにデプロイしてるとかではないと思う。

ともあれ、Hexoでdeployしている場合、ソースも全部保存されているのでみなさん安心しましょう。

静的サイトジェネレータで重複しないURLを生成するにはハッシュ関数を使えばよかった

早く言えシリーズ。

静的サイトジェネレータでURLを自動生成したい

過去何度か書いていますが、私はWordPressなどでいう「スラッグ」というものが嫌いです。スラッグを考えるのも、過去記事と被っていないか確認するのも面倒だからです。

なので、JekyllやHexoなどの静的サイトジェネレータでは、基本的に日付ベースのURLを利用しています。しかし、同日に複数更新する場合に、どのように一意のURLを発行するかが悩みの種でした。

現在、ブログの構築に利用しているHexoでは、ランダムなURLを生成する機能があります。しかし、これは再生成時に変わってしまうのが問題でした。

そこで、Alfred Workflow経由で、/dev/urandomを利用した乱数を生成し、それをパーマリンクに組み込むようにしていました。

しかし、そんなものより遥かにスマートな解法があることに気付きました。ハッシュ関数です。

文字列からハッシュ関数を生成

ハッシュ関数とは、ファイルなどのデータから一意の値(ハッシュ値)を求めるプログラムです。ハッシュ値が一致すればデータそのものも一致すると考えられるため、ファイルの同一性検証などに用いられます。

ここで、文字列からハッシュ値を求めれば、互いに異なる文字列に対して、容易に一意のkeyを得ることができます。

やってみた

以下のようなシェルスクリプトで、記事タイトルからハッシュ値を生成し、それを組み込んだ記事ファイルを作成することができます。

1
2
3
4
5
6
7
8
9
10
11
12
cd ~/heroku/wbtmiu.herokuapp.com/source/_posts/
title=`echo "{query}" | sed -e 's/ /-/g' -e 's/[\.\/]//g'` #クエリ文字列を置換して変数に格納
key=`md5 -qs "$title"`
cat << eof > `date '+%Y-%m-%d-'`$title.md #記事ファイルを作成
title: {query}
date: `date '+%Y-%m-%d %H:%M'`
pid: $key
tags:
-
---
eof
open -a FoldingText `date '+%Y-%m-%d-'`$title.md #記事ファイルをFoldingTextで開く
  • これが→key=`cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10
  • これに変わっている→key=`md5 -qs "$title"`

なんということでしょう。

しかも、/dev/urandomを利用する方法と違い、仮に何らかのトラブルでkeyが失われたとしても、タイトルさえ判明していれば同一のハッシュ値を得ることができるのです。

Alfred Workflowからの実行にも成功しましたので、さっそくこの記事から利用することにします。

Hexoで下書き作成+下書き化+公開化するAlfred Workflow

HexoのDraftsとPublishの欠点

Hexoにはもともと下書き作成と、下書きを公開対象フォルダに移すコマンドが用意されているのですが、そのままではちと使いづらいです。

hexo draftで新規下書きを作成できますが、ダイレクトに編集に移れないなど、hexo newと同様の問題があります。

また、hexo publish タイトルのコマンドで、source/_draftsから対象ファイルをsource/_postsに移動することができます。しかし、これだとタイトル(ファイル名ですらない!)を指定するのが面倒ですし、複数の下書きを処理する場合はなおさらです。

そこで、いつものようにAlfred Workflowにお任せ。

構成

ベースとして、新規作成用のAlfred Workflowがあります。

作りたいのは、以下のようなワークフローです。

ワークフロー

つまり、

  • 新規下書き作成
  • 下書きを公開へ
  • 公開用原稿を下書きへ

の、3つの要素が必要になります。これを、Alfred Workflowとして作ってみます。

下書きを公開へ(hexo publishの置き換え)

フロントマターにstatus:という項目を作っておき、ここの値がpublishであるものを検出して処理するようにしました。

以下のシェルスクリプトを組み込みます。Escapingは全部オフにしています。

1
2
3
4
5
6
7
8
cd ~/heroku/wbtmiu.herokuapp.com/source/_drafts/ #下書きフォルダに移動
for file in `grep -l '^status: publish$' *.md` #対象ファイルを検出してループ処理
do
title=`grep -m1 'title:' $file | cut -c 8- | sed -e 's/ /-/g' -e 's/[\.\/]//g'` #クエリ文字列を置換して変数に格納
cat $file | (rm $file; sed -e '/^status: /d' > $file) #status項目を削除
cat $file | (rm $file; sed -e "s/^date: .*$/date: `date '+%Y-%m-%d %H:%M'`/" > $file) #date項目に日時を追加
mv $file ../_posts/`date '+%Y-%m-%d'`-$title.md #公開用フォルダに移動
done

ポイントは以下の通りです。

  • 日付は公開日であるべきなので、この時点で生成する
  • status:は公開時には必要ないため、削除する
  • タイトルの修正もありうるので、title:項目の内容でファイルをリネームする

title:項目の取得は、grepで先頭がtitle:である「1個目の」行を取得し、cutで先頭7文字を削っています。

これで、下書きフォルダ内の全ての完成原稿を、まとめて公開対象フォルダに移動することができます。

公開用原稿を下書きへ

Hexoにはないコマンドですが、欲しかったので作りました。status: draftを検出条件とし、下書きに戻したい場合は自分でstatus: draftを記述するようにします。

以下のシェルスクリプトを組み込みます。Escapingは全部オフにしています。

1
2
3
4
5
6
7
cd ~/heroku/wbtmiu.herokuapp.com/source/_posts/ #カレントディレクトリを原稿フォルダに移動
for file in `grep -l '^status: draft$' *.md` #対象ファイルを検出してループ処理
do
name=`echo $file | cut -c 12-` #ファイル名から日付を削除して変数に格納
cat $file | (rm $file; sed 's/^\(date: \).*$/\1/' > $file) #date項目から日付を削除
mv $file ../_drafts/$name #ファイルを公開用フォルダに移動
done

ファイル名から日付を削除する際は、単純に先頭11文字(yyyy-mm-dd-)を削ります。

新規下書き作成(hexo new draftの置き換え)

新規記事作成時のコマンドをベースに、日付を挿入せず、status:項目を挿入するよう改変します。

以下のシェルスクリプトを組み込みます。Escapingはスペースだけオフにしています。

1
2
3
4
5
6
7
8
9
10
11
12
cd ~/heroku/wbtmiu.herokuapp.com/source/_drafts/ #下書きフォルダに移動
key=`cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10` #ランダムな文字列を生成
title=`echo "{query}" | sed -e 's/ /-/g' -e 's/[\.\/]//g'` #クエリ文字列をファイル名として適切に置換して変数に格納
cat << eot > $title.md #記事ファイルを作成。status項目を含め、日付は含めない
title: {query}
date:
pid: $key
tags:
-
---
eot
open -a FoldingText $title.md #記事ファイルをFoldingTextで開く

pidについては以下参照。

まとめ・補足

  • title:date:項目を処理する際、特にフロントマターとコンテンツを区別していないので、コンテンツに含まれる一致項目が検出/処理される恐れがあります。そのため、「1個目の一致項目」のみを処理するようにします。
    • grepの場合は-mオプションを指定する
    • sedの場合は検索式でgオプションを「指定しない」
  • 常時status:項目を設けておいたほうが楽なような気もしますが、とりあえず下書きフォルダにある間だけにしておきます。
  • このように、静的サイトジェネレータを利用する場合、フロントマターに適当な項目を勝手に作ってしまえば、シェルスクリプトでの自動処理で活用することができます。
  • あと、Alfred WorkflowでGNU sedが使えなくてめちゃくちゃ苦労しました。その件は後ほど。
  • しかしまあ、もはやなんのためにHexo使ってるのか分かんなくなってきたな!

静的サイトジェネレータでテンプレート言語のコードを記述する場合のエラー対処方法

HexoでGenerateに失敗しまくった

このブログはHexoで作成していますが、ある時、Generateの際にやたらと失敗続きになることがありました。

消したり戻したりしながら原因を特定した結果、Codeblock内の一部記述が問題であることが分かりました。

具体的には、とりあえず画像で示しますが、AppleScriptの以下の部分。

AppleScript

そして、Jekyll templateの以下の部分です。

html

どうも、{}(BRACE/CURLY BRACKET)が重なっていたり、%と絡んでいる部分が引っかかる模様。

MarkdownにおけるInine code/Codeblock/Fenced codeblock全てで同様の問題が発生します。エラーが出ない場合でも、Codeの内容が空になったり。

どこでエラーが発生しているのか?

ここで、静的サイトジェネレータを利用したコンテンツ生成フローは、以下のようになっています。

静的サイトジェネレータによるコンテンツ生成フロー1

例えばインラインコードの記述を例に取るなら、まあ内容は適当ですが、以下のような順序でコンテンツが生成されます。

静的サイトジェネレータによるコンテンツ生成フロー2

どうやら、問題が起こっているのはテンプレートエンジンによる処理の部分。Hexoで利用されているテンプレートエンジンであるSwigのシンタックスとかぶるため、Swigで処理しようとしているようです。

うん、まず、<code>内をテンプレートエンジンで処理しようとするのバカなの死ぬのと言いたいのはやまやまなのですが、そうなるものは仕方ない。

となると、テンプレートエンジンに渡すデータ内で、Code部分がエスケープしてあればいいということになります。

Markdownパーサにおける特殊文字エスケープ

ここで、Markdownパーサにおける特殊文字エスケープ方法を確認してみましょう。

HTMLでは、特別な考慮を要する文字が2つあります: < と & がそれです。小なり記号はタグを書き出すのに使用しますし、アンパサンドはHTMLエンティティを示すのに使います。もしこれらの文字をそのものとして使いたければ、それぞれ< と & とHTMLエンティティの形式にエスケープ処理を施す必要があります。

Markdownはあなたの代わりに必要なエスケープ処理を実施することで、これらの文字をそのままのかたちで使用できるようにしてくれます。もしあなたがアンパサンドをHTMLエンティティの宣言のために使用している場合はそれはそのまま変換されずに置かれます。それ以外の場合には& に変換されます。

Gruber wrote a Perl script, Markdown.pl, which converts marked-up text input to valid, well-formed XHTML or HTML and replaces left-pointing angle brackets (‘<’) and ampersands with their corresponding character entity references.

ジョン・グルーバーによる「元祖Markdownパーサ」であるmarkdown.plは、<,>,&を自動的にエンティティに置き換えてエスケープします。そして、その他の特殊文字については\による手動エスケープが可能(必要)なわけです。

これはHexoでも同様で、CURLY BRACKETをBACKSLASHで/{のようにエスケープできます。(なお、ドキュメントは見付からなかったが、Hexo公式サイトトップページを見ると、たぶんGitHub Flavored Markdownに基づくパースを行っている)

1
\{% for page in site.pages %\}

しかし、Code部分でこの方法を使うと、\自体がブラウザで出力されてしまいます。上記の記述例自体がまさにそうです。

うん、Markdownパーサ、キミは悪くないそれで正しい。しかし解決には結びつかない。

HTMLエンティティによるエスケープ

ならば、HTMLエンティティ(実体参照)に置き換えたらどうか。例えば、{,}のエンティティは&#123;, &#125;ですから、以下のように記述できます。

1
&#123;% for page in site.pages %&#125;

ところが、これはCode部分ではうまく働かない場合があります。そう、&自体が自動的にエンティティに置き換えられるためです。

つまりこうです。

静的サイトジェネレータによるコンテンツ生成フロー3

ほん!とに!ややこしいなもう!

ということは、アレか。ここの処理でMarkdownパーサ挟まなければいいのか。

HTMLエンティティにエンコードし、さらに<code>,<pre>を直接記述する

結論として。

MarkdownにおけるInine code/Codeblock(Fenced codeblock)の代わりに、<code>/<pre><code>を使用し、さらにHTMLエンティティに置き換えた場合、正常に記号が表示されました。

つまり、以下のような記述です。

1
<code>&#123;% for page in site.pages %&#125;</code>

ただし、これだと静的サイトジェネレータのシンタックスハイライト機能を活かせません。

外部ソース(Gist)でEmbed

これはMarkdownパーサ由来の問題ですから、Codeblock/Fenced codeblockの場合、コード本体を外部に置いて参照する方法もあります。

自分の手が加わっているコードなら、Private GistにしてEmbedするのが、比較的楽でシンタックスハイライトも利きます。

自分の手が一切加わってないコードの場合、Gistで管理するのも抵抗がある、というか邪魔なので、おとなしく<pre><code>するほうがいいと思われます。

まとめ

  • テンプレートエンジンに渡すソースに、テンプレート言語と解釈できる部分があると、処理されてしまう
    • Codeとして記述しても無意味
  • HTMLエンティティによるエスケープは有効
    • ただし、その前にMarkdownパーサで処理するとそのままテンプレートエンジンに渡せない場合がある
  • Markdownソース内で<code>タグを利用することで、HTMLエンティティによってエスケープしたコードをそのままテンプレートエンジンに渡すことができる
  • 外部ソースからのEmbedは極めて有効

HexoのLightテーマでタグが全部ぐぐられてたのでnocontentしてみた

ようやくぐぐらぶるになったと思ったら検索結果が論外だった

このブログがしばらくGoogleにインデックスされてなくて、サイト内検索も死んでたんですが最近ようやくインデックスされました。

それでサイト内検索して気付いたんですが、各ページのサイドカラムにあるタグ一覧が検索対象になってて、全ページに含まれてるので検索の精度がガタ落ちでした。

そうかー静的サイトジェネレータってこういう欠点があるのかーと呆然としつつ対策をぐぐったらすぐ見付かった……はいいものの。

nocontentクラス属性を付ける

  • 定型的なコンテンツの除外 - カスタム検索 ヘルプ

    nocontent を指定しても、Google ウェブ検索でのサイトの掲載結果や Google によるサイトへのクロールが何らかの影響を受けることはありません。タグを付けたコンテンツ内のリンクは、引き続きクロールの対象になります。異なる点は、カスタム検索エンジンでの掲載順位の決定にその中のキーワードを使用しないことだけです。

微ッ妙〜〜〜。え、フツーのWeb検索結果には一切影響しないってこと?

しかも、カスタム検索に反映させるのすら何段階も手順必要とか……

まあやりましたけどね。

Lightテーマの場合

まず、nocontentを付けるためにsidebar.ejsを以下の通り修正。

1
2
3
4
5
<div class="nocontent">
<% theme.widgets.forEach(function(widget){ %>
<%- partial('../_widget/' + widget) %>
<% }); %>
</div>

さらに、カスタム検索タグ変更のため、search.ejsを以下のように修正。<script>部分は、カスタム検索 - 検索エンジンの編集から、「検索エンジンの編集 > デザイン > 保存してコードを取得」で取得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="search">
<script>
(function() {
var cx = '000776476307438838679:x7fkvdccok0';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
'//www.google.com/cse/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:searchbox-only></gcse:searchbox-only>
</div>

とりあえずこれでインデックス反映までまったりと待って様子見してみます。

タグ一覧部分を単純なJavascriptとかで置き換えたほうがシンプルで実用的な気もする。

はてなブログからHexoへの移行

【注意】ローカルのテキストファイルを元に移行しました!

私は細かいところを間違えていたりして記事の修正をすることが多いので、もともとローカルにテキストファイルで記事を保存してありました。実際に修正する場合は、テキストエディタで修正してからコピペするわけです。

それが面倒だったので、Postach.io経由で静的サイトジェネレータまで移ってきたわけですが、ともあれ、基本的に以下の内容は全部ローカルファイルの修正なのでご注意ください。

フロントマターを作成する

まず、元ファイルには本文しか含まれていないので、フロントマターを作成する必要があります。ターミナルから処理しました。

1
2
3
4
5
6
7
8
IFS=$'\n'
for file in `ls`
do
title=`echo "$file" | gsed -e "s/^\(.*\)\.txt/\1/"` #ファイル名から拡張子を除いて変数に格納
key=`cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10` #ランダムな文字列を生成
gsed -i -e "1i title: $title\ndate: 2014-\npid: $key\ntags:\n- \n---\n" $file #正規表現でフロントマターを挿入
mv $file $title.md #拡張子をmdに変更
done

最初、catを使ってテンプレート部分を変数に格納し、それをgsedで挿入していたのですが、複数行のリテラルだとうまくいかないようなので全部正規表現で記述しました。

ファイル名にスペースが含まれるため、以下の記事を参考にエラーを回避しています。

これで、以下のようなフロントマターが1行目に挿入されます。

1
2
3
4
5
6
title:
date: 2014-
pid: aJ8EdeeI12
tags:
-
---

pidはHexoのユーザ定義変数で、パーマリンクに挿入します。

あとは手作業でdate:tags:を入力。date:は最低限日付だけでいいんですが、同日に複数の記事がある場合は適当に時刻を入力しました。

日付を挿入+特殊文字をエスケープしてファイルをリネーム

1
2
3
4
5
6
7
IFS=$'\n'
for file in `ls`
do
new=`echo $file | sed -e 's/ /-/g' -e 's/\/\.//g'` #hexo newで作成されるファイル名ルールに基づき文字エスケープして変数に格納
date=`grep '^date:' $file | cut -c 7-16` #date項目から日付を抽出
mv $file $date-$new #ファイルをリネーム
done

hexo newコマンドにおける文字エスケープルールは、確認できた限りでは以下の通りなので、これを再現しています。

  • 半角スペースを-に置換
  • .を削除
  • /を削除

date:項目からの日付の抽出は、最初正規表現でやろうとしてうまくいかなかったので、単純に行頭からの文字数で判断しました。

これで、現在Hexo運用上のルールとしている、2014-09-17-はてなブログからHexoへの移行.md形式のファイル名に変換されました。

はてなブログ特有の記法を変換

もともとMarkdownで書いてはいたのですが、はてな特有の記法というかブログパーツ?も利用していたので、変換が必要でした。

私が対処する必要があったのは、以下の記法です。

  • App Store
  • フォトライフ
  • Twitter

以下、ほぼ根性。なお、ブログ内リンクは諦めました。別に元記事消すわけじゃないし……

App Store

変換前の記法は、以下のような形式です。

[https://itunes.apple.com/jp/app/cloud-outliner-lite/id594107751?mt=12&uo=4:embed]

変換後のHTMLタグは、Alfred Workflow「AppTag」を利用しています。

これが存在する以上はシェルで自動置換もできるはずですが、大した数じゃないので、Sublime Textで検索して手動で書き換えました。

実は、「はてなブログが生成するApp Store紹介タグを改変した、App Storeにないアプリの紹介タグ」というのもあって、CSSが違うのでちょっと表示が荒れてるんですが、めどいのでパス。

フォトライフ

変換前の記法は、以下のような形式です。

[f:id:catfist:20140412012614p:plain]

これこそシェルで置換したかったのですが、はてなブログからの移行で自動ダウンロードしたよ的な記事を読むと、入力として画像IDだけでなくURLが必要なようです。

というわけで、大した数じゃないし(2回目)Sublime Textで検索してブラウザでフォトライフ見ながら画像URLコピーしてTextExpanderでMarkdown画像記法に変換して以下略。

Twitter

変換前の記法は、以下のような形式です。

[https://twitter.com/catfist/status/458955182957924353:embed#なお、EnterはFn+Returnで入る模様!辿り着くまで5分かかったわい!]

Sublime Textで検索して(略)せめてClickableUrlsパッケージですぐ開けるようにして(略)

移行メッセージを記事に挿入

ここまでやって見落としがありそうだったので、フロントマター直下に以下のメッセージを挿入しました。

(はてなブログからの移行記事です。正常に表示されていない場合、ご連絡いただけると助かります。)

シェルで処理。

1
2
3
4
for file in `ls`
do
gsed -i -e "s/^---$/---\n(はてなブログからの移行記事です。正常に表示されていない場合、ご連絡いただけると助かります。)\n/" $file
done

これで、最近の記事は全部まとめて読めるようになりました。

気付いたらHexoのパーマリンクが変わっていたので自動生成+固定にできるよう対応

気付いたら、過去記事のURLが変わっていた

超びっくりした。Twitterに張ってあったリンク踏んだらなんもねーの。

HexoにはランダムなIDを生成する機能があり(Permalinks | Hexo)、これをパーマリンクに組み込んでいたのですが、どうやらこのIDがどこかのタイミングで再生成されているようです。

というわけで再現条件を検証してみましたところ、hexo cleanで生成されたコンテンツを削除し、再度生成するとIDが変わることが分かりました。

シェルのログ

1
2
sa-b:id_insp user$ hexo new testfile
[info] File created at ~/id_insp/source/_posts/testfile.md

記事ファイルを作成しておく。

1
permalink: :year/:month/:day/:id/

上記のように_config.ymlで指定して、パーマリンクにランダム生成されるIDが含まれるようにしておく。

1
2
3
4
5
6
7
8
9
10
11
sa-b:id_insp user$ hexo g
...
[create] Generated: 2014/9/15/cks0z54eyu3897ql (23ms)
...
sa-b:id_insp user$ hexo clean
[info] Deleted cache file
[info] Deleted public directory
sa-b:id_insp user$ hexo g
...
[create] Generated: 2014/9/15/qbrylfdpnfpcismr (24ms)
...

hexo cleanを行う前後でIDが変わっている。

テーマを変えるときとか、間違って作ってしまったタグをタグ一覧から削除するときなど、けっこう頻繁にhexo cleanするのに、そのたびにパーマリンクが変わってしまうのはひどすぎます。即刻やめることにしました。

用意されているパーマリンク用変数を検証する

上記ページより変数表を引用します。

変数名 説明
:year Published year of posts (4-digit)
:month Published month of posts (2-digit)
:i_month Published month of posts (Without leading zeros)
:day Published day of posts (2-digit)
:i_day Published day of posts (Without leading zeros)
:title Filename
:id Post ID
:category Categories. If the post is uncategorized, it’ll be category_dir setting.

後述しますが、実はslugという変数もあります。内容はtitleと同じです。

基本的には日付だけでいいのですが、それだと同日複数更新に対応できません。(全部最初の記事にリンクされる)

となると:titleを使うしかないのですが、個人的に日本語URLが嫌いなので、できれば使いたくありません。

title変数の詳細

まず、これはファイル名であってフロントマターのtitle:の内容ではないことに注意することがあります。

もっとも、hexo newでの生成時には同じ内容が挿入されるのですが、どちらかを変更すれば当然互いに異なる内容になります。つまり、hexo newでの記事生成の際にパーマリンクにする内容を入力し、その後ファイル内のtitle:項目を修正すればいい、という話ではあります。

しかし、いわゆるスラッグを考えるのは大嫌いだし、title:項目を毎回削除して書き直すのもごめん被りたいところです。

どうでもいいですが、ファイル名が2014-09-15-タイトルとかの場合、なぜかtitle変数の内容はタイトルになるようです。日付っぽいのをエスケープするのかな…?

hexo newコマンドを使わずAlfred Workflowで対応してみる

だったらhexo自体をカスタマイズすればいいわけですが、そこまでの知識はないので、とりあえずAlfred Workflowで対応することにしました。ターミナルコマンドを利用します。

ランダムな文字列を生成する

ユニークなURLを生成するために、ランダムな文字列を生成してみます。

上記を参考にしました。

1
key=`cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10`

完全にランダムなので、かぶる可能性がなくはないですが、同じ日付でかぶらなければいいので、そんな天文学的確率は気にしなくていいでしょう。

ユーザ定義変数を利用して任意にスラッグをつける

Hexoでは、各記事のフロントマターに適当な項目を入力すると、変数として参照することができます。

この際、Hexoに定義されている変数名は上書きできないため避けなければなりません。実は、slugが使えなかったので定義されていることに気付いたのだったり。

記事ファイルのフロントマターに、以下のような記述を追加するとします。

1
pid: hoge

_config.ymlで以下のように記述すると、パーマリンクで参照できます。

1
permalink: :year/:month/:day/:pid/

結果として生成された記事のURLは、以下のようになります。

http://wbtmiu.herokuapp.com/2014/09/15/hoge/

後は、pid:項目をランダム生成してしまえばいいわけです。

完成したAlfred Workflow用シェルスクリプト

Alfred Workflowでこういうのを作ります。

Hexo Alfred Workflow

上記を参考に、ターミナルコマンドを作成しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
cd ~/heroku/wbtmiu.herokuapp.com/source/_posts/ #hexoプロジェクトフォルダに移動
key=`cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10` #ランダムな文字列を生成
title=`echo "{query}" | sed -e 's/ /-/g' -e 's/[\.\/]//g'` #クエリ文字列を置換して変数に格納
cat << eot > `date '+%Y-%m-%d-'`$title.md #記事ファイルを作成
title: {query}
date: `date '+%Y-%m-%d %H:%M'`
pid: $key
tags:
-
-
---
eot
open -a FoldingText `date '+%Y-%m-%d-'`$title.md #記事ファイルをFoldingTextで開く

これで、以下の処理が行われます。

  • Hexoソースフォルダに記事ファイルを作成
    • ファイル名を生成
      • クエリをファイル名として適正にする
      • 日付を挿入
    • フロントマターを生成
      • クエリを素のままtitleに挿入
      • 日付をdateに挿入
      • pidをランダムに生成
  • FoldingTextで記事ファイルを開く

過去記事をこの形式に修正

当然、過去記事も修正する必要があります。

もともとのフロントマター形式は以下の通りです。

1
2
3
4
5
6
7
title: 気付いたらHexoのパーマリンクが変わっていたので自動生成+固定にできるよう対応
date: 2014-09-15 21:21
tags:
- Hexo
- Mac
- Alfred Workflow
---

これを、以下のように修正します。

1
2
3
4
5
6
7
8
title: 気付いたらHexoのパーマリンクが変わっていたので自動生成+固定にできるよう対応
date: 2014-09-15 21:21
pid: JQDleI4soG
tags:
- Hexo
- Mac
- Alfred Workflow
---

基本的には、sedによる置換で、tags:行の上にpid:行を挿入していくことになります。

sed内部で文字列生成(失敗)

1
find source/_posts/ -type f -exec gsed -i -e "s/^tags/pid: `cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10`\ntags/g" {} \;

一見よさそうですが、生成される文字が全部同じなので意味ないです。

for文でループ処理(成功)

上記を参考にしました。

1
2
3
4
5
6
cd ~/heroku/wbtmiu.herokuapp.com/source/_posts/
for file in `ls`
do
key=`cat /dev/urandom | LC_CTYPE=C tr -dc '[:alnum:]' | head -c 10` #ランダムな文字列を生成
gsed -i -e "s/^tags/pid: $key\ntags/" $file
done

内容としては、

  • lsでファイル一覧を取得してfile変数に格納
  • ファイルごとにループ処理
    • 文字列生成してkey変数に格納
    • key変数込みで置換

この場合、1ループごとに文字列生成されるためファイルごとに違う文字列が挿入されます。

というか、「ファイルごとの複数処理」ってこんなんでよかったのか。もっと早くわかってればあれもこれも……うっ頭が……

Postach.ioからHexoへの移行

Postach.ioの記事ファイルをHexoが解釈できるように編集する

記事がローカルのテキストファイル(この場合はMarkdownファイル)として残るため、それを他のフレームワークに放り込むだけで概ね移行が完了するのが、静的サイトジェネレータのよいところです。

とは言え、Postach.ioとHexoではフロントマターの構文が異なるため、少しは手を加える必要がありました。その少しがなかなか大変でしたが。

Postach.ioとHexoのフロントマター構文

Postach.io
1
2
3
4
5
title: Postach.ioからHexoへの移行
date: 2014-09-14 20:07
tags:
- Postach.io,Hexo
type: post

以上が指定でき、また全て必須です。

Hexo
1
2
3
4
5
6
title: Postach.ioからHexoへの移行
date: 2014-09-14 20:07
tags:
- Postach.io
- Hexo
---

他にも指定できる項目はありますが、初期設定ではhexo newコマンドで生成されるのは以上の項目です。

ターミナルで一括処理

必要な処理は以下の通りです。

  1. type項目の削除
  2. ---を挿入
  3. tags項目をList形式に修正

これを手作業で、77個くらいの記事ファイルすべてに対して行うのは発狂しそうなので、ターミナルで処理しようと思いました。

とりあえず、1,2については以下のコマンドでできました。

1
find ./ -name "*.md" -print0 | xargs -0 gsed -i -e '5i ---' -i -e '/type/d'

前提条件として、GNU sed(とHomebrew)のインストールが必要です。

1
brew install gnu-sed

内容を分解すると、以下のような感じです。

  • findで指定フォルダ以下の「ファイル名に.mdを含む」ファイルを検索してパイプで渡す
    • ファイル名に半角スペースを含む場合があるので-print0オプションを付ける
  • xargsfind結果を受け取り、gsedコマンドで正規表現置換
    • -iで上書き保存、-eで検索式を利用
      • 5行目に---を挿入
      • typeを含む行を削除
    • ファイル名に半角スペースを含む場合があるので-0オプションを付ける

3についてもgrepでできるとは思うんですが、検索式をすぐに書けそうになかったので、泣きながら手作業でやりました。

少しでも楽をするために、Sublime Textを利用。

  • 記事ファイルがあるフォルダを読み込んでおく
  • 改行とListフォーマットの挿入は正規表現置換で行う

ファイルのリネーム

ここまでやればHexoでのレンダリングは問題なくできるのですが、hexo newで自動生成されるファイルとファイル名ルールが違います。

禁止文字のエスケープ

タイトルに含まれる近似文字は強制的にエスケープされます。前にも書きましたが、確認した限りでは以下の通りです。

  1. 半角スペースを-に置換
  2. .を削除
  3. /を削除

3はもともと自分でエスケープしてあるので、今回必要なのは1,2です。

これはrenameを利用すれば簡単でした。事前にインストールが必要です。

1
brew install rename

renameをインストールして、

1
2
3
rename "s/\.//g" *.md #ドットを削除
rename "s/ /-/g" *.md #半角スペースをマイナスに置換
rename "s/md$/.md/" *md #間違えて拡張子のドットまで消しちゃったので直すよてへぺろ

一部アヤしい処理も見えましたがキニシナイ。

日付の挿入

まず、Hexoでは_config.ymlの記述によっては作成日をファイル名に自動挿入できるため、そのようにしてあります。

Hexoでは別に必須ではないのですが(Jekyllだと必須)、付けておいたほうが後の管理も楽なので、付けておきたいところです。

要するに、grepdate項目から日付を切り出して、それでリネームするシェルスクリプトを書いて、Automatorでループ処理してやればいいのだと思いますが、元のファイル名取得が難航して結局これも手作業に。

Sublime TextにSIdebarEnhancementパッケージを入れて、リネームにショートカットキーを割り当てておくと少しだけ楽です。

(追記)あとで知ったけど、for使えば良かった。こんなん。

1
2
3
4
5
for file in *.md
do
date=`grep '^date:' $file | cut -c 7-16`
mv $file $date-$file
done

test.md → yyyy-mm-dd-test.md

デプロイ

これでHexo形式への変換が完了しました。

後は、Hexoプロジェクトフォルダ内のsource/_postsフォルダにコピーしてhexo d -gでデプロイするだけです。

はてなブログからの移行が全く進んでいない件

一応、Markdownフォーマットで保存してはあるんですが……はてなからとなると、特有のブログパーツがあるからめんどくさいんですよねえ。

ただし、Hexoには各種フォーマットからの移行(Migration)機能があります。

とりあえずRSSからの移行を試してみましたが、7記事しか移行できませんでした。

そこで、はてなブログ→WordPress→Hexoというルートが考えられるのですが、疲れたのでまた今度にします。

Hexoが新規記事ファイルを開いてくれないのでAlfredに頼んだ

Hexoでの新規記事作成

Hexoで記事を書き始める場合、以下のターミナルコマンドが利用できます。

1
hexo new 記事タイトル

すると、source/_postsに指定した命名ルールに基づいたファイルを作って、さらにフロントマターまで自動入力してくれるのですが、エディタで開いてくれません。

なまじ深い階層に作ってくれるので、普通に開こうとすると大変です。それだったら、コマンドに頼らず自分でやったほうが楽なくらいですが、それももったいない。

というわけで、困ったときのAlfred Workflow。

作ってみた

起動はキーワードで、以下のターミナルコマンドを実行します。最初シェルスクリプトでやろうとしたのですが、なんでかうまくいかないので諦めました。

(追記:Alfred Workflowのシェルスクリプトだとhexoコマンドにパスが通ってないのが原因でした。スクリプト内でパス通せばOK。)

1
2
3
4
5
6
PATH=/usr/local/bin:$PATH #hexoコマンドにパスを通す
cd ~/heroku/wbtmiu.herokuapp.com/ #hexoプロジェクトフォルダに移動
title=`echo "{query}" | sed -e 's/ /-/g' -e 's/[\.\/]//g'` #クエリ文字列を置換して変数に格納
hexo new "{query}" #新規記事を作成
open -a FoldingText source/_posts/`date '+%Y-%m-%d-'`$title.md #記事ファイルをFoldingTextで開く。Hexoの設定によりファイル名に日時が挿入されているためdateコマンドを使用
exit #ターミナルセッションを終了。iterm2だと同時にウインドウを閉じる

クエリに置換を入れているのは、hexo newコマンドでファイルが作成される際、ファイル名として不適切な文字がエスケープされるためです。確認した限りでは、以下の処理が行われます。

  • 半角スペースを-に置換
  • .を削除
  • /を削除

このような場合、クエリ文字列をそのままopenコマンドに渡すとエラーになるため、予めhexoが行うのと同様のルールで処理した文字列を渡します。hexo newの引数はファイル名だけでなく、記事タイトルとしても使われるため、生のクエリを渡します。

hexo newで表示されるメッセージを捕捉してFoldingTextに渡したほうが確実ではある。

ついでに、同様の方法でデプロイもAlfredからできるようにしておきました。

1
2
3
4
5
PATH=/usr/local/bin:$PATH #hexoコマンドにパスを通す
cd ~/heroku/wbtmiu.herokuapp.com/
hexo d -g #コンテンツを生成してデプロイ
heroku open #確認のためサイトをブラウザで開く
exit

FoldingTextだとフロントマターをうまく解釈できないので、変な装飾が入るのですが、まあしゃーなし。

黒魔法も慣れてくると便利

数ヶ月前まで、ターミナルとか極力触れたくなかったのですが、嫌々使ってるうちにだんだんなんでも黒魔法でやるようになってきました。

  • 記事の修正とかめどいのでレンタルブログとかWebベースCMS使いたくない
  • 記事はローカルに置いておくのだ!
  • Postach.ioのDropbox同期最高!
  • Postach.ioで日本語通らなくなった死ねよ
  • scriptogr.amなあ…開発止まってるしなあ…
  • もう静的サイトジェネレータ使うしかないじゃない!
  • 呪文多い…つらひ…
  • でも呪文使うと色々自動化できて案外楽…

という感じで。

半分くらい意味分かんないで唱えてるのでヒヤヒヤ感も楽しめて残暑の季節に最適。

しかし、Windowsからの更新どうしよう。

追記

その後、色々要素が追加されました。