カスタムプロバイダーを作ってみよう その4

前回の続きです。

今回はタブ補完から実装します。

タブ補完への対応

タブ補完に対応させるには、以下のメソッドをオーバーライドします。

  • void GetChildNames(string path, ReturnContainers returnContainers)

実装する前にタブ補完とはどのような機能なのかということを説明しておきます。

PowerShellでcdやremなどのコマンドを実行する時に、その引数としてファイル名などのパスを渡す必要がありますが、その時パスを全て入力するのではなく、例えば「100.txt 」を入力する時に「1」だけ入力してTabキーを押すと自動的に補完してくれます。再度Tabキーを押すと次の候補を表示してくれる、という機能がタブ補完です。

PS c:\> cd W

Tabキーを押すと、

PS c:\> cd WINDOWS

補完される。

では、さっそく実装してみましょう。

protected override void GetChildNames(string path, ReturnContainers returnContainers) {
     foreach(var f in Directory.GetFiles(path)) {
         base.WriteItemObject(Path.GetFileName(f), f, false);
     }
}

やる事は単純です。渡されたパスに存在するファイルの一覧を列挙して、ファイル名だけをパイプラインに出力します。これだけです。

こうすることで、タブ補完の候補がPowerShellに渡される事になります。後はPowerShell側が勝手にやってくれます。

デバッグ実行して、動作を試してみて下さい。何故かこのメソッドは二回呼び出されますが気にしないでおきましょう。

まだ実装していない機能で、

  • Set-Item
  • Get-Item
  • Rename-Item
  • Copy-Item

などがありますが、これらはほとんど今までと同じ方法で実装できるのでこれ以上の説明はしません。

なので、他とは少し変わった機能であるGet-ContentSet-Contentコマンドレットを実装してみます。

Get-Contentコマンドレットへの対応

Get-Contentコマンドレットに対応するにはプロバイダーはIContentCmdletProviderインターフェースを実装する必要があります。

IContentCmdletProviderインターフェースには以下のメソッドが定義されています。

  • void ClearContent(string path)
  • object ClearContentDynamicParameters(string path)
  • IContentReader GetContentReader(string path)
  • object GetContentReaderDynamicParameters(string path)
  • IContentWriter GetContentWriter(string path)
  • object GetContentWriterDynamicParameters(string path)

動的パラメータのありなしをペアとして三つのメソッドがあります。

この中でGet-Contentの為に必要なのはGetContentReaderメソッドとそれの動的パラメータ版です。

GetContentReaderメソッドはIContentReaderインターフェースを実装したオブジェクトを返す必要があります。

IContentReaderインターフェースには以下のメソッドが定義されています。

  • void Close()
  • IList Read(long readCount)
  • void Seek(long offsert, SeekOrigin origin)
  • void Dispose()

なんとなくストリームっぽい感じですね。それぞれの役割はだいたいわかるでしょう。

とりあえずこのインターフェースを実装したクラスを作ってみます。

HogeFileContentReader.cs

public class HogeFileContentReader : IContentReader {
     private StreamReader sr;
     
     public HogeFileContentReader(string path) {
         sr = File.OpenText(path);
     }
     ~HogeFileContentReader() {
         Dispose();
     }

     public void Close() {
         if(sr != null) {
             sr.Close();
             sr = null;
         }
     }
     public IList Read(long readCount) {
         var lines = new List<string>();

         for(var i = 0; i < readCount; i++) {
             var line = sr.ReadLine();

             if(line == null) break;

             lines.Add(line);
         }
         return lines;
     }
     public void Seek(long offset, SeekOrigin origin) {
         throw new NotImplementedException();
     }

     public void Dispose() {
         Close();

         GC.SuppressFinalize(this);
     }
}

コンストラクタで引数にパスを指定してファイルを読み込み、ReadメソッドでreadCountの分だけファイルを読み込んで、その結果をリストで返しています。後はお馴染みの処理ですね。

あとはこのクラスをGetContentReaderメソッドでインスタンス化して返すだけです。

public IContentReader GetContentReader(string path) {
     return new HogeFileContentReader(path);
}

public object GetContentReaderDynamicParameters(string path) {
     return null;
}

動的パラメータはいらないのでnullを返しておきます。

これでデバッグ実行して、以下のコマンドを実行してみて下さい。

PS Hoge:\> Get-Content 300.txt

これでファイルの中身が出力されるはずです。

ちなみにこのままではUTF8のファイルしか正常に表示できないので、動的パラメータでエンコードを指定できるようにしておきましょう。

まずはパラメータクラスを定義します。

GetContentParameters.cs
[Serializable]
[DebuggerStepThrough]
public class GetContentParameters : FileSystemContentDynamicParametersBase {
}

Get-Contentの動的パラメータクラスはFileSystemContantDynamicParameterBaseクラスから派生すると便利です。Encodingプロパティも既に定義されています。

このクラスのインスタンスを返すようにGetContentReaderDynamicParametersメソッドを変更しておきます。

public object GetContentReaderDynamicParameters(string path) {
     return new GetContentParameters();
}

HogeFileContentReaderのコンストラクタもエンコードを指定できるように変更しておきます。

public HogeFileContentReader(string path, Encoding encoding) {
     sr = new StreamReader(path, encoding);
}

GetContentReaderメソッドも動的パラメータを取得するように変更しておきます。

public IContentReader GetContentReader(string path) {
     var gcParams = base.DynamicParameters as GetContentParameters;
     var encoding = gcParams != null ? gcParams.EncodingType : Encoding.UTF8;

     return new HogeFileContentReader(path, encoding);
}

これでGet-Contentコマンドレットでエンコードを指定できるようになりました。

PS Hoge:\> Get-Content -Encoding UTF8 300.txt 

Get-Contentの説明が長くなってしまったので、Set-Contentは次回にします。