2
2

自作の.NET製CLIツールにタブ補完機能を付ける(その2)~入力候補を動的に取得する

Posted at

タブ補完の入力候補を動的に取得する

この記事は、以前の記事の続きとなります。

前回は、.NETのコンソールアプリケーションにタブ補完機能を追加するサンプルを紹介しました。

今回は、そのサンプルアプリを修正し、Web APIなどから動的に取得した値をコマンドパラメータの入力候補として利用できるようにします。

ソースコード

ソースコードは以下のリンクからご覧いただけます。

Googleサジェストから入力候補を動的に取得する

今回のサンプルでは、以下のようなコマンドを作成します。

  • Google検索を行うコマンドを作ります
  • --queryパラメータに検索ワードを指定して検索します
  • 検索ワードには、入力した文字からGoogleのサジェストAPIを使って予測候補を提示します

google-command.gif

–queryオプションの後に検索キーワードを入力します。その後、[Tab]キーまたは[Ctrl]+[Space]キーを押すと、コード補完が作動します。これにより、入力したキーワードに関連する候補が表示されます。

Googleコマンド

まず、Google検索を行うコマンドを実装します。指定した検索クエリを付けたGoogle検索のURLでブラウザを開くだけのシンプルなコマンドです。

Commends\Google.cs
partial class MyCommands
{
    /// <summary>
    /// 指定したクエリでGoogle検索ページを開きます
    /// </summary>
    /// <param name="query">検索クエリ</param>
    [Command("google")]
    public void GoogleSearch(string query)
    {
        //既定のブラウザURLを開く
        var process = new Process()
        {
            StartInfo = new ProcessStartInfo()
            {
                FileName = "pwsh",
                Arguments = $"-Command start https://www.google.com/search?q={System.Web.HttpUtility.UrlEncode(query)}"
            }
        };
        process.Start();
    }
}

ICommandCompletionItemの実装

次に、Googleコマンド用の補完候補を返すICommandCompletionItemを実装したクラスを作成します。

Commends\Google.cs
/// <summary>
/// Googleコマンド用 ICommandCompletionItem の実装
/// </summary>
class GoogleCompletionItem : ICommandCompletionItem
{
    private HttpClient _httpClient;

    private static readonly IEnumerable<string> options = ["--query"];

    public GoogleCompletionItem(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public string CommandName => "google";

    public async Task<IEnumerable<string>> GetCompletionItemsAsync(string optionName, string wordToComplete)
    {
        if (optionName != "--query" || wordToComplete=="") { return []; }

        var response = await _httpClient.GetAsync($"https://google.com/complete/search?q={System.Web.HttpUtility.UrlEncode(wordToComplete)}&output=toolbar");
        using var stream = await response.Content.ReadAsStreamAsync();
        var xml = XDocument.Load(stream);
        return xml.Descendants("suggestion")
            .Select(element => $"\"{(element.Attribute("data")?.Value ?? "")}\"");
    }

    public IEnumerable<string> GetAllOptions() => options;

    public IEnumerable<string> GetOptions(string wordToComplete) 
        => options.Where(o => o.Contains(wordToComplete, StringComparison.InvariantCultureIgnoreCase));
}

HttpClientの利用 

GoogleサジェストへのリクエストにはHttpClientを使用します。HttpClientのインスタンスはコンストラクタで受け取ります。

メソッドの非同期化

GetCompletionItemsメソッドは、非同期メソッドGetCompletionItemsAsyncに変更されています。これはメソッドの中でHttpClientの非同期メソッドを利用するためです。この非同期化の修正はMyCommandsクラスのCompleteメソッドまで伝播します。

interface ICommandCompletionItem
{
    //...(省略)
    //非同期対応
    Task<IEnumerable<string>> GetCompletionItemsAsync(string optionName, string wordToComplete);
}

interface ICommandCompletionProvider
{
    void Add(ICommandCompletionItem item);
    //非同期対応
    Task<IEnumerable<string>> CompleteAsync(string wordToComplete, string input, int cursorPosition);
}
partial class MyCommands
{
    //非同期対応
    public async Task Complete(string wordToComplete, string input, int cursorPosition)
    {
        var items = await CompletionProvider
            .CompleteAsync(wordToComplete, input, cursorPosition);
        foreach (var item in items)
        {
            Console.WriteLine(item);
        }
    }
}

HttpClientをDIサービスに登録

使用するHttpClientのインスタンスはDIを利用してIHttpClientFactory経由で取得します。
AddHttpClient()メソッドを利用するにはパッケージMicrosoft.Extensions.Httpを追加する必要があります。

Program.cs
using ConsoleAppFramework;
using Microsoft.Extensions.DependencyInjection;

//HttpClientをサービスに登録
var services = new ServiceCollection()
    .AddHttpClient();
using var serviceProvider = services.BuildServiceProvider();
ConsoleApp.ServiceProvider = serviceProvider;

var app = ConsoleApp.Create();
app.Add<MyCommands>();
await app.RunAsync(args);

CommandCompletionProviderに登録

最後に、MyCommandsのコンストラクタでCompletionProviderGoogleCompletionItemを登録します。
GoogleCompletionItemを生成する際、コンストラクタにHttpClientを渡します。

Program.cs
partial class MyCommands
{
    private readonly CommandCompletionProvider CompletionProvider;

    public MyCommands(HttpClient httpClient)//DIでHttpClientを注入
    {
        CompletionProvider =
            [
                //Google用CompletionItemの登録
                new GoogleCompletionItem(httpClient),
                new CommandCompletionItem("search")
                {
                    {"--category", ["books","movies","music"]},
                    {"--sort", ["relevance","date","popularity"]},
                    {"--filter", ["free","paid","all"]}
                },
                //...(省略)
            ];
    }
}

まとめ

今回の修正により、タブ補完の入力候補を動的に取得できるようになりました。この仕組みを利用すれば、より柔軟に入力候補を提供することが可能になります。

これにより、CLIツールの使いやすさと効率性が向上し、より多様なシチュエーションでの使用が可能になると思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2