以前こんな記事を書きましたが、
coma2n さんのこちらの記事に、属性とリフレクションを用いるやり方が書かれていました。
こちらの方が、オプションを定義する際に楽ですね。
使い方
下記ソースコード冒頭のコメントを参照のこと。
実行結果
ヘルプメッセージはこんな感じで表示されます。
ソースコード
自分なりに咀嚼してみたのが以下のコード。
実行するには、別途 NDesk.Options をインストールしてください。
CommandlineParser.cs
// # 参考
// * [NDesk.Options(Mono) - コマンドラインパーサー - Architect Life](http://d.hatena.ne.jp/coma2n/20091210/1260433596)
/*
# CommandlineParser クラスの使い方
// 自分のアプリケーション用のオプションクラスを定義
public class MyOptions
{
[CommandlineOption("h", "help", Description = "このヘルプを表示します。", OptionType = CommandlineOptionType.BoolSwitch)]
public bool NeedToShowHelp { get; set; }
[CommandlineOption("n", "name", Description = "名前を指定します。")]
public string Name { get; set; }
[CommandlineOption("s", "switch", Description = "スイッチ", OptionType = CommandlineOptionType.BoolSwitch)]
public bool Switch { get; set; }
}
// コマンドライン引数解析
var parser = new CommandlineParser();
var commandline = parser.Parse<MyOptions>( Environment.GetCommandLineArgs() );
// 解析されたオプション
commandline.Options.NeedToShowHelp
commandline.Options.Name
commandline.Options.Switch
// (オプションと認識されなかった)その他のコマンドライン引数
commandline.Values
// ヘルプメッセージの表示
Console.WriteLine( parser.GetHelpMessage<MyOptions>() );
*/
namespace Rohinomiya
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System;
using NDesk.Options;
/// <summary>
/// コマンドラインオプションのタイプ
/// </summary>
public enum CommandlineOptionType
{
/// <summary>
/// ノーマルスイッチ(ファイル名等を1つ引数に取る)
/// </summary>
Normal,
/// <summary>
/// 単なるBooleanスイッチ
/// </summary>
BoolSwitch
}
/// <summary>
/// CommandlineOption カスタム属性の定義
/// NDesk.Options.Add() への引数を生成するための情報を保持する
/// </summary>
public class CommandlineOptionAttribute : Attribute
{
/// <summary>
/// オプションの名称(--help とかの help の部分)
/// </summary>
public List<string> Names;
/// <summary>
/// オプションの説明
/// </summary>
public string Description;
/// <summary>
/// オプションのタイプ
/// </summary>
public CommandlineOptionType OptionType;
/// <summary>
/// デフォルトコンストラクタ
/// </summary>
public CommandlineOptionAttribute()
{
Names = new List<string>();
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="names">オプション名(例:オプションが -h -help であれば "h", "help" )</param>
public CommandlineOptionAttribute(params string [] names)
{
this.Names = names.ToList();
}
}
/// <summary>
/// NDesk.Options を使ってコマンドラインを解析するクラス
/// </summary>
public sealed class CommandlineParser {
/// <summary>
/// バインディング、およびリフレクションによるメンバーと型の検索方法を制御するフラグ
/// </summary>
private static readonly BindingFlags bindingFlags =
BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance;
/// <summary>
/// 指定したコマンドライン引数を解析します。
/// </summary>
/// <typeparam name="TOptions">オプション定義クラスの型</typeparam>
/// <param name="args">コマンドライン引数</param>
/// <returns>解析結果</returns>
public CommandlineParseResult<TOptions> Parse<TOptions>(IEnumerable<string> args) {
var opts = Activator.CreateInstance<TOptions>();
var extra = CreateOptSet<TOptions>(opts).Parse(args); // optsのプロパティに値がセットされる
return new CommandlineParseResult<TOptions>(extra, opts);
}
/// <summary>
/// コマンドラインオプションの説明文を取得する
/// </summary>
/// <returns>コマンドラインオプションの説明文</returns>
public string GetCommandlineUsage<TOptions>() {
using(var sw = new StringWriter()) {
CreateOptSet<TOptions>().WriteOptionDescriptions(sw);
return sw.ToString();
}
}
OptionSet CreateOptSet<TOptions>() {
return CreateOptSet<TOptions>(default(TOptions));
}
/// <summary>
/// コマンドライン引数を解析するために必要な OptionSet オブジェクトを生成する
/// </summary>
/// <param name="opts">CommandlineOption属性を指定されたオプションクラスのインスタンス</param>
/// <returns>値がセットされたNDesk.Option.OptionSet オブジェクト</returns>
OptionSet CreateOptSet<TOptions>(TOptions opts) {
var optSet = new OptionSet();
var tp = typeof(TOptions);
var propAttrs = tp.GetProperties(bindingFlags)
.Select(p => {
var attr = p.GetCustomAttributes(typeof(CommandlineOptionAttribute), true).FirstOrDefault();
return attr != null ? new { Prop = p, Attr = (CommandlineOptionAttribute)attr } : null;
})
.Where(o => o != null);
foreach(var pa in propAttrs) {
var optType = pa.Attr.OptionType;
var propInfo = pa.Prop;
var prototype = string.Join("|", pa.Attr.Names.ToArray());
// Normalなら=を付加する。
if(optType == CommandlineOptionType.Normal) prototype += "=";
// optSet.Add(string prototype, string description, Action<T> action)
optSet.Add(prototype,
pa.Attr.Description,
// インスタンスoptsのプロパティに値をセットする
v => propInfo.SetValue(
opts,
(optType == CommandlineOptionType.BoolSwitch) ?
(object) v != null :
(object) Convert.ChangeType(v, propInfo.PropertyType),
null
)
);
}
return optSet;
}
/// <summary>
/// ヘルプメッセージの取得
/// </summary>
/// <returns>ヘルプメッセージ</returns>
public string GetHelpMessage<TOptions>()
{
var descriptionOfOptions = GetCommandlineUsage<TOptions>();
var vi = FileVersionInfo.GetVersionInfo(PathEx.MyExecutablePath());
var sb = new StringBuilder();
sb.AppendLine(string.Format("{0} {1}",vi.ProductName, vi.ProductVersion));
sb.AppendLine(vi.Comments); // sb.AppendLine(vi.FileDescription);
sb.AppendLine();
sb.AppendLine("[Usage]:");
sb.AppendLine(string.Format("{0} [Options] [files..]",PathEx.MyExecutableFileName()));
sb.AppendLine();
sb.AppendLine("[Options]:");
sb.AppendLine(descriptionOfOptions);
return sb.ToString();
}
}
/// <summary>
/// コマンドライン引数の解析結果を保持するクラス
/// </summary>
/// <typeparam name="TOptions">オプションの型</typeparam>
public sealed class CommandlineParseResult<TOptions> {
/// <summary>
/// オプションを覗いた残りの引数
/// </summary>
private List<string> values = new List<string>();
/// <summary>
/// オプション
/// </summary>
private TOptions options;
/// <summary>
/// コマンドラインの値の一覧を取得します(読み取り専用)。
/// </summary>
public ReadOnlyCollection<string> Values { get { return values.AsReadOnly(); } }
/// <summary>
/// コマンドラインのオプションを取得します。
/// </summary>
public TOptions Options { get { return options; } }
/// <summary>
/// 指定した値の一覧を設定するコンストラクタ
/// </summary>
/// <param name="values">オプション</param>
public CommandlineParseResult(IEnumerable<string> values)
{
this.values.AddRange(values);
}
/// <summary>
/// 指定した値の一覧とオプションを設定するコンストラクタ
/// </summary>
/// <param name="values">値の一覧</param>
/// <param name="options">オプション</param>
public CommandlineParseResult(IEnumerable<string> values, TOptions options) : this(values)
// : this(values) = コンストラクター初期化子:引数1つのコンストラクタを呼ぶ
{
this.options = options;
}
}
}