2014年3月21日金曜日

Emacsのブックマーク機能

Google+で流れてきたEmacsのチュートリアルを日本語に訳してみました。

Emacsのブックマーク機能を使う

このページはEmacsに搭載されたブックマーク機能のチュートリアルです。
Emacsのブックマーク機能は、ブラウザのそれによく似ています。
Emacsのブックマークは、しばしば必要になるファイルを簡単に開けるようにします。

ブックマークを使う
ファイルをブックマークへ追加する
追加したいファイルを開いてください。
続いて、bookmark-setを呼ぶ[C-x r m]と、名前を入力するプロンプトが表示されます。

ブックマークファイルを開く
ブックマークを開くには、bookmark-bmenu-listを呼びます[C-x r l]。
ブックマーク一覧が表示されます。
この一覧でEnterを入力するかクリックすれば、ファイルを開きます。
また、oを入力すれば分割されたウィンドウでファイルを開きます。

ブックマークを保存する
ブックマークを保存するには、bookmark-saveを呼びます(ブックマーク一覧でsを入力する)。
もし保存しなければ、新たに追加されたブックマークは現在のセッションでのみ有効です。
つまり、Emacsを再起動すると表示されなくなってしまいます。

ブックマークを削除または名前を変更する
ブックマーク一覧を開いた状態で、Dでマークすればブックマークを削除します。
また、rを入力すればブックマークの名前を変更します。

ブックマークのコマンドとショートカットを忘れないようにするには
ブックマークコマンドは、メニューのEdit->Bookmarksにあります。
コマンドやショートカットを忘れてしまったときは、メニューを見てください。
メニューはEmacsのコマンドを覚えるための偉大なる道です。

また、ブックマークコマンドはすべてbookmark-で始まります。
本当に覚えなければいけないのは、bookmark-bmenu-listだけです。
いったんブックマーク一覧を開いてしまえば、describe-mode[F1 m]を呼ぶことでコマンドとショートカットの一覧を見ることができます。

起動時にブックマークを表示する
起動時にブックマークを表示するには、次のelispを初期化ファイルへ追加してください。
(setq inhibit-splash-screen t)
(require 'bookmark)
(bookmark-bmenu-list)
(switch-to-buffer "*Bookmark List*")

ブックマークファイルの場所
emacs 24.xでは、ブックマークファイルは~/.emacs.d/bookmarksにあります。emacs 23.xでは、~/.emacs.bmkにあります。
デフォルトの場所は、変数bookmark-default-fileによって制御されます。describe-variable[F1 v]を呼び、見てみてください。
デフォルトの場所は、次のように設定できます。
(setq bookmark-default-file  (concat user-emacs-directory "bookmarks"))
ブックマークファイルの読み込みは、次のようにします。
(bookmark-load bookmark-default-file t)

ファイルを早く開くほかの方法
最近開いたファイルを開くのも便利な機能です。参考:Emacs: Open File Fast: recentf-mode
ブックマークの1つの問題は、ファイルを直接開くキーがないことです。しかし、Emacs Lisp: Hotkeys to Open File Fastで実現できます。

参考
Emacs: Using Bookmark Feature
Emacsのブックマーク機能

2014年3月17日月曜日

PythonでEvernoteへメールを送る

Evernoteへメールを送ることでノートを追加できるのはご存知かと思います。
最近、古いネットブックをUSBブートのLinuxで使用しています。ネットブック上で作成したファイルをEvernoteへアップロードしたいと思うことがあるのですが、ブラウザから操作するのは厳しいと感じています。
Pythonからメールでファイルを送るようにすればいいのではないかと思い、標準入力から読み込んだ内容をEvernoteへ送るプログラムを書きました。

プログラム
# -*- coding: utf-8 -*-
# ever_mail.py

# ---- CONFIGURATIONS ----
EVERNOTE_MAIL_ADDRESS = 'evernote_mail_address'
FROM_ADDRESS = 'from_address'
SMTP_HOST = 'smtp._host'
SMTP_PORT = 25
DEBUG = True
# ---- CONFIGURATIONS ----

import datetime
import email.header
import optparse
import smtplib
import sys

from email.mime.text import MIMEText

class EverMailClient(object):
    def __init__(self, smtp):
        assert isinstance(smtp, smtplib.SMTP)
        self._smtp = smtp

    def send(self, msg):
        assert isinstance(msg, MIMEText)
        mail_from = msg['From']
        mail_to = msg['To']
        self._smtp.sendmail(mail_from, mail_to, msg.as_string())


class EverMailTitle(object):
    def __init__(self, title, notebook, tags):
        self._title = title if title else str(datetime.datetime.now)
        self._notebook = notebook if notebook else ''
        self._tags = tags if tags else []

    def __str__(self):
        result = self._title
        if 0 < len(self._notebook):
            result += ' @' + self._notebook
        for tag in self._tags:
            result += ' #' + tag
        return result


class EverMailOptionParser(optparse.OptionParser):
    def __init__(self):
        optparse.OptionParser.__init__(self)
        self.add_option('--notebook', '-n', action='store')
        self.add_option('--tag', '-t', action='append')


def create_smtp_client():
    smtp = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
    if DEBUG:
        smtp.set_debuglevel(1)
    return smtp

def create_message_from_stdin(title):
    assert isinstance(title, EverMailTitle)
    body = sys.stdin.read()
    msg = MIMEText(bytes(body))
    msg['Subject'] = email.header.Header(str(title), 'utf-8')
    msg['From'] = FROM_ADDRESS
    msg['To'] = EVERNOTE_MAIL_ADDRESS
    msg.set_charset('utf-8')
    return msg


if __name__ == '__main__':
    option_parser = EverMailOptionParser()
    (options, args) = option_parser.parse_args()
    note_title = args[0] if args and 0 < len(args) else ''
    title = EverMailTitle(note_title,
                          options.notebook,
                          options.tag)
    msg = create_message_from_stdin(title)
    smtp = None
    try:
        smtp = create_smtp_client()
        client = EverMailClient(smtp)
        client.send(msg)
    finally:
        if smtp: smtp.close()


使用例
$ cat your_important_file | python ever_mail.py TITLE -nNOTEBOOK -tTAG -tTAG

工夫したところは、optparseモジュールを使ってオプションの解析を実装したところです。
暇があれば、画像の添付なども対応したいと思います。

参考
15.5. optparse — コマンドラインオプション解析器
Eメールを送信するだけでEvernoteに簡単送信!

2014年3月10日月曜日

DIに1年間はまってみて気づいたこと

2013年は、個人的にDIを推進しました。
1年間活動してみた結果、気づいたことをまとめておきます。

DIコンテナは使いませんでした。自作のファクトリクラスでインスタンス化するようにしました。理由は2つあり、1つはDIコンテナを組み込む権限がプロジェクト内で与えられていなかったから、もう1つは自分の担当範囲で使用するだけなので、シンプルな仕組みで十分だったからです。

気づいたこと
クラスを設計するときの考え方が変わりました。新しいクラスを追加するとき、「そのクラスが機能を実現するにはどんなクラスが必要か」ということを考えるようになりました。こうすることで、クラスが果たすべき責務が明確になり、クラス設計が改善されました。

反面、注意すべきことも見えてきました。「依存する側」と「依存される側」という観点でクラスをどんどん分割していきますが、やり過ぎると何もしないクラスができてしまいます。望ましい設計について、「2回測って1回で切る」とどこかで読んだことがあります。自分なりに解釈すると、1回目は機能をどんどん分割していく設計、2回目は無駄なクラスを統合していく設計とするとうまくいくような気がしました。

自作のファクトリにも問題点が見えてきました。小さいアプリケーション(2画面/5人日)だったのでファクトリクラスを1つしか用意しなかったのですが、巨大なクラスになりかけていました。ファクトリを分割し、ファクトリ同士のDIも必要になると思いました。ただ、そこまで複雑になるようであれば、DIコンテナの採用を検討するべきだと思います。

2014年3月3日月曜日

Pythonで単体テストをするには

pythonでunittestをするには」にて、
しかし、複数のテストファイルを作成した後でまとめてテストを実行する手段がないようなので、以下のようなコードを書いてみました。
という記載がありますが、Python 2.6にてまとめてテストを実行する手段が追加されましたので、勝手にフォローさせていただきます。動作確認にはPython 3.3を使用しました。

テストプログラム
テストプログラムのディレクトリ構成とプログラムは以下のとおりです。
test
├test1.py
├test2.py
└testsub
 ├__init__.py
 └testsub1.py

test/test1.py
# -*- coding: utf-8 -*-

import unittest

class TestCase1(unittest.TestCase):
    def test_success(self):
        pass

    def test_fail(self):
        self.fail()

class TestCase2(unittest.TestCase):
    def test_success(self):
        pass

    def test_fail(self):
        self.fail()

suite = unittest.TestSuite()
suite.addTest(TestCase1('test_success'))
suite.addTest(TestCase2('test_success'))


test/test2.py
# -*- coding: utf-8 -*-

import unittest

class TestCase1(unittest.TestCase):
    def test_success(self):
        pass

    def test_fail(self):
        self.fail()



test/testsub/__init__.py
空のファイルです。testsubをモジュールとして認識させるために必要です。

test/testsub/testsub1.py
# -*- coding: utf-8 -*-

import unittest

class TestCase1(unittest.TestCase):
    def test_success(self):
        pass

    def test_fail(self):
        self.fail()



フォルダ配下のテストをすべて実行する
pythonの-mオプションのパラメータとしてunittestを渡します。詳細な出力を得るために、-vオプションも追加します。
~/py/test $ python3 -m unittest -v
test_fail (test1.TestCase1) ... FAIL
test_success (test1.TestCase1) ... ok
test_fail (test1.TestCase2) ... FAIL
test_success (test1.TestCase2) ... ok
test_fail (test2.TestCase1) ... FAIL
test_success (test2.TestCase1) ... ok
test_fail (testsub.testsub1.SubTestCase1) ... FAIL
test_success (testsub.testsub1.SubTestCase1) ... ok

======================================================================
FAIL: test_fail (test1.TestCase1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/cygdrive/c/home/myamamo/py/test/test1.py", line 10, in test_fail
    self.fail()
AssertionError: None

======================================================================
FAIL: test_fail (test1.TestCase2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/cygdrive/c/home/myamamo/py/test/test1.py", line 17, in test_fail
    self.fail()
AssertionError: None

======================================================================
FAIL: test_fail (test2.TestCase1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/cygdrive/c/home/myamamo/py/test/test2.py", line 10, in test_fail
    self.fail()
AssertionError: None

======================================================================
FAIL: test_fail (testsub.testsub1.SubTestCase1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/cygdrive/c/home/myamamo/py/test/testsub/testsub1.py", line 10, in test_fail
    self.fail()
AssertionError: None

----------------------------------------------------------------------
Ran 8 tests in 0.004s

FAILED (failures=4)


モジュールを指定してテストする
上のオプションに加え、モジュール名を追加するとモジュールのテストになります。
~/py/test $ python3 -m unittest -v test1
test_fail (test1.TestCase1) ... FAIL
test_success (test1.TestCase1) ... ok
test_fail (test1.TestCase2) ... FAIL
test_success (test1.TestCase2) ... ok

======================================================================
FAIL: test_fail (test1.TestCase1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test1.py", line 10, in test_fail
    self.fail()
AssertionError: None

======================================================================
FAIL: test_fail (test1.TestCase2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test1.py", line 17, in test_fail
    self.fail()
AssertionError: None

----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=2)


クラスを指定してテストする
さらにクラス名を追加します。
~/py/test $ python3 -m unittest -v test1.TestCase1
test_fail (test1.TestCase1) ... FAIL
test_success (test1.TestCase1) ... ok

======================================================================
FAIL: test_fail (test1.TestCase1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test1.py", line 10, in test_fail
    self.fail()
AssertionError: None

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)


テストメソッドを指定してテストする
さらにメソッド名を追加します。
~/py/test $ python3 -m unittest -v test1.TestCase1.test_fail
test_fail (test1.TestCase1) ... FAIL

======================================================================
FAIL: test_fail (test1.TestCase1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test1.py", line 10, in test_fail
    self.fail()
AssertionError: None

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)


テストスイートを指定してテストする
テストスイートを指定することもできます。要領はクラスやメソッドを指定する時と同様です。
~/py/test $ python3 -m unittest -v test1.suite
test_success (test1.TestCase1) ... ok
test_success (test1.TestCase2) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK



一括して実行できるだけでなく、任意のテストメソッドを選択することもできるので、テンポよく開発が進められると思いました。

2014年2月24日月曜日

lispでiniファイルからalistをconsする

lispでiniファイルからalistをconsするプログラムを書きました。

サンプルのiniファイルはこのようになっています。
[Section1]
Key1=Value1
Key2=Value2

[Section2]
Key1=Value1

[Section3]


プログラム本体は以下のとおりです。
;; inifile.l
(defun read-lines (input)
  (loop as line = (read-line input nil nil)
        while line
        collect line))

(defun sectionp (line)
  (and (stringp line)
       (<= 2 (length line))
       (eq #\[ (char line 0))
       (eq #\] (char line (1- (length line))))))

(defun keyvaluep (line)
  (and (stringp line)
       (<= 3 (length line))
       (find #\= line)
       (< 0 (position #\= line))))

(defun strip-section (line)
  (if (not (sectionp line))
      line
    (subseq line 1 (1- (length line)))))

(defun split-keyvalue (line)
  (if (not (keyvaluep line))
      line
    (list (subseq line 0 (position #\= line))
          (subseq line (1+ (position #\= line))))))

(defun convert-to-alist (alist line)
  (cond ((sectionp line)
         (cons (list (strip-section line)) alist))
        ((keyvaluep line)
         (cons (append (car alist) (list (split-keyvalue line))) (cdr alist)))
        (t alist)))

(defun ini-assoc (section key alist)
  (car (last
        (assoc key
               (cdr (assoc section alist :test 'string-equal))
               :test 'string-equal))))


;; 以下実行部
(setf ini-alist (reduce 'convert-to-alist (read-lines *standard-input*)
                        :initial-value '()))
(format t "~A~%" ini-alist)
(format t "~A ~A = ~A~%" "Section2" "Key1"
        (ini-assoc "Section2" "Key1" ini-alist))
(format t "~A ~A = ~A~%" "SECTION1" "KEY2"
        (ini-assoc "SECTION1" "KEY2" ini-alist))


実行すると、以下のように出力します。
$ cat sample.ini | clisp inifile.l
((Section3) (Section2 (Key1 Value1)) (Section1 (Key1 Value1) (Key2 Value2)))
Section2 Key1 = Value1
SECTION1 KEY2 = Value2


工夫したところは、関数"ini-assoc"でstring-equalを使用し、大文字と小文字を区別し内容にしたところです。
alistで返すことが目標だったので、パフォーマンスはよくありません。
今までの書き方であれば、DictionaryやMapを更新しまくるプログラムを書いてました。reduceに渡す関数を実装するにあたり、「新しいリストを返す」という発想に辿り着くまで時間がかかりました。

2014年2月17日月曜日

IronPythonで.NETモジュールをテストする

MSDN Magazineの記事を読んでいたら、タイトルのような記事を見つけました。
どれどれ、と読んでみるとせっかくIronPythonを使用しているのに、unittestモジュールを使用していないではありませんか。
もしかしたら当時のIronPythonには、まだunittestモジュールが含まれていなかったのかもしれません。
unittestモジュールを使用すれば、もっと簡単にテストが書けると思われるので、試してみます。
unittestモジュールの詳しい説明は割愛します。公式のドキュメントなどを参照してください。

テストコード
unittestモジュールを使用して書きなおしたテストが以下になります。
元の記事と同様、3番目のテストケースはスキップするようにしました。
# -*- coding: shift-jis -*-

import clr
import unittest

clr.AddReferenceToFileAndPath('Class1.dll')
from TwoCardPokerLib import Hand

class HandTest(unittest.TestCase):
    def test_royal_flush_wins_pair_aces(self):
        h1 = Hand('Ac', 'Kc')
        h2 = Hand('Ad', 'As')
        self.assertEquals(1, h1.Compare(h2))

    def test_straight_flush_diamonds_eq_straight_flush_hearts(self):
        h1 = Hand('Td', '9d')
        h2 = Hand('Th', '9h')
        self.assertEquals(0, h1.Compare(h2))

    @unittest.skip('')
    def test_straight_loses_ace_high(self):
        h1 = Hand('Ah', '2c')
        h2 = Hand('As', '9c')
        self.assertEquals(1, h1.Compare(h2))

    def test_flush_9_6_high_loses_flush_9_7(self):
        h1 = Hand('9h', '6h')
        h2 = Hand('9d', '7d')
        self.assertEquals(-1, h1.Compare(h2))

    def test_king_high_loses_ace_high(self):
        h1 = Hand('Kc', 'Jh')
        h2 = Hand('As', '5d')
        self.assertEquals(-1, h1.Compare(h2))


テスト結果
テストの実行結果は以下のようになります。
C:\>ipy.exe -m unittest -v testhand
test_flush_9_6_high_loses_flush_9_7 (testhand.HandTest) ... FAIL
test_king_high_loses_ace_high (testhand.HandTest) ... ok
test_royal_flush_wins_pair_aces (testhand.HandTest) ... ok
test_straight_flush_diamonds_eq_straight_flush_hearts (testhand.HandTest) ... ok
test_straight_loses_ace_high (testhand.HandTest) ... skipped ''

======================================================================
FAIL: test_flush_9_6_high_loses_flush_9_7 (testhand.HandTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:\testhand.py", line 29, in test_flush_9_6_high_loses_flush_9_7
    self.assertEquals(-1, h1.Compare(h2))
AssertionError: -1 != 0

----------------------------------------------------------------------
Ran 5 tests in 0.164s

FAILED (failures=1, skipped=1)

元記事との比較
元記事と比較して、テストランナーを実装していない分、工数を減らす効果はあったと思います。
反面、テストケースの追加については、元記事ではテキストファイルに1行追加するだけなのに対して、unittestを使用した場合はメソッドの定義が必要になっています。サードパーティ製のユニットテストフレームワークであるpytestはパラメータ化されたテストにも対応しているようなので、検討する価値があると思いました。

参考

2014年2月10日月曜日

Traceを使用してログを出力するときの設定例(Filter)

ログに要求される機能の1つとして、レベルによるフィルタリングがあります。
Traceを使用したログ出力にも、同様の機能があります。
具体的には、<filter>タグを使用します。

設定例
<configuration>
  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <clear />

        <add name="file"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="TEXT-WRITER.log">
            <!-- Warning以上を出力 -->
            <filter type="System.Diagnostics.EventTypeFilter"
                    initializeData="Warning" />
        </add>

      </listeners>
    </trace>
  </system.diagnostics>

</configuration>

出力例
TraceTest Warning: 0 : TraceWarning
TraceTest Error: 0 : TraceError

Informationレベルのログが出力されなくなりました。

参考
<filter> Element for <add> for <listeners> for <trace>

関連記事
Traceを使用してログを出力するときの設定例(TraceListener)
Traceを使用してログを出力するときの設定例(TraceOutputOptions)