Sphinx&Graphvizで日本語を含むグラフを出力する方法

※ 環境は以前(id:re_guzy:20101012)と同じ

基本的にはドキュメントを見ればOKだが、日本語を記述すると文字化けするので、その回避策をメモしておく。

まずはconf.pyでgraphviz拡張を有効にする。

# conf.pyを修正
extensions = ['sphinx.ext.autodoc','rst2pdf.pdfbuilder', 'sphinx.ext.graphviz']

この時点でも以下のようにすれば、日本語を使うことができる。

.. digraph:: testname

 node[fontname=ipag]
 "bar" -> "baz" -> "日本語"

ただ、毎回フォントを指定するのも面倒なので、以下のようにconf.pyに追記してしまう。

# conf.pyに追記, IPAゴシックを使う場合
graphviz_dot_args = [
    '-Gfontname=ipag',
    '-Nfontname=ipag',
    '-Efontname=ipag',
]

そうすると、以下のように記述しても日本語が文字化けしません。

.. digraph:: testname

 graph[rankdir=LR]
 "bar" -> "baz" -> "日本語"

出力した図はこんな感じ。


※ フォントが見つからない場合、環境変数DOTFONTPATHを指定する必要があるかも

SET DOTFONTPATH=%WINDIR%\Fonts;YOUR_FONT_DIR;

Windows環境でSphinxを使って日本語PDFを作成するメモ

参考URL

前提条件

easy_installで依存ライブラリもインストールされるはず。今回試したのは以下のバージョン。

ディレクトリ構成はこんな感じ

WORK_DIR
  +-build ・・・ 成果物格納ディレクトリ
  +-fonts ・・・ フォント格納ディレクトリ
  +-souce ・・・ ソース格納ディレクトリ
        +- index.rst ・・・ ソース
        +- conf.py
        +- ja.json
  +- make.bat
  +- Makefile

日本語フォント

VLゴシックフォントIPAフォントをダウンロードしてきて、WORK_DIR\Fontsにコピーする。C:\WINDOWS\Fontsディレクトリにインストールしてもいいが、Sphinxの設定はPythonコードなので、フォントパスを指定してあげればどこに配置しても構わないはず。

conf.py修正

参考URLのように修正すればいいが、次の2点は以下のように修正する。

  • pdf_stylesheetsは、sphinxとjaのみ指定する。そうしないと余計なフォントを探しに行って、PDF作成にとても時間がかかる。
  • pdf_font_pathには、先程ダウンロードしてきたフォントのコピー先を指定する。
pdf_stylesheets = ['sphinx','ja']
import os
font_dir = os.path.abspath(os.path.join(os.path.split(__file__)[0], os.pardir, 'fonts'))
pdf_font_path = [font_dir, 'C:\WINDOWS\Fonts']
pdf_language = "ja"

ja.json

これはほぼ参考URLのとおり。ただし、IPAフォントは*.otfではなくて*.ttfなので、そこだけ修正する。

{
  "embeddedFonts" : [
    [
        "VL-Gothic-Regular.ttf",
        "VL-PGothic-Regular.ttf",
        "ipam.ttf",
        "verdanaz.ttf"
     ]
  ],
  "fontsAlias" : {
    "stdFont": "VL-PGothic-Regular",
    "stdBold": "VL-PGothic-Regular",
    "stdItalic": "VL-PGothic-Regular",
    "stdBoldItalic": "VL-PGothic-Regular",
    "stdMono": "VL-PGothic-Regular",
    "stdMonoBold": "VL-PGothic-Regular",
    "stdSanBold": "VL-PGothic-Regular",
    "stdSansBold": "VL-PGothic-Regular"
  },
  "styles" : [
    ["base" , {
      "wordWrap": "CJK",
      "kerning" : true
    }],
    ["literal" , {
      "wordWrap": "None"
    }]
  ]
}

バッチファイル修正

make.batを修正して、"make pdf"でPDFを作成できるようにする。

if "%1" == "pdf" (
	%SPHINXBUILD% -b pdf %ALLSPHINXOPTS% %BUILDDIR%/pdf
	echo.
	echo.Build finished. The PDF files are in %BUILDDIR%/pdf
	goto end
)

これで一応日本語PDFが作成できるんだけども、外部リンクを貼るときターゲット定義とリンクを分割するとエラーが発生する。原因はよくわからない。


続きを読む

py2exeで生成するexeの名前を指定する方法

いくら検索してもpy2exeで生成するexeの名前を指定する方法が見つからなかったのでメモしておく。
キーになるのは「dest_base」。生成するexeの種類によって指定の仕方が異なるので注意。

# -*- encoding: utf-8 -*-
from distutils.core import setup


py2exe_options = {
  'compressed': 1,
  'optimize': 2,
  'bundle_files': 1
}


#メインスクリプトがmain.pyの場合
setup(
    options = { 'py2exe' : py2exe_options },
    #コンソールアプリの場合、cosole=の行のコメントアウトを外す
    #console=[dict(script='main.py', dest_base='console_exename'),],
    #Windowsサービスの場合、service=の行のコメントアウトを外す
    #service=[dict(modules='main', dest_base='service_exename'),],
)

インストールしないでPython2.6を動かしてみた

ずいぶん前にした質問を自己解決できそうなので、その手順をメモしておく。
使ったのはPython2.6.6+Windows7。コピーして試してみたのはWindowsXP。

Python2.6.6のインストール


まずはネタ元のPythonをインストール。重要なのは「Install just for me」。このオプションを選択しないと、python26.dllがインストール先ディレクトリにコピーされない。
コピー先PCでPythonが動作させるには以下のdllが必要なので、忘れずに選択すること。あとの選択はどうでもいい。以下は「C:\python26」にインストールしたものとする。

  • python26.dll
  • msvcr90.dll
  • Microsoft.VC90.CRT.manifest

必須ライブラリのインストール

easy_install、IPythonは必須だろうから、インストールしておく。

CD /d C:\python26
python ez_setup.py
CD scripts
easy_install ipython
easy_install pyreadline

pywin32のインストール

あるといろいろ便利なのでpywin32をインストールしておく。ただし、「easy_install pywin32」ではコピー先PCでDLLがロードできないので、ソースからインストールする。理由は不明。
このときCコンパイラを設定しておく必要があるかも。よく覚えてない。
pywin32を動作させるためには、pythoncom26.dllとpywintypes26.dllが必要なので、「C:\python26」にコピーしておく。

CD /d pywin32_source_directory
python setup.py install
CD /d C:\python26\Lib\site-packages\pywin32_system32
COPY pythoncom26.dll C:\python26
COPY pywintypes26.dll C:\python26

Fabricのインストール

デプロイツールFabricもあると便利なのでインストールしておく。

CD /d C:\python26\Scripts
easy_install fabric

pythonのコピー

以上の作業を行った後、「C:\python26」以下のファイルをコピー先PCにコピーする。これでもpython単体は動作するが、easy_installやIPython、Fabricが動作しない。
これは「C:\python26\Scripts」以下のpyファイルに記述されているpythonのパスがおかしいため。コピー先PCでも「C:\python26」にコピーすれば動くかもしれないが、それだといまいちなので、どこにコピーしても動作するようにパスを修正する。

こんなスクリプト「fix_pythonpath.py」をコピー先ディレクトリに配置して、

# -*- encoding: utf-8 -*-
# fix_pythonpath.py
import glob
import sys


def fix_pythonpath():
    """ pythonスクリプトのパスを修正します。
    """
    for file in glob.glob(r'python\scripts\*.py'):
        template = open(file, 'r')
        try:
            lines = template.readlines()
        finally:
            template.close()

        target = open(file, 'w')
        try:
            is_first_line = True
            for line in lines:
                if is_first_line:
                    target.write('#!%s\n' % sys.executable)
                    is_first_line = False
                else:
                    target.write(line)
        finally:
            target.close()


if __name__ == '__main__':
    fix_pythonpath()

こんなバッチをコピー先ディレクトリに配置する。

REM fix_pythonpath.bat
SET CURRENT_DIR=%~DP0
SET PYTHONHOME=%CURRENT_DIR%python
SET PATH=%PYTHONHOME%;%PYTHONHOME%\Scripts;%PATH%

python fix_pythonpath.py

あとはバッチを実行してやればOK。

CD /d PYTHON_COPY_DIR
fix_pythonpath.bat

これでpythonはもちろん、easy_installやIPython、Fabricも動作するようになる。

今後の予定

  1. py2exeのインストールと、windowsサービスのexe化
  2. Inno Setup 5 による、All-In-Oneなインストーラの作成
  3. Apache Http ServerをAll-In-Oneなインストーラに組み込む

EnumをComboBoxに表示する。

アセンブリHogeAsmに含まれるEnum、HogeEnumをComboBoxに表示。

<UserControl
    x:Class="Hoge.HogeView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    xmlns:hoge="clr-namespace:Hoge;assembly=HogeAsm">
    <UserControl.Resources>
        <ObjectDataProvider x:Key="EnumList" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="hoge:HogeEnum"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </UserControl.Resources>

    <Grid>
        <ComboBox ItemsSource="{Binding Source={StaticResource EnumList}}" />
    </Grid>
</UserControl>

TracLightningでMercurialを使ってみた

 サーバリプレースのついでに、自己流でセットアップしたTrac0.10からTracLightning2.3.2に移行してみた。ついでにTracをmod_wsgiで動かしてみたり、Mercurialと連携させてみたり、チケットの自動クローズとかできるようにしてみたのでメモしておく。

環境はこんな感じ。

TracLightningをmod_wsgiで動かす

参考URL

これは参考URLのまま。
1.2つ目のURLからmod_wsgi.soをダウンロードして、C:\TracLight\CollabNetSVN\httpd\modulesにコピー
2.C:\TracLight\CollabNetSVN\httpd\conf\httpd.confの書き換え

--- a/CollabNetSVN/httpd/conf/httpd.conf	Fri Nov 20 14:55:06 2009 +0900
+++ b/CollabNetSVN/httpd/conf/httpd.conf	Fri Nov 20 15:05:18 2009 +0900
@@ -496,7 +496,7 @@
 LoadFile "../../python/python25.dll"
 LoadModule authz_svn_module modules/mod_authz_svn.so
 LoadModule dav_svn_module modules/mod_dav_svn.so
-LoadModule python_module modules/mod_python.so
+#LoadModule python_module modules/mod_python.so
 #LoadModule fcgid_module modules/mod_fcgid.so
 
 
@@ -534,14 +534,19 @@
 # PythonDebug On 
 
 #DefaultInitEnv TRAC_ENV_PARENT_DIR "C:\TracLight\projects\trac"
-ScriptAlias /trac "C:\TracLight\CollabNetSVN\httpd\cgi-bin\trac.cgi"
+#ScriptAlias /trac "C:\TracLight\CollabNetSVN\httpd\cgi-bin\trac.cgi"
+LoadModule wsgi_module modules/mod_wsgi.so
+WSGIScriptAlias /trac "C:/TracLight/CollabNetSVN/httpd/cgi-bin/trac.wsgi"
 
 <Location "/trac">
-  SetHandler mod_python
-  PythonHandler trac.web.modpython_frontend
-  PythonOption TracEnvParentDir "C:\TracLight\projects\trac"
-  PythonOption TracUriRoot /trac
-  PythonOption PYTHON_EGG_CACHE "C:\TracLight\projects\.egg-cache"
+#  SetHandler mod_python
+#  PythonHandler trac.web.modpython_frontend
+#  PythonOption TracEnvParentDir "C:\TracLight\projects\trac"
+#  PythonOption TracUriRoot /trac
+#  PythonOption PYTHON_EGG_CACHE "C:\TracLight\projects\.egg-cache"
+  WSGIApplicationGroup %{GLOBAL}
+  Order deny,allow
+  Allow from all
 </Location>
 
 <Location "/svn/">

3.C:\TracLight\CollabNetSVN\httpd\cgi-bin\trac.wsgiを作成

#!C:\TracLight/python/python.exe
# -*- coding: utf-8 -*-
#

import sys
sys.stdout = sys.stderr

import os
os.environ['TRAC_ENV_PARENT_DIR'] = 'C:/TracLight/projects/trac'
os.environ['PYTHON_EGG_CACHE'] = 'C:/TracLight/projects/.egg-cache'

import trac.web.main

application = trac.web.main.dispatch_request

Mercurialとの連携

参考URL

Mercurialはバイナリインストーラは使わず、easy_installでインストールする。でないと、TracMercurialを入れても「Unsupported version control system "hg"」と言われてしまう。

なので、まずはeasy_installから。ez_setup.pyをダウンロードして、コマンドラインから以下のコマンドを実行する。

python ez_setup.py


次に、Mercurialをコンパイルできるようにするため、2つ目のURLを参考にMinGWをインストール。
環境変数PATHに以下の設定を追加して、

%PATH%;C:\MinGW\bin;

C:\TracLight\python\Lib\distutilsの中に「distutils.cfg」というファイルを作成し、そのファイルに下記を記述して保存。

[build]
compiler=mingw32

あとは以下のコマンドでMercurialがインストールできる。

easy_install mercurial


次はTracMercurial – The Trac Project

easy_install -UZ http://svn.edgewall.com/repos/trac/sandbox/mercurial-plugin-0.11

SVNでチェックアウトできない場合は、ソースをダウンロードして、以下のコマンドでインストールする。

python setup.py install


最後にtrac.iniを変更する。
Mercurialリポジトリを「C:\TracLight\projects\hg」に作ることにして、「C:\TracLight\projects\hg\SampleProject」にリポジトリを作成。

--- a/projects/trac/SampleProject/conf/trac.ini	Fri Nov 20 15:05:18 2009 +0900
+++ b/projects/trac/SampleProject/conf/trac.ini	Fri Nov 20 16:59:36 2009 +0900
@@ -29,7 +29,8 @@
 resolve.permissions = TICKET_MODIFY
 
 [trac]
-repository_dir = C:\TracLight\projects/svn/SampleProject
+repository_dir = C:\TracLight\projects/hg/SampleProject
+repository_type = hg
 
 authz_module_name = SampleProject 
 [mainnav]
@@ -53,6 +54,7 @@
 timingandestimationplugin.ticket_daemon.timetrackingticketobserver = disabled
 timingandestimationplugin.ticket_webui.ticketwebuiaddon = disabled
 timingandestimationplugin.webui.timingestimationandbillingpage = disabled
+tracext.hg.* = enabled
 
 [ticket-custom]
 due_assign = text
@@ -73,8 +75,11 @@
 private_wikis = SECRET
 
 #[searchhyperestraier]
 #index_path = C:\TracLight\casket
 #replace_left = C:\TracLight\rep
 #url_left = /
 
+[hg]
+show_rev = yes
+node_format = short

チェンジセット1がない、とエラーにはなるが、コミットすれば問題なく使えるようになる。

チケットの自動クローズ

参考URL

これが一番苦労した。なぜかバッチファイルはどんなものを指定しても実行してくれない。権限の関係?

悩んでても解決しないので、1個目のURLを参考に別の方法でアプローチする。


まずMercurialリポジトリをhttpで公開する。
2個目と3個目のURLを参考に、C:/TracLight/CollabNetSVN/httpd/cgi-bin/hgweb.wsgiを作成して、

import os
os.environ['HGENCODING'] = 'UTF-8'

import mercurial.hg as hg
from mercurial.ui import ui
from mercurial.hgweb.hgweb_mod import hgweb
from mercurial.hgweb.hgwebdir_mod import hgwebdir
from mercurial.hgweb.request import wsgiapplication

def listdir_dironly(base_dir):
    results = []    # prepare for failure
    for root, dirs, files in os.walk(base_dir):
        results = map(lambda d: os.path.join(root, d), dirs)
        dirs[:] = []
    results.sort()
    return results

def get_repo_for_path(path):
    return hg.repository(ui(interactive=False,
                            report_untrusted=False),
                         path=path)

def isrepo(path):
    if False:   # too redundant
        try:
            get_repo_for_path(path)
            return True
        except hg.RepoError:
            return False
    else:
        return os.path.isdir(os.path.join(path, '.hg'))

def make_hgweb_maker(path):
    return lambda: hgweb(path, os.path.split(path)[1])
    #return lambda: hgweb(get_repo_for_path(path))

def make_hgwebdir_maker(path):
    dirs = listdir_dironly(path)
    repos = [(os.path.split(dir)[1], dir) for dir in dirs]
    return lambda: hgwebdir(repos)

def hgweb_wsgiapp(path):
    if isrepo(path):
        return wsgiapplication(make_hgweb_maker(path))
    else:
        return wsgiapplication(make_hgwebdir_maker(path))

# for WSGI
def application(environ, start_response):
    def filter_headers(status, response_headers):
        # stringify header content
        headers = [(key, str(value)) for key, value in response_headers]
        return start_response(status, headers)
    def error_dialog(message):
        headers = [('Content-Type',   'text/plain'),
                   ('Content-Length', str(len(message)))]
        start_response('500 Internal Server Error', headers)
        return [message]

    if 'hgweb.reposdir' in environ:
        reposdir = environ['hgweb.reposdir']
    else:
        return error_dialog("You must specify 'hgweb.reposdir' environment")

    wsgiapp = hgweb_wsgiapp(reposdir)
    return wsgiapp(environ, filter_headers)

# for CGI
if os.environ.get('GATEWAY_INTERFACE', '').startswith('CGI/'):
    import cgitb
    cgitb.enable()

    import mercurial.hgweb.wsgicgi as wsgicgi
    wsgicgi.launch(hgweb_wsgiapp('/path/to/repos'))

さらにhttpd.confを修正する。この時点でHTTP経由でリポジトリが参照できるはず。

--- a/CollabNetSVN/httpd/conf/httpd.conf	Fri Nov 20 16:59:36 2009 +0900
+++ b/CollabNetSVN/httpd/conf/httpd.conf	Fri Nov 20 17:30:12 2009 +0900
@@ -588,3 +588,19 @@
   ProxyPass        http://127.0.0.1:8010/hudson
   ProxyPassReverse http://127.0.0.1:8010/hudson
 </Location>
+
+WSGIScriptAlias /hg "C:/TracLight/CollabNetSVN/httpd/cgi-bin/hgweb.wsgi"
+
+<Location "/hg">
+  WSGIApplicationGroup %{GLOBAL}
+  SetEnv hgweb.reposdir "C:/TracLight/projects/hg/"
+  Order deny,allow
+  Allow from all
+
+  AuthType Digest
+  AuthName trac
+  AuthUserFile "C:\TracLight\projects\trac.htdigest"
+  <LimitExcept GET PROPFIND OPTIONS REPORT>
+    Require valid-user
+  </LimitExcept>
+</Location>

次に4個目のURLを参考に、C:\TracLight\python\Lib\site-packages\hg_post_commit.pyを作成する。これは複数リポジトリがあってもいいように、少し修正してある。

# -*- encoding: utf-8 -*-

hook_script = 'C:/TracLight/python-lib/trac/contrib/trac-post-commit-hook'

# style:: 'short', 'long', or 'number'
changeset_id_style = 'short'

import os

def invoke_trac_hook(trac_env, rev):
    def _make_smart_rev(trac_env, rev):
        if changeset_id_style == 'long':
            return rev
        else:
            import trac.env
            env = trac.env.open_environment(trac_env)
            # instance of mercurial.hg.repository
            repo = env.get_repository().repo
            ctx = repo.changectx(rev)
            if changeset_id_style == 'short':
                import mercurial.node
                return mercurial.node.short(ctx.node())
            else:   # 'number'
                return str(ctx.rev())

    smart_rev = _make_smart_rev(trac_env, rev)

    f = open(hook_script, 'r')
    try:
        import imp
        ext = os.path.splitext(hook_script)[1]
        m = imp.load_module('trac_hook', f, f.name, (ext, f.mode, imp.PY_SOURCE))
        m.CommitHook(project=trac_env, rev=smart_rev)
    finally:
        f.close()

def hook(ui, repo, **kwargs):
    hg_dir, _ = os.path.split(repo.path)
    hg_root_dir, project_name = os.path.split(hg_dir)
    trac_dir = os.path.join(os.path.join(hg_root_dir, os.path.pardir, 'trac', project_name))

    if 'PYTHON_EGG_CACHE' not in os.environ:
        os.environ['PYTHON_EGG_CACHE'] = os.path.join(trac_dir, '.egg-cache')

    invoke_trac_hook(trac_dir, kwargs.get('node'))

最後に「C:\TracLight\projects\hg\SampleProject\.hg\hgrc」を作成して、上記のスクリプトを指定する。

[hooks]
incoming.trac = python:hg_post_commit.hook

[web]
push_ssl = false
allow_push = *
deny_push = unauthenticated_user

あとはSVNのときと同じようにコミットしてpushすれば、チケットが自動クローズされる。