JupyterNotebookでMarkdownの記事を書く(3) - Zennで実践
前回のこちらの記事の通り、JupyterNotebookを使用してZennの記事を書くのが
概ね納得行くLvになったのでもう少し詳しくやったことをまとめてみようと思います。
Dockerインストール コンテナ起動
まず、ローカルでの執筆環境構築にはDockerを使用します。
Docker側のセットアップは別途まとめてあります。
(DockerでJupyterNotebookの環境を起動するまで)
docker-compose -f "docker/docker-compose.yml" up -d --build
docker-compose.ymlを使用すれば、必要なポート設定も
起動部分も自動で実行できます。
Dockerのコンテナを起動して http://localhost:8888 にアクセスすると、
執筆環境のフォルダがマウントされた状態になります。
記事を書く
記事を書くには、NotebookのMarkdownとCodeを使い分ければ良いですが
Zennの記事用のヘッダーに関してはMarkdownに書くのではなく
1つ目のセルに、指定の変数を定義するようにしました。
title = "ipynbでZennの記事を書こう"
emoji = "😸"
text_type = "idea"
topics = ["Zenn","JupyterNotebook"]
published = True
Notebook上で見るとこのようになります。
Markdownで書くと、改行ちゃんとしないと表示が崩れたりして
面倒だったのですが、この方式なら違和感なく表示できます。
変換処理
執筆準備ができたので、次はNotebookのMarkdown変換です。
変換には nbconvertを使用します。
markdownを変換したときの見た目は tplファイル(Jinja)で書きます。
長いので重要なところのみ。
In:[~]部分の表示
{% block in_prompt %}
{% if not cell.execution_count%}
----
{% endif %}
mkdocsの場合は、
このようにセルの番号をいれていたのですが、
Zennの場合は見づらいので
間に線を入れるようにしました。
コードの実行結果
{% block error %}
{{ super() | add_prompts("> ","> ")}}
{% endblock error %}
実行結果を、もともと
:::message
:::
このメッセージ表示を利用していたのですが、
それだと若干見づらかったので引用を使用するようにしました。
Jinjaで行の頭に文字を挿入したい場合。
add_prompts は、デフォルトだと
>>> True
...
このようなプロンプトの実行結果のような表示をいれてくれます。
その引数でオプションを指定すると
行の頭に追加する文字列を変更できます。
これを使用して、行の頭に > を追加しています。
結果。
程よい結果表示になりました。
nbconvert
準備ができたので、ipynbからmarkdownに変換するスクリプトを書きます。
# -*- coding: utf-8 -*-
import glob
import os.path
import os
import codecs
import nbconvert
import nbformat
os.makedirs('articles', exist_ok=True)
for ipynb in glob.glob("./ipynb/*.ipynb"):
with codecs.open(ipynb, 'r', 'utf-8') as f:
lines = f.readlines()
f = nbformat.reads("".join(lines), as_version=4)
cell = f['cells'].pop(0)
exec(cell['source'])
header = ["---",
f'title: "{title}"',
f'emoji: "{emoji}"',
f'type: "{text_type}"',
"topics: " + "[" + ",".join([f"\"{x}\"" for x in topics]) + "]",
"published: " + ("true" if published else "false"),
"---"]
exporter = nbconvert.TemplateExporter(template_file="template/markdown.tpl")
(body, resources) = exporter.from_notebook_node(f)
with codecs.open(f'articles/{os.path.splitext(os.path.basename(ipynb))[0]}.md', 'w', 'utf-8') as f:
f.write("\n".join(header) + "\n" + body)
nbformat.read() を使用すると、ipynbをDict型してくれます。
セルの情報は、cellsに配列ではいっているので
1つ目のセルは、Zenn用の情報が書かれているものとして
配列から取り出して、いい感じにヘッダーになるように整形して
TemplateExporterを使用してテンプレート適応後の文字列に付け足します。
あとは、articlesフォルダ以下にmdとして保存します。
GithubActions
最後に以上のスクリプトをGithubActionsで実行します。
name: IpynbConvert
on:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Git config
run: |
git config --global core.symlinks true
git config --global user.name "Megumi Ando"
git config --global user.email "sample@sample"
- uses: actions/checkout@master
- uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
- name: Docker Compose
run: docker-compose -f docker/docker-compose.yml up --build -d
- name: Convert Ipynb to Markdown
run: docker exec usdnotebook python ipynb_convert.py
- name: push
run: |
git add articles/*.md
git commit -m "add md" -a
git push
GithubActionsの環境は、ローカル執筆環境と同じDockerコンテナを使用します。
起動したコンテナ上でスクリプトを実行するには
docker exec usdnotebook python ipynb_convert.py
docker exec ~ を使用します。
スクリプトを実行すると、 articles にmarkdownが出力されるので
Zennでデプロイするために、articles以下のmarkdownをPushします。
まとめ
結果構築したリポジトリ。
スクリプトの書き方などの記事は、Notebookで実行しつつ
間にMarkdownの説明を挟んで、書き終わったらipynbをPushして公開...
これができるようになったので、Zennでスクリプト関係の記事も書きやすくなるのでは?と思います。
USD関連のPythonスクリプトなど書いてみようかな?と考え中です。
概ねできていますが、いくつか調整がひつようなことがあって
GithubActions上でmasterにプッシュしているので
記事を書く前にPullしておく必要があるところ。
これは、記事をPushするブランチを分ければ解決しそうです。
もう1つが、更新がない場合はPushしないようにすること。
Push下ファイルに ipynb が含まれていなければ何もしない...
ようにすれば対応できるかなと思います。
Discussion