pixiv engineering blog

ピクシブのエンジニアがサービスの開発、ソフトウェアやインフラストラクチャ、 培ってきたノウハウや技術などをまとめるブログです

pixiv-novel-modeを作った

こちらは ピクシブ株式会社 Advent Calendar 2014 の12月19日の記事です。


こんにちはこんにちは、うさみです。2012年に自宅警備を廃業して現在に至ります。入社前はRubyを書いてましたが、最近はもっぱらPHPを楽しんでます p(ixi)v

pixivでは主に… 何が主なのか全然わからないのですが、現在のメイン業務以外ではpixivの言語回りの改善にも携ってます。

さて

Advent Calendarを引き受けたものの、普段の技術情報は定期的にQiitaとかに書いてるから、おもしろいねたが浮かばない。しかし既に半日使ってしまった、どうしよう。

あ、最近、pixivの小説が結構見直されてて、スマートフォン版のWebで縦書き表示ができるようになったりして、結構いい感じなんですよ。しかし筆者は他の業務の方を優先してたので小説の実装に参加できなくてぐぬぬ…といった有様です。

むしゃくしゃするので、そうだ、Lispでも書きませう。

用意

Flycheckは世話焼きですが、細かいところまで突っ込みを入れてくれるので捗ります。

メジャーモードの雛形をつくる

;;; pixiv-novel-mode.el --- Major mode for pixiv novel

;; Copyright (C) 2014 pixiv

;; Author: USAMI Kenta <tadsan@zonu.me>
;; Keywords: novel pixiv
;; Version: 0.0.1

;; This file is NOT part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This is a major-mode for editing novel pixiv Novel format.

;;; Code:

; ここにコード書く

(provide 'pixiv-novel-mode)
;;; pixiv-novel-mode.el ends here

テンプレです。ここをちゃんと書かないとFlycheckさんが怒ってきます。

ところで前から疑問なんですけど、Emacs LispのライセンスってGPL以外有効なんですかね…? しかしGPLにしておけば問題はないので、GPLです。

pixivの小説記法

さて、pixivの小説の仕様について。記法については [pixiv]ヘルプセンター|小説作品の本文内に使える特殊タグについて、それぞれの機能を教えてください にあります。

[newpage]
[chapter:章タイトル]
[pixivimage:イラストID]
[jump:リンク先ページ番号]
[[jumpuri:タイトル > リンク先URL]]

サポートする必要がある記法はこんな感じですね。

サンプルの小説を用意してみます。 (今回はテスト用として日本における著作権が消滅した文章を転載しますが、pixivには自分で書いた小説を投稿してくださいね!)

[chapter:作者の言葉]
[[jumpuri:Wikipedia: パンドラの匣 > http://ja.wikipedia.org/wiki/パンドラの匣_(小説)]]

[pixivimage:40246869]

[jump:1] [jump:2]

[newpage]

この小説は、「健康道場」と称する或ある療養所で病いと闘っている二十歳の男の子から、その親友に宛あてた手紙の形式になっている。手紙の形式の小説は、これまでの新聞小説には前例が少かったのではなかろうかと思われる。だから、読者も、はじめの四、五回は少し勝手が違ってまごつくかも知れないが、しかし、手紙の形式はまた、現実感が濃いので、昔から外国に於おいても、日本に於いても多くの作者に依よって試みられて来たものである。

[newpage]

「パンドラの匣はこ」という題に就ついては、明日のこの小説の第一回に於て書き記してある筈はずだし、此処ここで申上げて置きたい事は、もう何も無い。

甚はなはだぶあいそな前口上でいけないが、しかし、こんなぶあいそな挨拶あいさつをする男の書く小説が案外面白おもしろい事がある。

筆者の環境での初期表示はこんな感じです。

f:id:zonu_exe:20141219233246p:plain

メジャーモード定義

手始めに、これを綺麗に色付けできるメジャーモードを作ります。

(define-derived-mode pixiv-novel-mode fundamental-mode "pixivNovel"
  "Major mode for pixiv novel"
  (setq font-lock-defaults '(pixiv-novel-syntax-keywords)))

define-derived-modeはべんり機能で、メジャーモードはたったこれだけで定義できます。

カラーリング

ぐぐってみたところ、Emacsでキーワードに色付けをするにはEmacsWiki: Font Lock Keywordsの仕組みに乗るのが良さげです。

そんなわけで、綺麗に表示するための正規表現を書いてみます。

(defconst pixiv-novel-syntax-keywords
  (list
   '("\\[newpage\\]" . font-lock-keyword-face)
   (cons
    (concat
     "\\(\\[\\)"
     "\\(chapter:\\)"
     "\\([^\]]+\\)"
     "\\(\\]\\)")
    '((1 font-lock-keyword-face)
      (2 font-lock-builtin-face)
      (3 font-lock-function-name-face)
      (4 font-lock-keyword-face)))
   (cons
    (concat
     "\\(\\[\\[\\)"
     "\\(\\jumpuri:\\)"
     "\\([^>]+\\)"
     "\\( > \\)"
     "\\(https?://[^\]]+\\)"
     "\\(\\]\\]\\)")
    '((1 font-lock-keyword-face)
      (2 font-lock-builtin-face)
      (3 font-lock-function-name-face)
      (4 font-lock-keyword-face)
      (5 font-lock-string-face)
      (6 font-lock-keyword-face)))
   (cons
    (concat
     "\\(\\[jump:\\)"
     "\\([1-9][0-9]*\\)"
     "\\(\\]\\)")
    '((1 font-lock-keyword-face)
      (2 font-lock-function-name-face)
      (3 font-lock-keyword-face)))
   (cons
    (concat
     "\\(\\[\\pixivimage:\\)"
     "\\([1-9][0-9]*\\)"
     "\\(\\]\\)")
    '((1 font-lock-keyword-face)
      (2 font-lock-function-name-face)
      (3 font-lock-keyword-face)))))

もっと簡潔に書けるマクロある気がするのですが、この程度の数なので手書きしました。Emacsの正規表現は独特なので注意は必要です。

これを適用すると、見ためはこうなります。

f:id:zonu_exe:20141219233432p:plain

入力支援コマンドを定義する

pixiv小説の独自記法を覚えておくのはめんどくさいので、コマンドを定義しておきます。

(defun pixiv-novel/insert-newpage ()
  "Insert [newpage] tag."
  (interactive)
  (insert "[newpage]\n"))

(defun pixiv-novel/insert-jump-page (page)
  "Insert [jump] tag that move to `PAGE'."
  (interactive "nPage:")
  (insert (concat "[jump:" (number-to-string page) "]\n")))

(defun pixiv-novel/insert-jump-url (url)
  "Insert [[jumpurl]] tag that referes `URL'."
  (interactive "sURL:")
  (insert (concat "[jump:" url "]\n")))

(defun pixiv-novel/insert-chapter (title)
  "Insert [chapter] tag that named `TITLE'."
  (interactive "sTitle:")
  (insert (concat "[chapter:" title "]\n")))

(defun pixiv-novel/insert-illustration (id-or-url)
  "Insert [pixivimage] tag that insert illustration by `ID-OR-URL'."
  (interactive "spixiv Illustration ID or URL:")
  (insert (concat "[pixivimage:" (pixiv-novel/parse-pixiv-illustration-id id-or-url) "]\n")))

(defun pixiv-novel/parse-pixiv-illustration-id (input)
  "Parse pixiv URL by `INPUT'."
  (string-match "\\([1-9][0-9]*\\)" input)
  (match-string 0 input))

(define-derived-mode pixiv-novel-mode fundamental-mode "pixivNovel"
  "Major mode for pixiv novel"
  (set (make-local-variable 'font-lock-defaults) '(pixiv-novel-syntax-keywords)))

pixiv-novel/parse-pixiv-illustration-idは超手抜きですが、IDの数字だけを入力しても、URLをコピペしてきてもいいことにします。

作ったコマンドをキーマップに割り当てる

(defvar pixiv-novel-mode-map
  (let ((map (make-keymap)))
    (define-key map (kbd "C-c C-i n") 'pixiv-novel/insert-newpage)
    (define-key map (kbd "C-c C-i c") 'pixiv-novel/insert-chapter)
    (define-key map (kbd "C-c C-i i") 'pixiv-novel/insert-illustration)
    (define-key map (kbd "C-c C-i p") 'pixiv-novel/insert-jump-page)
    (define-key map (kbd "C-c C-i u") 'pixiv-novel/insert-jump-url)
    map)
  "Keymap for pixiv novel major mode.")

define-key

オートロードできるようにする

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.pxv\\(\\.txt\\)?\\'" . pixiv-novel-mode))

;;;### autoloadは「autoload cookie」と呼ばれます。こんなふうに書いてやると、 novel.pxv.txt みたいなファイルを開いたときに pixiv-novel-mode が読み込まれるようになります。

パッケージマネージャを使ってない場合はわかりませんが、package.elやel-getを利用すればよしなにやってくれるはずです。

インストールする

ソースコードは zonuexe/pixiv-novel-mode.el に置いたので、これをコピペして load-path の通ったディレクトリに置くか、Caskなどを使ってインストールできます。

Cask ファイルに以下のような一行を書いてやればインストールできます。

(depends-on "pixiv-novel-mode" )

MELPAにレシピが追加されたので、M-x package-installからインストールできます(El-GetはPR中)。

まとめ

  • 世話焼きのFlycheckは頼りになります
  • pixivの小説記法のためのメジャーモードを書きました
    • 正規表現が独特なので馴れるまでは手こずる

筆者個人の趣味の話で恐縮ですが、やる夫スレとか読むひとは Emacsでもアスキーアートが読める - Qiita とか読んでいただけると幸せになれます。

ピクシブ株式会社ではいっしょにpixivやBOOTHなどのサービスを作り上げる仲間を募集中ですが、開発環境は各個人が好きなものを選択できるし、希望すれば有償のIDEも利用できます!

戦争のない平和なオフィスへようこそ

次回のAdvent Calendarは?

明日はいっくんこと@alpaca_taichouがVimのいい話をしてくれるのでは…?






おまけ

あれ、サンプルの文章は青空文庫からコピペしてきたので、よく見ると振り仮名の部分が変な感じになってますね。修正修正。

[chapter:作者の言葉]
[[jumpuri:Wikipedia: パンドラの匣 > http://ja.wikipedia.org/wiki/パンドラの匣_(小説)]]

[pixivimage:40246869]

[jump:1] [jump:2]

[newpage]

この小説は、「健康道場」と称する[[rb:或 > あ]]る療養所で病ひと闘ってゐる二十歳の男の子から、その親友に[[rb:宛 > あ]]てた手紙の形式になってゐる。手紙の形式の小説は、これまでの新聞小説には前例が少かったのではなからうかと思はれる。だから、読者も、はじめの四、五回は少し勝手が違ってまごつくかも知れないが、しかし、手紙の形式はまた、現実感が濃いので、昔から外国に[[rb:於 > お]]いても、日本に於いても多くの作者に[[rb:依 > よ]]って試みられて来たものである。

[newpage]

「パンドラの[[rb:匣 > はこ]]」といふ題に[[rb:就 > つい]]ては、明日のこの小説の第一回に於て書き記してある[[rb:筈 > はず]]だし、[[rb:此処 > ここ]]で申上げて置きたい事は、もう何も無い。

[[rb:甚 > はなは]]だぶあいそな前口上でいけないが、しかし、こんなぶあいそな[[rb:挨拶 > あいさつ]]をする男の書く小説が案外[[rb:面白 > おもしろ]]い事がある。

Coming soon…?