-
Notifications
You must be signed in to change notification settings - Fork 380
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
ArgParser
rewrite
#3946
base: master
Are you sure you want to change the base?
ArgParser
rewrite
#3946
Conversation
If we do this, is there a way to get rid of the |
|
Should |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably an improvement for end-users (well, assuming you get --help
to actually display stuff on windows) and about neutral for understanding code flow (it's a complete mess still).
public static class ArgParser | ||
{ | ||
/// <exception cref="ArgParserException"><c>--socket_ip</c> passed without specifying <c>--socket_port</c> or vice-versa</exception> | ||
public static void ParseArguments(out ParsedCLIFlags parsed, string[] args) | ||
private sealed class BespokeOption<T> : Option<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why this class is necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BizHawk/src/BizHawk.Client.Common/ArgParser.cs
Lines 127 to 131 in dfe4a72
GeneratedOptions = root.Options.Where(static o => | |
{ | |
var t = o.GetType(); | |
return !t.IsGenericType || t.GetGenericTypeDefinition() != typeof(BespokeOption<>); // no there is no simpler way to do this | |
}).ToArray(); |
(essentially
root.Options.Where(o => o is not BespokeOption<>)
; this is necessary for --help
as you already noted in another comment)The other way, possibly the intended way, is to positively identify the
HelpOption
, but that type isn't exposed so you'd be comparing qualified names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my testing checking for Option<>
seems to filter out the HelpOption
, did you try that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I missed this comment. What exactly did you try?
-var t = o.GetType();
-return !t.IsGenericType || t.GetGenericTypeDefinition() != typeof(BespokeOption<>);
+return o is Option<>;
...doesn't compile.
return typeof(Option<>).IsInstanceOfType(o);
...compiles but matches nothing.
@@ -199,7 +199,8 @@ tl;dr: | |||
|
|||
#### Passing command-line arguments | |||
|
|||
EmuHawk takes some command-line options which aren't well-documented; you might be able to figure them out from [the source](https://github.com/TASEmulators/BizHawk/blob/78daf4913d4c8e47d24fc14d84ca33ddef913ed4/src/BizHawk.Client.Common/ArgParser.cs). | |||
EmuHawk takes some command-line options which, starting from 2.9.2, can be listed with `--help`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't actually work on windows release builds due to the WinExe
target, so this will need some consideration.
// .UseVersionOption() // "cannot be combined with other arguments" which is fair enough but `--config` is crucial on NixOS | ||
.UseHelp() | ||
// .UseEnvironmentVariableDirective() // useless | ||
.UseParseDirective() | ||
.UseSuggestDirective() | ||
// .RegisterWithDotnetSuggest() // intended for dotnet tools | ||
// .UseTypoCorrections() // we're only using the parser, and I guess this only works with the full buy-in | ||
// .UseParseErrorReporting() // we're only using the parser, and I guess this only works with the full buy-in | ||
// .UseExceptionHandler() // we're only using the parser, so nothing should be throwing | ||
// .CancelOnProcessTermination() // we're only using the parser, so there's not really anything to cancel | ||
.Build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's necessary to comment all options that you're not setting, but whatever. What do the Use*Directive
calls actually accomplish though? From the very useful documentation I found they supposedly enable certain commandline syntax, but also it seems to require EnableDirectives
and even when setting that, I couldn't get the syntax to work, so... ?
Also, what's with the version option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To explain, this list is the expansion of UseDefaults
. I commented the ones I didn't use so that, if and when the list changes, there'll be a record of what didn't work in the past.
I'll be honest, I didn't play with the directives. I left them in just to follow the .NET convention and in the hope that someone would find a use for them.
We already have a --version
which works well enough. When I (deleted that and) enabled UseVersionOption
, it failed because it saw --config=.../config.json --version
due to the launch script I use.
var triggeredGeneratedOption = GeneratedOptions.FirstOrDefault(o => result.FindResultFor(o) is not null); | ||
if (triggeredGeneratedOption is not null) | ||
{ | ||
// means e.g. `./EmuHawkMono.sh --help` was passed, run whatever behaviour it normally has... | ||
var exitCode = result.Invoke(); | ||
// ...and maybe exit | ||
if (exitCode is not 0 | ||
|| triggeredGeneratedOption.Name is "help") // `Name` may be localised meaning this won't work? I can't grok the source for `HelpOption` | ||
{ | ||
cmdRom = arg; | ||
return exitCode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks kinda ugly... is there no way to standardize this into a single Invoke()
call? I'm assuming not because the way bizhawk's argument parsing system works is so fundamentally different from what the CommandLine library expects.
Maybe a first step could be to separate long-term options that must stay for the entirety of the program's lifetime and could be stored in a struct like what already exists, and options that could be passed to the mainform directly. I don't really know, this just feel weird.
I mainly dislike the fact that trying to figure out how certain arguments are used and passed through takes multiple redirection steps: Search for --dump-name
-> find OptionAVDumpName
-> it gets passed to new ParsedCLIFlags as cmdDumpName
argument -> the constructor sets the field this.cmdDumpName
-> check where that field is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mainly dislike the fact that trying to figure out how certain arguments are used and passed through takes multiple redirection steps: Search for
--dump-name
-> findOptionAVDumpName
-> it gets passed to new ParsedCLIFlags ascmdDumpName
argument -> the constructor sets the fieldthis.cmdDumpName
-> check where that field is used.
I could rename the options to match the ParsedCLIFlags
fields, or better yet, rename the fields to these more descriptive names. I was hesitant to do that initially because I wanted to avoid churn in MainForm
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks kinda ugly... is there no way to standardize this into a single
Invoke()
call? I'm assuming not because the way bizhawk's argument parsing system works is so fundamentally different from what the CommandLine library expects.
That's it exactly. The library assumes it will be your Program
implementation, and devolve its execution privilege to your peasant code. And also it heavily uses async
. See the example here: https://learn.microsoft.com/en-us/dotnet/standard/commandline/handle-termination
I'd still like to improve the documentation further, but this is functional already.If you have scripts for e.g. encoding please test them.Don't merge without checking the commit hash in
README.md
.Also, it's kebab case, not snake case 🤦