ManyConsole icon indicating copy to clipboard operation
ManyConsole copied to clipboard

Parsing Text and Multiple Parameters

Open Morgma opened this issue 12 years ago • 6 comments

Command parameters are not parsed entirely as I would expect and I do not see anything in the documentation to point me to a solution at this point.

Comments in the source of ManyConsole suggest that the behavior for parsing parameters should be as such:

// this command my-command --integer 1 --string "some long text value" --collection param1 param2 param3

// should evaluate to --integer == 1 --string == "some long text value" --collection = ["param1", "param2", "param3"]

http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx

Instead, the option --string ignores any quote and parses the space delimiter.

// actual parameterization --string == "some --collection == param1

// then everything else falls into a generic container: AdditionalArguments == long, text, value", param2, param3

How can several values be collected in one parameter call?

// In the derived ConsoleCommand constructor I want to avoid something like: my-command --string some --string long --string text --string value

// and then doing: HasOption<string>("s|string=", v => StringList.Add(v));

// finally: var stringValue = string.Join(" ", StringList);

I could parse out the remaining arguments after all single value command options are captured, but it will be difficult to come up with a constant number of parameters for the "HasAdditionalArguments" override.

Here's a sample of what I'm doing:

public class TestCommand : ConsoleCommand {

        public TestCommand() {
            IsCommand("my-command");

            StringList = new List<string>();
            HasOption("s|string=", "captures a text parameter", s => StringList.Add(s));
        }

        public List<string> StringList; 
        public override int Run(string[] remainingArguments) {
            var stringValue = string.Join(" ", StringList);

            System.Console.WriteLine("Received input: " + stringValue);
            return 0;
        }
    }

Let me know if I'm missing any existing configuration here. Perhaps I can override the argument delimiter per option?

Morgma avatar Nov 06 '12 16:11 Morgma

Hi Matt. Let me see if I can help.

When you're not sure how many additional arguments there are, you can use AllowsAnyAdditionalArguments (See https://github.com/fschwiet/ManyConsole/blob/master/SampleConsole/MattsCommand.csas an example). I'm afraid I don't know of a way to specify a parameter could have multiple values (--collection one two three), the closest I can suggest is what I linked.

With regards to the first issue, I'd expect my-command --string "some long text value" to give you the full string that is within the quotes. I added a string parameter to the sample command I just linked in order to verify this is the case. I'm not sure why you're only seeing '"some', but maybe if you can provide sample code I'd be able to figure out how that is happening. Right now I'm not able to reproduce the problem.

good luck

On Tue, Nov 6, 2012 at 8:47 AM, Matthew Morgan [email protected]:

Command parameters are not parsed entirely as I would expect and I do not see anything in the documentation to point me to a solution at this point.

Comments in the source of ManyConsole suggest that the behavior for parsing parameters should be as such:

// this command my-command --integer 1 --string "some long text value" --collection param1 param2 param3

// should evaluate to --integer == 1 --string == "some long text value" --collection = ["param1", "param2", "param3"]

http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx

Instead, the option --string ignores any quote and parses the space delimiter.

// actual parameterization --string == "some --collection == param1

// then everything else falls into a generic container: AdditionalArguments == long, text, value", param2, param3

How can several values be collected in one parameter call?

// In the derived ConsoleCommand constructor I want to avoid something like: my-command --string some --string long --string text --string value

// and then doing: HasOption("s|string=", v => StringList.Add(v));

// finally: var stringValue = string.Join(" ", StringList);

I could parse out the remaining arguments after all single value command options are captured, but it will be difficult to come up with a constant number of parameters for the "HasAdditionalArguments" override.

Here's a sample of what I'm doing:

public class TestCommand : ConsoleCommand {

    public TestCommand() {
        IsCommand("my-command");

        StringList = new List<string>();
        HasOption("s|string=", "captures a text parameter", s => StringList.Add(s));
    }

    public List<string> StringList;
    public override int Run(string[] remainingArguments) {
        var stringValue = string.Join(" ", StringList);

        System.Console.WriteLine("Received input: " + stringValue);
        return 0;
    }
}

Let me know if I'm missing any existing configuration here. Perhaps I can override the argument delimiter per option?

— Reply to this email directly or view it on GitHubhttps://github.com/fschwiet/ManyConsole/issues/9.

fschwiet avatar Nov 07 '12 21:11 fschwiet

Replying in email kind of mangled message, reposting:

Hi Matt. Let me see if I can help.

When you're not sure how many additional arguments there are, you can use AllowsAnyAdditionalArguments (See https://github.com/fschwiet/ManyConsole/blob/master/SampleConsole/MattsCommand.cs as an example). I'm afraid I don't know of a way to specify a parameter could have multiple values (--collection one two three), the closest I can suggest is what I linked.

With regards to the first issue, I'd expect my-command --string "some long text value" to give you the full string that is within the quotes. I added a string parameter to the sample command I just linked in order to verify this is the case. I'm not sure why you're only seeing '"some', but maybe if you can provide sample code I'd be able to figure out how that is happening. Right now I'm not able to reproduce the problem.

fschwiet avatar Nov 07 '12 21:11 fschwiet

I apologize for taking so long to reply.

I've included code for a complete working example. I added a bit of parsing logic to try to get the output I expect, but parsing the additional parameters is not reliable as we are expecting that only one command may accept a text parameter and use "remainingArguments[]" as a container.

Console Program:

using System;
using System.Collections.Generic;
using System.Linq;
using ManyConsole;

namespace ManyConsoleExamples {
    internal class Program {
        private static void Main(string[] args) {

            if (!args.Any()) {
                System.Console.WriteLine("Please enter a command or enter q to quit.");
                args = System.Console.ReadLine().Split(' ');

                if (args[0] == "q")
                    return;
            }

            // Locate any commands in the assembly
            IEnumerable<ConsoleCommand> commands = GetCommands();

            var consoleRunner = new ConsoleModeCommand(GetCommands);

            commands = commands.Concat(new[] { consoleRunner });

            // run the command
            try {
                ConsoleCommandDispatcher.DispatchCommand(commands, args, System.Console.Out);
            }
            catch (Exception ex) {
                System.Console.WriteLine(@"Command failed to execute. Please see the following exception.");
                System.Console.WriteLine(@"--------------------------------------------------------------");
                System.Console.WriteLine(ex);
            }
            finally {
                args = new string[] { }; // clear arguments
            }

            if (!Environment.UserInteractive) // allow for use as an api
                return;

            Main(args); // callback with empty argument set to reset the console
        }

        private static IEnumerable<ConsoleCommand> GetCommands() {
            return ConsoleCommandDispatcher.FindCommandsInSameAssemblyAs(typeof(Program));
        }

    }
}

Command Class:

using System;
using ManyConsole;

namespace ManyConsoleExamples.Commands {
    public class AcceptsLongStringParameter : ConsoleCommand {
        public string comments;

        public AcceptsLongStringParameter() {
            IsCommand("echo");

            HasOption("c|comment=",
                           "enter a comment delimited by double quotes, i.e. - \"See Matt's poorly written code\"",
                           v => comments = v);

            AllowsAnyAdditionalArguments("<foo1> <foo2> <fooN> where N is a word");
        }

        public override int Run(string[] remainingArguments) {

            if(string.IsNullOrWhiteSpace(comments)) {
                Console.WriteLine("You made no comment");
                return 0;
            }

            // I'm parsing the remainingArguments[] here to give you an example output that more closely represents what I
            // would expect to happen by default when containing a string parameter in double quotes
            Console.WriteLine("Your comment is: " + comments + " " + String.Join(", ", remainingArguments).Replace(',', ' '));
            return 0;
        }
    }
}

You can see here that the only captured argument is "hello and the remaining arguments are there and world"

Example Output

Morgma avatar Nov 12 '12 18:11 Morgma

I see, it seems this problem is specific to when commands are run via the ConsoleModeCommand, and not from the command line. The fix would likely belong in https://github.com/fschwiet/ManyConsole/blob/master/ManyConsole/Internal/StringExtensions.cs, where ToCommandLineArgs are used by ConsoleModeCommand.

That function depends on CommandLineToArgvW, which we may be using incorrectly. If you're able to dig in and find a solution that'd be great, I'm not sure when I can look at this though I might this weekend. I wonder if the problem is related to http://blogs.msdn.com/b/oldnewthing/archive/2010/09/16/10062818.aspx? It seems like we do have the bug mentioned there, it might be worthwhile adding "fake.exe " to the beginning of the input and then dropping that segment within ToCommandLineArgs.

fschwiet avatar Nov 14 '12 21:11 fschwiet

Can you get the latest version of the source and see if you can repro this with SampleCommand? When I run it, whether via powershell, cmd or some other shell I get:

C:> .\SampleConsole.exe run-console

Executing run-console (Run in console mode, treating each line of console input as a command.):

Enter a command or 'x' to exit or '?' for help echo -c "one two three" "four five six" seven eight nine

Executing echo: comments : one two three

Your comment is: one two three The remaining arguments were ["four five six","seven","eight","nine"]

Enter a command or 'x' to exit or '?' for help

fschwiet avatar Nov 25 '12 08:11 fschwiet

(this seems like the expected result, and not what you were getting"

fschwiet avatar Nov 25 '12 08:11 fschwiet