Jenkinsプラグイン探訪 - List View column plugins

はじめに

Jenkins Advent Calender 2011 16日目担当の id:tyuki39(@tyuki39) です。
小ネタを物量でカバーする形になって恐縮ですが、よろしくお願いします。
なお、前の日は id:ssogabe さんの「Karotzさんといっしょ」でした。

ビューにカラムを追加しよう!

皆さん、Jenkinsのビューを活用しているでしょうか?
デフォルトの【すべて】ビューだけを使っている、なんてことはないですよね?

Jenkinsのトップページにアクセスしたとき、または、マイビューにアクセスしたときに、
・ 自分自身が関わっているジョブの状態がさっと一覧され、
・ 設定画面がさっと開けたり、
・ 失敗の原因を探るべく失敗ビルドのコンソールがさっと開けたり
すると便利です。

Jenkinsのプラグインの中には、そんなニーズに応えてくれて、ビューをもっと使いやすくしてくれるリストビューカラムプラグインが存在します。

今回はそんなリストビューカラムプラグイン達と関連プラグイン達を一気に紹介したいと思います。

リストビューカラムを紹介する前に

ビューのカスタマイズについて

Jenkins実践入門のP281「11.4 ビューをカスタマイズする」を参照すると良いでしょう。

デフォルトビューの変更について

Jenkinsのデフォルトビューは、【すべて】ですが、次の方法でデフォルトビューを変更することができます。

  • 1つ以上のビューを新規に作成します。
  • [Jenkinsの管理] > [システムの設定] から [デフォルトビュー] を変更して、設定を保存します。

【すべて】ビューは、カラムを増減できませんが、追加したビューは、カラムを増減できます。デフォルトビューを追加したビューに変更することで、以降で紹介するリストビューカラムプラグイン達を効果的に配置できます。

カラムの並び替えについて

ビューの変更画面では、 id:suzukima さんの「ビルド処理の順序はドラッグ&ドロップで変更できます」と同じ要領で、カラムの並び順を変更できます。


プラグインの紹介

Built-on Columnプラグイン

ビュー上に、最新ビルドを実行したノードを表示することができます。

環境の整備不足が原因で、ビルドが失敗することもあります。ビルドを実行したノードが一目で分かると、すぐに調査に取りかかれます。

Compact Columnsプラグイン

ビューにカラムを追加していくと、ビューの横幅が長くなりがちです。
このプラグインを使用すると、「ジョブに関する情報」や「最新ビルドや最新に関する情報」をツールチップで確認できるようにして、カラムの表示内容を簡素化することができます。



Console Columnプラグイン

ビュー上に、次のショートカットボタンを追加することができます。

  • 最新ビルドのコンソール
  • 最新の成功ビルドのコンソール
  • 最新の失敗ビルドのコンソール
  • 最新の安定ビルドのコンソール
  • 最新の不安定ビルドのコンソール
  • 最新の不成功ビルドのコンソール

ビュー上に、すべてのボタンを配置する必要はありません。「最新ビルドのコンソール」と「最新の失敗ビルドのコンソール」の2つを配置するだけでも十分便利になるのではないかと思います。

Cron Columnプラグイン

ビュー上に、ジョブに設定されたビルドトリガを表示することができます。

定期的に実行するジョブが多い場合に役立つと思います。ただし、ビルドトリガの設定値よりも、次の実行時刻の方が気になるのではないかと思います。
その場合は、後述のNext Executionsを使用して下さい。

Description Columnプラグイン

ビュー上に、ジョブの説明を表示することができます。
このプラグインの機能は、Extra Columnsに完全に取り込まれているので、説明を割愛します。
Extra Columnsによって追加される他の機能が不要な場合のみ採用して下さい。

Extra Columnsプラグイン

ビュー上に、次のショートカットボタンと情報の表示機能を追加することができます。

  • ジョブの設定
  • ジョブの有効化/無効化
  • ジョブの説明
  • テスト結果

運用し始めのジョブは、設定を見直す機会が多いので、ビュー上に「ジョブの設定」ボタンが置かれていると便利です。

また、ジョブの概要は、ジョブ名からではなく、ジョブの説明から判断した方が分かりやすいので、ビュー上に「ジョブの説明」を表示しておくとメンテナンスしやすくなると思います。

「ジョブの説明」では、<br/>までを1行とカウントして、指定した行数分の説明のみを表示することもできます。この設定値に基づいて「ジョブの説明」の記述ルールを決めておけば、ビューが煩雑になることを避けられます。

Job Type Columnプラグイン

ビュー上に、ジョブタイプを表示することができます。

ここで、ジョブタイプとは、Freestyle(フリースタイル・プロジェクト), Maven(Maven2/3プロジェクト), Matrix(マルチ構成プロジェクト), External(外部ジョブの監視)のことを指します。
様々なタイプのジョブを作成している場合に、役立つかと思います。

Next Executionsプラグイン

ビュー上に、ビルドの実行予定時刻を表示することができます。
また、サイドバーにも実行予定のビルド情報を表示することができます。

15分に一回実行と設定したつもりが、毎時15分に実行という設定になっているかも知れません。実行予定時刻を表示しておくことで、このような誤りに気づきやすくなります。

Last Success Version Columnプラグイン

ビュー上に、最新の成功ビルドのバージョンを表示することができます。

標準機能の「最新の成功ビルド」を、YYYY/MM/DD hh:mm:ss(ビルド番号)形式で表示するだけで、機能としては重複しているので、このプラグインを使用する必要はないと思います。
また、このプラグインは後述するビルド名に対応していないので注意して下さい。

Last Failure Version Columnプラグイン

ビュー上に、最新の失敗ビルドのバージョンを表示することができます。

標準機能の「最新の失敗ビルド」を、YYYY/MM/DD hh:mm:ss(ビルド番号)形式で表示するだけで、機能としては重複しているので、このプラグインを使用する必要はないと思います。
また、このプラグインは後述するビルド名に対応していないので注意して下さい。

Last Success Description Columnプラグイン

ビュー上に、最新の成功ビルドの説明を表示することができます。

成功ビルドのログの中に、成功ビルドの特徴(例えば、モジュール名やモジュールのバージョンなど)を表す文字列が現れる場合に役立ちます。
このような場合、後述のDescription Setterを使用してログから文字列を抽出し、その文字列を利用してビルドの説明を自動設定します。
これで、「最新の成功ビルドの説明」に成功ビルドの特徴が表示されるようになります。

Progress Bar Columnプラグイン

ビュー上に、ビルドの進捗バーを表示することができます。

役立つ場面が思いつきませんが、Jenkinsと各ノードが頑張ってくれているなという雰囲気を、より強く味わうことができそうです。

Build Name SetterプラグインDescription Setterプラグイン

下図は、あるビルドに「ビルド名」と「ビルドの説明」を設定した状態を表しています。


Build Name Setterを使うと、ビルド名を#n以外の形式に自動設定できるようになります。


Description Setterを使うと、ビルドログの中に現れる文字列を使用して、ビルドの説明を自動設定できるようになります。

Project Description Setterプラグイン

下図は、あるジョブに「ジョブの説明」を設定した状態を表しています。


Project Description Setterを使うと、ワークスペース内のファイルを読み込んで、ジョブの説明を自動設定できるようになります。


例えば、ジョブごとにREADMEファイルを作成する決まりにしておくと、次のメリットが得られます。

  • READMEの内容は、ビルドする度に読み込まれるので、ビルドの説明をREADMEに沿った最新の状態に保つことができます。
  • ビルドの説明をJenkinsから分離してファイル化し、構成管理することができます。
  • READMEの内容を、上述のExtra Columnsの設定値を配慮したものにしておけば、ビュー上にジョブの概要を示すことができます。

気に入ったリストビューカラムがないときは...

ListViewColumn (Jenkins core 2.177 API)に書かれている基本要件と、上記のプラグインのコードを参考にしつつ、プラグインを開発してみましょう!

やっつけ実装なので公開できるレベルではありませんが、下図のように、上流ジョブ(ここではPARENT)を起点にして「PARENT => CHILD => GRANDCHILD」の順で実行されたビルドチェーン全体の所要時間を表示することもできます。

おわりに

標準のままだと2手3手かかる「ジョブの設定画面の表示」や「コンソール出力画面の表示」が、「ジョブの設定ボタン(Extra Columns)」や「最新ビルドのコンソール出力ボタン(Console Column)」によって1手で済むので、この手のプラグインは結構重宝しています。

皆さんにとっても、何かお役に立てるプラグインが紹介できたなら幸いです。

明日は id:mallowlabs さんです。

お詫び

今回の記事内で紹介したプラグインの大半は、記事を書く際にローカライズしました。このため、大半のスナップショットがローカライズ後のものになっています。
しかし、ローカライズに使っていたマシンがクラッシュしたため、リソースファイルなどをGitHub/SVNに登録するところまでしか済ませていません。
Update Centerから入手できるプラグインは、ローカライズ前のものですので、その点はご了承下さい。

紹介したプラグインの一覧

紹介したリストビュープラグインは以下の通りです。
プラグイン Wiki URL ダウンロード URL リポジトリURL
Built-on Column https://wiki.jenkins-ci.org/display/JENKINS/Built-on+Column http://updates.jenkins-ci.org/download/plugins/built-on-column/ https://github.com/jenkinsci/builton-column-plugin
Compact Columns https://wiki.jenkins-ci.org/display/JENKINS/Compact+Columns http://updates.jenkins-ci.org/download/plugins/compact-columns/ https://svn.jenkins-ci.org/trunk/hudson/plugins/compact-columns
Console Column https://wiki.jenkins-ci.org/display/JENKINS/Console+Column+Plugin http://updates.jenkins-ci.org/download/plugins/console-column-plugin/ https://github.com/jenkinsci/console-column-plugin
Cron Column https://wiki.jenkins-ci.org/display/JENKINS/Cron+Column+Plugin http://updates.jenkins-ci.org/download/plugins/cron_column/ https://svn.jenkins-ci.org/trunk/hudson/plugins/cron_column
Description Column https://wiki.jenkins-ci.org/display/JENKINS/Description+Column+Plugin http://updates.jenkins-ci.org/download/plugins/description-column-plugin/ https://github.com/jenkinsci/description-column-plugin
Extra Columns https://wiki.jenkins-ci.org/display/JENKINS/Extra+Columns+Plugin http://updates.jenkins-ci.org/download/plugins/extra-columns/ https://github.com/jenkinsci/extra-columns-plugin
Job Type Column https://wiki.jenkins-ci.org/display/JENKINS/Job+Type+Column+Plugin http://updates.jenkins-ci.org/download/plugins/jobtype-column/ https://github.com/jenkinsci/jobtype-column-plugin
Next Executions https://wiki.jenkins-ci.org/display/JENKINS/Next+Executions http://updates.jenkins-ci.org/download/plugins/next-executions/ https://github.com/jenkinsci/next-executions-plugin
Last Success Version Column https://wiki.jenkins-ci.org/display/JENKINS/Last+Success+Version+Column+Plugin http://updates.jenkins-ci.org/download/plugins/lastsuccessversioncolumn/ https://github.com/jenkinsci/lastsuccessversioncolumn-plugin
Last Failure Version Column https://wiki.jenkins-ci.org/display/JENKINS/Last+Failure+Version+Column+Plugin http://updates.jenkins-ci.org/download/plugins/lastfailureversioncolumn/ https://github.com/jenkinsci/lastfailureversioncolumn-plugin
Last Success Description Column https://wiki.jenkins-ci.org/display/JENKINS/Last+Success+Description+Column+Plugin http://updates.jenkins-ci.org/download/plugins/lastsuccessdescriptioncolumn/ https://svn.jenkins-ci.org/trunk/hudson/plugins/lastsuccessdescriptioncolumn
Progress Bar Column https://wiki.jenkins-ci.org/display/JENKINS/Progress+Bar+Column+Plugin http://updates.jenkins-ci.org/download/plugins/progress-bar-column-plugin/ https://github.com/jenkinsci/progress-bar-column-plugin

お役立ち度0%?のコマンドシェル情報

はじめに

昔書いたバッチファイルとシェルスクリプト(sh系)の比較情報を見つけたので、役に立たないなと思いつつネットに晒してみます。

カレントディレクトリの取得

カレントディレクトリの取得方法は次のとおりです。

バッチ シェル
環境変数 CD で取得 環境変数 PWD で取得

空行の出力

ログを見栄え良く出力するために空行を出力する場合は、次の方法を用います。

バッチ シェル
ECHO の直後にドット(.)を記述 echo のみを記述

算術式の評価

コマンド実行回数のカウントアップやカウントダウンなどの簡単な演算を行いたい場合は、次の方法を用います。

バッチ シェル
SETコマンドを /A オプション付きで実行 exprコマンドを実行

日付と時刻の取得

実行ログの名称に日付や時刻を含めることがよくあります。
日付と時刻の取得方法は次のとおりです。

バッチ シェル
dateコマンドのフォーマット指定を使用 環境変数DATEとTIMEを使用

注意(バッチ): 環境変数DATEとTIMEは同時に取得できないので、上記の方法では厳密には23:59:59から00:00:00に切り替わる瞬間でDATEが一日前の日付になってしまうことがあります。

デフォルト値の利用

環境変数にデフォルト値を採用しておくと、上書きしたい値だけを設定しなおせば良くなるので便利です。
デフォルト値を使用する方法は次のとおりです。

バッチ シェル
IF NOT DEFINED を使用 ${parameter=word}構文を使用

標準入力からの入力待ち

標準入力からの入力を待ち、入力されたデータを変数に設定する方法は次のとおりです。

バッチ シェル
SETコマンドを /P オプション付きで実行 readコマンドを実行

標準出力と標準エラー出力の切り分け方法

標準出力と標準エラー出力を切り分ける方法は次のとおりです。

バッチ シェル
標準出力をファイルに出力する場合は、> または 1> を使用
標準エラー出力をファイルに出力する場合は、2> を使用
両方を同じファイルに出力する場合は、2>&1 または 1>&2 を使用
同左

標準出力または標準エラー出力の破棄

標準出力または標準エラー出力を破棄する方法は次のとおりです。

バッチ シェル
nulに出力 /dev/nullに出力

AND条件とOR条件

AND条件とOR条件の記述方法は次のとおりです。
ただし、Windowsの場合はあくまでもAND条件とOR条件を同等の表現方法で扱うときの記述方法です。

バッチ シェル
環境変数を結合した文字列を使って条件式を記述 -a または -o を使って条件式を記述

注意(バッチ): 環境変数 EA に値 VAVA が設定されていた場合でも、EA == VA が成立してしまいます。

AND条件しか書かない場合は、次の方法を採用するべきです。

バッチ シェル
環境変数を結合した文字列を使って条件式を記述 -a または -o を使って条件式を記述

if-else if-...-else構文

if-else if-...-else構文の記述方法は次のとおりです。

バッチ シェル
IF (...) ELSE IF (...) ELSE (...)と記述 if ... then elif ... then else ... fi と記述

終了ステータスの取得方法

実行したコマンドの終了ステータスを取得する方法は次のとおりです。

バッチ シェル
ERRORLEVELを使用 $?を使用

注意(バッチ): 「IF ERRORLEVEL <数値>」形式の構文も存在します。この構文を用いた場合、終了ステータスが「<数値>以上」のときに判定結果が真になるので、特定の値と等しいこと/等しくないことを判定することができません。

終了ステータスのリセット方法

終了ステータスをリセットする方法は次のとおりです。

バッチ シェル
date /t > nulなどを実行 特別な処理は不要

補足(バッチ): UNIXのコマンドインタプリタの場合は、setなどのビルトインコマンドであっても、処理が正常終了したのか、異常終了したのかに関わらず、行儀良く終了ステータスを変更するようになっています。しかし、Windowsのコマンドインタプリタの場合は、setなどのビルトインコマンドが正常終了した場合に、終了ステータスを変更してくれないことがあります。

終了ステータスの簡略判定方法

処理を終了するときなどに、毎回if文を記述して終了ステータスを判定するのは煩わしいだけでなく、バッチファイルやシェルスクリプトを読みにくくする原因になります。
読みやすさを保ちつつ、終了ステータスを適切に扱うための簡略記法は次のとおりです。

バッチ シェル
コマンドの後ろに && または || を記述 コマンドの後ろに && または || を記述

&&を使用した場合、&&より前のコマンドが終了ステータス 0 で終了した場合に、&&より後のコマンドが実行されます。
||を使用した場合、||より前のコマンドが終了ステータス 0 で終了しなかった場合に、||より後のコマンドが実行されます。

サブルーチンの作成方法

処理を共通化してサブルーチン化する場合は、次の方法を用います。

バッチファイルの場合
REM サブルーチンの定義方法
:サブルーチン名
処理を記述...(%1, %2, ...でパラメータを取得)
EXIT /B 終了ステータス

REM サブルーチンの呼び出し方法
CALL :サブルーチン名 パラメータ1 パラメータ2
シェルスクリプトの場合
# サブルーチンの定義方法
サブルーチン名() {
  処理を記述...($1, $2, ...でパラメータを取得)
  return 終了ステータス
}

# サブルーチンの呼び出し方法
サブルーチン名 パラメータ1 パラメータ2 ...
バッチ シェル

例では、2つのパラメータを受け取るサブルーチンを定義しています。
パラメータ1が0を受け取った場合は、標準出力に何も表示せずに終了ステータス0でサブルーチンを終了します。
パラメータ1が0以外を受け取った場合は、標準出力にNGと表示し、終了ステータスにパラメータ2の値を設定してサブルーチンを終了します。

全体処理の終了方法

処理が正常な状態であるのか、異常な状態であるのかに関わらず、バッチファイルやシェルスクリプトは終了ステータスを明示して全体の処理を終了するべきです。
特に、バッチファイルでは、「GOTO :EOF」でバッチファイルの末尾にジャンプして、バッチ処理を事実上終了することもできますが、終了ステータスが曖昧になるのでお勧めできません。
終了ステータスを明示して終了する方法は次のとおりです。

バッチ シェル
/B付きでexitコマンドを実行 exitコマンドを実行

注意(バッチ): /B オプションなしでEXITコマンドを実行した場合、バッチを起動しているコマンドインタプリタごと処理が終了してしまいます。
これはコマンドプロンプトで作業している場合、そのコマンドプロンプトも閉じられるということを意味します。
終了ステータスを指定せずにEXITコマンドを実行した場合、バッチファイルまたはシェルスクリプトの終了ステータスは、最後に実行したコマンドの終了ステータスになります。

サブルーチンの終了方法

サブルーチンを終了する方法は次のとおりです。

バッチ シェル
/B付きでexitコマンドを実行 returnコマンドを実行

別ファイルを呼び出す方法

別ファイルに記述されているバッチファイルまたはシェルスクリプトを呼び出す方法は次のとおりです。

バッチ シェル
CALLコマンドを使用 . を使って取り込み

注意(バッチ): CALLコマンドは指定されたバッチファイルを呼び出すだけで、呼び出されたバッチファイル内のサブルーチンを取り込むことはできません。
注意(バッチ): CALLされる側のバッチファイルは、CALLされる側に処理を戻すために、必ずEXIT /Bで処理を終了する必要があります。
補足(シェル): シェルスクリプトの場合は、ファイル内のサブルーチンを取り込むことができます。

コマンド実行結果の取得

シェルスクリプトの場合は、コマンドをバッククォート(``)で囲んでコマンドの実行結果を取得できますが、バッチファイルの場合は、この方法ではコマンドの実行結果を取得できません。
しかし、実行結果を取得する方法が全く無いわけではありません。シェルスクリプトに比べて適用範囲や記述の柔軟さは圧倒的に劣りますが、次の方法でコマンドの実行結果を取得することができます。

バッチ シェル
サブルーチンを作成してコマンドの実行結果を取得 バッククォートを使用してコマンドの実行結果を取得

補足(バッチ): 第1パラメータに実行結果を受け取る環境変数名(DATETIME)、第2パラメータに実行したいコマンド(date /t)を与えてサブルーチンを呼び出しています。

Windowsの遅延環境変数(SETLOCAL ENABLEDELAYEDEXPANSION)

文字列置換

遅延環境変数の展開を併用すると、0埋めした文字列を簡単に作成することができます。

例では、数値の先頭を0埋めした3桁の文字列を作成しています。

変数の評価タイミング

UNIXのコマンドインタプリタは、コマンドの実行時に変数を展開しますが、Windowsのコマンドインタプリタはコマンドを囲む文を評価するタイミングで変数を展開してしまいます。
UNIXのコマンドインタプリタに近いタイミングで変数を展開させる場合は、遅延環境変数の展開を有効にします。

バッチ シェル

バッチファイルでは、1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 を計算するつもりで処理を記述していますが、最初のECHO %SUM% の結果は、期待に反して 10 になります。
これは、Windowsのコマンドインタプリタが、FOR文を実行する前に %〜% で囲まれた環境変数の値を展開し、SET /A SUM=%SUM% + %%i を SET /A SUM=0 + %%i に置き換えてからFOR文を実行するからです。
これに対して、2番目の ECHO %SUM% の結果は期待通り 55 になります。
これは、「SETLOCAL ENABLEDELAYEDEXPANSION」を使用して、遅延環境変数の展開を有効にした上で、環境変数を %SUM% ではなく、 !SUM! で参照しているからです。
!SUM! と記述すると、FOR文を実行する前ではなく、環境変数を参照するタイミングで環境変数の値が展開されます。

遅延環境変数の展開の有効/無効状態判定

バッチファイルを記述する際に、「SETLOCAL ENABLEDELAYEDEXPANSION」が設定されているという前提で処理を記述したい場合があります。
CALLコマンドを用いて、あるバッチファイルから別のバッチファイルを呼び出す場合が、このケースに該当します。
この場合は、バッチファイルの先頭に次のようなコマンド列を記述することで、「SETLOCAL ENABLEDELAYEDEXPANSION」が設定されていないときにバッチファイルの実行を中止することができます。

例では、「SETLOCAL ENABLEDELAYEDEXPANSION」が無効になっていると判断した場合に、終了ステータス1でEXITコマンドを実行し、呼び出し元に戻ります。

おわりに

項目の順番は気が向いたら直したいと思いますが、ひとまず列挙してみました。また、乱文になっていると思いますが平にご容赦を。

Groovyのテンプレートエンジンで改行の出力を調整する方法

Groovyでテンプレートエンジン - No Programming, No Lifeで紹介されているとおり、Groovyではテンプレートエンジンと呼ばれるものが使えます。

このテンプレートエンジンを使っていて、改行のみの不要な行が出力されることに悩まされていたのですが、テンプレートファイル側を調整することで解決できたのでメモしておきます。

テンプレートの呼び出し側のコード(共通)

def f = new File('sample.template')
def engine = new groovy.text.SimpleTemplateEngine()

def binding = [
                'title' : '',
                'items1' : [],
                'items2' : [],
              ]

binding.title  = 'サンプル'
binding.items1 = [ 1, 2 ]
binding.items2 = [ 'A', 'B' ]

def template = engine.createTemplate(f).make(binding)
println template.toString()

Groovyでテンプレートエンジン - No Programming, No LifeのSimpleTemplateEngine版のコードを、少し実践寄りのコードに変更しています。

調整するのはテンプレートファイル側なので、このGroovyコードは共通です。

sample.template(不要な改行のみの行が出力される版)

まずは、調整前のテンプレートファイルと、出力結果を示します。

#
# タイトル: <%= title %>
#

<%
items1.each { item1 ->
  items2.each { item2 ->
%>
アイテム: <%= item1 %><%= item2 %>
<%
  }
}
%>
C:\Groovy\Template>groovy sample.groovy
#
# タイトル: サンプル
#


アイテム: 1A

アイテム: 1B

アイテム: 2A

アイテム: 2B



C:\Groovy\Template>

テンプレートファイル内のGroovyコード「<% 〜 %>」が記述されている箇所が、改行のみの行として出力されるので出力結果が美しくありません。

sample.template(不要な改行のみの行が出力されない版)

次に、調整後のテンプレートファイルと、出力結果を示します。

#
# タイトル: <%= title %>
#

<%
items1.each { item1 ->
  items2.each { item2 ->
%>\
アイテム: <%= item1 %><%= item2 %>
<%
  }
}
%>\
C:\Groovy\Template>groovy sample.groovy
#
# タイトル: サンプル
#

アイテム: 1A
アイテム: 1B
アイテム: 2A
アイテム: 2B


C:\Groovy\Template>

「%>」と書いていた箇所を、「%>¥」と書くだけのお手軽さです。
この手に気づくまでは、「%>」の後ろに改行を入れないようにして改行箇所を調整していたのですが、テンプレートファイルの可読性が極めて悪くなっていました。
この手に気づいてからは、可読性を下げずにテンプレートファイルが書けるようになりました。

Sphinxの魔法にかかってみた

はじめに

最近ドキュメントの整備が最優先重要課題になってきたので、ドキュメントを効率よく書くための方法を調査していました。
年初くらいから調査していて、

ようやく「Sphinxにしよう!」と決心したので、Windows環境にSphinxを導入してみました。

そこで、この記事ではWindows環境にSphinxを導入するための手順を紹介したいと思います。
記事の作成にあたって、次のサイトを参考にさせていただきました。

目標

この記事では、次のことが実現できる環境を構築します。

  • Sphinxを使って、テキストファイル(reStructuredText形式)からHTMLファイルを自動生成できる
  • ダイアグシリーズ(blockdiag, seqdiag, actdiag, nwdiag)を使って、テキストファイルから自動生成した遷移図などをHTMLファイルに組み込むことができる
  • OMakeを使って、テキストファイルの更新を監視して、HTMLファイルを継続監視ビルドできる
  • 2つ以上の別々のドキュメントを少ない負担で管理できる
  • ローカルHTMLの更新時に、ブラウザを自動リロードできる(2011/5/3追記)

インストール

インストール物

インストールを始める前に次のファイルを対応するURLから入手しておきます。

ファイル名 URL 概要
python-2.7.1.msi http://www.python.jp/Zope/download/pythoncore Python 2.7.1のインストーラ
setuptools-0.6c11.win32-py2.7.exe http://pypi.python.org/pypi/setuptools Python 2.7用setuptoolsのインストーラ
VLGothic-20110414.zip http://vlgothic.dicey.org/ VLゴシックフォントを含むzipファイル
omake-0.9.8.5-2.msi http://omake.metaprl.org/download.html Omake 0.9.8.6のインストーラ
インストール後の状態

インストール後の状態が次のとおりになるようにします。

項目 インストール後の状態
Pythonのインストールディレクト C:\Python27
OMakeのインストールディレクト C:\OMake
環境変数PATH C:\Python27, C:\Python27\Scripts, C:\OMake\bin が追記されていること
ドキュメント作成の作業用ルートディレクト C:\AllDocs
追加フォントのインストールディレクト C:\AllDocs\Fonts
Pythonのインストール

python-2.7.1.msiを実行して、インストーラの手順に従ってインストールして下さい。
インストール後に、環境変数PATHに C:\Python27 と C:\Python27\Scripts を追記して下さい。

Python setuptoolsのインストール

setuptools-0.6c11.win32-py2.7.exeを実行して、インストーラの手順に従ってインストールして下さい。
インストールすると、easy_installが使用できるようになります。

Sphinxと拡張モジュールのインストール

easy_installを使用してSphinxと拡張モジュールをインストールします。
コマンドプロンプトから次のコマンドを実行して下さい。

2011/5/28追記: sphinxcontrib-seqdiag, sphinxcontrib-actdiag, sphinxcontrib-nwdiagのインストールを追記
2011/5/28追記: 以前の記事を参照してダイアグシリーズをインストールした方は、ダイアグシリーズを最新版にアップグレードするために、easy_install --upgrade blockdiagのように、--upgradeオプションを用いて easy_install を実行して下さい。

PILを書き換える

ダイアグシリーズで日本語を使用する場合は、PILを書き換える必要があります。
blockdiag を WindowsXP で動かす」の「Step.5 -- PILを書き換える」に従って、_imagingft.pydを修正して下さい。

追加フォントのインストール
  • VLGothic-20110414.zipを解凍します。
  • 解凍先ディレクトリ内の「VL-Gothic-Regular.ttf」と「VL-PGothic-Regular.ttf」を C:\AllDocs\Fonts にコピーします。

※ C:\WINDOWS\Fonts にコピーする方法が一般的だと思いますが、環境への依存度を減らすためにシステムフォルダとは独立したディレクトリにファイルを配置します。

OMakeのインストール

omake-0.9.8.5-2.msiを実行して、インストーラの手順に従ってインストールして下さい。
インストールすると、環境変数PATHに自動的に C:\OMake\bin が追記されます。

インストール状態の確認

コマンドプロンプトを開きなおして、次のとおりにコマンドを実行して下さい。

2011/5/28追記: sphinxcontrib-seqdiag, sphinxcontrib-actdiag, sphinxcontrib-nwdiagのチェックを追記


一部割愛していますが、pythonとomakeはツールのバージョン、easy_installでインストールしたものはモジュールとごに already the active version in easy-install.pth と出力されればOKです。

C:\AllDocs>python --version 
Python 2.7.1

C:\AllDocs>omake --version 
OMake 0.9.8.5 (release 2):
	build [Fri Aug 10 16:10:42 2007]
	on jaoquin-nt
(略)

C:\AllDocs>easy_install sphinx 
Searching for sphinx
Best match: sphinx 1.0.7
Processing sphinx-1.0.7-py2.7.egg
sphinx 1.0.7 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install blockdiag 
Searching for blockdiag
Best match: blockdiag 0.8.1
Processing blockdiag-0.8.1-py2.7.egg
blockdiag 0.8.1 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install sphinxcontrib-blockdiag 
Searching for sphinxcontrib-blockdiag
Best match: sphinxcontrib-blockdiag 0.8.3
Processing sphinxcontrib_blockdiag-0.8.3-py2.7.egg
sphinxcontrib-blockdiag 0.8.3 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install seqdiag  
Searching for seqdiag
Best match: seqdiag 0.3.4
Processing seqdiag-0.3.4-py2.7.egg
seqdiag 0.3.4 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install sphinxcontrib-seqdiag 
Searching for sphinxcontrib-seqdiag
Best match: sphinxcontrib-seqdiag 0.1.1
Processing sphinxcontrib_seqdiag-0.1.1-py2.7.egg
sphinxcontrib-seqdiag 0.1.1 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install actdiag  
Searching for actdiag
Best match: actdiag 0.1.5
Processing actdiag-0.1.5-py2.7.egg
actdiag 0.1.5 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install sphinxcontrib-actdiag 
Searching for sphinxcontrib-actdiag
Best match: sphinxcontrib-actdiag 0.1.1
Processing sphinxcontrib_actdiag-0.1.1-py2.7.egg
sphinxcontrib-actdiag 0.1.1 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install nwdiag 
Searching for nwdiag
Best match: nwdiag 0.2.4
Processing nwdiag-0.2.4-py2.7.egg
nwdiag 0.2.4 is already the active version in easy-install.pth
(略)

C:\AllDocs>easy_install sphinxcontrib-nwdiag 
Searching for sphinxcontrib-nwdiag
Best match: sphinxcontrib-nwdiag 0.1.1
Processing sphinxcontrib_nwdiag-0.1.1-py2.7.egg
sphinxcontrib-nwdiag 0.1.1 is already the active version in easy-install.pth
(略)

2011/5/28更新: sphinxcontrib-seqdiag, sphinxcontrib-actdiag, sphinxcontrib-nwdiagのチェック結果を含む形で全体を更新

Sphinxドキュメントの作成

作業用ディレクトリの構成

2つ以上の独立したドキュメントを作成していくことを考慮して、次のディレクトリ構成になるようにします。

C:\AllDocs          // 全ドキュメント共通の作業用ルートディレクトリ
  |                 // ディレクトリ名は任意
  |   
  +---Fonts         // 追加フォントのインストールディレクトリ
  |                 // ディレクトリ名は後述の additional_fontpath の設定で変更可能
  |
  +---document01    // ドキュメント固有の作業用ディレクトリ
  |                 // ディレクトリ名は任意
  |
  +---document02    // ドキュメント固有の作業用ディレクトリ
  |                 // ディレクトリ名は任意
  |
  +---...
1つ目のドキュメントの作成

sphinx-quickstartを実行して、Sphinxドキュメントの基本ファイル群を生成します。
コマンドプロンプトから次のコマンドを実行して下さい。

mkdir C:\AllDocs\document01
cd C:\AllDocs\document01
sphinx-quickstart


途中で色々と確認されますがリターンキーを連打すればOKです。デフォルト値が適用できる項目の場合は、次の入力項目に進みます。デフォルト値が適用できない3つの項目(「Project name」,「Author name(s)」,「Project version」)の場合は、入力が完了するまで次の入力項目に進みません。
デフォルト値が適用できない3つの項目の入力例は次のとおりです。

The project name will occur in several places in the built documentation.
> Project name: document01
> Author name(s): tyuki39

Sphinx has the notion of a "version" and a "release" for the
software. Each version can have multiple releases. For example, for
Python the version is something like 2.5 or 3.0, while the release is
something like 2.5.1 or 3.0a1.  If you don't need this dual structure,
just set both to the same value.
> Project version: 0.1.2

sphinx-quickstartが完了すると、document01内が次のとおりになります。

C:\AllDocs\document01
  |   conf.py      // Sphinxの設定ファイル
  |   index.rst    // Sphinxで整形する前のテキストファイル(reStructuredText形式)
  |   make.bat     // Sphinxが自動生成するドキュメントのビルド用バッチ
  |   Makefile     // (この記事では不使用)Sphinxが自動生成するドキュメントのビルド用Makefile
  |   
  +---_build       // ビルド結果の格納先
  +---_static      // (この記事では不使用)静的コンテンツの格納先
  \---_templates   // (この記事では不使用)カスタムテンプレートの格納先

補足: Sphinxが整形する前のテキストファイルのデフォルトの拡張子は rst です。

common.pyの作成

Sphinxの設定ファイル(conf.py)は、Pythonコードそのものになっています。
このことを利用して、「document01固有の設定」と「今後作成するドキュメントにも共通する設定」を分離するために、 C:\AllDocs\common.py を作成します。

common.py の内容例は次のとおりです。

2011/5/28更新: actdiag, seqdiag, nwdiagの sphinxcontrib 拡張モジュール名の変更に合わせて、extensionsの内容を更新(例: sphinxcontrib_actdiag -> sphinxcontrib.actdiag)
2011/5/28更新: 誤記 acttdiag_antialias を actdiag_antialias に訂正


各設定の概要は次のとおりです。

設定項目 対象モジュール 概要
additional_fontpath この記事独自 追加フォントのインストールディレクトリを設定しています。この記事では、common.pyからの相対パス(./Fonts)を設定しています。
extensions Sphinx Sphinxの拡張モジュールを追加設定しています。blockdiag, actdiag, seqdiag, nwdiagを有効にしています。
source_encoding Sphinx Sphinxに与えるテキストファイル(reStructuredTextファイル)の文字コードを設定しています。Windows環境の場合は、shift_jisを設定した方がよいかもしれません。
language Sphinx Sphinxが自動生成するドキュメントの言語を設定しています。
html_theme Sphinx Sphinxドキュメントのテーマを設定しています。個人的にはデフォルトのテーマよりも sphinxdoc の方が好きです。
html_last_updated_fmt Sphinx HTMLに出力するドキュメント生成日時のフォーマットを設定しています。
blockdiag_fontpath blockdiag blockdiagで使用するフォントを設定しています。
actdiag_fontpath actdiag actkdiagで使用するフォントを設定しています。
seqdiag_fontpath seqdiag seqkdiagで使用するフォントを設定しています。
nwdiag_fontpath nwdiag nwdiagで使用するフォントを設定しています。
blockdiag_antialias blockdiag blockdiagでアンチエイリアスを有効化しています。
actdiag_antialias actdiag actdiagでアンチエイリアスを有効化しています。
seqdiag_antialias seqdiag seqdiagでアンチエイリアスを有効化しています。
nwdiag_antialias nwdiag nwdiagでアンチエイリアスを有効化しています。

補足: common.pyには、conf.pyの設定に追加するもの、またはconf.pyの設定を上書きするものを列挙します。
補足: 上記はあくまでも設定の一例です。
補足: 設定項目の詳細については、対象モジュールのドキュメントを参照して下さい。

conf.pyの編集

common.pyを読み込むためのPythonコードをconf.pyの末尾に追記します。

HTMLの生成

ここまででSphinxの利用環境が整いました。
コマンドプロンプトから次のコマンドを実行すると、SphinxがHTMLを生成してくれます。

make html

コマンドがエラーなく終了し、_build\htmlにindex.htmlなどが生成されればOKです。

ドキュメントの自動継続ビルド

index.rstなどのrstファイルを変更する度に make html と打ってHTMLファイルを作り直すのは大変です。
そこで、OMakeの継続監視ビルド機能を利用して、rstファイルの変更に合わせてHTMLファイルを生成できるようにします。

全ドキュメントの一括ビルド用規則の作成

まず、次のコマンドを実行します。

cd C:\AllDocs
omake --install

これで C:\AllDocs がOMakeビルドツリーの基点になります。

次に、OMakefileの中身をいったん空にして、次の一行だけを含むファイルを作成して下さい。

これで C:\AllDocs\document01 がOMakeビルドツリーに組み込まれます。

ドキュメント固有のビルド規則の作成

最後に、C:\AllDocs\document01内に次の内容のOMakefileを作成します。

補足: Sphinxのビルドコマンドは sphinx-build です。
補足: このビルド規則では HTMLファイルを生成していますが、sphinx-buildは他の形式のファイルを生成するためのオプションも備えています。

OMakeによるビルド

この状態でコマンドプロンプトから次のコマンドを実行すると、document01内のrstファイルに対してビルドが実行されます。

cd C:\AllDocs\document01
omake

また、次のコマンドを実行すると、継続監視ビルドモードになります。

cd C:\AllDocs\document01
omake -P --verbose

document01ディレクトリツリー内のrstファイルを変更して、ビルドが自動実行されることを確認して下さい。

2つ目のドキュメントの作成

common.pyの作成」と「全ドキュメントの一括ビルド用規則の作成」を除く次の手順をdocument02内で実施して下さい。

  • sphinx-quickstartの実行
  • conf.pyの編集
  • ドキュメント固有のビルド規則の作成(document01で作成したファイルのコピーでOKです)

また、C:\AllDocs\document02 をOMakeビルドツリーに組み込むために、C:\AllDocs\OMakefile を次のとおりに変更します(空白を一つあけて document02 と記述します)。

これでdocument02も継続監視ビルドが実施できる状態になります。

また、次のコマンドを実行すると document01 と document02 の両方を一括ビルドすることができます。

cd C:\AllDocs
omake

ブラウザの自動リロード(2011/5/3追記)

ローカルのHTMLファイルが更新される度に、ブラウザをリロードするのも大変です。
Firefoxでの開発を高速化する自動リロードスクリプト」で公開されている「AutoReload」ブックマークレットを使用すると、この手間を省くことができます。
rstファイルを変更すると、継続監視ビルドがローカルHTMLファイルを更新し、ブラウザがローカルHTMLファイルを自動リロードしてくれるので、さらに効率よくドキュメントを書き進められるようになります。

最終状態

以上で目標にしていた環境が構築できました。ビルドによって生成されるファイルを除くと C:\AllDocs ディレクトリツリーの内容は次のとおりになります。

C:\AllDocs
  |   common.py        // 全ドキュメントの共通設定を定義するためのSphinx用ファイル
  |   OMakefile        // 全ドキュメントのビルド規則を定義するためのOMake用ファイル
  |   OMakeroot        // 全体的なビルド規則を定義するためのOMake用ファイル
  |   
  +---document01
  |   |   conf.py      // document01固有の設定を定義するためのSphinx用ファイル
  |   |   index.rst    // Sphinxで整形する前のテキストファイル(reStructuredText)
  |   |   make.bat     // Sphinxが自動生成するドキュメントのビルド用バッチ
  |   |   Makefile     // (この記事では不使用)Sphinxが自動生成するドキュメントのビルド用Makefile
  |   |   OMakefile    // document01のビルド規則を定義するためのOMake用ファイル
  |   |   
  |   +---_build       // ビルド結果の格納先
  |   +---_static      // (この記事では不使用)静的コンテンツの格納先
  |   \---_templates   // (この記事では不使用)カスタムテンプレートの格納先
  |
  \---document02
  |   |   conf.py      // ファイルの用途はdocument01と同じ
  |   |   index.rst    // ファイルの用途はdocument01と同じ
  |   |   make.bat     // ファイルの用途はdocument01と同じ
  |   |   Makefile     // ファイルの用途はdocument01と同じ
  |   |   OMakefile    // ファイルの用途はdocument01と同じ
  |   |   
  |   +---_build       // ディレクトリの用途はdocument01と同じ
  |   +---_static    // ディレクトリの用途はdocument01と同じ
  |   \---_templates   // ディレクトリの用途はdocument01と同じ
  |
  \---Fonts
          VL-Gothic-Regular.ttf   // VLゴシックフォント
          VL-PGothic-Regular.ttf  // VLゴシックフォント

注意点

継続監視ビルド中に作成したrstファイルは、継続監視ビルド対象外になるようです。また、継続監視ビルド中は、ディレクトリツリー内に新しいrstファイルを作成できなくなることがあります。
新しいrstファイルを作成する場合は、継続監視ビルドをいったん中止して下さい。

おわりに

ワードプロセッサスプレッドシートを使用していて次のような問題で困っていないでしょうか。

  • ドキュメントの体裁を気にしすぎて作成作業が進まない。
  • オートコレクトや自動インデントの機能が余計に働いて困る。
  • ドキュメントが比較しにくい。
  • ドキュメントを分割して構造化するのが難しい。
  • 同一ファイルを複数人が並行作業しつつ変更するのが難しい。
  • ドキュメントの一部を抜き出して新たなドキュメントを作成するのが難しい。

そのような方はSphinxの魔法にかかってみることをお奨めします。

Sphinxドキュメントの構成は、「Sphinxのドキュメントサンプル:業務利用例 — Python製ドキュメンテーションビルダー、Sphinxの日本ユーザ会」を参考にすると良いでしょう。

変更履歴

  • 2011/5/3: ブラウザの自動リロードに関する記述を追記しました。
  • 2011/5/28: ダイアグシリーズのインストール構成の変更に合わせて、記事を更新しました。
    common.pyの誤記を訂正しました。

お題:時間帯重複チェック(応用編)を解いてみた。

はじめに

先日解いた「時間帯重複チェック」の応用編(「お題:時間帯重複チェック(応用編) - No Programming, No Life」)が出題されていたので、こちらも解いてみました。

コードは次の通りです。

実装手順

第一ステップ

前回のお題を解くときに、開始時刻と終了時刻をRange化して「時間帯」を表現しました。
ただし、少しサボって「時間帯」クラスを作っていなかったので、MyDurationクラスを作成して「開始時刻 <= 終了時刻」のチェックをMyDurationのコンストラクタ内に記述しました。

第二ステップ

追加仕様1:「重複していた場合には、その重複している時間帯を必要分だけ返すようする」は、複数の時間帯をArrayListに入れてから、ArrayList内の時間帯のすべてのペアに対して重なりを求める方法で対応しました。
rawListを作る部分のコードが該当箇所です。

第三ステップ

追加仕様2: 「返却する時間帯は開始時間が早いものから順に並べ替える」は、MyDurationにComparableを実装して、compareToを定義することで対応しました。
compareToはrawListをソートする箇所で使用しています。

第四ステップ

「同じ時間帯が2回以上重複している場合でも一つの時間帯とする」は、MyDuration同士のOR演算を定義することで対応しました。また、概念的に対になるAND演算も定義しました。

OR演算とAND演算の定義は次の通りです。

  • OR演算( orメソッド )
    • 重なりがある場合は両方を包含する一つの時間帯にまとめて、一要素のArrayListを返す。
    • 重なりがない場合は二つの時間帯のままにして、二要素のArrayListを返す。
  • AND演算( andメソッド )
    • 重なりがある場合は両方の共通部分を一つの時間帯にまとめて、一要素のArrayListを返す。
    • 重なりがない場合は要素なしのArrayListを返す。

OR演算は、rawList内のMyDurationを先頭から順に取り出して、取り出したMyDurationをresultListの末尾のMyDurationに結合する箇所で使用しています。

おわりに

今回のお題は、RangeのOR演算を表現する方法で悩みました。RangeにはAND演算相当のintersectがあるので、OR演算に相当するメソッドが簡単に見つかると思っていたのですがありませんでした。
Rangeには元々、andメソッドとorメソッドがないので、プログラミングの世界では範囲の和に関する最適な概念がないのかも知れません。スッキリ当てはまる概念をお持ちの方はアドバイスを頂けると幸いです。

Jenkinsプラグイン探訪 - Sidebar-Link Plugin

はじめに

システムレベルのお話

Jenkinsのページのうち、参照頻度の高いページが少し深い階層に存在していることがあります。Permalinkが設定されているページであれば、ビューの説明欄にそのページへのリンクを記述して、階層を浅くすることができます。
この説明欄を用いる方法は、表現力が高く、柔軟にカスタマイズできるところが魅力ですが、ビューごとにリンクを記述する必要があるので、複数箇所に同じリンクを記述したい場合に少し不便です。

プロジェクトレベルのお話

開発プロジェクトで情報共有用サイトを運用している場合、そのサイトにJenkinsへのハイパーリンクを記述しているのではないでしょうか。しかし、その逆(Jenkinsからサイトへ)はというと、サイトへのリンクを設定していない場合が多いのではないかと思います。
ここで上記と同様に、プロジェクトの説明欄にそのサイトへのリンクを記述して、相互リンクにすることができます。しかし、プロジェクトの説明欄は、プロジェクトのトップページにしか存在しないので、アクセシビリティがあまり向上しません。

そこで...

今回はこれらの状況を改善するための一手段として、Sidebar-Linkプラグインを紹介したいと思います。

なお、このプラグインは、簡潔に述べると、

  • Jenkinsのグローバルメニュー(サイドバー)や
  • Jenkinsのプロジェクトメニュー(サイドバー)に
  • ユーザ定義のハイパーリンク

追加するための機能を提供します。

インストール

Sidebar-Link Pluginの名称と関連URLは次のとおりです。

プラグイン Wiki URL ダウンロード URL GitHub URL
Sidebar-Link Plugin http://wiki.jenkins-ci.org/display/JENKINS/Sidebar-Link+Plugin http://updates.jenkins-ci.org/download/plugins/sidebar-link/ https://github.com/jenkinsci/sidebar-link-plugin

機能概要

Sidebar-Linkプラグインの機能概要は次のとおりです。

  • グローバルメニューに追加するハイパーリンクのURL、テキスト、アイコンを、システム設定から複数個登録することができます。


  • プロジェクトメニューに追加するハイパーリンクのURL、テキスト、アイコンを、各プロジェクトの設定から複数個登録することができます。


機能詳細と注意事項

Sidebar-Linkプラグインの機能詳細は次のとおりです

  • アイコンのサイズは24x24です。
  • 24x24より大きなサイズで登録されたアイコンは、描画時に24x24にリサイズされます。
  • 「JENKINS_HOME/war/images/24x24」ディレクトリ内のアイコン(例えば、clipboard.png)を使用する場合は、リンクアイコン欄に「clipboard.png」と記述します。
  • 「JENKINS_HOME/userContent」ディレクトリ内に配置した自作のアイコン(例えば、image.png)を使用する場合は、リンクアイコン欄に「/userContent/image.png」と記述します。
  • その他は「機能概要」を参照してください。

利用のポイント

  • Jenkinsのページのうち、参照頻度が高いページへのリンクをサイドバーに登録することで、アクセシビリティが向上します。(JekinsのルートURLから相対的に指定して下さい)
  • 疎な連携ではありますが、(開発プロジェクトごとに運用している)情報共有用サイトとの相互リンクを充実させることで、サイト同士の一体感が向上します。

おわりに

どんなUIでもそうですが、すべての人にベストマッチするUIというものはあり得ません。ツールを浸透させるためには、ちょっとした工夫を施してチームにとって使いやすいと思えるUIにカスタマイズしていくことも重要だと思います。
また、サイト同士を連携させることによって、必要な情報を得るための負担を多少なりとも減らすことができます。

お題:時間帯重複チェックを解いてみた。

はじめに

具体的なお題を解いていくと言語を効率よく勉強できるので、「お題:時間帯重複チェック - No Programming, No Life」に乗っからせていただきました。

最初のコード

Groovyは勉強中で発想が貧困なので、まずはベタなコードを書くことから始めました。

さすがにベタすぎてGroovyらしさがないだけでなく、isNotGreaterThanとかセンスの欠片もありませんね。(^^;;

次のコード

次にhourとminutesをメンバにもつクラスMyTimeを作ってみることにしました。
独自のクラスではなく、Dateを使おうかとも思ったのですが、

  • 年月日秒の概念が余計である
  • お題で(24,0)がアリになっている
  • 使わない方が勉強になるかも

という理由でMyTimeを作ることにしました。


クラス化したことで「宇宙船演算子 <=>」(Groovyインアクション P55参照)が使えることに気づきました。
そこで、宇宙船演算子を使うために、MyTimeに

  • Comparableインタフェースを実装して
  • compareToメソッドを定義しました

これで、MyTime同士の比較が次のように素直に表現できるようになりました。(例えば、r1.start <= r1.endの部分)

ここでは、

  • @Newifyを使って new を省いたり
  • 生成直後に有効なインスタンスにしたいので isValid をコンストラクタに入れたり
  • エラーメッセージの可読性をあげるために toString を定義したり
  • 入力値と期待値を Map で定義して可読性を向上させたり

といったちょっとした配慮も加えています。

最後のコード

クラス化したことで少しマシなコードになりました。しかし、開始時刻と終了時刻の関係性が薄い気がしてどうもすっきりしません。
お題の (1, 0, 5, 30) という書き方は 「開始時刻と終了時刻で一つのグループを構成する」 ということを表現できているので、意味的にこの表現に近づけたいと思っていました。

そんなこんなでモヤモヤと考えていたところ、Rangeにすれば良いことに気づきました。Range なら「開始」と「終了」を明確に表現できますし、お題のゴールである「重なり」も intersect でズバリ表現できます。
そこで、Range化してintersectが使えるようにするために、MyTimeに

  • nextメソッドと
  • previousメソッドを

定義して次のコードをこしらえました。

お題をクリアするためには、範囲1と範囲2に対して、

  • 範囲1が範囲2を完全に包含している
  • 範囲2が範囲1を完全に包含している
  • 範囲1が左、範囲2が右に位置して中間に重なりがある
  • 範囲2が左、範囲1が右に位置して中間に重なりがある

の4つを表現しなければなりませんが、intersectを使えるようにしたことで、Groovyがよしなに処理してくれるようになりました。(^^

おわりに

今回のお題も色々と発見があり、Groovy力が少し上がりました。お題を提供してくださった id: fumokmm さんありがとうございました。