11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C# で 属性を定義することで楽にコマンドライン解析を行う

Posted at

以前こんな記事を書きましたが、

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;
        }
    }
    
}

11
10
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
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?