JupyterNotebookでMarkdownの記事を書く(1) - 環境構築編

5 min read読了の目安(約5100字

まずは動くところまで最低限の環境を構築していきます。

なにがしたいのか

そもそもも始まりは、
もともと、USDにしても他のコードにしても
Pythonを利用してコードの動作などをテストする場合、JupyterNotebookを使用していて

https://fereria.github.io/reincarnation_tech/60_JupyterNotebook/NoteSample/
そのNotebookをGithubPagesで公開しています。

Pythonを使用する場合、
こういったコードの挙動であったり、書き方、Tips的なものは
Notebookで書くと、
実行結果を表示できたり、ipynbをダウンロードすればそのまま再現できるというメリットがあったり、
さらに、NotebookにはMarkdownを書くこともできるので
コードの間に補足を入れたりみたいなこともできるので
コード解説を主体にしつつ、Markdownで補足を書くような記事は
Notebookで書きたいなーと常々思っていました。

mkdocsのページは、GithubにPushする前に事前に ipynb -> md のコンバートをしていたのですが
Zennに上げる場合は事前にコンバートもめんどくさい。
というわけで、表題の通りなのですが
今回はJupyterNotebook(ipynb)をPushしたらZennの記事として公開できるようにしてみよう!
と思います。

Githubのリポジトリを準備する

まずはリポジトリを作ります。
最近のZennの更新で、複数のリポジトリから記事を構築できるようになったので
ipynbから記事を作るほうはリポジトリを分けておきます。

リポジトリ以下の構造は、Zennの記事 md を配置する articles と、
その記事のもとになるipynb置き場、そして ipynb to markdown 用のテンプレートファイルの3つ。
+GithubActionsの処理を書く yml 置き場(.github/workflows ) です。

Dockerで環境を作る

リポジトリを作ったら、次にGithubActionsを実行するための環境を用意します。
環境は、mkdocs のときのようにpipenvを利用してもよいのですが
今回は勉強をかねてDockerを使います。

事前にDockerfileを用意して、DockerHubにイメージをアップします。

https://qiita.com/reflet/items/4b3f91661a54ec70a7dc

Dockerfileはこちらのサイト様を参考に作成して、

RUN pip install nbconvert

markdownにコンバートするためのnbconvertのインストールと

RUN apt-get update && apt-get install -y git

を追加します。
GithubActionsでは、GitにPushする必要があるので git を使えるようにする必要がありますが
Dockerのデフォルトの環境だとインストールされていません。
なので、 Dockerfile側に gitインストールを入れておかないと、のちの処理でエラー になります。

templateを書く

Dockerの準備ができたら、 markdown に変換するあたりを用意します。

https://fereria.github.io/reincarnation_tech/10_Programming/99_Documentation/03_ipynb2mkdocs/

細かいところは、以前 mkdocs 用に書いたこちらの記事と同じですが、Zenn用+Docker上で動かすために
少し修正をします。

# -*- coding: utf-8 -*-

import glob
import subprocess
import os.path
import os

os.makedirs('articles', exist_ok=True)


for ipynb in glob.glob("./ipynb/*.ipynb"):

    p = subprocess.Popen(['python',
                         '-m', 'nbconvert',
                          '--to', 'markdown',
                          '--output', f'../articles/{os.path.splitext(os.path.basename(ipynb))[0]}.md',
                          '--template', 'template/jupyter_template.tpl',
                          ipynb])
    p.wait()

GithubActionsで呼び出すipynb to markdown を実行するコマンドを リポジトリ直下に作成します。
これで ipynb 以下にあるファイルを markdown に変換することができます。

{% extends 'markdown.tpl'%}

{% block header %}
{% endblock header %}

{% block markdowncell%}
{{cell.source}}
{% endblock markdowncell%}

{% block in_prompt %}
{% if cell.execution_count > 0%}
#### [{{ cell.execution_count if cell.execution_count else ' ' }}]:
{% endif %}
{% endblock in_prompt %}

{% block stream %}
:::message{{"\n"}}
{{- output.text|replace("^    ","") -}}
:::
{% endblock stream %}

{% block execute_result%}
:::message
{{- output.text|replace("^    ","") -}}  
:::

{% endblock execute_result%}
{% block error %}
:::message alert
{{- output.text|replace("^    ","") -}}
:::
{% endblock error %}

markdown に変換するためのテンプレートはこんな感じになります。
mkdocsのときは、メッセージの表記部分はインデントする必要があったので問題なかったのですが
Zennの場合インデントをしてしまうと

ちょっとイケてない見た目になるので、replaceを使うことによって
行頭のスペース4つ分を削ります。

https://jinja.palletsprojects.com/en/2.11.x/templates/#replace

このテンプレートはJinjaで作られているので、Jinjaのテンプレートをもう少し理解すれば
いい感じの見た目に改良でる(はず)

結果。

GithubActionsを書く

準備ができたので、最後にGithubActionsで ↑のPythonスクリプトを実行して
markdownをarticles 下にPushするようにします。

name: IpynbConvert

on:
  push:
    branches: [master]
jobs:
  build:
    runs-on: ubuntu-latest
    container: docker://fereria/nbconvert:latest
    steps:
      - name: Git config
        run: |
          git config --global core.symlinks true
          git config --global user.name "Megumi Ando"
          git config --global user.email "###@###"
      - uses: actions/checkout@master
      - name: Convert Ipynb to Markdown
        run: python ipynb_convert.py
      - name: push
        run: |
          git add articles/*.md
          git commit -m "add md" -a
          git push

masterブランチになにかをPushしたら指定したDockerのコンテナを利用して
ipynb_convert.py を実行して、その結果をpushします。

jobs.build.steps以下に実際に実行したいプロセスを書いていきます。
自分で指定のコマンドを実行する場合は name と run( run: | 以降複数行書ける)

uses を使用すると、指定のリポジトリ以下にある自動処理を使うことができます。

https://github.com/actions

uses: actions/checkout@master の場合は、
actions以下のcheckoutリポジトリ(https://github.com/actions/checkout)のmasterブランチの
内容を実行しろ...という意味になります。
その名の通りこのGithubActionsを実行しているリポジトリをチェックアウトするための
Actionです。
チェックアウトしたら、 python コマンドを実行します。
これで ipynbが markdown に変換されるので、その後Pushすれば完了です。

結果、

https://github.com/fereria/zenn_ipynb/blob/master/ipynb/sample-ipynb.ipynb
これが
https://github.com/fereria/zenn_ipynb/blob/master/articles/sample-ipynb.md
こうなります。

記事は非公開にしてるのでZenn上には表示していませんが
プレビューするとこんな感じになります。

https://github.com/fereria/zenn_ipynb

サンプルリポジトリ。

次回の改良

一応やりたいことはできたのですが、このままだといくつか問題があります。

  1. ipynb以外がPushされても実行されてしまう
  2. 変更がない場合GithubActionsがエラー扱いになってしまう
  3. Zenn用のタイトル指定周りがJupyterNotebook上だとみため崩壊してるのがやだ
  4. 記事の最後に ipynb へのリンクがほしい
  5. JinjaTemplateの改良(見た目の改善)

と、不満なところが結構あるので
もう少しGithubActionsのドキュメントを読みつつ処理を改良して
実際に公開できるところまでやろうと思います。