Skip to content

Is there a way to implement multiple level verbs? #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ericnewton76 opened this issue Nov 4, 2017 · 13 comments
Open

Is there a way to implement multiple level verbs? #13

ericnewton76 opened this issue Nov 4, 2017 · 13 comments

Comments

@ericnewton76
Copy link
Member

Issue by akfish
Thursday Oct 31, 2013 at 19:48 GMT
Originally opened as gsscoder/commandline#107


I am trying to make an app with multiple level of verbs, something like:

app repo list
app repo add
app account list

What I tried is:

    class Options
    {
        #region Global Options
        //...
        #endregion

        #region Help
        //..
        #endregion

        #region Verbs
        [VerbOption("account", HelpText = "Manage mail accounts")]
        public AccountOptions AccountVerb { get; set; }

        [VerbOption("repo", HelpText = "Manage repositories")]
        public RepoOptions RepoVerb { get; set; }
        #endregion
    }

In each verb, nested a sub verb:

    class AccountOptions : IOptions
    {
        [VerbOption("add", HelpText = "Add an account")]
        public AddOption AddVerb { get; set; }

        //And so on....

        public class AddOption : IOptions
        {
        }
        //And so on....

    }

main:

    class Program
    {
        static void Main(string[] args)
        {
            string theVerb = null;
            IOptions theOptions = null;

            var options = new Options();
            if (CommandLine.Parser.Default.ParseArguments(args, options, (verb, subOptions) =>
            {
                theVerb = verb;
                if (verb != null)
                    theOptions = subOptions as IOptions;
            }))
            {
                theOptions.Execute(options);
            }
            else
            {
                Console.Error.WriteLine("Command line parsing error.");
            }
        }
    }

I was able to get first level of verbs to working (e.g. app repo, app account, as the documents said. But the second level ones (app account list, app account add) didn't.

So is there a way to do this? Thanks.

@ericnewton76
Copy link
Member Author

Comment by akfish
Friday Nov 01, 2013 at 14:57 GMT


After some messing around I found that the sub verbs did get initialized correctly. So the problem was to get the last level of verb instance. That's easily solve by some recursive checking.

Though it would be cool to have a build-in way to get the parsed verb-path in these multiple level situation.

@ericnewton76
Copy link
Member Author

Comment by nemec
Saturday Nov 02, 2013 at 01:26 GMT


There was some talk of this in #17, but I don't think nested verbs ever made it into the code. As you mentioned, it's probably quite easy if we switch to a recursive parsing model.

@ericnewton76
Copy link
Member Author

Comment by akfish
Saturday Nov 02, 2013 at 13:23 GMT


I see. So are there any plans to support nested verbs in future versions?

@ericnewton76
Copy link
Member Author

Comment by nemec
Saturday Nov 02, 2013 at 17:27 GMT


Not as far as I know. I'm sure @gsscoder would accept a pull request for it, but I haven't seen him around in a while.

@ericnewton76
Copy link
Member Author

Comment by Nate-Wilkins
Tuesday Dec 02, 2014 at 06:33 GMT


This would probably be a breaking change. But I definitely want this. Was anyone working on a PR?

@ericnewton76
Copy link
Member Author

Comment by akfish
Tuesday Dec 02, 2014 at 07:01 GMT


@Nate-Wilkins I was able to implement this feature in my project without actually modifying this project.
Here is my implementation:
https://github.com/akfish/git-mail/tree/develop/git-mail

You should know that the project linked above was never finished, since later I moved on to node.js for cross-platform purpose and I haven't been working on .Net platform for almost a year.
But the CLI part was completed and would hopefully give you some hints.

@ericnewton76
Copy link
Member Author

Comment by Nate-Wilkins
Tuesday Dec 02, 2014 at 07:26 GMT


@akfish Great thanks! I'll take a gander

@ericnewton76
Copy link
Member Author

Comment by cosmo0
Tuesday Dec 02, 2014 at 08:07 GMT


If you create this feature that doesn't break the existing usage, I accept PRs into my fork (which is the only one more or less active at this point, I believe)

@ericnewton76
Copy link
Member Author

Comment by Nate-Wilkins
Tuesday Dec 02, 2014 at 17:28 GMT


@nemec Could you point me to some of the code that does the verb parsing? I'll be taking a look at this but no promises :(

@cosmo0 I'll send the PR to your fork should I also send one here? (This is all assuming I implement the feature...)

@akfish Looks like the general gist is to use a sub parser 👍 project looks pretty cool too.

@ericnewton76
Copy link
Member Author

Comment by cosmo0
Tuesday Dec 02, 2014 at 17:45 GMT


Well, you can do both, but @gsscoder has vanished from the web, so I'm pretty sure he won't merge it.

@ericnewton76
Copy link
Member Author

Comment by nemec
Tuesday Dec 02, 2014 at 19:28 GMT


@Nate-Wilkins try this file or this file.

@cosmo0 I did manage to get a reply from Giacomo, he said he's busy at work these days and doesn't have time for this project.

@ericnewton76
Copy link
Member Author

Comment by cosmo0
Wednesday Dec 03, 2014 at 08:00 GMT


@nemec : great to hear that he's still alive, at least.

@laterwet
Copy link

laterwet commented Oct 14, 2018

A small trick I found that works quite well. When I find a match with an existing sub-parser (verb), I skip the 1st argument, and forward it to another Parser. This could be done recursively, and probably in a better way than reflection.

    interface ISubParser
    {
        int RunAndReturnExitCode(string[] args);
    }

    [Verb("repo", HelpText = "Manage repositories.")]
    public class RepoVerb : ISubParser
    {
        int ISubParser.RunAndReturnExitCode(string[] args)
        {
            return Parser.Default.ParseArguments<AddOptions, RemoveOptions>(args)
                .MapResult(
                  (AddOptions opts) => AddRepo.RunAndReturnExitCode(opts),
                  (RemoveOptions opts) => RemoveRepo.RunAndReturnExitCode(opts),
                  errs => 1);
        }
    }

    class Program
    {
        private static Type GetSubParser(string verb)
        {
            return Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x =>
                typeof(ISubParser).IsAssignableFrom(x)
                && x.GetCustomAttribute<VerbAttribute>()?.Name == verb);
        }

        static int Main(string[] args)
        {
            if (args.Length > 0)
            {
                Type type = GetSubParser(args[0]);
                if (type != null)
                {
                    // Command with sub-verbs
                    return (Activator.CreateInstance(type) as ISubParser).RunAndReturnExitCode(args.Skip(1).ToArray());
                }
            }

            return Parser.Default.ParseArguments<RepoVerb, AccountVerb>(args)
                .MapResult(
                  (RepoVerb opts) => 1,
                  (AccountVerb opts) => 1,
                  errs => 1);
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants