メモを取る時、ブログを書くときなどにも使われるMarkdownをiPadのメモアプリでも使いたいと思ったことはありませんか?
メモアプリにMarkdown記法でテキストを書くことはできるのですが、HTMLでプレビューする方法がないので積極的にメモアプリでMarkdownを活用しようとは思わないですよね。
今回、Pythonistaを使ってメモに記載されたMarkdown記法をプレビューする機能を作ってみます。
Markdownとは
Markdownのユーザー会のページに下記のように説明されています。
Markdown(マークダウン)は、文章の書き方です。デジタル文書を活用する方法として考案されました。特徴は、
* 手軽に文章構造を明示できること
* 簡単で、覚えやすいこと
* 読み書きに特別なアプリを必要としないこと
* それでいて、対応アプリを使えば快適に読み書きできることなどです。
日本語Markdownユーザー会より引用(https://www.markdown.jp/what-is-markdown/)
Markdownという書き方を使うことで、簡単に太字やリンクや画像表示などを行うことができます。
今ではチャットのメッセージや、ブログのエディタなどに採用しているアプリやサイトが増えてきています。
iPadのメモアプリでMarkdownを表示するためのアプリ
今回はMarkdownを表示するためにPythonistaの機能を使います。
PythonistaはAppStoreからダウンロードします。
下記のアイコンが目印です。
有料アプリですが、今回のように機能を拡張するだけでなく、プログラミングの学習にも使えます。
買って損はないアプリですのでオススメです。
出来上がりイメージ
Markdownで記載したメモ。
# H1
## H2
### H3
#### H4
```python
print('Hello World!! ')
```
* リスト1
* ネスト1
* ネスト ネスト1
* リスト2
* ネスト2
*italic*, **bold**, ***italic-bold***
[リンク](https://ja.m.wikipedia.org/wiki/Markdown)
|左寄せ|中央寄せ|右寄せ|
|:-----|:-----:|-----:|
|左1|中1|右1|
|左2|中2|右2|
> **Wikipedeia**
> Markdown(マークダウン)は、文書を記述するための軽量マークアップ言語のひとつである。本来はプレーンテキスト形式で手軽に書いた文書からHTMLを生成するために開発されたものである。しかし、現在ではHTMLのほかパワーポイント形式やLATEX形式のファイルへ変換するソフトウェア(コンバータ)も開発されている。各コンバータの開発者によって多様な拡張が施されるため、各種の方言が存在する。
これが下記のように表示されます。
Markdown表示機能の作り方
事前準備
stashのインストール
PythonでMarkdownを扱うためにパッケージを追加します。
しかし、Pythonistaにはパッケージを追加するために使うpipが標準では備わっていません。
pipを使えるようにするために「stash」と呼ばれるツールを使います。
stashはPythonista用に開発されたシェルのようなもので、公式サイトに記載されています。
stashのインストールは簡単で、コンソールで下記を貼り付けて実行するだけです。
import requests as r; exec(r.get('http://bit.ly/get-stash').text)
stashがインストールできたらメニューから下記のようにlaunch_stash.pyを選択します。
その後右上の三角ボタンをタップして実行します。
下記の画面が表示されるのでpipを実行する準備が整いました。
mistuneモジュールのインストール
stashが起動できたら下記のコマンドを実行してmistuneパッケージをインストールしてください。
pip install mistune
実はPythonistaにはMarkdownを扱うためのパッケージが既にインストールされていますが、テーブル表示に対応していないなど機能が不足しているため、mistuneパッケージを使うことにしています。
実装
パッケージが導入できたら準備完了です。
続いてMarkdownをプレビューするためのコードを追加します。
新しくPythonのソースファイルを作成し、下記のコードを追加してください。
import appex
import mistune
import ui
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
class MyRenderer(mistune.Renderer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.formatter = HtmlFormatter(cssclass='hll')
def block_code(self, code, lang=None):
try:
l, _, filename = lang.partition(':')
except AttributeError:
l, filename = '', ''
try:
lexer = get_lexer_by_name(l)
except:
res = f'\n<div class="hll"><pre>{code}</pre></div>\n'
else:
res = highlight(code, lexer, self.formatter)
if filename:
new = f'<pre><span class="file">{filename}</span>\n\n'
else:
new = '<pre>\n\n'
res = res.replace('<pre>', new, 1)
return res
def _md2html(text):
with open('template.html', 'r', encoding='utf-8') as f:
template = f.read()
renderer = MyRenderer()
content = mistune.markdown(text, renderer=renderer)
html = template.replace('{{__CONTENT__}}', content)
return html
v = ui.WebView()
text = appex.get_text()
html = _md2html(text)
print(html)
v.load_html(html)
v.present('sheet')
次にMarkdownの見た目を調整するためStyleを含めたテンプレートHTMLファイルを作成します。
今回作成するにあたりQiitaのH1rono_Kさんの記事を参考にさせていただきました。
下記の内容をtemplate.htmlという名前で作成してください。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
<style>
html {
background-color: white;
font-family: "Avenir Next", sans-serif;
font-size: 20px;
}
body {
padding: 10px;
margin: 10px;
}
h1 { font-size: 34px; }
h2 { font-size: 29px; }
h3 { font-size: 26px; }
h4 { font-size: 24px; }
h5 { font-size: 23px; }
h6 { font-size: 22px; }
blockquote {
color: gray;
border-width: 3px;
border-style: none none none solid;
border-color: gray;
padding: 10px;
margin: 10px;
}
table {
padding: 0px;
border: 1px solid black;
}
th, td{
border: 0.5px solid gray;
}
code, pre {
font-family: "Menlo", monospace;
}
code {
padding: 0px 5px;
}
pre {
padding: 3px 10px 20px 10px;
overflow-wrap: break-word;
}
.file {
background-color: gray;
padding: 3px;
}
/* cool-glow style */
.hll { background-color: #0A0D2A; color: #E0E0E0; }
.gd { color: #A00000; } /* Generic.Deleted */
.ge { font-style: italic; } /* Generic.Emph */
.gr { color: #E5ABB3; } /* Generic.Error */
.gh { color: #E0E0E0; font-weight: bold; } /* Generic.Heading */
.gi { color: #E0E0E0; } /* Generic.Inserted */
.go { color: #E0E0E0; } /* Generic.Output */
.gp { color: #E0E0E0; font-weight: bold; } /* Generic.Prompt */
.gs { font-weight: bold; } /* Generic.Strong */
.gu { color: #E0E0E0; font-weight: bold; } /* Generic.Subheading */
.gt { color: #E5ABB3; } /* Generic.Traceback */
.o { color: #E0E0E0; } /* Operator */
.ow { color: #40F1E2; } /* Operator.Word */
.k { color: #40F1E2; } /* Keyword */
.kc { color: #40F1E2; } /* Keyword.Constant */
.kd { color: #40F1E2; } /* Keyword.Declaration */
.kn { color: #40F1E2; } /* Keyword.Namespace */
.kp { color: #40F1E2; } /* Keyword.Pseudo */
.kr { color: #40F1E2; } /* Keyword.Reserved */
.kt { color: #40F1E2; } /* Keyword.Type */
.m { color: #F8FBB1; } /* Literal.Number */
.mb { color: #F8FBB1; } /* Literal.Number.Bin */
.mf { color: #F8FBB1; } /* Literal.Number.Float */
.mh { color: #F8FBB1; } /* Literal.Number.Hex */
.mi { color: #F8FBB1; } /* Literal.Number.Integer */
.il { color: #F8FBB1; } /* Literal.Number.Integer.Long */
.mo { color: #F8FBB1; } /* Literal.Number.Oct */
.s { color: #A0FAA2; } /* Literal.String */
.sa { color: #A0FAA2; } /* Literal.String.Affix */
.sb { color: #A0FAA2; } /* Literal.String.Backtick */
.sc { color: #A0FAA2; } /* Literal.String.Char */
.dl { color: #A0FAA2; } /* Literal.String.Delimiter */
.s2 { color: #A0FAA2; } /* Literal.String.Double */
.se { color: #A0FAA2; } /* Literal.String.Escape */
.sh { color: #A0FAA2; } /* Literal.String.Heredoc */
.si { color: #A0FAA2; } /* Literal.String.Interpol */
.sr { color: #A0FAA2; } /* Literal.String.Regex */
.s1 { color: #A0FAA2; } /* Literal.String.Single */
.ss { color: #A0FAA2; } /* Literal.String.Symbol */
.sx { color: #A0FAA2; } /* Literal.String.Other */
.sd { color: #A0FAA2; font-style: italic; } /* Literal.String.Doc */
.nv { color: #E0E0E0; } /* Name.Variable */
.vc { color: #6DB6F2; } /* Name.Variable.Class */
.vg { color: #E0E0E0; } /* Name.Variable.Global */
.vi { color: #E0E0E0; } /* Name.Variable.Instance */
.vm { color: #E0E0E0; } /* Name.Variable.Magic */
.na { color: #E0E0E0; } /* Name.Attribute */
.nb { color: #40F1E2; } /* Name.Builtin */
.bp { color: #40F1E2; } /* Name.Builtin.Pseudo */
.nf { color: #6DB6F2; } /* Name.Function */
.fm { color: #6DB6F2; } /* Name.Function.Magic */
.nc { color: #6DB6F2; } /* Name.Class */
.nn { color: #C29AD3; } /* Name.Namespace */
.no { color: #E0E0E0; } /* Name.Constant */
.nd { color: #C1DBF2; } /* Name.Decorator */
.ni { color: #E0E0E0; font-weight: bold; } /* Name.Entity */
.ne { color: #E5ABB3; } /* Name.Exception */
.nl { color: #E0E0E0; } /* Name.Label */
.nt { color: #40F1E2; } /* Name.Tag */
.c { color: #AEAEAE; font-style: italic; } /* Comment */
.ch { color: #AEAEAE; font-style: italic; } /* Comment.Hashbang */
.cm { color: #AEAEAE; font-style: italic; } /* Comment.Multiline */
.c1 { color: #AEAEAE; font-style: italic; } /* Comment.Single */
.cs { color: #AEAEAE; font-style: italic; } /* Comment.Special */
.cp { color: #AEAEAE; font-style: italic; } /* Comment.Preproc */
.cpf { color: #AEAEAE; font-style: italic; } /* Comment.PreprocFile */
.err { border: 1px solid #E5ABB3; } /* Error */
.w { color: #bbbbbb; } /* Text.Whitespace */
.p { color: #E0E0E0; } /* Punctuation */
</style>
</head>
<body>
<div class="content">
{{__CONTENT__}}
</div>
</body>
</html>
メモアプリから呼び出せるように設定する
作成したスクリプトをメモアプリから呼び出せるようにします。
下記の図のようにShortcutsのSare Extensionを選択してください。
続いて「+」ボタンを押してショートカットを追加します。
先ほど作成したスクリプトを追加して完了です。
実行方法
メモアプリにMarkdownを追加したら下記の画像の通りにスクリプトを起動します。
メモアプリのメニューを開いて「コピーを送信」を選択します。
コピーを送信という表現が少し分かりづらいですね。
続いて「Run Pythonista Script」を選択します。
先ほど作成したショートカットが表示されるので選択します。
プレビューが表示されましたね!
最後に
Pythonistaを使ってiPadの機能をどんどん拡張できるようになっています。
iPadの標準アプリは便利なものがあるのですが、どこか物足りないと感じることもあります。
そんな時こそPythonistaの出番で、自分で足りない機能を補うことができます。
Pythonistaは機能を拡張するだけでなく、このような機能を作ることでプログラミングの勉強にもなりますね。
手持ちの環境がどんどん便利になって、自分も成長できる。
とても楽しいですね。それでは、また。