CSVの解説と各プログラミング言語での実装例

CSVの仕様

まず重要な点は、「CSVは、ファイルを読んで、1行ずつ、単純に「,」で分割していけばいいってものではない」ということである。

CSVの標準的な仕様として、現在RFC4180が定義されている。

http://www.kasai.fm/wiki/rfc4180jp
http://www.ietf.org/rfc/rfc4180.txt

しかしながら、これは2005年10月に公開された後追いであり、おそらく、実際、それぞれの現場で使っている物と異なる箇所があると思われる。

この仕様で述べている主な特徴は次の通りになる。

・各レコードは、改行(CRLF)を区切りとする
・ファイル末尾のレコードの終端には、改行があってもなくてもいい。
・ファイルの先頭にはヘッダ行が存在してもいいしなくてもいい。
・各行とヘッダはコンマで区切られたフィールドを持つ。
・フィールドの数は、ファイル全体を通じて同一であるべき。
・最後のフィールドは、コンマで終わってはいけない。
・各フィールドはダブルクォーテーションで囲んでもいいし、囲まなくてもいい。
・改行を含むフィールドはダブルクォーテーションで囲むべき。
・ダブルクォーテーションを含むフィールドはダブルクォーテーションで囲み、フィールド中のダブルクォーテーションの前にダブルクォーテーションを付与する。

xxx,"test""test"

各プログラミング言語での実装例

各プログラミング言語で次のCSVを操作する例を示す。
今回のサンプルでは次のCSVファイルがどのように取り込めるか検証する。

test1.csv 一般的なCSVの例

ジャック,12,戦士,説明1
バーン,17,騎士,説明2
マァム,15,僧侶,説明3

test2.csv 1行目がコメントで、フィールドに改行、ダブルクォーテーション、コンマが混入している場合

# 名前,歳,クラス,説明に(",)を入れる
"ジャック","12","戦士",",や""が入力可能"
"バーン","17","騎士","説明2
改行を行う"
"マァム","15","僧侶","説明3"

test3.csv 空行が存在する例

# 空行の扱い

"ジャック","12","戦士",",や""が入力可能"
"バーン","17","騎士","説明2

ダブルクォート中に空の改行がある場合"
# 途中のコメント
"マァム","15","僧侶","説明3"

test4.csv 各レコードの列数が異なる例

# 列の数が異なる場合

"ジャック","12"
"バーン"
"マァム","15","僧侶","説明3"

.NET(C#)での実装例

TextFieldParserを使用する例

「Microsoft.VisualBasic」を参照することで使用できるTextFieldを用いて解析する例を以下に示す。

CSV読み込みのサンプル

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualBasic.FileIO;  //追加

// 空行を読み飛ばすのは仕様
// http://msdn.microsoft.com/ja-jp/library/microsoft.visualbasic.fileio.textfieldparser.readfields.aspx

namespace csvTest
{
    class Program
    {
        static void Main(string[] args)
        {
            dumpCsv("C:\\dev\\csv\\test1.csv");
            dumpCsv("C:\\dev\\csv\\test2.csv");
            dumpCsv("C:\\dev\\csv\\test3.csv");
            dumpCsv("C:\\dev\\csv\\test4.csv");

            Console.ReadLine();
        }

        static void dumpCsv(string file)
        {
            Console.WriteLine(file + "================================");

            TextFieldParser parser = new TextFieldParser(file,
                                                          System.Text.Encoding.GetEncoding("Shift_JIS"));
            parser.TextFieldType = FieldType.Delimited;
            parser.SetDelimiters(","); // 区切り文字はコンマ
            parser.CommentTokens = new string[1] {"#"};
            int line = 0, col = 0;
            while (!parser.EndOfData)
            {
                ++line;
                col = 0;
                string[] row = parser.ReadFields(); // 1行読み込み
                Console.WriteLine("{0}", line);
                // 配列rowの要素は読み込んだ行の各フィールドの値
                foreach (string field in row)
                {
                    ++col;
                    Console.WriteLine("{0}:{1}", col, field);
                }
                Console.WriteLine("----------------------------");
            }
            parser.Close();
        }
    }
}

実行結果

C:\dev\csv\test1.csv================================
1
1:ジャック
2:12
3:戦士
4:説明1
----------------------------
2
1:バーン
2:17
3:騎士
4:説明2
----------------------------
3
1:マァム
2:15
3:僧侶
4:説明3
----------------------------
C:\dev\csv\test2.csv================================
1
1:ジャック
2:12
3:戦士
4:,や"が入力可能
----------------------------
2
1:バーン
2:17
3:騎士
4:説明2
改行を行う
----------------------------
3
1:マァム
2:15
3:僧侶
4:説明3
----------------------------
C:\dev\csv\test3.csv================================
1
1:ジャック
2:12
3:戦士
4:,や"が入力可能
----------------------------
2
1:バーン
2:17
3:騎士
4:説明2
ダブルクォート中に空の改行がある場合
----------------------------
3
1:マァム
2:15
3:僧侶
4:説明3
----------------------------
C:\dev\csv\test4.csv================================
1
1:ジャック
2:12
----------------------------
2
1:バーン
----------------------------
3
1:マァム
2:15
3:僧侶
4:説明3
----------------------------

この結果からTextFieldParserの動作として、次のことが言える。
・特別なライブラリのインストールが不要である。
・CommentTokensを設定することでコメント行を指定できる。
・空行は読み飛ばす。これはダブルクォーテーション中であっても例外でない。
 この挙動は仕様である。
 http://msdn.microsoft.com/ja-jp/library/microsoft.visualbasic.fileio.textfieldparser.readfields.aspx
・CSVを作成する機能は、存在しない。

CsvHelperを使用する例

TextParserの空行を読み飛ばす仕様が問題であったり、CSVを作成する必要がある場合はCsvHelperを使用するといい。

https://github.com/JoshClose/CsvHelper

上記からダウンロードして自分でビルドするか、パッケージマネージャーで次のコマンドを実行する。

Install-Package CsvHelper

ここでの検証は.NET3.5で行ったが、ライブラリとしては.NET2.0~4.5までサポートしている。

CSV読み込みのサンプル

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using CsvHelper;

// .NET3.5で検証
namespace csvTest
{
    class Program
    {
        static void Main(string[] args)
        {
            dumpCsv("C:\\dev\\csv\\test1.csv");
            dumpCsv("C:\\dev\\csv\\test2.csv");
            dumpCsv("C:\\dev\\csv\\test3.csv");
            dumpCsv("C:\\dev\\csv\\test4.csv");
            Console.Read();
        }

        static void dumpCsv(string file)
        {
            Console.WriteLine(file + "================================");
            var parser = new CsvReader(new StreamReader(file,
                                                        System.Text.Encoding.GetEncoding(932)));
            parser.Configuration.Encoding = System.Text.Encoding.GetEncoding(932);
            parser.Configuration.AllowComments = true;
            parser.Configuration.Comment = '#';
            parser.Configuration.HasHeaderRecord = false;

            while (parser.Read())
            {
                for (var i = 0; i < parser.CurrentRecord.Length; ++i)
                {
                    Console.WriteLine("{0}:{1}", i, parser.CurrentRecord.ElementAt(i));
                }
                Console.WriteLine("----------------------------");
            }
            parser.Dispose();
        }
    }
}

実行結果

C:\dev\csv\test1.csv================================
0:ジャック
1:12
2:戦士
3:説明1
----------------------------
0:バーン
1:17
2:騎士
3:説明2
----------------------------
0:マァム
1:15
2:僧侶
3:説明3
----------------------------
C:\dev\csv\test2.csv================================
0:ジャック
1:12
2:戦士
3:,や"が入力可能
----------------------------
0:バーン
1:17
2:騎士
3:説明2
改行を行う
----------------------------
0:マァム
1:15
2:僧侶
3:説明3
----------------------------
C:\dev\csv\test3.csv================================
0:ジャック
1:12
2:戦士
3:,や"が入力可能
----------------------------
0:バーン
1:17
2:騎士
3:説明2

ダブルクォート中に空の改行がある場合
----------------------------
0:マァム
1:15
2:僧侶
3:説明3
----------------------------
C:\dev\csv\test4.csv================================
0:ジャック
1:12
----------------------------
0:バーン
----------------------------
0:マァム
1:15
2:僧侶
3:説明3
----------------------------

CSV書き込みのサンプル

            var csv = new CsvWriter(new StreamWriter("C:\\dev\\csv\\out.csv", false, System.Text.Encoding.GetEncoding(932)));
            csv.WriteField("私はカゴメ");
            csv.WriteField(12);
            csv.WriteField(true);
            csv.WriteField("\",などの記号");
            csv.NextRecord();

            csv.WriteField("2行目");
            csv.WriteField("改行を2つ\n\nまぜる");
            csv.NextRecord();

            csv.Dispose();

出力されたファイル

私はカゴメ,12,True,""",などの記号"
2行目,"改行を2つ

まぜる"

この結果からCsvHelperの動作として、次のことが言える。
・空行があってもスキップをしない。
・Configuration.AllowComments,Commentでコメントの有無を指定できる。
・Configuration.HasHeaderRecordで一行目をヘッダとしてあつかうかどうかを指定できる。
・CSVファイルの作成も容易に行える。
・ここではやっていないが、Mapping を使用してオブジェクトと関連づけることもできる。

ExcelVBAでの実装例

Excelを用いて、CSVを開く方法

ExcelはCSVを開くことができるので、その機能を使用して実装してみる。

Public Sub test()
    Call DumpCsv("C:\\dev\\csv\\test1.csv")
    Call DumpCsv("C:\\dev\\csv\\test2.csv")
    Call DumpCsv("C:\\dev\\csv\\test3.csv")
    Call DumpCsv("C:\\dev\\csv\\test4.csv")
End Sub

Private Sub DumpCsv(ByVal path As String)
    Debug.Print path & "=============================="
    Dim wkb As Workbook
    Dim wks As Worksheet
    Application.ScreenUpdating = False
    Set wkb = Application.Workbooks.Open(path)
    Application.Windows(wkb.Name).Visible = False
    Set wks = wkb.Sheets(1)

    Dim r As Long
    Dim c As Long
    Dim maxRow As Long
    Dim maxCol As Long
    maxRow = wks.Cells(1, 1).SpecialCells(xlLastCell).Row
    maxCol = wks.Cells(1, 1).SpecialCells(xlLastCell).Column
    For r = 1 To maxRow
        For c = 1 To maxCol
            Debug.Print c & ":" & wks.Cells(r, c).Value
        Next c
        Debug.Print "----------------------"
    Next r

    Call wkb.Close(False)
    Application.ScreenUpdating = True
End Sub

実行結果

C:\\dev\\csv\\test1.csv==============================
1:ジャック
2:12
3:戦士
4:説明1
----------------------
1:バーン
2:17
3:騎士
4:説明2
----------------------
1:マァム
2:15
3:僧侶
4:説明3
----------------------
C:\\dev\\csv\\test2.csv==============================
1:#名前
2:歳
3:クラス
4:説明に("
5:)を入れる
----------------------
1:ジャック
2:12
3:戦士
4:,や"が入力可能
5:
----------------------
1:バーン
2:17
3:騎士
4:説明2
改行を行う
5:
----------------------
1:マァム
2:15
3:僧侶
4:説明3
5:
----------------------
C:\\dev\\csv\\test3.csv==============================
1:#空行の扱い
2:
3:
4:
----------------------
1:
2:
3:
4:
----------------------
1:ジャック
2:12
3:戦士
4:,や"が入力可能
----------------------
1:バーン
2:17
3:騎士
4:説明2

ダブルクォート中に空の改行がある場合
----------------------
1:#途中のコメント
2:
3:
4:
----------------------
1:マァム
2:15
3:僧侶
4:説明3
----------------------
C:\\dev\\csv\\test4.csv==============================
1:#列の数が異なる場合
2:
3:
4:
----------------------
1:
2:
3:
4:
----------------------
1:ジャック
2:12
3:
4:
----------------------
1:バーン
2:
3:
4:
----------------------
1:マァム
2:15
3:僧侶
4:説明3
----------------------

このことより、Excelを用いて、CSVを開く場合、次のことがいえる。
・コメントの制御は行えない。
・Excel上のシートでCSVを開くのでデータの操作は、通常のExcelVBAと同様でいい。
・Excel上のシートで展開しているため、メモリ上のデータをアクセスする場合に比べてパフォーマンスは落ちる。

ADOを利用する場合

参照設定で「Microsoft ActiveX Data Objects x.x Library」を参照すると、データベースを操作するようにCSVを扱うことができる。

Public Sub tstAdo()
    Call DumpCsvByADO("test1.csv")
    Call DumpCsvByADO("test2.csv")
    Call DumpCsvByADO("test3.csv")
    Call DumpCsvByADO("test4.csv")

End Sub
Private Sub DumpCsvByADO(ByVal path As String)
    Dim cnn As ADODB.Connection
    Set cnn = New ADODB.Connection
    cnn.Open ("Driver={Microsoft Text Driver (*.txt; *.csv)};" & _
                 "DBQ=C:\dev\csv\;" & _
                 "FirstRowHasNames=0;")
    Dim rs As ADODB.Recordset
    'FirstRowHasNames=0でヘッダを不要にできるが、バグで動かない。
    ' http://support.microsoft.com/kb/288343/ja
    'http://support.microsoft.com/kb/257819/JA
    Dim i As Long
    Set rs = cnn.Execute("SELECT * FROM " & path)
    Debug.Print path & "=============================="
    Do While Not rs.EOF
        For i = 0 To rs.Fields.Count - 1
            Debug.Print rs.Fields(i).Value
        Next i
        Debug.Print "-------------------------"
        rs.MoveNext
    Loop
    rs.Close
    cnn.Close

    Set cnn = Nothing
    Set rs = Nothing
End Sub

実行結果

test1.csv==============================
バーン
 17 
騎士
説明2
-------------------------
マァム
 15 
僧侶
説明3
-------------------------
test2.csv==============================
ジャック
12
戦士
,や"が入力可能
Null
-------------------------
バーン
17
騎士
説明2
改行を行う
Null
-------------------------
マァム
15
僧侶
説明3
Null
-------------------------
test3.csv==============================
Null
Null
Null
Null
-------------------------
ジャック
12
戦士
,や"が入力可能
-------------------------
バーン
17
騎士
説明2

ダブルクォート中に空の改行がある場合
-------------------------
# 途中のコメント
Null
Null
Null
-------------------------
マァム
15
僧侶
説明3
-------------------------
test4.csv==============================
Null
Null
Null
Null
-------------------------
ジャック
12
Null
Null
-------------------------
バーン
Null
Null
Null
-------------------------
マァム
15
僧侶
説明3
-------------------------

この結果より、ADOを使用した場合は次のことがいえる。
・データベースのようにCSVファイルを扱える。
・存在しないフィールドはNullとなる。
・コメントは効かない。
・一行目は必ずヘッダとして扱かわれる。そして、これはバグにより回避できない。
 http://support.microsoft.com/kb/288343/ja
 http://support.microsoft.com/kb/257819/JA

Pythonの実装例

csvモジュール

Pythonの場合は、標準で入っているcsv モジュールで対応可能。
http://docs.python.jp/2/library/csv.html

CSV読み込み

# -*- coding: utf-8 -*-
import csv

def dumpCsv(path):
    print ('%s==============' % path)
    reader = csv.reader(open(path,'rb'))
    for row in reader:
        print (', '.join(row))

dumpCsv('C:\\dev\\csv\\test1.csv')
dumpCsv('C:\\dev\\csv\\test2.csv')
dumpCsv('C:\\dev\\csv\\test3.csv')
dumpCsv('C:\\dev\\csv\\test4.csv')

実行結果

C:\dev\csv\test1.csv==============
ジャック, 12, 戦士, 説明1
バーン, 17, 騎士, 説明2
マァム, 15, 僧侶, 説明3
C:\dev\csv\test2.csv==============
# 名前, 歳, クラス, 説明に(", )を入れる
ジャック, 12, 戦士, ,や"が入力可能
バーン, 17, 騎士, 説明2
改行を行う
マァム, 15, 僧侶, 説明3
C:\dev\csv\test3.csv==============
# 空行の扱い

ジャック, 12, 戦士, ,や"が入力可能
バーン, 17, 騎士, 説明2

ダブルクォート中に空の改行がある場合
# 途中のコメント
マァム, 15, 僧侶, 説明3
C:\dev\csv\test4.csv==============
# 列の数が異なる場合

ジャック, 12
バーン
マァム, 15, 僧侶, 説明3

csvの書き込み

writer = csv.writer(open('C:\\dev\\csv\\out.csv', 'wb'),quoting=csv.QUOTE_ALL)
writer.writerow(['test', 'ああああ\nああああ', '記号",'])
writer.writerow(['test'])

出力結果

"test","ああああ
ああああ","記号"","
"test"

以上の結果より、csvモジュールを使用した場合は次のことがいえる。
・コメントは取り扱えない。
・ダブルクォーテーションで囲んだ場合のコンマ、改行、ダブルクォーテーションも扱える。
・CSVの作成も容易にできる。

Node.jsの実装例

node-csv を利用する例。

Node.jsの場合は、node-csvを用いることで、CSVの読み書きができる。
もし、cp932などの文字コードも使用する場合は、iconvモジュールも使用する。

インストール方法

node-csvのインストール

npm install csv

iconvのインストール

npm install iconv

CSVの読み込み例

var csv = require('csv');
var fs = require('fs');
var Iconv = require('iconv').Iconv;
var conv = new Iconv('cp932','utf-8');
dumpCsv('test1.csv');
dumpCsv('test2.csv');
dumpCsv('test3.csv');
dumpCsv('test4.csv');

function dumpCsv(path) {
  fs.readFile(path, function(err, sjisBuf) {
    var buf = conv.convert(sjisBuf);
    console.log(path + '================');
    csv.parse(buf.toString(),{comment:'#'}, function(err, data) {
      console.log(err);
      console.log(data);
    });
  });
}

実行結果

test1.csv================
null
[ [ 'ジャック', '12', '戦士', '説明1' ],
  [ 'バーン', '17', '騎士', '説明2' ],
  [ 'マァム', '15', '僧侶', '説明3' ] ]
test2.csv================
[Error: Invalid closing quote at line 1; found "ジ" instead of delimiter ","]
undefined
test3.csv================
null
[ [ 'ジャック', '12', '戦士', ',や"が入力可能' ],
  [ 'バーン', '17', '騎士', '説明2\r\n\r\nダブルクォート中に空の改行がある場合' ],
  [ 'マァム', '15', '僧侶', '説明3' ] ]
test4.csv================
null
[ [ 'ジャック', '12' ], [ 'バーン' ], [ 'マァム', '15', '僧侶', '説明3' ] ]

test2.csvの読み込み結果がエラーになっている。
csv.parseは次のようにコメント中にダブルクォーテーションのあるデータを読み込めない。

# 名前,歳,クラス,説明に(",)を入れる

これを直すには、node_modules/csv/node_modules/csv-parse/lib/index.jsに次のようなパッチを適用する必要がある。

--- node_modules/csv/node_modules/csv-parse/lib/index_bk.js 2014-06-20 17:36:56.000000000 +0900
+++ node_modules/csv/node_modules/csv-parse/lib/index.js    2014-07-22 22:06:12.826116745 +0900
@@ -253,7 +253,7 @@
         this.closingQuote = i;
         i++;
         continue;
-      } else if (!this.field) {
+      } else if (!this.field && !this.commenting) {
         this.quoting = true;
         i++;
         continue;

これを当てた場合の結果は次のようになる。

実行結果

test2.csv================
null
[ [ 'ジャック', '12', '戦士', ',や"が入力可能' ],
  [ 'バーン', '17', '騎士', '説明2\r\n改行を行う' ],
  [ 'マァム', '15', '僧侶', '説明3' ] ]

CSVの書き込み例

var csv = require('csv')
var data = [
  ['xx', 'ああああ', 1],
  ['id', '"atagfa,asteata','#teat','aaa'],
  ['newline', 'tests\n\ntesat']
];
console.log(data);
csv.stringify(data, {quoted: true}, function(err, output) {
  console.log(err);
  console.log(output);
});

作成される文字列

"xx","ああああ","1"
"id","""atagfa,asteata","#teat","aaa"
"newline","tests

tesat"

以上の結果より、node-csvを使用した場合は次のことがいえる。

・node-csvを使用するとCSVの読み書きが行える。
・コメントを取り扱うことも可能である。
・ただし、コメント周りに上記で説明した通りのバグか仕様があるので自分でパッチを作成して適用するか、コメントを使用しない。

CSVの設計について考察

ここではCSVファイルの書式をどう設計するか考察する。

CSVファイルを見ただけで、フィールドの意味がわかるようにヘッダファイルを指定した方が望ましい。
この際、コメントとして扱うようにするとよいだろう。
いくつかのライブラリを見たが、デフォルトでは"#"をコメントして扱う場合が多かった。

また、CSVを採用するメリットとして、Excelでデータが編集しやすいこともある。
Excelさえ使えれば、データを容易にデータを作成できるのは大きな魅力であり、そのメリットを崩すようなデータフォーマットにする場合は注意が必要である。

階層構造のデータ表現

ここでは、以下のように社員を含む部署データをCSVで表現する方法を考える。
無題.png

別のファイルとして扱う

親用のファイルと子用のファイルに分割する方法

もっとも単純なのは部署用データと社員用データを別のCSVとして扱うことである。

# 部署名,住所,電話番号
営業,東京,12345
開発,大阪,34566
管理,夢の国,44444
# 社員名,所属部署,入社日,メール
大空翼,営業,1999/1/2,tubase@a.co.jp
岬太郎,営業,1999/1/3,misaki@a.co.jp
ジョセフジョースター,開発,2000/1/1,jojo@a.co.jp
空条丈太郎,開発,2000/1/3,jojo3@a.co.jp
しまこうさく,管理,2003/1/1,sima@a.co.jp

この欠点は、ファイルが完全に分かれリンクしていないので、データの親子関係がわかりづらいことにある。

子用のファイルを複数用意する案

別ファイルとして扱う方法の別の考え方として、社員のファイルを部署毎に分割して、部署.csvで紐づける方法がある。

# 部署名,住所,電話番号,社員ファイル
営業,東京,12345,営業社員.csv
開発,大阪,34566,開発社員.csv
管理,夢の国,44444,管理社員.csv
大空翼,1999/1/2,tubase@a.co.jp
岬太郎,1999/1/3,misaki@a.co.jp
ジョセフジョースター,2000/1/1,jojo@a.co.jp
空条丈太郎,2000/1/3,jojo3@a.co.jp
しまこうさく,2003/1/1,sima@a.co.jp

このメリットは、親の部署用のCSVから社員のCSVが紐づいていて親子関係がわかりやすいのと、部署名という親の名前を各レコードに記述しないですむので、ファイル数は増えるがトータルの容量が削減できることである。

1つのファイルとして扱う

もし、部署と社員を同一ファイルで扱わなければいけない場合を考える。

まず、本当にCSVファイル1つで表現しないとダメなのかを検討する。
XMLでよければ、それを採用する方法もあるし、複数にファイルを分割していい場合は、分割する。

しかし、XMLの編集はCSVより容易ではないし、ファイルをサーバーにアップロードするときなど、ファイルとして1つにまとめておきたいという要求があるのも事実である。
ここでは、階層的データをどのように、一つのファイルにまとめるかを考える。

なお、この場合、標準仕様でうたっている「フィールドの数は、ファイル全体を通じて同一であるべき」は満たせない。

1列目にデータの区分をつける

先頭のフィールドをデータ区分として「部署」なのか「社員」なのかを指定する。

部署,営業,東京,12345
部署,開発,大阪,34566
部署,管理,夢の国,44444
社員,大空翼,営業,1999/1/2,tubase@a.co.jp
社員,岬太郎,営業,1999/1/3,misaki@a.co.jp
社員,ジョセフジョースター,開発,2000/1/1,jojo@a.co.jp
社員,空条丈太郎,開発,2000/1/3,jojo3@a.co.jp
社員,しまこうさく,管理,2003/1/1,sima@a.co.jp

実際にデータが多くなった場合に、部署と社員が入り乱れてわかりづらくなると考えられる。

1フィールドに子の情報をすべて含める

部署1行で社員をすべて格納する。
部署の情報の後のフィールドに社員の数だけフィールドを用意する。
1フィールドに社員の情報をすべてふくめる。

営業,東京,12345,"大空翼,1999/1/2,tubase@a.co.jp","岬太郎,1999/1/3,misaki@a.co.jp"
開発,大阪,34566,"ジョセフジョースター,2000/1/1,jojo@a.co.jp","空条丈太郎,2000/1/3,jojo3@a.co.jp"
管理,夢の国,44444,"しまこうさく,2003/1/1,sima@a.co.jp"

この方法はExcelでデータを編集する際に、ユーザの入力が難しくなる。
たとえば部署の住所はセルのコピーアンドペーストで簡単に作成できるが、ユーザーのメールアドレスなどはセルのコピーアンドペーストができなくなる。

子のデータを特殊の文字で区切る

部署1行で社員をすべて格納する
その際、社員のデータが区別できるように開始文字と終端文字を既定する。
たとえば、フィールドに「\$大空翼」という文字があった場合、「\$」が現れるまでは、社員:大空翼のデータが格納されていると見なす。

営業,東京,12345,$大空翼,1999/1/2,tubase@a.co.jp,$,$岬太郎,営業,1999/1/3,misaki@a.co.jp,$
開発,大阪,34566,$ジョセフジョースター,2000/1/1,jojo@a.co.jp,$,$空条丈太郎,開発,2000/1/3,jojo3@a.co.jp,$
管理,夢の国,44444,$しまこうさく,2003/1/1,sima@a.co.jp,$

Excelによる編集はセルのコピーが有効になるので、楽になる。
しかし、特殊な文字の決定などのルール決めが必要で、データ作成者との意識合わせが必要になる。

階層データのまとめ

いずれも一長一短であるので、CSVを破棄してXMLで行う等を含め、なにを優先するのか見極めて書式を決める必要がある。

コードレビューのツールについての調査

このドキュメントはコードレビューを支援するツールについての調査を行った結果を記述する。

ツールの紹介

Redmine Code Review プラグイン

Redmineのプラグインとして動作する。
http://www.r-labs.org/projects/1/wiki/Code_Review

インストール方法

通常のRedmineのプラグインと同様

使用方法

リポジトリ―でコミット済みのリビジョンの差分を取り、レビュー用のコメントを記述する。

review.png

作成したレビューはプロジェクトのコードレビュータブで一覧として表示される。
review.png

記述したレビューはチケットとして登録される。
そして、レビューで作成したチケットはリポジトリから返信とステータスの遷移が行える。

Trac Peer Review Plugin

Tracのプラグインとして動作する。
http://trac-hacks.org/wiki/PeerReviewPlugin

インストール方法

Trac0.11以前でしか動作しない。

現在のtrac-lightning 3.2.0でインストールされるtrac0.12に適用しても動作しない。

以前の使用感

以前0.11で動作してた時の使用感は以下の通り。

コミットされた、特定のリビジョンのファイルを指定してコメントを記述する。
Redmineのプラグインと違って差分にコメントを記述するのではない。

登録した内容はPeerReviewタブで確認できる。

Trac CodeReviewerPlugin

Trac0.12以降のプラグインとして動作する。
http://trac-hacks.org/wiki/CodeReviewerPlugin

Redmineのプラグインと同様に作成したコメントはチケットとして登録される。

インストール方法

現在のtrac-lightning 3.2.0でインストールされるtrac0.12に適用したが、期待通りに動作しなかった。

Rietveld

Google App Engine で動作しており、下記の構成管理が利用できる。
・Git
・Mercurial
・Subversion
・Perforce
https://code.google.com/p/rietveld/

下記のページを見る限り、コミット済みのものをレビューするのでなく、メインラインのコードと、自分が作ったコードの差分を作りそれをチェックするようだ。
http://d.hatena.ne.jp/Wacky/20090720/1248087982

Google App Engine での動作が前提になっているので今回は未検証。
(ローカルのサーバーだけ動作させることができないので、使いづらい)

ReviewBoard

Pythonで作成されたコードレビューツール。
下記の構成管理ツールが使用できる。
・Bazaar
・CVS
・Git
・Mercurial
・Perforce
・Subversion
コミット前のレビューとコミット後のレビューの両方ができる。
https://www.reviewboard.org/

インストール方法

Debian7にて検証

1.下記のインストールを行う。

$apt-get install apache
$apt-get install libapache2-mod-wsgi
$apt-get install babel python-babel -y
$apt-get install memcached
$easy_install python-memcached
$apt-get install patch
$apt-get install python-dev
$easy_install ReviewBoard

# RBToolsはクライアント側にも必要。
$easy_install -U RBTools

2.レビューボードのサイトを作成する

rb-site install /var/www/reviews

このコマンドを実行すると対話形式でサイトを作成する。
DataBaseはSQLiteにしておくとデータベースの構築が楽になる。

3.作成したサイトをapacheからアクセスできるように所有者を変更しておく

$chown -R www-data /var/www/reviews

4./etc/apache2/sites-enabled/000-default に以下を追記

    WSGIPassAuthorization On
    WSGIScriptAlias "/reviews" "/var/www/reviews/htdocs/reviewboard.wsgi/reviews"

    <Directory "/var/www/reviews/htdocs">
        AllowOverride All
        Options -Indexes +FollowSymLinks
        Allow from all
    </Directory>

    # Prevent the server from processing or allowing the rendering of
    # certain file types.
    <Location "/reviews/media/uploaded">
        SetHandler None
        Options None

        AddType text/plain .html .htm .shtml .php .php3 .php4 .php5 .phps .asp
        AddType text/plain .pl .py .fcgi .cgi .phtml .phtm .pht .jsp .sh .rb

        <IfModule mod_php5.c>
            php_flag engine off
        </IfModule>
    </Location>

    # Alias static media requests to filesystem
    Alias /reviews/media "/var/www/reviews/htdocs/media"
    Alias /reviews/static "/var/www/reviews/htdocs/static"
    Alias /reviews/errordocs "/var/www/reviews/htdocs/errordocs"
    Alias /reviews/favicon.ico "/var/www/reviews/htdocs/static/rb/images/favicon.png"

使用方法

管理者権限を持つユーザについては、My accountを選択した場合は通常ユーザ、Adminを選択した場合は管理者として動作する。

review.png

管理画面

review.png

管理画面では次のようなことが行える。
・各種設定の確認と変更
・ユーザの追加
・レビューグループの追加
・リポジトリの追加
・レビューの実施状況の確認

一般ユーザ

review.png

一般ユーザは次のようなことが行える。
・レビューのリクエストを行う
・他人のコードをレビューする。

コミット前のファイルのレビューのリクエスト

コミット前のファイルで差分ファイルを作成してサーバーにアップロードする。
review.png

その後、Summary,Description,TestingDone,Groupsを入力してPublishを押下することでレビューをリクエストできる。

コミット後のファイルのレビューのリクエスト

クライアント側のリポジトリで、「rbt post」コマンドを使用するといい。

# リポジトリとReviewBoardの関連を行う
$git config --global reviewboard.url http://debian/reviews/

# 特定のリビジョン間の差分をReviewBoardに送信
$rbt post 1d00c734b018545a4c8a853b985028d383b475
39 6c904335c092de5f2aff1dec81d32f7f310416ec

すると、DraftとしてAll Review Requestsに登録される。このリクエストは他人には見えない。

review.png

あとは、コミット前のファイルのレビューのリクエストと同様に必要な項目を入力してPublishを行うとリクエストが公開される。

詳しい方法は下記を参照。
Review Board に差分を投稿する時の tips (特にgit)
http://qiita.com/thrakt/items/b758a3990560555efb1a

レビューの方法

レビュワーはレビューリクエストにコメントを記述するが、出荷可能なら「ShipIt!」にチェックをつけて返信する。
review.png

レビューの対象者は、その意見を元に変更を「Submitted」で適用するか、「Discarded」で捨てるか判断をする。
review.png

Codestriker

Perlで作られたツールで、CVS, Subversion, Clearcase, Perforce, Visual SourceSafe 、Bugzillaなどが使用できる。
http://codestriker.sourceforge.net/

2009-11-02以降更新していない。

インストール方法

Perl5.8と5.10しかサポートしていないので、新しいバージョンだと正常に動作しない可能性がある。
http://sourceforge.net/p/codestriker/discussion/135212/thread/956df489/

インストール方法は下記を参照。

Codestrikerのインストール方法
http://forza.cocolog-nifty.com/blog/2009/04/codestriker-7c9.html

・codestriker.confに使用するDBの情報と、使用するリポジトリの一覧を入力する。

・SQLiteも一応動くが、動作が不安定である。

・./bin/install.plを実行するとエラーメッセージがでるので、それに合わせて足りない、ライブラリを「cpan」を使用してインストールしていく。

・それ以前に、以下のようなエラーが発生する場合、

(Do you need to predeclare croak?)

"croak" を"die"に置き換えていく。

・Apacheのリスタートで 以下のようなエラーが発生する場合、

 ... waiting [Thu Jul 24 06:09:54 2014] [warn] The Alias directive in /etc/apache2/sites-enabled/000-default at line 74 will probably never match because it overlaps an earlier ScriptAlias.

"/var/www/cgi-bin/codestriker/cgi-bin/"と"/var/www/cgi-bin/codestriker/html/"の記述の順番を変えると上手くいく。

・アクセスしてもCSSやJSが見つからないとかいう不具合がでたら、下記のスクリプトを修正する。ここで、パスを作成している。

vim  ../lib/Codestriker/Http/Response.pm

使用方法

review.png

トピックとしてレビュー対象を登録する。
このとき、変更前のリビジョン番号、変更後のリビジョン番号、対象のファイルまたはディレクトリ名を指定する。

レビュワーは対象のコードにコメントをつけていく。
review.png

Barkeep

gitのみ。未検証
http://getbarkeep.org/

gerrit

gitのみ。未検証
https://code.google.com/p/gerrit/

まとめ

新規にレビュー用のツールを導入する場合で、Subversion,Gitの両方の選択肢を残しておきたいなら、「Redmine Code Review プラグイン」または「ReviewBoard」が有力な選択候補となる。

ReviewBoardは豊富な機能を有していて、特にコミット前のレビューが可能なのが魅力ではある。
しかしながら、日本語化が不十分なツールを別途導入することは作業者の学習コストなどのコストがかかることが予想される。

RedmineCodeReviewプラグインはコミット前のレビューはできないものの、Redmineのプラグインとして動作し、レビュー結果もチケットにすぎないので、導入コストは、ほぼないと言える。

レビューを行わない文化の組織ではRedmine Code Reviewプラグインを導入した方がスムーズにすすむ。
そして、レビューが必須な文化になった時点で、Redmine Code ReviewプラグインがものたりなくなったらReviewBoardを導入するのが望ましいだろう。

なお、構成管理がGitの場合、pull requestや、強力なブランチの作成機能で、特別にレビューツールを導入しなくてもコードレビューが可能であると考えられる。

あなたのおっしゃるレビューってどのことかしら?

ソフトウェアのレビュー

ソフトウェアの開発において、レビューが品質の確保をするために有効であることは私達は直感的、経験的に理解しています。
人は間違いを犯しますし、間違った本人よりも他人のほうが誤りを見つけ易いものです。

ここまでは、認識を共通できるものでしょう。
しかし、レビューと一言で言った場合に、その実態にかなりのギャップが生じます。

ある人にとっては、気の合う同僚とコーヒーでも飲みながら成果物をチェックしてもらう事かもしれません。
しかし、別の人にとっては会議室で衆目の前で細かい所を吊るし上げられる苦行のことかもしれません。

ある人にとっては、口で簡単に説明するだけかもしれませんし、メールやツールでコメントを書くだけかもしれません。
しかし、別の人にとっては、準備の為に大量の資料を作り、終わった後にも大量の報告書を書く事かもしれません。

プロジェクトを初めて、レビューといった場合、注意しなければいけない点として、人によって、組織によって、レビューというものに大きい差があることです。

なので、私達は最初に確認し合わなければなりません。「あなたのおっしゃるレビューってどのことかしら?」と。

レビューの種類

ジェラルド・M・ワインバーグの述べるレビュー

ワインバーグの書籍にはよく「技術レビュー」という単語が出てきます。
彼は、「パーフェクトソフトウェア」で技術レビューを次のように紹介しています。

システムのテストに知力を集結する最も単純な方法は技術レビューである。技術レビューとは、仲間がみなで集まって、コード、設計、仕様書、ユーザマニュアルといった技術製品の長所や欠点を分析し、文章化することである。

技術レビューは「情報を何らかの目的に使えるようにするために、製品に関する情報を収集する」プロセスだからである。

これらの説明を読む限り、ワインバーグの考える技術レビューを行う場合は、レビューした結果を残さなければなりません。同僚とコーヒーを飲みながら雑談まじりにやる、レビューはレビューとして認めてもらえないのです。

カール・E・ウィーガーズの述べるレビュー

次に、Karl E.Wiegersが「ピアレビュー 高品質ソフト開発のために」で述べている定義を紹介します。

まず、プロジェクトでレビューと言った場合、欠陥の発見を目的としたもの以外があり、それを次の表で紹介します。

レビューの種類 目的
教育レビュー プロジェクトに関係する技術的トピックスについて他の関係者の知識レベルを引き上げるために行う
マネージメントレビュー,準備レビュー,ゲートレビュー 上級の管理者に情報を提供して、製品のリリース、プロジェクトの継続・中断、提案の採用・却下、リソースの調整などのコミットメントの変更を決定する
ピアレビュー 作業成果物の欠陥と改善の機会を探す。
プロジェクト終了後レビュー 完了直後のプロジェクト、フェーズ終了の反省を行い、将来のプロジェクトのための教訓を得る
ステータスレビュー プロジェクトマネージャおよび他のメンバーでマイルストーンに対する進捗、発生した問題、リスクの確認を行う

私たちがソフトウェアのレビューで想像するのは、ここで言うピアレビューのことだと思います。
Peer:ピアは同僚の意味で、同僚が作成した成果物を評価するということになります。
(成果物を評価するのであって、同僚の作業実績をみるものではありません!!)

このピアレビューは、公式度によって次のように区分しています。

公式度 名称 説明
最も公式 インスペクション 最も体系的で厳格です。作成者以外の参加者はミーティング時に役割が与えられます。モデレーターがミーティングを主導し、読み手が資料を提示して自分の解釈で説明します。そしてレビュワーが指摘した問題点などは、書記が記録します。
発見された欠陥はデータとして収集して、分析をおこないます。
チームレビュー 公式なレビューですが、インスペクションほど厳格ではありません。
作成者がモデレーターを兼ねてかまいませんし、読み手の役割は存在せずモデレーターが資料を説明していきます。記録はしますが、そのデータの収集、分析は必要とされていません。
ウォークスルー 非公式なレビューです。作成者が成果物を同僚に説明してコメントをもとめます。
インスペクションがチームの品質の満足にあるのに対して、ウォークスルーは作成者の満足を求めるものです。
通常、定義された手順も存在せず、記録もしません。
ペアプログラミング ペアプログラミングも一種の非公式なレビューといえます。
ピアデスクチェック 机上チェック。自分で書いたものを自分で丹念に調べる
最も非公式 アドホックレビュー 即席のレビュー。つかまえにくいバグをつきとめるのに同僚にチェックを頼む行為があたります。
プロセスとして組み込まれておらず、目下の問題の解決以上の成果はないです。

「ピアレビュー 高品質ソフト開発のために」では当然、それぞれの詳細について記述してありますが、ここで述べるには余りにも量が多いので割愛します。
ただ、レビューという概念が一つではないということが理解いただけるかと思います。

レビューの種類のまとめ

今回は2つの解釈を紹介しました。
おそらく、ここで紹介したモノ以外の解釈や言葉の定義はあるでしょう。
本稿では、簡潔にするため、レビューの種類を次のように定義します。

・公式のレビュー
チームを主体としたレビュー。チーム内で合意した手順で実施して、記録を取ります。
・非公式のレビュー
作成者を主体としたレビュー。特定の手順はとりません。

公式レビューの実施方法の考察

ここでは、どのような手順でレビューを行うとチームにとってメリットがあるか考えます。

いつレビューするか?

ソフトウェアのプロセスが概要設計、詳細設計、実装と進むとした場合を考えましょう。
前の工程で通過してきた欠陥は、次の工程でx倍に増幅されます。
これは当然で、誤った前提で作業を行えば当然、間違えますし、後の工程ほど作業量は多いので、欠陥は確実に増幅されます。
もし、後工程に渡す前にレビューをした場合、レビューが欠陥のフィルター機能として働き、後工程の欠陥の発生を抑止する事が期待できます。

なにをレビューするか?

理想をいうなら「全て」でしょう。
しかし、それは工数的に難しいかもしれません。

ワインバーグは「最悪を最初に」を基本としてレビューすべきだと述べています。
たとえば、アルゴリズムに欠陥があるのに、スペルミスにこだわってもしょうがありません。
なにが最悪かを考え、それを防ぐための物からレビューをします。

また、カール・E・ウィーガーズは「誤りが製品全体に影響し、手戻りのコストが高くつく、あるいは失敗するようなリスクがないかを考慮にいれてインスペクションの対象を選択してください」と述べています。
たとえば、基本的な初期フェーズの文章(要求仕様書)や、クリティカルな決定の基礎になる文章、繰り返し使用されるコンポーネントなどです。

他の選択基準として、「Code Craft」では次の例を上げています。

  • 複雑度を分析して、複雑度の高いコード
  • バグの発生率が高いコード
  • 中心となるコンポーネントの中核コード
  • 信頼できないやつの書いたコード

レビューミーティングの開催

少なくとも、作者、書記、そしてミーティングを主導する司会(インスペクションでのモデレーターの役割)は必要です。
参加人数は、最小限にしておいたほうがよいでしょう。船頭が多いと山を登ることになりますし、リソースの無駄使いになります。
参加者は事前準備が必要ですが、一人2時間を超えないようにしたほうがいいです。
ミーティング自体の開催時間も2時間以内がいいでしょう。

ミーティング自体が不要ではないかという意見もあります。
たとえば、メールですませたり、Reviewboardなどの支援ツールを使用する事でミーティングを行わなくてすむかもしれません。
開発メンバーが時差のある場所に点在している場合は、魅力的な選択にみえるかもしれません。

しかし、ミーティングは開催したほうがいいと思います。
なぜならば、ミーティングには、相乗作用があります。
1人の観察が他の人の思考を刺激する格好で、ミーティングの最中にレビュアー間に相互作用が働き、その結果新しい問題が発見されることがあります。
インスペクション手法を開発したMichael Faganは、この相乗作用を「Phantom Inspector」と呼びました。

レビューの心得

作成者は、恐れないでください。
レビューは罰ゲームではありません。
レビューをしてもらうことで新しい知識を学ぶチャンスであります。
同僚は貴方の敵ではないです。彼らが指摘しているのは、共に解決すべき問題であり、貴方自身ではありません。

レビュー担当者は人ではなく成果物を批評してください。
そして、自身に謙虚になり、作成者に敬意を持ちましょう。

1からつくるより、批評する方が楽です。
できあがってしまえば、簡単にみえますが、実際やるのと、見るだけでは違います。
(ホラ、野党の時、威勢のよかった政治家が、自分の政権でどうなったか、私たちは知っているはずです!)

どんな酷いコードにみえても、相応の苦労があったので、そこは敬意を払うべきです。
そして毎回、建設的で有意義な意見を述べるように努力してください。

レビューの記録

発生したレビューの結果は記録する必要があります。
指摘事項はRedmineやTracのチケットとして登録し、議事録をWikiに記述するのもいいでしょう。
あるいは、ReviewBoardなどの専用のツールを使用するのも選択肢の一つです。
これらのツールについては下記を参照してください
コードレビューのツールについての調査
http://qiita.com/mima_ita/items/150043dea467c5842b49

可能であれば、レビュー対象の情報(複雑度やステップ数)、レビューに使用した工数と、検出した欠陥数は記録しておきましょう。
今後の見積もりや、レビュー自体の見直しに利用できます。

しかし、レビューの欠陥報告を個々の開発者の業績評価のデータとして利用するのは避けてください。
自分が報告したデータのために、自分あるいは同僚が罰せられたが最後、管理者に正確な情報を報告しなくなります。
可能であれば、プロジェクトメンバー間だけの共有情報として扱うべきです。
もし、それが難しいなら、データの集計をサボったほうがマシです。(データはなくても製品は作れますが、チームメンバーが正しい報告をしないと製品は作れません!)

レビューのガイドラインの作成

レビューを行う際は事前にガイドラインを作成し、メンバーの同意を得た上で行うべきです。
ここではガイドラインの例を紹介します。

「実践ソフトウェアエンジニアリング」でのガイドライン

ロジャー・S・プレスマンは「実践ソフトウェアエンジニアリング」で、最低限のガイドラインとして次のものを上げています。

  1. 人ではなく成果物をレビューすること。
  2. 議題を設定し守ること
  3. 議論と反論を制限する事。(全員の同意が得られない時に、その場で議論しないで別の機会に討議するべき)
  4. 問題は明確にするが、すべて解決しようとしないこと。
  5. ノートをとること
  6. 参加者を制限し、事前の準備を義務づけること
  7. レビューする成果物毎にチェックリストを作成する事
  8. 公式技術レビュー実施するためのリソースとスケジュールを割り当てること
  9. レビュー担当者全員に教育訓練を実施すること
  10. 初期のレビューはレビューすること。(ガイドライン自体レビューしろ)

レビューとインスペクションの指針となる原則

カール・E・ウィーガーズは「ソフトウェア開発の持つべき文化」でレビューとインスペクションの指針となる原則として次のことを提唱しています。
・エゴは入り口で預けてしまうこと
・作者でなく成果物を批評する事
・インスペクション中に問題を修正しようとはしないこと
・インスペクションの会議は最長2時間までにすること
・パフォーマンスやわかりやすさに影響を及ぼさない限り、スタイルの問題は差し控えること
・早い時期から頻繁に、公式でも非公式でもレビューを行うこと。

考察

ここであげたガイドラインや原則は一例でしかありません。
他の書籍を見れば別の例が出てくるでしょう。

しかし、残念なことに私や貴方の組織にピッタリと適用できるガイドラインは存在しないでしょう。
第一、他人が作った基準やガイドラインに喜んで付き従える人は中々いません。

ここは面倒でも、自分自身の組織と文化にあったガイドラインを自分たちで合意を得ながら作り、保守していくしかありません。

レビューの文化

ここまで、公式のレビューについて考えました。
しかし、多くの場合、レビューの文化自体がない事があります。

その場合は、レビューを行う文化を築くことが優先になります。
先にあげた項目を一気に適用するのは、無理です。
非公式で十分なので、レビューを行う文化を作りましょう。

まずは、自分の成果物を人に見てもらいましょう。
次に、他人の成果物をチェックしましょう。
この時は、クールなコードは賞讃したり、新しいことを伝授したりして、レビューを受ける人間がレビューが自分にとってメリットであると認識させましょう。

参考文献

ソフトウェア開発の持つべき文化
http://www.amazon.co.jp/dp/4798108715

ピアレビュー 高品質ソフトウェア開発のために
http://www.amazon.co.jp/dp/489100388X

実践ソフトウェアエンジニアリング
http://www.amazon.co.jp/dp/4817161485

Code Craft
http://www.amazon.co.jp/dp/4839921946

Perfect Software
http://www.amazon.co.jp/dp/B00EQ25B1Q

Windowsのレジストリについての概要とアプリケーションでの利用方針

概要

Microsoft Windows OSのための設定や拡張情報を格納するデーターベースです。 ハードウェアの情報や、OSの情報、各種アプリケーションの情報が含まれています。 これはWindows3.1のINIファイルに代わる仕組みで、Windows95から導入されています。

regedit32.exe または regedit.exeで、レジストリのデータベースを参照、変更することが可能です。

構造

キーと値

レジストリには2つの基本的要素 キーと値があります。

www37.atwiki.jp.png

キー

レジストリのキーはフォルダに似ていて、この中に値を追加します。キーの中にサブキーを含むことができます。

 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows

主なキーには以下のようなものがあります。

HKEY_CLASSES_ROOT (HKCR)

HKEY_CLASSES_ROOTではアプリケーションの関連付けや、拡張子・アイコン等の情報ならびに、またOLE(Object Linking Embedding)に関する情報も格納されています。

なお、OLEとは、Windowsにおいて、アプリケーションソフト間でデータを転送・共有するための仕組みの事を指します。

HKEY_CURRENT_USER (HKCU)

HKEY_CURRENT_USER では現在ログイン中のユーザの設定を格納しています。

HKEY_USER以下の一致するユーザのキーへのリンクになっています。このことにより、HKEY_CURRENT_USERキー以下の設定を変更すると、HKEY_USERの該当するユーザの設定も変更されます。

これらの設定は"Document And Setting"の各ユーザの下のNTUSER.DATとUSRCLASS.DATに格納されています。

Windows7の場合はフォルダオプションで「保護されたオペレーティングシステムをファイルを表示しない」のチェックをはずしてC:\Users\名前の直下にあります。

なお、サービスプログラムはHKEY_CURRENT_USER以下のレジストリに通常アクセスすることはできません。

(ImpersonateLoggedOnUserを使用してスレッドのユーザーを偽装すればアクセスが可能です。)

HKEY_LOCAL_MACHINE (HKLM)

HKEY_LOCAL_MACHINE ではローカルのコンピュータの設定を保存します。

HKLMに含まれる4つのサブキー SAM,SECURITY,SOFTWARE,SYSTEMは%systemRoot%System32\configのフォルダ中に実際のファイルが存在します。

HARDWAREに関しては揮発性でファイルとして、その情報をファイルに保存していません。ハードウェアのドライバとサービスについての情報はSYSTEM以下に格納します。

値は名前とデータをペアにしてキーに格納します。値の名前にバックスラッシュを含めることは可能ですが、辞めましょう。識別が困難になります。

値にはいくつかの型が存在します。

下記に主な型の例を示します。

Name 説明
REG_SZ NULL で終わる文字列です。Unicode 版の関数を使っているときは Unicode 文字列、ANSI 版の関数を使っているときは ANSI 文字列が格納されます。
REG_BINARY バイナリー値
REG_DWORD 32ビットの数値
REG_QWORD 64ビットの数値
REG_MULTI_SZ 複数行の文字値。NULL で終わる複数の文字列からなる 1 つの配列です。配列の最後にもう 1 つの NULL 文字が追加されます。
REG_EXPAND_SZ 環境変数の展開前の表記("%PATH%" など)を保持している、NULL で終わる文字列です。Unicode 版の関数を使っているときは Unicode 文字列、ANSI 版の関数を使っているときは ANSI 文字列が格納されます。環境変数の表記を展開するには、ExpandEnvironmentStrings 関数を使ってください。

レジストリのキーから値を含めた構成をハイブ(Hives)と呼びます。

 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ComputerNam\ComputerName

キー HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ComputerName
値 ComputerName

レジストリの編集方法

レジストリの修正は慎重におこなってください。

手動

レジストリエディタで参照、編集が行えます。
コマンドラインからregeditを実行してください。
なお、使用には管理者権限が必要です。

regファイル

regファイルをテキストエディタで作成することにより、レジストリの操作を対話なしで行うことができます。詳細は下記の記事を参考にしてください。

http://www.atmarkit.co.jp/fwin2k/win2ktips/1119wrtregfil/wrtregfil.html

アプリケーション設計のヒント

インストーラーの方針

インストーラーが書き込むレジストリはHKEY_LOCAL_MACHINEのみにするべきです。

なぜならば、Windowsのユーザーが追加される可能性があるからです。

アプリケーションの実装として、HKEY_LOCAL_MACHINEの値はHKEY_CURRENT_USER の値が存在しない場合に利用するようにします。

これにより、Windowsユーザの追加に対応できます。

アプリケーションの方針

管理者権限のない場合、HKEY_LOCAL_MACHINEなどマシン全体に影響を与えるレジストリへの書き込みは制限されます。

このため、基本的に書き込みが必要な情報はHKEY_CURRENT_USER に行ってください。

また、USBに入れて持ち運ぶ等のインストール不要で動作することが目的の場合、レジストリを使用してはいけません。

サイズ

レジストリエントリの長さは、利用可能なメモリによって制限を受けます。長いレジストリエントリ(2,048 バイトを超えるもの)はファイルに格納し、レジストリでそのファイル名を格納するべきです。
この結果、レジストリの効率を向上させるのに役立ちます。アイコンやビットマップなどアプリケーションの要素も同様にファイルに格納し、レジストリに直接格納することを避けてください。

セキュリティの方針

パスワードやクレジットカードの番号などの機密情報の扱いを行う場合、他のユーザから見られないように暗号化を行うべきです。

CryptProtectData/CryptUnprotectDataを使用することにより、暗号化を行うことが可能になります。

このAPIで作成した暗号文は同じユーザーでなければ複合化することができません。

CryptProtectData Function
http://msdn.microsoft.com/en-us/library/aa380261.aspx
※Win2Kでも使えますがszDataDescrを指定してください。

CryptUnprotectData Function
http://msdn2.microsoft.com/en-us/library/aa380882(VS.85).aspx

レジストリ操作のAPI

Name 説明
RegCloseKey 指定されたレジストリキーのハンドルを閉じます。RegCloseKey関数が制御を返した時点で、必ずしもレジストリへの情報の書き込みが完了したとは限りません。キャッシュの内容をハードディスクへがフラッシュする(書き込む)まで、数秒かかることがあります。レジストリ情報をハードディスクに明示的に書き込むにはRegFlushKey関数を使います。ただし、この関数は多くのシステムリソースを消費するので、どうしても必要な場合にのみ呼び出してください。
RegConnectRegistry ほかのコンピュータ上の定義済みレジストリハンドルとの接続を確立します。
RegOpenKeyEx 指定されたレジストリキーを開きます。
RegCreateKeyEx 指定されたレジストリキーを作成します。そのキーが既に存在している場合、そのキーを開きます
RegQueryInfoKey 指定されたレジストリキーに関する情報を取得します。
RegQueryMultipleValues 開いているレジストリキーに対応する値名のリストのタイプとデータを取得します。
RegDeleteKey 1 つのサブキーを削除します。
キー名の途中でNULLがまざっているとこのAPIでは消せないようです。その場合は、下記のツールを使用して消してください。
RegDelNull
http://technet.microsoft.com/ja-jp/sysinternals/bb897448
RegDeleteValue レジストリの指定されたキーから、指定されたレジストリエントリを削除します。
RegQueryValueEx 指定されたレジストリキーに所属している、指定されたレジストリエントリのデータ型とデータを取得します。
RegEnumKeyEx 指定された 1 つのレジストリキーのサブキーを列挙します。この関数を 1 回呼び出すたびに、1 つのサブキーに関する情報を取得します。
RegReplaceKey 指定されたレジストリキーとそのすべてのサブキーの裏付けとなっている 1 つのファイルを、他のファイルへを置き換えます。システムを次に再起動すると、キーとすべてのサブキーに、新しいファイルに格納されているレジストリエントリが割り当てられます。
RegRestoreKey 指定されたファイル内のレジストリ情報を読み取り、指定されたキーを上書きコピーします。このレジストリ情報で、キーとそのすべてのサブキーを指定します。
RegEnumValue 指定された 1 つのレジストリキーに所属するレジストリエントリを列挙します。この関数を 1 回呼び出すたびに、1 つのレジストリエントリの名前とそれに対応するデータ(レジストリエントリのデータ)を取得します。
RegSaveKey 指定されたキーと、そのキーに所属するすべてのサブキーとレジストリエントリを新しいファイルに保存します。
RegFlushKey 指定されたキーのすべての属性を、レジストリに書き込みます。キーに変更を加えた場合でも、RegFlushKey 関数を呼び出す必要はありません。レジストリに変更を加えた後、レジストリの遅延フラッシュ(遅延書き込み)機能がその変更結果をディスクへ書き込みます。また、システムのシャットダウン時にも、レジストリの変更結果がフラッシュされます。
RegSetKeySecurity 既に開いているレジストリキーのセキュリティを設定します。
RegGetKeySecurity 既に開いているレジストリキーを指定し、そのキーを保護するセキュリティ記述子のコピーを取得します。
RegSetValueEx 指定されたレジストリキーに所属する、指定された 1 つのレジストリエントリのデータとデータ型を設定します。
RegNotifyChangeKeyValue 指定されたレジストリキーの属性または内容が変更されようとしているときに、そのことを呼び出し側へ通知します。指定されたキーが削除されようとしているときは、呼び出し側への通知を行わないことに注意してください。
RegLoadKey HKEY_USER または HKEY_LOCAL_MACHINE の下にサブキーを作成し、指定されたファイル内の登録情報をそのサブキーに格納します。この登録情報は、ハイブ形式で指定しておきます。ハイブとは、レジストリのツリー構造のルートのすぐ下にある HKEY_USER などの枝の総称であり、この中にキー、サブキー、レジストリエントリという個別のオブジェクトが存在しています。ハイブには、1 つの行と 1 個の .LOG ファイルが対応しています。呼び出し元のプロセスに、SE_RESTORE_NAME 特権を割り当てておかなければなりません。
RegUnLoadKey 指定されたキーとそのサブキーを、レジストリから削除します。呼び出し元のプロセスに、SE_RESTORE_NAME 特権を割り当てておかなければなりません。

監視用ツール

レジストリの読み書きの監視はProcess Monitorでリアルタイムに監視できます。
http://technet.microsoft.com/ja-jp/sysinternals/bb896645

参考

インサイド MS WINDOWS 上 (マイクロソフト公式解説書)
http://www.amazon.co.jp/exec/obidos/ASIN/4891004738

開発・テスト用DB環境の管理と自動テストの考察

ここではSQLSERVER,ORACLEといったサーバーを構築するタイプのデータベースを使用した開発やテスト環境の作成・管理と自動テストについての考察を行う。
SQLITEやACCESSといったファイルベースのものは対象外とする。

作業者に与える環境について

作業者にあたえるデータベースの環境をどうするべきか?

最善の方針は1作業者が1つのデータベースのインスタンスを扱えるようにすることである。
インスタンスが困難なら、スキーマーで分離するのも手である。

もっとも不味いのは、1つの環境で複数の作業者が、開発やテストを行うことになることだ。
これは、開発者がおこなったテストが別の開発者に影響をあたえる可能性があるからだ。
このことは余計なリスクを作成する。

作業者のローカルPCにデータベースをいれるかどうか?

作業者のローカルPCにデータベースをインストールすべきかどうかは議論が出る。
作業のしやすさでいったら、ローカルPCに置いた方が良い。

これには少人数で管理が行き届く場合に限る。
なぜならば、管理が行き届かない場合、必ず古い環境で開発を続行し続けるという人間がでる。
これは余計なリスクを作ることになる。

作業者の力量が信用できるなら、ローカルで構築させたほうが間違いなく大きな成果がでるだろう。
しかし、力量が足りないなら、1つのデータベースのサーバで、管理者が、すべての作業者の環境を用意してやるほうがよい。

環境の同期

作業者毎に環境を用意した場合、問題になるのはDatabaseの同期である。

ストアドプロシージャーやファンクションの管理については、スクリプトをテキストとして管理したほうがよい。
これを通常の構成管理(SubversionやGit)で管理する。そこからダウンロードしてきて、データベースに適用すればよい。
それ用のバッチファイルなりシェルスクリプトを記述して配布する。

適用するタイミングはどうするか?

開発者に対してのデータベースなら、自分でやらせればいいだろう。
これは他のソースコードの同期と同じだ。

テスト用のデータベースは少し考えたほうがよい。
テスト中はどのバージョンでテストしているかを明確にするため、更新は避けるべきだ。
テストの区切りがついたタイミングで同期を行うといいだろう。

あと確実に問題になるのは、データベースに最新のプロシージャがあたっているかどうかのチェックが問題になると思われる。
ストアドプロシージャはデータベースの特定のテーブルに格納されているので、それを抜き出してきて比較してやればよい。
SQLSERVERの時は以下のGetSpMd5.wsfで各ストアドをmd5の値にして、それを比較してチェックをおこなえる。
https://github.com/mima3/SqlServerScript

テーブルの構成などが変わった場合の更新は難しい。
テストデータを初期化していいなら、スクリプトを流すだけでいいだろう。
しかし、テストデータを保持したい場合は、状況に合わせた臨機応変な対応を求められる。
場合によっては更新用のスクリプトを書く必要もある。

データベースに関係するテストの自動化

データベースを使った自動テストについて考察する。自動テストは3段階にわかれる。

1 テスト環境の準備
2.テストの実行
3.結果の検証。

1はテーブルの作成、ストアドの作成、初期データの作成などを行う。
この工程はスクリプトを記述しておいたほうがよい。
既存にデータベースが存在する場合でも、これらのスクリプトは現在の状態から作成できる機能があるはずだ。
(仮になくても、これらの情報はすべてシステムテーブルに書き込まれているので、自分でつくれるはず)

2 テストの実行は、まずテストデータを作成した後に、テスト対象の処理を実行する。

テストデータをCSVでもつか、SQL文のスクリプトで管理するか議論があるだろう。

可能なら、CSVでもって、テスト時になんらかの処理でSQLに変換したほうがよい。
CSVならExcelを使用してテストデータを作成できるので、楽である。
ここまではすべてスクリプトに記述できる処理である。

テスト対象の処理の実行が自動化できるかどうかは、その処理次第だろう。
ただ、仮にGUIであったとしてもUWSCなどでやろうと思えばやれる。この辺りは費用対効果で考えた方が良い。

3 テスト結果の検証。
基本的にはテスト対象の処理を実行した後に、テーブルの内容がどのように変わるか、あるいはテスト対象が出力したファイルなりが、期待通りであるかをチェックする。

テーブルの内容がどう変わったかチェックするのは、スクリプトで出来るだろう。期待値を外部ファイルなりに保持しておき、それと現在の内容をつき合わせて確認する。
期待値もテストデータと同様CSVでもてば、Excelで編集できる。

一つ注意しないといけないのは、実行のたびに変わるデータの扱いだ。たとえば、更新日や更新者などは実行のたびに変わるだろう。

これは比較の対象から外しておいた方がいいだろう。
もし、更新日時の確認をどうしてもしたい場合、単純なdiffでなく、更新日時がテスト実行開始〜終了の間に収まっているかどうかチェックする。

あるいは期待値用のデータに特定の文字列を入力しておけばいい。たとえば、「@USER」ならば現在ユーザに変換してからチェックする。

このように、実行のたびに変わるデータの扱いは多少工夫が必要だろう。

更新ロックがかかっているかどうかの試験も、自動でやろうと思えばできる。
これは、別プロセスでロック用のスクリプトを実行して指定のテーブルに更新ロックを掛けてコミット、ロールバックせずに待機する。
その間にテスト対象の処理を実行して例外がかえってくることを確認する。
テストがおわったらロック用のスクリプトを終了してやって、再度、テスト対象を実行して今度は正常に動作することを確認すればよい。

テスト用のスクリプトを書く際にはどの環境でも、動くようにを記述することが重要だ。
まちがっても、絶対パスでファイルの場所などを書いてはいけない。
また、ユーザ名などはテスト共通のユーザを使ったほうが楽だろう。

懸案事項

DBのテストの最大の問題は時間がかかることだ。
コミット時の自動テストには不適切な速度になる可能性が高い。
なので、テストには必ずやる重要項目と、それ以外にわけておき、オプションですべてテストするかどうかを指定できるようにしたほうがのぞましい。

また、本番環境で実行されないようにする工夫が必要だ。
本番環境とテスト環境のログインユーザーなどが同じにしておくと、本番データを吹き飛ばす可能性があって危険だ。テストデータをスクリプトで管理していると、それをまちがって本番に流しかねないリスクがある。

可能なら、テストデータのスクリプトの先頭に想定以外のDBではテストを中断するなどのロジックが必要かもしれない。

VBAでのエスケープ処理

通信を行なう際に、JavaScriptのescape関数は英数字以外の記号や日本語を%21とか%u3044というコードに変換してくれる。

以下のページでは、VBAでesacape処理を行うサンプルを示している。
http://www.xtremevbtalk.com/showthread.php?t=152882

残念なことに、このサンプルは日本語などのUNICODEについては考慮されていない。そこで、上記のサンプルを元に日本語対応したものを以下に示す。

Option Explicit

'*
'* 特殊文字をエスケープする
'* 参考:
'* http://www.xtremevbtalk.com/showthread.php?t=152882
'* @param[in] StringToEncode 文字
'* @return エスケープした文字 Unicodeの場合は%u
'*
Public Function escape(ByVal StringToEncode As String) As String
    Dim i As Integer
    Dim acode As Integer
    Dim char As String

    escape = StringToEncode

    For i = Len(escape) To 1 Step -1
        acode = AscW(Mid$(escape, i, 1))
        Select Case acode
            Case 48 To 57, 65 To 90, 97 To 122
                ' don't touch alphanumeric chars

            Case 32
                ' replace space
                escape = Left$(escape, i - 1) & "%20" & Mid$(escape, i + 1)

            Case Else
                ' replace punctuation chars with "%hex"
                char = Hex$(acode)
                If Len(char) > 2 Then
                    If Len(char) = 3 Then
                        char = "0" & char
                    End If
                    escape = Left$(escape, i - 1) & "%u" & char & Mid$(escape, i + 1)
                Else
                    If Len(char) = 1 Then
                        char = "0" & char
                    End If
                    escape = Left$(escape, i - 1) & "%" & char & Mid$(escape, i + 1)
                End If
        End Select
    Next
End Function
'* http://www.xtremevbtalk.com/showthread.php?t=152882
Public Function unescape(ByVal StringToDecode As String) As String
    Dim i As Long
    Dim acode As Integer, sTmp As String
    unescape = StringToDecode

    If InStr(1, unescape, "%") = 0 Then
         Exit Function
    End If
    For i = Len(unescape) To 1 Step -1
        acode = Asc(Mid$(unescape, i, 1))
        Select Case acode
            Case 48 To 57, 65 To 90, 97 To 122
                ' don't touch alphanumeric chars
                DoEvents

            Case 37: ' Decode % value
                If Mid$(unescape, i + 1, 1) = "u" Then
                    sTmp = CStr(CLng("&H" & Mid$(unescape, i + 2, 4)))
                    If IsNumeric(sTmp) Then
                        unescape = Left$(unescape, i - 1) & ChrW$(CInt("&H" & Mid$(unescape, i + 2, 4))) & Mid$(unescape, i + 6)
                    End If
                Else
                    sTmp = CStr(CLng("&H" & Mid$(unescape, i + 1, 2)))
                    If IsNumeric(sTmp) Then
                        unescape = Left$(unescape, i - 1) & ChrW$(CInt("&H" & Mid$(unescape, i + 1, 2))) & Mid$(unescape, i + 3)
                    End If
                End If
        End Select
    Next
End Function

実行例:

?escape("あたしはかこめabcdefg%#?<>*+[]<>/\'""")
%u3042%u305F%u3057%u306F%u304B%u3053%u3081abcdefg%25%23%3F%3C%3E%2A%2B%5B%5D%3C%3E%2F%5C%27%22

?unescape("%u3042%u305F%u3057%u306F%u304B%u3053%u3081abcdefg%25%23%3F%3C%3E%2A%2B%5B%5D%3C%3E%2F%5C%27%22")
あたしはかこめabcdefg%#?<>*+[]<>/\'"

gitoliteを用いてDebianとWindowsでGitのクライアントサーバーを作成する

このドキュメントは下記のようなGitサーバーの環境を構築するためのメモである。

種別 OS
サーバー Debian
クライアント Windows7

Gitのインストール

Debian側

apt-get install git

以降,gitコマンドが使用できる。

Windows側

MSysGitを下記からインストールする。
http://msysgit.github.io/

Git Bashがインストールされ、そこからGitコマンドが操作可能。
git.png

Gitoliteのインストール

Gitoliteはユーザー管理やアクセス管理を行うためのツールである。
https://github.com/sitaramc/gitolite

以下にその導入手順を説明する。

  1. Debian側でGitoliteを動作させるためのgitユーザを作成する。
adduser git

2. Gitoliteの管理を行うためのユーザを作成する。クライアント側で公開キーと秘密キーを作成する。GitBashで以下のコマンドを実行する。

$ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/xxxx/.ssh/id_rsa): admin
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in admin.
Your public key has been saved in admin.pub.
The key fingerprint is:
1f:98:a4:47:16:bd:d9:9f:ea:97:6e:37:50:38:29:dd xxxx@xxxx-PC

これによりカレントディレクトリに以下のファイルが作成される。

名前 説明
admin 秘密キー
admin.pub 公開キー

3.クライアント側の「.ssh」フォルダを作成する。

下記のフォルダが存在するか、なければ作成する。
C:\Users\ユーザ名.ssh

先ほど作成してadmin,admin.pubを.sshフォルダにコピーする。

また以下のようなconfigファイルを作成する

config

Host debian
    User git
    Hostname debian
    Port 22
    Identityfile ~/.ssh/admin
名前 説明
Host 接続時に使用する名前
User ログインユーザ名
Hostname GitサーバーのIPまたはホスト名
Port 上記のポート番号
Identityfile 秘密鍵へのパス

4.サーバー側に「git」ユーザーでログインして、同ユーザがアクセスできる場所に2.で作成した公開鍵をコピーする。

5.サーバー側で「git」ユーザでgitoliteのソースコードのダウンロードする。

git clone git://github.com/sitaramc/gitolite gitolite-source

6.インストール先のフォルダを作成して、インストールを行う

mkdir -p $HOME/bin
gitolite-source/install -to $HOME/bin

7.「~/.ssh/authorized_keys」 が空であるか、存在しないこと。

8.gitolite のセットアップを行う

$HOME/bin/gitolite setup -pk admin.pub

WARNING: /home/git/.ssh/authorized_keys missing; creating a new one
    (this is normal on a brand new install)

9.クライアント側でgitolite-adminを取得する

$ git clone git@debian:gitolite-admin
Cloning into 'gitolite-admin'...
Enter passphrase for key '/c/Users/xxxx/.ssh/admin':★2で入力したpassphrase を入力する
remote: Counting objects: 39, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 39 (delta 2), reused 0 (delta 0)
Receiving objects: 100% (39/39), 5.57 KiB | 0 bytes/s, done.
Resolving deltas: 100% (2/2), done.
Checking connectivity... done.

gitolite-adminフォルダが作成されている。
このフォルダには2つフォルダが含まれている。

confフォルダは管理するリポジトリとユーザの情報を設定するファイルが存在する。
keydirフォルダには、認証対象のユーザの公開鍵を格納する。

ユーザの管理やリポジトリの追加はすべて、このgitolite-adminリポジトリを更新することで行う。

testingリポジトリの確認

gitolite-adminの初期設定にはtestingリポジトリが用意されている。
ここではadminユーザでtestingリポジトリを操作してみる。

1.testingリポジトリの取得

$ git clone ssh://debian/testing
Cloning into 'testing'...
Enter passphrase for key '/c/Users/xxx/.ssh/admin':★adminのパスフレーズを入力
warning: You appear to have cloned an empty repository.
Checking connectivity... done.

ここでtestingという空のリポジトリが作られる。

2.いつものgitの操作のようにファイルを追加してローカルにコミットする

$cd testing
$vim test.txt
$git add test.txt
$git commit -m "first commit"

3.サーバー側に反映させる。

$ git push origin master
Enter passphrase for key '/c/Users/xxxx/.ssh/admin':★adminのパスフレーズを入力
Counting objects: 3, done.
Writing objects: 100% (3/3), 218 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://debian/testing
 * [new branch]      master -> master

リポジトリの追加とユーザの追加

ここではリポジトリの追加と、ユーザの追加の方法を説明する。

1.クライアント側でadminの時と同様にaliceというユーザを作成して、その公開鍵をadminを操作する端末にコピーする。

2.admin側でalice.pubをgitolite-admin/keydirにコピーする。

3.admin側でgitolite-admin/conf/gitolite.confを編集する

gitolite.conf

repo gitolite-admin
    RW+     =   admin

repo testing
    RW+     =   @all

repo sample
    RW+    =    admin
    RW+    =    alice

この例ではsampleというリポジトリを新たに作成して、adminとaliceに読み書きの権限を付与している。

4.admin側でgitolite-adminのローカルリポジトリをコミットする。

$git add conf
$git add keydir
$git commit -m "add sample rep"

5.サーバー側を更新する

$ git push origin master
Enter passphrase for key '/c/Users/xxxx/.ssh/admin':★adminのパスフレーズを入力

6.aliceの端末でリポジトリを取得する。

git clone ssh://debian/sample

参考

ここでは必要最小限の情報しか記述していない。
さらなる詳細は下記を参考の事。

4.8 Git サーバー - Gitolite
http://git-scm.com/book/ja/Git-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-Gitolite
https://github.com/sitaramc/gitolite
http://gitolite.com/gitolite/#contact

GitPrepを使用してプライベートのDebianにGitHubのようなサイトを構築する

本ドキュメントではGitPrepを使用してプライベートなGitHubのようなサイトを構築する方法について記述する。

環境

Debian7
Perl v5.14.2

インストール方法

1.下記のサイトからZIPをダウンロード
https://github.com/yuki-kimoto/gitprep

2.Debianでフォルダを展開して、下記のシェルスクリプトを実行

./setup.sh

このときのログを次のコマンドで確認できる。

vi setup/build.log

今回は下記の項目がエラーとなった。

FAIL Failed to fetch distribution Validator-Custom-0.22
FAIL Failed to fetch distribution Params-Check-0.36
FAIL Failed to fetch distribution Module-Load-Conditional-0.54
FAIL Failed to download http://backpan.perl.org/authors/id/D/DA/DAGOLDEN/Perl-OSType-1.003.tar.gz
FAIL Failed to fetch distribution IPC-Cmd-0.80

インストールに失敗したモジュールを自前でインストールする。

 perl cpanm Validator::Custom
 perl cpanm Params::Check
 perl cpanm Module::Load::Conditional
 perl cpanm Perl::OSType
 perl cpanm IPC::Cmd

3.gitprepを起動する

./gitprep

以降、以下のようなURLでアクセスが可能になる。
http://debian:10020/

なお、WebServerをとめるには下記のコマンドを実行する。

./gitprep --stop

gitprepの使い方

管理ユーザの登録

1.初回に下記のアドレスにアクセスすると管理用のユーザのパスワードの登録を求められる。

http://debian:10020/
git2.png

2.管理ユーザの登録に成功すると次のようなメッセージが表示される。

git5.png

通常ユーザの登録

1.画面右上の「Sign in」を入力して今登録した管理ユーザでログインする。
管理ユーザではユーザの作成は行えるが、リポジトリの作成は行えない。

2.Usersを押すとユーザを登録する画面に遷移できる。
git5.png

git5.png

3.ユーザ登録画面で下記のように「alice」と「Joe」を追加したものとする。
git5.png

以降、alice,Joeでログインが可能になる。

リポジトリの追加

1.aliceでログインをする。

git5.png

管理ユーザとことなり、「Create Repogitory」のメニューが追加されていることがわかる。

2.必要な情報を入力して「testproject」を登録する。
git4.png

3.作成に成功すると、リポジトリ―の操作例が表示される。
git5.png

また、aliceのホームページには今追加した
testprojectが表示される。
git4.png

4.クライアントで作成したtestprojectを操作してみる。

# リポジトリを作成する
$ mkdir testproject
$ cd testproject
$ git init

# READMEの追加
$ vim README
// なんらかのファイルを作成

$ git add README
warning: CRLF will be replaced by LF in README.
The file will have its original line endings in your working directory.

# ファイルをコミット
$ git commit -m "first commit"
[master (root-commit) 4802d3e] first commit
warning: CRLF will be replaced by LF in README.
The file will have its original line endings in your working directory.
 1 file changed, 2 insertions(+)
 create mode 100644 README

# リモートサーバーの登録
$ git remote add origin http://debian:10020/alice/testproject.git
XXX@XXXX-PC ~/git/testproject (master)

# リモートサーバーにpush
$ git push -u origin master
Username for 'http://debian:10020': alice
Password for 'http://alice@debian:10020': <<< aliceのパスワード
Counting objects: 3, done.
Writing objects: 100% (3/3), 216 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To http://debian:10020/alice/testproject.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

リポジトリに作業者を追加する。

1.aliceでtestprojectを開いて、「Settings」ボタンを押す
git4.png

2.Collaboratorsを押下する。
git4.png

3.「joe」を入力して追加する。
git4.png

4.以降「joe」がtestprojectへの操作を行える。

$ git clone http://debian:10020/alice/testproject.git
$ cd testproject
$ vim README
$ git add README
$ git commit -m "Joe Commit"
$ git push -u origin master
Username for 'http://debian:10020': joe
Password for 'http://joe@debian:10020': << joeのパスワード

その他留意点

・gitprep/gitprep.confでタイムゾーンやgitのパスなどの各種項目を変更できる。

・adminのパスワードを忘れたり、変更したくなったら、gitprep/gitprep.confのreset_password=1を設定するといい。

・apache経由でもできるらしいが、以下のエラーが出たので諦めた
https://github.com/yuki-kimoto/gitprep/issues/52

参考

http://d.hatena.ne.jp/perlcodesample/20130421/1366536119

https://github.com/yuki-kimoto/gitprep

WinMergeでSQLiteの比較を行う方法

このドキュメントではWinMergeを用いてSQLiteの比較を行う方法について解説する。

導入方法

1.SQLite用のODBCをインストールする。
http://www.ch-werner.de/sqliteodbc/

下記のいずれか、または両方インストールすること。

sqliteodbc.exe
sqliteodbc_w64.exe

2.次のようなファイルを作成する

SqliteToText.sct

<scriptlet>

<implements type="Automation" id="dispatcher">
    <property name="PluginEvent">
              <get/>
        </property>
    <property name="PluginDescription">
              <get/>
        </property>
    <property name="PluginFileFilters">
              <get/>
        </property>
    <property name="PluginIsAutomatic">
              <get/>
        </property>
        <method name="UnpackFile"/>
        <method name="PackFile"/>
</implements>

<script language="VBS">
Option Explicit

Function get_PluginEvent()
         get_PluginEvent = "FILE_PACK_UNPACK"
End Function

Function get_PluginDescription()
         get_PluginDescription = "SqliteToText"
End Function

Function get_PluginFileFilters()
         get_PluginFileFilters = "\.sqlite(\..*)?$;\.sqlite3(\..*)?$;\.db(\..*)?$"
End Function

Function get_PluginIsAutomatic()
         get_PluginIsAutomatic = True
End Function

Function UnpackFile(fileSrc, fileDst, pbChanged, pSubcode)
    Dim cnn
    Dim Filename
    Dim rs
    Dim i
    Dim tableNameDict
    Dim name
    Dim fso
    Dim fo

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set fo = fso.CreateTextFile(fileDst, True)

    Set cnn = CreateObject("ADODB.Connection")
    cnn.CursorLocation = 3 '
    FileName = "test.sqlite"
    cnn.Open "DRIVER=SQLite3 ODBC Driver;Database=" & fileSrc & ";"

    Set rs = cnn.Execute("SELECT * FROM sqlite_master;")
    i = 0

    rs.MoveFirst
    Do While Not rs.EOF
        name = rs.Fields("Name").Value
        fo.WriteLine "[" & name & "]"
        fo.WriteLine rs.Fields("sql").Value
        If rs.Fields("Type").Value = "table" Then
            call showTable(fo, cnn , name)
        End if
        rs.MoveNext
        i = i + 1
    Loop 

    rs.Close
    Set rs = Nothing

    cnn.Close
    Set cnn = Nothing

    fo.Close
    Set fo = Nothing
    Set fso = Nothing

    pbChanged = True
    pSubcode = 0
    UnpackFile = True

End Function

Function PackFile(fileSrc, fileDst, pbChanged, pSubcode)
    PackFile = False
End Function

Private Sub showTable(byref fo, byref cnn, byval tableName)
    Dim rsTable
    Dim i
    Dim fieldCount
    Dim data
    Set rsTable = cnn.Execute("SELECT * FROM " & tableName & " ORDER BY 1")
    fieldCount = rsTable.Fields.count
    rsTable.MoveFirst
    Do While Not rsTable.EOF
        For i = 0 To fieldCount - 1
            if i = 0 Then
                data = rsTable(i).Value
            Else
                data = data & vbTab & rsTable(i).Value
            End If
        Next
        rsTable.MoveNext
        fo.WriteLine data
    Loop
    rsTable.Close
    Set rsTable = Nothing
End Sub

</script>
</scriptlet>

3.WinMergeの展開プラグインを自動にするか、SqliteToText.sctを明示して比較
を実行する。
無題.png

このように、テーブル、トリガー、ビュー、インデックスの情報、とテーブルの中身を比較する。

解説

SQLiteのメタ情報

sqlite_master

SQLiteはsqlite_masterにtable,index,view,trrigerの情報を格納している。
このテーブルを取得することで下記の情報が取得できる。

名前 説明
type オブジェクトの情報を示す。'table', 'index', 'view','trigger'のいずれか
name オブジェクト名
rootpage テーブルとインデックスのためのroot b-treeページのページ番号
sql SQL

なお、このテーブルには一時テーブルの情報は格納されない。
一時テーブルの情報を取得するには下記のテーブルからデータを取得する。

・sqlite_temp_master

テーブルの列情報

ここでは取得してないが、テーブルの列情報は下記のSQLで取得できる。

PRAGMA table_info('テーブル名');

シーケンス情報

AUTOINCREMENTをPRIMARYキーで作成した時にsqlite_sequenceが作成される。
AUTOINCREMENTはsqlite_sequenceを元に作成される。

コマンドラインを利用したダンプ方法

最新のコマンドラインツールがあるなら、以下の方法で似た情報が取得できる。

sqlite3 test.sqlite .dump

参考

The SQLite Database File Format
http://www.sqlite.org/fileformat.html

Gitのフックの説明と挙動の検証

このドキュメントではフックの説明と、フックの挙動を検証するための方法とその結果を記述する。

フックの説明

Gitは特定のコマンドが実行された場合に、スクリプトを起動させることができる。
そのスクリプトは.git/hooks フォルダに特定の名称で作成することで実行される。

検証プログラム

検証環境は以下の通り
Debian7.0
git version 1.7.10.4

フックの動作を検証するために、各フックスクリプトに下記を記述する。

# !/bin/sh
logger "********************************************"
logger ${0##*/}
logger "param $*"
logger "param cont $#"
logger "input..."
while read i; do
  logger ${i}
done

後は下記のコマンドでsyslogを監視しながらgitの操作を行う

tail -f /var/log/messages

これにより、フックスクリプトの発生順と、パラメータ、標準入力の検証が行える。

フックの確認

commitコマンドのフック

commitコマンドのフックの実行順序は以下の通りである。

1.commit コマンド
2.pre-commitスクリプト実行
3.デフォルトのログメッセージの準備
4.prepare-commit-msgスクリプト実行
5.コミットメッセージ入力用のエディタ起動
6.commit-msgスクリプト実行
7.コミットの作成
8.post-commitスクリプト実行
9.--amendで実行した場合はpost-rewriteスクリプト実行

pre-commit

「git commit」によって呼び出される。commitコマンドに「--no-verify」オプションをつけると呼び出されない。

引数は存在しない。
0以外の終了コードでコマンドを中断する。

prepare-commit-msg

「git commit」によって呼び出される。デフォルトログメッセージの準備が終わった後、そしてエディターが起動する前に呼ばれる。

最大で3つの引数をとる。
第一引数はコミットメッセージを保存したファイルへのパス
第二引数はコミットのタイプである。

Type 説明
なし オプションなし
message -m, -Fオプションがある場合
template -tオプションなどでテンプレートが指定された場合
merge mergeによるコミットの場合
squash --squash オプションでブランチのコミットをまとめた場合
commit -c, -C ,--amendをオプションとして使用した場合。この場合、第三引数にSHA-1が与えられる

0以外の終了コードでコマンドを中断する。

commit-msg

「git commit」によって呼び出される。commitコマンドに「--no-verify」オプションをつけると呼び出されない。

1つの引数を取る
第一引数は現在のコミットメッセージを保存した一時ファイルへのパスになる。

0以外の終了コードでコマンドを中断する。

post-commit

「git commit」によって呼び出される。
引数は存在しない。
コミットを作成したあとに呼ばれ、このスクリプトはgit commit に影響を与えない。
通常、commitの通知に使用される。

post-rewrite

「git commit --amend」や「git rebase」などでコミットログの書き換えが発生された場合に実行される。

引数は1つのみで、amend または rebaseとなる。

また標準入力から以下のデータを取得できる。

<old-sha1> SP <new-sha1> [ SP <extra-info> ] LF

extra-infoはコマンド依存。これが空の場合、前のSPも省略される。

このスクリプトはgit commit に影響を与えない。

am コマンドのフック

git format-patch で作ったパッチを git am で適用する際に実行されるフックの順番は以下のとおりになる。

  1. git am コマンド実行
  2. applypatch-msgスクリプト実行
  3. パッチが適用される
  4. pre-applypatchスクリプト実行
  5. コミットの作成
  6. post-applypatchスクリプト実行

このamコマンドでコミットが作成されても、commit用のフックスクリプトは実行されない。
デフォルトのapplypatch-msgなどは、コミット用のフックスクリプトを実装するサンプルになっている。

applypatch-msg

「git am」コマンド実行時に呼び出される。
引数を一つとり、それはミットメッセージを含む一時ファイル名になる。
0以外の終了コードでコマンドを中断する。

pre-applypatch

「git am」コマンド実行時に呼び出される。
パッチが適用されたのちに、コミットを作成する前に、呼び出される。
引数は存在しない。
0以外の終了コードでコマンドを中断する。

post-applypatch

「git am」コマンド実行時に呼び出される。
パッチが適用され、コミットが作成されたのちに呼び出される。
引数は存在しない。
このスクリプトは「git am」の結果に影響を与えない。
通常は通知に用いられる。

rebaseコマンドのフック

rebaseコマンドを実行する前にpre-rebaseスクリプトが実行される。

その後、次のフックスクリプトが実行されていた。
1.post-checkoutスクリプト実行
2.applypatch-msgスクリプト実行
3.pre-applypatchスクリプト実行
4.post-applypatchスクリプト実行
5.post-rewriteスクリプト実行

2~4はコミットの数だけ複数回。

pre-rebase

rebaseコマンドを実行する前にpre-rebaseスクリプトが実行される。

引数として最大2つ取る。
第一引数は再配置先のブランチ名
第二引数は再配置をするブランチ名で、現在ブランチの場合はブランクになる。

0以外の終了コードでコマンドを中断する。

checkoutコマンドのフック

checkoutコマンドが完了した時にpost-checkoutスクリプトを実行する。

post-checkout

このスクリプトはcheckoutコマンドでワークツリーが更新された後に実行される。
次の3つの引数を取る。
第1引数 変更前のHEADのSHA
第2引数 変更後のHEADのSHA
第3引数 ブランチの変更があったかどうかのフラグ 1:変更あり 0:なし

このスクリプトはコマンドの結果に影響を与えない。

mergeコマンドのフック

mergeコマンドを実行した場合、次の順番でフックメッセージが発生する

  1. prepare-commit-msgスクリプト実行
  2. コミットメッセージの入力
  3. コミットの完了
  4. post-mergeスクリプト実行

post-merge

meregeコマンドが完了されたら実行される。
このスクリプトはsquashマージかどうかの引数を1つだけ与える。
このスクリプトはコマンドの結果に影響を与えない。

pushコマンドのフック

クライアントサイド

  1. push コマンドを実行
  2. pre-push スクリプトを実行

リモート側

  1. クライアントからのpushを受信
  2. pre-receiveスクリプトを実行
  3. ブランチ単位でそれぞれ一度ずつupdateスクリプトを実行
  4. push処理が完了
  5. post-receiveスクリプトを実行
  6. post-updateスクリプトを実行

pre-push

pushを実行する前にクライアントで実行されるフックスクリプト
未検証。
Version 1.8.2 から。

pre-receive

pushを受信したらリモート上で実行されるスクリプト。
このスクリプトは引数を取らない。
しかし、プッシュされた参照のリストを標準入力から受け取る。
0以外の終了コードでコマンドを中断する。

update

pushを実行した場合update はブランチ単位でそれぞれ一度ずつリモート上で実行される。
3つの引数をとる。
第一引数:関連する参照名 例:refs/heads/master
第二引数:Push前の参照のオブジェクトのSHA
第三引数:新しい参照のSHA

0以外の終了コードでコマンドを中断する

post-receive

pushが完了したらリモート上で一度だけ呼ばれる。
引数はないが、標準入力からpre-receiveと同じ情報が取得できる。
このスクリプトはコマンドの結果に影響を与えない。

このスクリプト中の標準出力(echo)の結果はクライアントに返される。

echo  ECHO POST RECEIVE
root@debian:/share/testgit/hooktest_clone# git push origin test3:master
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 279 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
remote: ECHO POST RECEIVE <<<<< remoteからのメッセージ
To /share/testgit/hooktest
   a8e54a0..437df47  test3 -> master
root@debian:/share/testgit/hooktest_clone#

post-update

すべての参照が実行されたあとにリモート上で呼び出される。
このスクリプトの引数は可変引数となっており、実際に更新された参照(ex. refs/heads/master)が与えられる。
このスクリプトはコマンドの結果に影響を与えない。

このスクリプト中の標準出力(echo)の結果はクライアントに返される。

参考

7.3 Git のカスタマイズ - Git フック
githooks(5) Manual Page