-
Notifications
You must be signed in to change notification settings - Fork 481
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
Subverbs #353
Comments
It does not. There was a comment in one of the issues where someone mentioned they remove the first item from Psuedocode Example: var args = new List<string>{"git", "push", "--help"}; // this comes from main
while(true)
{
var result = Parser.Default.ParseArguments<GitVerb, PushVerb, PullVerb>(args);
if(/* result is gitverb */)
{
args.Remove(0);
continue;
}
RunProgram(result);
} |
@nemec What if we treate the subverbs as values and after parsing we can use switch statement to determine what value(subcommand) it is and perform things accordingly. That might be a hack as well right?
If we run |
nemecs comment seems legit. I would use that functionality at least for the time being. frankcchen, your solution will definitely get you in trouble because your sub verbs then wont get as good of handling the deeper you go. Verbs that "consume" the args actually is an elegant solution for the problem until subverbs can be formally accounted for in the library. (Frankly this is probably how the library would handle sub verbs!... Lol) |
How would you handle the automatic generation of help for subverbs with this approach? |
@simonech I recently needed to implement subverbs for a commandline tool I made so I put my solution on GitHub. The code to use it is a bit more complicated than the pseudo code provided by @nemec but it should handle the auto help/version well enough. It also does not allow a user to pass, for example:
Edit: I was wrong sorry, the above would display an error that the verb was not recognized but does not show the available verbs under |
Instead of subverbs, just use hyphenation on your verbs.
I would not recommend subverbs due to the complexity, and I would classify it as a commandline smell (like a code smell but on the command line) |
@ericnewton76 I completely agree that hyphenation should be considered a better solution most of the time. In the program I had to make my subverb extension project for however, the amount of commands that are crammed in far exceeds what Most of those commands now have been grouped in five subverb categories which in my opinion made it a much nicer tool to work with. One could argue that having that many commands/verbs available in a single command line app is a code/commandline smell in and of itself. I would definitely agree with that as well but in this particular case the decision to break the app up into five different ones is not mine to make. So having the option to use subverbs still would've been nice for me in this situation. I actually tried implementing this feature in this project first, since it was marked as |
We're always interested in new ideas. If you have a different approach, I'd love to discuss it! They are always welcome. Personally, I have an approach that I wanted to implement, similar to node commander package. Instead of using classes to dictate the command line options (and verbs), you use a fluent interface. In the end you end up with a "program" (or whatever you wanna call it) object that contains the values or undefined or defaults. Its so different from the current implementation that it would practically be a very different class library itself. Which makes it somewhat off-topic here but I wanted to mention it. Even gits "sub-verbs" are actually separate programs, just using a convention that the main git.exe understands to make the subprocess call. For example, |
Sorry for the late reply. I wanted to take another look at the code base and understand it a bit better before submitting an approach but did not get around to doing so until now. Your idea of using a fluent interface to build a sort of program sounds pretty interesting. I'm having trouble imagining how one would implement that without resorting to "cheating" by using My experience is mostly with C# so my best guess would be that you'd still define an interface and then fluently built an implementation for that interface using something like Castle Core's DynamicProxy. Or perhaps something like a type provider library in F# could handle it? I don't have a lot of experience with that but I came across Rezoom.SQL once which does some cool/funky stuff using database schema scripts and queries. I guess you could do something similar by treating the help text of the command line app as the schema and then generate a type/program from that. If it isn't considered to be to much off-topic I'd like to hear what kind of implementation you came up with. As for my current suggested approach to implement the subverb feature, after looking at the code base again I believe the following could work well enough. Consumer sideMy first thought/attempt at implementing this was to simply allow verbs to define properties of types that are also decorated with the [Verb("main")]
public class MainVerb
{
public SubVerb Sub { get; set; }
}
[Verb("sub")]
public class SubVerb
{
[Value(0)]
public string SomeValue { get; set; }
} This didn't go very well for a number of reasons, the main one being that verbs, options and values could be mixed in strange ways. // While technically possible it would be strange to allow for
// 'program.exe main -s sub ...' to be parsed in my opinion.
[Verb("main")]
public class MainVerb
{
[Option('s')]
public bool SomeSwitch { get; set; }
public SubVerb Sub { get; set; }
}
[Verb("sub")]
public class SubVerb
{
// opts/values
} So following that failure my suggestion now on how to implement this feature would be to define another attribute called a VerbSet. This attribute would (currently at least) have the same properties as the Verb attribute but instead of Option and Value properties only types that are decorated with either a Verb or VerbSet would be allowed. // Same attribute props as Verb.
[VerbSet("set1", HelpText = "Verb set one.", Hidden = false)]
public class VerbSet1
{
// No [Verb] attribute needed since the Verb1 type should already be decorated with it.
public Verb1 VerbOne { get; }
// Further nesting can be done by adding a property for a type decorated with [VerbSet].
public VerbSet2 VerbSetTwo { get; }
// Option and Value props aren't even considered when parsing or building help.
[Option('o')]
public string IgnoredOption { get; set; }
[Value(0)]
public string IgnoredValue { get; set; }
}
[VerbSet("set2", HelpText = "Verb set two.")]
public class VerbSet2
{
public Verb2 VerbTwo { get; }
}
[Verb("verb0")]
public Verb0
{
[Option('x')]
public bool SomeSwitch { get; set; }
}
[Verb("verb1")]
public Verb1
{
[Option('s')]
public bool SomeSwitch { get; set; }
}
[Verb("verb2")]
public Verb2
{
[Value(0)]
public string SomeValue { get; set; }
} Using the above verbs and sets some of the available paths in the program would be:
You may have noticed that the verbset properties in the example only have a getter. This is to illustrate that set classes are only used to define the schema of the program and thus are never instantiated. To me it seemed unnecessary to create them since the Verb classes hold all the actual information used to run a command. The VerbSet attribute's target could also be set to Following that logic, usage with a Parser would be like this: ParserResult<object> result = Parser.Default.ParseArguments<VerbSet1, Verb0>(args);
// Only Verb classes are returned as a Parsed<T>.
result.MapResult(
(Verb0 verbZero) => ...,
(Verb1 verbOne) => ...,
(Verb2 verbTwo) => ...,
(Errors _) => ...);
// The following could be an edge case since VerbSet classes shouldn't be created.
ParserResult<VerbSet1> result = Parser.Default.ParseArguments<VerbSet1>(args);
// But the above would be a bit strange for a developer to do since sets only contain info
// about other VerbSet and Verb types.
// So parsing like this would create a needless extra value between the actual sets/verbs
// that should be parsed.
// If the developer really does want to do this, for whatever reason, a workaround would be:
ParserResult<object> result = Parser.Default.ParseArguments(args, typeof(VerbSet1)); BackendTo get the parsing logic working I suggest modifying sealed class Verb
{
public Verb(string name, string helpText, bool hidden, IEnumerable<Type> subverbTypes
{
// omitted.
this.SubverbTypes = subverbTypes;
}
// omitted.
public IEnumerable<Type> SubverbTypes { get; }
public bool IsVerbSet => this.SubverbTypes.Any();
public static Verb FromAttribute(VerbAttribute attribute)
{
return new Verb(
attribute.Name,
attribute.HelpText,
attribute.Hidden,
Enumerable.Empty<Type>() // Regular verbs cannot hold verb/set types.
);
}
public static Verb FromAttribute(VerbSetAttribute attribute, Type type)
{
var subVerbTypes = // get verb/set types.
return new Verb(
attribute.Name,
attribute.HelpText,
attribute.Hidden,
subVerbTypes
);
}
public static IEnumerable<Tuple<Verb, Type>> SelectFromTypes(IEnumerable<Type> types)
{
var verbsets = // get VerbSetAttribute Verbs.
var verbs = // get VerbAttribute Verbs.
// Return both combined.
return verbsets.Concat(verbs);
}
} By representing both attributes as Another reason to treat both attributes as a I think that covers at least most of what would need to be implemented. |
I think easiest solution would be add attribute, to capture all items after verb to IEnumerable, and then use that enumerable to make internal parser in verb handler.
But AFAIK no such attribute exist now. Help generation is another question, but it also can be handled by subparser, somehow. |
and #69 |
any update on this? |
Any update on this? |
One solution to this is to leave CommandLineParser alone and write a new class CommandTree that allows one to "mount" command line parsers on different "verb paths" like "git/push/folder". Each mount path points to a command line parser. When main is called, this command tree would use its registry of command line parsers to find and use the right command parser to parse the arguments after the mount point. Problem solved. |
In case it's helpful to see an alternate implementation, I've used Rust's clap crate in the recent past and I think it has a good model for subverbs. https://docs.rs/clap/latest/clap/_derive/_tutorial/chapter_2/index.html#subcommands Translating to C#, it would look a lot like @twaalewijn's proposal for the consumer side. |
Does this library support subverbs? Based on my current research it doesn't. For example, let's assume git is the top level verb, And it has several layers of subverbs.
git/commit
git/push/folder --> (file and folder are verbs as well)
git/push/file
git/pull/folder
git/pull/file
Is there anyway to write this kind of command line interface with multiple layers of subcommands/subverbs? Thanks
The text was updated successfully, but these errors were encountered: