MarkdownをMediumへポストするときにコードブロックをGistのリンクにする
「Markdownで書いたテキストをMediumへポストする(mdium)」で作った Markdown で書いたテキストを Medium へポストするツールですが,ちょくちょく改良を続けてます. 今回は Pandoc を利用して、コードブロックを Gist のリンクに置き換える機能を追加したので,そのことについてのメモ書きです.
Medium のシンタックスハイライト
Medium は普通にコードブロックを記述するとシンタックハイライトされずダサい:
これをなんとかする方法の一つに Gist のリンクを貼って埋め込みにするというのがある:
ただし,一つの Gist に一つのファイルを設定しないといけないのでコスパが最悪で,1つの記事に対してこんな感じに大量の Gist を作る必要がある:
最近はその Gist を使った方法をとっているが,いちいち Gist に手で置き換えるのはめんどいよね. ってことで,自作ツールの mdium が自動でやってくれるようにした.
作る
作業 PR はこれです:
Gist を作成する API
GitHub API には Gist を作成するものがある. しかし,私がよく使う Haskell の GitHub クライアントには Gist を作成する関数が作られてなかった. ので,作成してとりあえず PR を出した:
実は(リファレンスからは分かりにくい?)注意点があって:
files
は{"hoge.txt": {"content": "abc"}}
って感じのJSONオブジェクトdescription
パラメーターやfiles
のcontent
はnull
を渡せないpublic
をnull
にした場合レスポンスのpublic
もnull
になる
aeson で Maybe
を使ってオプショナルなパラメーターを表現した場合は強制的に null
が渡される. そのため, が,レビューで次のようにやればいいよって返ってきた:description
はオプショナルなパラメーターだが型を Text
にした
instance ToJSON NewGist where
NewGist { ... } = object $ filter notNull
toJSON "description" .= newGistDescription
[ "files" .= newGistFiles
, "public" .= newGistPublic
,
]where
Null) = False
notNull (_, = True notNull (_, _)
なるほど,レビュー感謝. で,public
は,すでにある Gist
型(レスポンスに使いたい)の public
が Maybe Bool
ではなく Bool
なので (3) の挙動が困る. そのため,public
パラメーターの型も Bool
にしたが,description
同様に filter notNull
効かせたので普通に Maybe Bool
で良くなった. ちなみに,デフォルトは false
.
Pandoc で書き換える
少しオーバースペックのような気がするが,Pandoc を利用して Markdown をパースする:
customizeContent :: Text -> RIO Env Text
= liftIO . Pandoc.runIOorExplode $ do
customizeContent content <- Pandoc.readCommonMark Pandoc.def content
p0 Pandoc.writeCommonMark Pandoc.def p0
readMarkdown
というのもあるが,GFM などを使う場合は readCommonMark
の方を使う. p0
というのが Pandoc
型の値で,Walkable
を利用することで任意のブロックに対してのみ変換を適用したりできる:
customizeContent :: Text -> RIO Env Text
= do
customizeContent content <- liftIO $ Pandoc.runIOorExplode (Pandoc.readCommonMark Pandoc.def content)
p0 <- Pandoc.walkPandocM replaceCodeBlockToGistLink p0
p1 $ Pandoc.runIOorExplode (Pandoc.writeCommonMark Pandoc.def p1)
liftIO
replaceCodeBlockToGistLink :: Pandoc.Block -> RIO Env Pandoc.Block
= \case
replaceCodeBlockToGistLink Pandoc.CodeBlock attr txt -> undefined -- 書き換える
-> pure block block
Gist がよしなにプログラミング言語を特定してシンタックスハイライトするにはファイルの拡張子が必要だ. コードブロックのバッククオートの後ろに指定している拡張子を取得するために attr
を見てみると ("", ["hs"], [])
となっていた(プログラミング言語名も指定できるけど,拡張子だけを指定してるとする). なので,これを利用する:
replaceCodeBlockToGistLink :: Pandoc.Block -> RIO Env Pandoc.Block
= \case
replaceCodeBlockToGistLink Pandoc.CodeBlock (_, [ext], _) txt -> do
<- lift $ createGist ext txt
gist pure $ Pandoc.Plain [Pandoc.Str (GitHub.getUrl $ GitHub.gistHtmlUrl gist)]
-> pure block
block
createGist :: Text -> Text -> RIO Env GitHub.Gist
= do
createGist ext txt let files = HM.fromList [("sample." <> ext, GitHub.NewGistFile txt)]
-- とりあえずエラーハンドリングは適当に throwM する
either throwM pure =<< MixGitHub.fetch (GitHub.createGistR $ GitHub.NewGist "" files True)
MixGitHub.fetch
というのはこれです. 便利です.
これでとりあえず,コードブロックから Gist を作ってリンクに置き換えることができるようになった.
Gist のファイル名を工夫する
現状だと全ての Gist ファイルが sample.hs
みたいに拡張子以外は一緒になってしまう. なのでプレフィックスを指定できるようにするのと,何個目のコードブロックかで sample1.hs
や sample2.hs
みたいにできるようにしたい. そのために State
モナドを利用する:
customizeContent :: Text -> RIO Env Text
= do
customizeContent content <- liftIO $ Pandoc.runIOorExplode (Pandoc.readCommonMark Pandoc.def content)
p0 <- fst <$> runStateT (Pandoc.walkPandocM replaceCodeBlockToGistLink p0) 1
p1 $ Pandoc.runIOorExplode (Pandoc.writeCommonMark Pandoc.def p1)
liftIO
replaceCodeBlockToGistLink :: Pandoc.Block -> StateT Int (RIO Env) Pandoc.Block
= \case
replaceCodeBlockToGistLink Pandoc.CodeBlock (_, [ext], _) txt -> do
<- State.get
cnt <- lift $ createGist (tshow cnt) ext txt
gist + 1)
State.modify (pure $ Pandoc.Plain [Pandoc.Str (GitHub.getUrl $ GitHub.gistHtmlUrl gist)]
-> pure block
block
createGist :: Text -> Text -> Text -> RIO Env GitHub.Gist
= do
createGist suffix ext txt let files = HM.fromList [("sample" <> suffix <> "." <> ext, GitHub.NewGistFile txt)]
either throwM pure =<< MixGitHub.fetch (GitHub.createGistR $ GitHub.NewGist "" files True)
プレフィックスはコマンドのオプションから指定して,適当に createGist
まで持ってくるだけなので割愛. これで完全に手作業してたのが自動化された.
おしまい
Pandoc パッケージのビルドがゲキオモなのがネックだけどね.