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)

2014年2月3日月曜日

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

Traceを使用してログを出力するときの設定例(TraceListener)でTraceListenerの設定方法を書きました。
しかし、あれだけでは最低限のメッセージしか表示されません。
この記事では、日時などを表示する方法について書きます。

メッセージ以外を表示するには、<listeners>の<add>のtraceOutputOptions属性を使用します。



<configuration>

  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <clear />

        <!-- テキストファイル -->
        <add name="file"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="TEXT-WRITER.log"
             traceOutputOptions="Callstack, DateTime, LogicalOperationStack, ProcessId, ThreadId, Timestamp" />

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

</configuration>

この属性を設定した時の出力例は、以下のようになります。

TraceTest.exe Information: 0 : TraceInformation
    ProcessId=5700
    LogicalOperationStack=
    ThreadId=1
    DateTime=2014-02-02T12:47:28.3398027Z
    Timestamp=6032477769
    Callstack=   場所 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   場所 System.Environment.get_StackTrace()
   場所 System.Diagnostics.TraceEventCache.get_Callstack()
   場所 System.Diagnostics.TraceListener.WriteFooter(TraceEventCache eventCache)
   場所 System.Diagnostics.TraceListener.TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, Int32 id, String message)
   場所 System.Diagnostics.TraceInternal.TraceEvent(TraceEventType eventType, Int32 id, String format, Object[] args)
   場所 TraceTest.TraceTest.Main(String[] args)

属性の詳しい説明については、TraceOutputOptions Valuesを参照してください。

2014年1月27日月曜日

Lispでxmlの実体参照変換

当ブログでは、プログラムをハイライト表示するときにSyntaxHighlighterを使用しています。
XMLを表示するときは、実体参照へ変換しなければいけません。
「普段使用しているエディタから変換できれば楽ちんだよね」と思い、xyzzy Lispで変換プログラムを書いてみました。

プログラム
; convert-xml
(defparameter *conversion-alist*
  '((#\< . "&lt;")
    (#\> . "&gt;")
    (#\& . "&amp;")
    (#\" . "&quot;")
    (#\' . "&apos;")))

(defun convert-xml ()
  (interactive "*")
  (let ((input-buffer (window-buffer (selected-window))))
    (with-output-to-temp-buffer ("*Converted*")
      (with-open-stream (s (make-buffer-stream input-buffer))
        (loop 
          (let ((c (read-char s nil)))
            (if c (format t "~A" (convert-char c *conversion-alist*))
              (return nil))))))))

(defun convert-char (c alist)
  (let ((conversion (assoc c alist)))
    (if conversion (cdr conversion)
      c)))

実行すると現在のバッファを読み込み、一時バッファへ変換後のxmlを表示します。
工夫したところは、変換の定義をalistにしたところです。

参考

2014年1月20日月曜日

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

.Netでログを出力するときの選択肢の1つとして、System.Diagnostics.Traceクラスを使用する方法があります。
この時のアプリケーション構成ファイルの記述例を記載します。
テストプログラムとして、次のプログラムを使用します。

namespace TraceTest 
{
  using System.Diagnostics;
  
  public class TraceTest {
    
    public static void Main(string[] args) {
      Trace.TraceInformation("TraceInformation");
      Trace.TraceWarning("TraceWarning");
      Trace.TraceError("TraceError");
    }
  }
}

次にTraceListenerの設定例を記載します。

<configuration>

  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <clear />

        <!-- 標準出力 -->
        <add name="console"
             type="System.Diagnostics.ConsoleTraceListener" />

        <!-- 区切りリスト -->
        <add name="delimited-list"
             type="System.Diagnostics.DelimitedListTraceListener"
             initializeData="DELIMITED-LIST.log" />

        <!-- イベントログ -->
        <add name="eventlog"
             type="System.Diagnostics.EventLogTraceListener"
             initializeData="Application" />

        <!-- テキストファイル -->
        <add name="file"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="TEXT-WRITER.log" />

        <!-- XML形式 -->
        <add name="xml-writer"
             type="System.Diagnostics.XmlWriterTraceListener"
             initializeData="XML-WRITER.log" />

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

</configuration>
参考
<trace> の <listeners> の <add> 要素

2014年1月13日月曜日

FormでShowing/Hidingイベントを実装

System.Windows.Forms.Formにありそうでないイベントの一つに、表示前/非表示前イベントがあります。
なければ作っちゃえということで、SetVisibleCoreをオーバーライドして実装してみました。
処理の流れは以下のようになります。

  1. 引数と現在のVisibleを比較し、等しければイベントを発生させずにSetVisibleCoreを呼んで終了する。
  2. 引数がtrueのとき、Showingイベントを発生させる
  3. 引数がfalseのとき、Hidingイベントを発生させる
  4. 2,3の結果、CancelEventArgs.Cancelがtrueのとき、引数を逆にしてSetVisibleCoreを呼び、終了する。
  5. SetVisibleCoreを呼び、終了する。

なお、注意点として、SetVisibleCoreでShow/Hideメソッドを呼ばないこと、Visibleプロパティを更新しないことがあります。これらに違反すると、StackOverflowExceptionが発生してしまいます。

サンプル


参考

Form.SetVisibleCore メソッド