gsscoder / commandline

Terse syntax C# command line parser for .NET with F# support
1.63k stars 293 forks source link

Option to convert Arguments object back into args string #114

Open issacg opened 10 years ago

issacg commented 10 years ago

Hi

In my use-case, I needed to (occasionally) construct command line parameters for another instance of my process, and overloaded my class' ToString method to do so. I'd love to contribute the code back, but not sure if it's something that's needed, or where the best place in the library would be to put it.

I've included the code here, and if there is interest, and someone would give me some basic guidance as to where the correct place to add this to the library would be, I'd be happy to make a pull request (time permitting ;))

class MyArguments {
        // Add some option-properties

        public override string ToString()
        {
            string res = "";
            // Get a list of all properties with OptionAttribute
            var props = this.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(OptionAttribute)));
            // Now search the list to extract the attribute data
            foreach (PropertyInfo prop in props)
            {
                object[] attrs = prop.GetCustomAttributes(true);
                foreach (object attr in attrs)
                {
                    OptionAttribute oa = attr as OptionAttribute;
                    if (oa != null)
                    {
                        // We found an OptionAttribute!
                        Type t = prop.GetType();
                        var data = prop.GetValue(this, null);
                        // Check if value is "default(t)" or not
                        if (data != (t.IsValueType ? Activator.CreateInstance(t) : null))
                        {
                            // Only print out non-empty parameters
                            res += "--" + oa.LongName + " " + data.ToString() + " ";
                        }
                    }
                }
            }
            return res;
        }
}
allonhadaya commented 10 years ago

Nice! Not sure about how this all fits into the library, but I played around with your code and came up with this. It uses some more linq and extracts the functionality into an abstract base class.

using System;
using System.Linq;
using CommandLine;

/// <summary>
/// A base class for options that can be turned back into an argument string.
/// </summary>
public abstract class ReversableOptions
{
    public override string ToString()
    {
        return string.Concat(this
            .GetType()
            .GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof(OptionAttribute)))
            // for all properties with option attributes
            .SelectMany(p => p
                .GetCustomAttributes(typeof(OptionAttribute), inherit: true)
                .Select(a => (OptionAttribute)a)
                // get the type, name, and data
                .Select(a => new {
                    type = p.GetType(),
                    name = a.LongName,
                    data = p.GetValue(this, null)
                })
                // where data is not default
                .Where(v => v.data != ((v.type.IsValueType) ? Activator.CreateInstance(v.type) : null))
                // make an argument string
                .Select(v => string.Format("--{0} {1} ", v.name, v.data))));
    }
}

I was kicking around the idea of making a tag interface with a single Reverse extension, but every implementer would have to do something like this:

class MyOptions : ISomeTag
{
    public override string ToString()
    {
        return this.Reverse();
    }
}

which seems a bit crufty.

Anyways, hope this helps.

issacg commented 10 years ago

Yeah, the Reverse tag would be a bit sucky. I don't really have a better idea, though. Maybe just leave the ToString code as a Gist and mention it in the docs...

I tried using your ToString as a drop-in replacement to mine, and it actually did not seem to work - I'll try to find some time tomorrow to look into it

allonhadaya commented 10 years ago

Yeah, I think I just found the bug... A small typo.

allonhadaya commented 10 years ago

Sorry about that, just updated my earlier comment with the fix. I think a gist would be a good place to start.

issacg commented 10 years ago

It works now.

https://gist.github.com/issacg/8024973