2015/01/14

sdeditでシーケンス図をかきかきして、Sphinxドキュメントに貼り付ける

Sphinxでインラインでシーケンス図を書くには、sphinxcontrib-seqdiagのほかに、sphinxcontrib-sdeditという選択肢もあります。

seqdiagの直感性は最強なのですが、ちょっといろいろ省略が入った図とか、条件文が入った図とかをつくろうとした時につまづいてしまうことが多いので、とりあえずsdeditのほうも試してみました。

(ちなみに、sdeditはすごく書きにくい上に、絵がダサいです。なので、ほんとに凝った図を作りたいとき以外はおすすめしません)


LinuxやMacだとそんなにハマることはない気がしますが、
Windows上のPython3でだと、いろいろとハマりどころがあります。

ハマりどころ1
 pip install しようとすると、PILのインストールが出来ずにコケる
Downloading/unpacking PIL
  Could not find any downloads that satisfy the requirement PIL
  Some insecure and unverifiable files were ignored (use --allow-unverified PIL to allow).
Cleaning up...


→ https://github.com/liori/sphinxcontrib-sdedit.git に、Pillowを使うよううまく改造した(らしい)sphinxcontrib-sdeditがあります。
pip install git+https://github.com/liori/sphinxcontrib-sdedit.git
とすれば、この特殊なsphinxcontrib-sdeditをインストールすることができます。

ハマりどころ2
 Unicode系のエラー。bytesとstrは足し算できんぞ!てきな。
@@ -70,10 +70,10 @@
     """
     Render sequence diagram into a PNG or PDF output file.
     """
-    hashkey = code.encode('utf-8') + str(options) + \
+    hashkey = code + str(options) + \
               str(self.builder.config.sdedit_args)
-    ofname = '%s-%s.%s' % (prefix, sha(hashkey).hexdigest(), format)
-    ifname = '%s-%s.sd' % (prefix, sha(hashkey).hexdigest())
+    ofname = '%s-%s.%s' % (prefix, sha(hashkey.encode("utf-8")).hexdigest(), format)
+    ifname = '%s-%s.sd' % (prefix, sha(hashkey.encode("utf-8")).hexdigest())
     infn = os.path.join(self.builder.outdir, ifname)
     if hasattr(self.builder, 'imgpath'):
         # HTML
@@ -92,8 +92,6 @@
     ensuredir(os.path.dirname(outfn))
     ensuredir(os.path.dirname(infn))
-    inputfile = open(infn, "w")
-    if isinstance(code, unicode):
-        code = code.encode('utf-8')
+    inputfile = open(infn, "w", encoding="utf-8")
     inputfile.write(code)
     inputfile.close()

こんなかんじでPython3対応させます


ハマりどころ3
 except OSError, err の構文エラー

sdedit.pyの例外構文をPython3対応すればよいです。
except XXError, err → except XXError as err


ハマりどころ4
 PILで「TypeError: integer argument expected, got float」

これはPILが悪いわけではなくて、使う側(sdedit.py)が悪い。
Python2では 3/2 = 1,  3.0/2 = 1.5
 のようになっていたのが
Python3では 3//2=1,  3/2 = 1.5
 のように'/'の意味が変わっているので、/を//に修正します。
@@ -161,7 +161,7 @@
 
 def html_tag_with_thumbnail(self, code, fname, new_width, image, imgcls):
     width, height = image.size
-    new_height = height * new_width / width 
+    new_height = height * new_width // width 
     image.resize((new_width, new_height), Image.ANTIALIAS)
     image.filter(ImageFilter.DETAIL)
     dir, file = os.path.split(fname)

これで、グラフが出力されるようになると思います。

sdedit.pyの修正結果はアップしておきます→https://gist.github.com/yi01/9d5a0480a50271c3794f

2015/01/13

SphinxとMatplotlibでお手軽にレポートを書こう

Matplotlibでグラフを作る方法を何度か紹介してきましたが、今回は、それをSphinx文書中に埋め込む方法をちょろっと書きます。


WindowsのPython 3.3+Matplotlib 1.4.0 という少々微妙な構成でやっています。
この構成だと、リファレンス通りにやってもエラーが出て動きません。

結論から言うと、

pip install ipython

でIPythonをインストールして
sys.path.append(os.path.abspath('sphinxext'))
extensions += [
          'matplotlib.sphinxext.mathmpl',
          'matplotlib.sphinxext.only_directives',
          'matplotlib.sphinxext.plot_directive',
          'IPython.sphinxext.ipython_directive',
          'IPython.sphinxext.ipython_console_highlighting']
のようにconf.pyに追記すればいいのです。
こうすれば、

インラインで。 :math:`(a + b)^2 = a^2 + 2ab + b^2` とか

ブロックで

.. math::

    (a + b)^2  $=$  (a + b)(a + b)

あと、ちょっとかっこいいので

.. math::

    \int_{-\infty}^{\infty} \lim_{n\to\infty}\sum_{i=0}^{n} \frac{\sin(ix)}{x} dx


.. plot::

    from pylab import *
    x=arange(0,10,0.1)
    y=sin(x)
    title(u"さいんかーぶだお")
    plot(x,y,"-")
    show()
みたいなことを書いて文書をビルドして、トップ画像のようなページができます。
「お、思ったより書く量が少ないぞ?!」って思いません?
ちょっと文法に癖はありますが、覚えてしまうと図もプログラムコードをそのまま埋め込んで書けますし、なによりWindowsじゃなくてLinuxでもMacでも動くのでMS Officeの心配しなくてもよいのです。
最初の1回はビルドするための環境構築が若干面倒ですが、それを乗り越えればAndroid端末ででもSONY VAIO Pみたいな超非力マシンでもメモだけはとれるので、かなり有用だと思います。

jsmathとかを使ったほうが .. math:: の処理はいろいろできるっぽいんですが、
  • ・いかんせん動作が重い
  • ・httpサーバを立ててファイルをおかないと(ローカルHTMLファイルを開くだけでは)数式が表示できない
という理由(とくに2点目が致命的)で却下・・・。


あ、最後に、どんなエラーに悩まされるか、少しだけ書いておきます。
リファレンスによると、conf.pyには
sys.path.append(os.path.abspath('sphinxext'))
extensions += [
          'matplotlib.sphinxext.mathmpl',
          'matplotlib.sphinxext.only_directives',
          'matplotlib.sphinxext.plot_directive',
          'matplotlib.sphinxext.ipython_directive',
          'sphinx.ext.autodoc',
          'sphinx.ext.doctest',
          'ipython_console_highlighting',
          'inheritance_diagram',
          'numpydoc']
と追記してやれば動くように書いてますが、時代は進んでそうはいかず、
ビルド時にIPythonを使えと言われます。
C:\Users\yi01\Desktop\sphinx\example>make html
Running Sphinx v1.2.3
C:\Python33\lib\site-packages\matplotlib\cbook.py:133: MatplotlibDeprecationWarning:
The Sphinx extension ipython_console_highlighting has moved from
matplotlib to IPython, and its use in matplotlib is deprecated.
Change your import from 'matplotlib.sphinxext.ipython_directive' to
'IPython.sphinxext.ipython_directive.
  warnings.warn(message, mplDeprecation, stacklevel=1)

Extension error:
Could not import extension matplotlib.sphinxext.ipython_directive (exception: Unable to import the necessary objects from IPython. You may need to install or upgrade your IPython installation.)
んで、ipythonを入れて、なんとなくextensionsの中身をmatpltlibからipythonに変えても、いろいろまだいわれます。
継承図をかくためにinheritance_diagramをインポートしないといけないんですが、それがインストールされていない、とかそういう系の。

継承図はわざわざMatplotLibを使って書かないと思うので、外して、最初の方に書いた結論に至りました。


おわり。

2014/12/01

中国のグレート・ファイアウォールをすり抜ける方法をいくつか

①VPNで家のルータにつなぐ


家のルータがVPNに対応しているなら、この方法が一番楽です。
プロトコルの制限とかもとくにないし、プロキシと違って、各アプリ側で設定が必要となることもほぼありません。

ただ、相性の問題で、ブチブチ切れることがあるので、そういう場合は②の方法を試しましょう。

②SSHプロキシで1080番ポートをフォワードして、SOCKSプロキシ


家のルータがVPN非対応の場合とか、対応していても不安定の場合はこっちの方法を使います。

ssh -D 1080 xxx.hogehoge.com -N
あとは、ネットワークの設定でSOCKSプロキシを「localhost:1080」に設定します。

VPSは有料契約が必要ですが、持っている人ならこれを利用するのがいいです。
家VPNに比べて速度がでます。
ただ、①とは逆に、アプリごとにプロキシ設定もってたりするとちょっと厄介です。


で、結局、PCはなんとでもなるのですが、
Androidスマートフォン使っていると、GooglePlayもGmailもハングアウトもGoogle検索もTwitterもFacebookもブロックされているので、かなり困ります。
SOCKSなんてAndroidでは使えませんし、VPNはセキュリティロック掛けないとダメとかで、色々面倒です。

で、私は結局どうやっているかというと、Macの「インターネット共有」を利用して、①で接続したルートをWifiで飛ばしてます。
これで意外にもスマートフォンからGooglePlayつながりますし、メールやTwitterもできました。

ふぅ、これで発狂を免れた。

2014/11/12

PythonのCairoでSVGをつくる

最近、Android用のキーボードの開発をやってます。

0からつくるわけにはいかんので、Mozc for Androidってのを使ってるんですが、
こことかにも書かれてるみたいに、Mozcにはキートップの画像が含まれていません。
(正確に言うと、画像はありますが無刻印キーボード的な画像が含まれています)


なので、ビルドするとこんなかんじになります。


キーボードの配列を覚える練習にはなるかもしれませんが、実用的ではないですね。


そんなMozcを↑のようにしたいわけですが、
ここのページで公開されてるキートップ画像では[t][y][u]の上下が微妙にずれていて、若干気持ちが悪いです。そこで、キートップ画像の自作をすることにしました。


ただ、社会人が片手間にやるには、InkScapeでチマチマと作る時間がありません。
そんなわけで、Pythonで一気に作っちゃいました、ってのが今回のお話。


さて、前置きが長くなりました。まずは
https://gist.github.com/yi01/1fe1bffabdc583c09de6
にコードは公開しちゃいます。間違いを発見した方は指摘ください。(笑)


で、あまり世の中には書かれていない?話をちょっとだけ書いておくと

・Mozcのキートップ画像の要件
キートップ用のSVGなんですが、textタグは使えません。pathタグで書く必要があります。
なので、Pythonだと、svgwriteとかは使えなくて、唯一の選択肢がcairoです。

<svg>に、id="style-keyicon-main"が指定されている必要があります。これがないと、スキンを暗い色にした時に、キートップの文字が白抜きにならない不具合が発生します。

QWERTYのキー画像サイズは48x74ピクセルです。cairoはptを単位系として使っているようなので、座標系を変換しなければなりません。
でも、変換がめんどくさすぎたので、今回は成果物のxmlからptの単位がついてるものを表示しなくしてみました、それにより、意図したサイズの画像が得られます。


・Cairoの文字描画
座標系がちょっと特殊(?)です。
xBearing, yBearing, width, height, xAdvance, yAdvance = context.text_extents(text)
で文字の描画位置やサイズが取得できるわけですが、座標系が下向きY軸正方向のものなので、直感とは逆になります。

で、もうひとつ注意点としては、そのへんのサンプルにある(xBearing+width/2, yBearing+height/2)という座標は、文字領域のど真ん中(赤色の四角の重心)であって、全体的な文章のラインの中心とは異なります。これが、冒頭に書いた「ずれていて若干気持ち悪い」ということです。

↑で、赤枠ベースでの配置だと上下して見えますよね。

じゃあ、どうすればいいのか、っていうのが青枠ベースの配置。

どうやら、フォントサイズと文字のラインの高さには3/4の関係があるみたいなので
↑にあるように、64ptのフォント指定の場合は、24px(=フォントサイズの3/8)だけ下に移動してやることで、行の中心をとらえた描画となるのです。




おまけ
E/MessageQueue-JNI(31310): java.lang.IllegalArgumentException: Duplicataed sourceId is found: Binary XML file line #1710
E/MessageQueue-JNI(31310):    at org.mozc.android.inputmethod.japanese.keyboard.KeyboardParser.parseKeyEntity(KeyboardParser.java:772)

みたいなエラーになった時のチェック用スクリプト
grep -r ":sourceId" kbd_*.xml | awk '{a[$2]+=1};END{for(k in a){if(a[k]>1){print k,a[k]}}}'

2014/11/09

Ubuntu 14.04のApache2のconfigを見て「アレ?」となった話

私は、仕事で(プライベートでも?)よくGitlistっていう便利なgitビューワを使っています。
正直、自分一人だったら要らないんですが、共同作業するなら、必ず一人はいますよね。「わたしgit使えないんで(キリッ」みたいな訳の分からないことをいう人が。

まぁ事情はともかく、Gitllistは、Gitビューワとして非常にシンプルで最低限の機能はあるものなので、いろいろなサーバに建てるために
git clone https://github.com/klaussilveira/gitlist.git
cd gitlist/
curl -s http://getcomposer.org/installer | php
php composer.phar install
chmod 777 cache/
sudo a2enmod rewrite
sudo /etc/init.d/apache2 restart
くらいは、いつでもコピペできるようにしてあったりします。

環境によっては
sudo a2enmod userdir
sudo /etc/init.d/apache2 restart
も必要ですね。まぁそれくらいは誤差の範囲です。

ただ、最近になって、Ubuntu 14.04のサーバを手にしたのですが、「アレッ?」ってなりました。

最終的には
http://aoba.web-hack.org/gitlist/
にあるように、ちゃんと動いたのですが、ユーザディレクトリでRewriteがなかなか動かなくてちょっと戸惑いました。


Directory /home/*/public_html/ > AllowOverride Allとか書く場所は…

正直、これだけで躓きました。他はフィーリングで行けます。

今まで(Apache 2.2)だと、/etc/apache2/sites-available/defaultあたりに、それっぽい記載があったので、真似して
<Directory /home/*/public_html>
    AllowOverride All
</Directory>

とかてきとーに書いていました。(セキュリティの云々はともかくとして…)

しかし、Apache 2.4になって、/etc/apache2/sites-available/defaultは見当たりません。
一体どこに行ったのか・・・。

じつは/etc/apache2/apache2.confにそれっぽい記載があります。
でも、本家confに書いちゃうのって微妙ですよね…。

というわけで、いろいろ試してみたところ、

/etc/apache2/conf-available/rewritable-userdir.conf
<Directory /home/*/public_html>
    AllowOverride All
</Directory>
こんなかんじで1ファイル作って、
sudo a2enconf rewritable-userdir
sudo /etc/init.d/apache2 restart
ってやれば、本家のconfigをオーバーライドする感じで、設定が出来ました。(confにするかsiteにするかは悩ましいところですが…)
Apache2で「あれ?AllowOverrideの設定箇所どこ行った??ユーザディレクトリでRewrite効かないぞ?!」ってなった方は、ぜひお試しあれ。



(参考)
以下、完全に個人的な趣向の問題ですが、Gitlistをちょっとだけ手直しして使ってます。

nano src/GitList/Config.php
    public static function fromFile($file)
    {
        if (!file_exists($file)) {
            die(sprintf('Please, create the %1$s file.', $file));
        }

        $data = parse_ini_file($file, true);
+       $repo = parse_ini_file($file.".repos.inc");
+       $data["git"]["repositories"] = $repo["repositories"];
        $config = new static($data);
        $config->validateOptions();

        return $config;
    }


これで、
config.ini
[git]
client = '/usr/bin/git' ; Your git executable path
default_branch = 'master' ; Default branch when HEAD is detached
;repositories[] = '/home/git/repositories/' ; Path to your repositories ★←コメントアウト
                                           ; If you wish to add more repositories, just add a new line

; WINDOWS USERS
;client = '"C:\Program Files (x86)\Git\bin\git.exe"' ; Your git executable path
;repositories[] = 'C:\Path\to\Repos\' ; Path to your repositories


config.ini.repos.inc
;QAEP
repositories[] = /home/yi01/mirror/LA.BF64.1.1-00110-8x94.0/

;AOSP
;repositories[] = /home/yi01/mirror/android-5.0.0_r2/

こうすることで、純粋なconfigと、リポジトリのメンテを別ファイルで行うことができるようになります。