Skip to content
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

Introduce mapped options as defined in #334 #451

Conversation

TheConstructor
Copy link
Contributor

So this is how I propose to implement #334. I hope I will get the last details right in the next days, as at least one exception needs another text (annotation with value not castable to property type). I also may add another test or two.

@natemcmaster @JakeSays: As you participated in the discussion on #334, please have a look, if the mechanics are as expected.

@TheConstructor TheConstructor force-pushed the feature/mapped-options-v3 branch 2 times, most recently from 457524a to 912b155 Compare April 3, 2021 18:53
@TheConstructor
Copy link
Contributor Author

Could you help me on the src/CommandLineUtils/PublicAPI.*.txt? Rider does offer me a quick fix for each violation, but I end up getting https://youtrack.jetbrains.com/issue/DEXP-583490 Probably my setup is to non-standard right now :/

@natemcmaster
Copy link
Owner

Visual Studio is the only IDE I have found that will properly apply the quickfix for the PublicAPI analyzer. VS Code doesn't work either (OmniSharp/omnisharp-roslyn#1592). If you don't have Visual Studio, the only other path here is to update the API files by hand. Look at the existing files for examples of the syntax. I usually put new API that hasn't been released into PublicAPI.Unshipped.txt.

@TheConstructor
Copy link
Contributor Author

I may do so, if you think that the change is worthy. In the end it's quite some work and I don't want to do it 'for fun' ;-)

@natemcmaster
Copy link
Owner

@TheConstructor up to you. As you've probably guessed, I'm not spending much time myself on this project, and certainly won't ask anyone else to. All time I spend on this project is "just for fun".

Side note: this PR has a lot of changes (almost 1,200 line changing). It makes reviewing a bit harder since I probably need to dedicate a time to really think about all the changes. In my day job, I usually recommend to my team that we keep changes < 200 lines per review. I know that's not always feasible when you just need a lot of changes to build something, but if you can split the change into smaller commits or even separate pull requests, it helps me logically follow how you're piecing together the feature.

@TheConstructor TheConstructor force-pushed the feature/mapped-options-v3 branch from 5252ea3 to 2e049a5 Compare May 25, 2021 16:07
@codecov
Copy link

codecov bot commented May 25, 2021

Codecov Report

Merging #451 (586c3c1) into main (fbc8668) will decrease coverage by 0.95%.
The diff coverage is 68.91%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #451      +/-   ##
==========================================
- Coverage   82.19%   81.24%   -0.96%     
==========================================
  Files         106      111       +5     
  Lines        3286     3481     +195     
==========================================
+ Hits         2701     2828     +127     
- Misses        585      653      +68     
Impacted Files Coverage Δ
src/CommandLineUtils/CommandOption.cs 73.23% <ø> (ø)
.../CommandLineUtils/Validation/AttributeValidator.cs 95.65% <ø> (ø)
...c/CommandLineUtils/Validation/DelegateValidator.cs 66.66% <ø> (ø)
...ommandLineUtils/Validation/SingleValueValidator.cs 13.63% <13.63%> (ø)
src/CommandLineUtils/MappedOption{T}.cs 50.00% <50.00%> (ø)
src/CommandLineUtils/CommandLineApplication.cs 91.62% <56.25%> (-2.01%) ⬇️
...mmandLineUtils/Attributes/MappedOptionAttribute.cs 77.77% <77.77%> (ø)
...ils/Conventions/MappedOptionAttributeConvention.cs 83.63% <83.63%> (ø)
...mandLineUtils/CommandLineApplication.Validation.cs 95.83% <85.71%> (+0.08%) ⬆️
src/CommandLineUtils/ConstantValueOption{T}.cs 95.65% <95.65%> (ø)
... and 10 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fbc8668...586c3c1. Read the comment docs.

- IOption for any option
- IOption{T} for options where a value other than string is offered
- IParseableOption for options directly accessible on the command-line by name
@TheConstructor TheConstructor force-pushed the feature/mapped-options-v3 branch from 2e049a5 to 586c3c1 Compare May 25, 2021 16:56
@TheConstructor TheConstructor marked this pull request as ready for review May 25, 2021 16:57
@TheConstructor
Copy link
Contributor Author

@natemcmaster I split up the commit - many lines (320) are in MappedOptionAttributeTests, but mostly this is something that needs to be hooked up in all different places to seamlessly work. I will see to increase coverage soon (tm)

Copy link
Owner

@natemcmaster natemcmaster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheConstructor thanks for taking time to make this pull request. I finally made time to sit down and thoroughly review. I've left comments below on some specific elements of the code, however, before you jump to addressing the individual comments, here's a summary of my overall thinking.

This pull request has a lot of potential, but I can't accept this pull request in its current form. I am still interested in implementing the behaviors described in #334. In this implementation, however, the scope has expanded beyond what I think is necessary to implement #334. I take particular issue with the addition of new abstractions IOption, IParseableOption, IOption<T> and changes some common APIs which today accept CommandOption. I think adding new abstractions like that could be useful for the library -- it's something I considered doing myself -- but if we do that, that's a separate feature which requires more discussion (for instance -- could we make a unified abstraction for CommandOption and CommandArgument?)

If you're willing to do more revisions to rework it, great! Let's discuss more in the #334 thread how we could implement the behaviors we're trying to achieve. I realize we didn't close on some important design considerations. For example, would --no-option actually accept values like --no-option foo, or is --no-option considered a special type of CommandOptionType.NoValue? (I had the second behavior in mind, but I didn't explicitly say so or justify this in the conversation in #334.)

If don't want to spend more time on this, let's close this one.

Either way, please let me know how you'd like to proceed.

Thanks,
Nate

{
var constantValueOption = new ConstantValueOption<T>(value);
_mappings.Add(constantValueOption);
_commandLineApplication.AddOption(constantValueOption);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should invert the relationship here. CommandOption/Argument are decoupled from CommandLineApplication. I'd like to see if we can do the same for MappedOption. The existing model is that option/arg objects can be constructed independently of the CommandLineApplication object, and then are added to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tricky thing for me was adding it to both places. I didn't want to change the parsing logic to take a list of strings for each argument style (short, long, symbol) - which I thought would be required to otherwise ensure that each possible flow of actions would lead to a working construction.

/// <param name="template">parameter-template e.g. <code>-a|--apple</code></param>
/// <param name="value">value to use if the mapped parameter was specified</param>
/// <returns>the mapping</returns>
public ConstantValueOption<T> Add(string description, string template, T value)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's re-order the arguments to string template, string, description, T value for consistency with CommandLineApplication.Option(string template, string, description,... and .Argument(string template, string, description,)

Comment on lines +61 to +77
public MappedOption<T> Map(string template, T value)
{
Add(template, value);
return this;
}

/// <summary>
/// Add mapping
/// </summary>
/// <param name="description">description for use in the help-text</param>
/// <param name="template">parameter-template e.g. <code>-a|--apple</code></param>
/// <param name="value">value to use if the mapped parameter was specified</param>
/// <returns><code>this</code></returns>
public MappedOption<T> Map(string description, string template, T value)
{
Add(description, template, value);
return this;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think users may find this method a bit confusing. When should a user call Add vs Map? They are both doing the same thing, except one can be chained. Can we remove one of these methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I was undecided, so I added both ^^; Do you prefer one style?

Comment on lines +165 to +170
private IEnumerable<T> MappedValues =>
_mappings.Where(m => m.HasValue())
.Select(m => m.ParsedValue);

/// <inheritdoc />
public T ParsedValue => MappedValues.FirstOrDefault();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me think we have the wrong abstraction for IParseableOption. What if there are two parsed values? Why are we only selecting the first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again there is ParsedValues just below

.FirstOrDefault();
}

void IOption.Reset()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this using an explicit interface implementation and not a normal method definition?

Comment on lines 582 to 590
public void AddOption(CommandOption option)
{
AddOption((IOption)option);
}

internal void AddOption(IOption option)
{
_options.Add(option);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void AddOption(CommandOption option)
{
AddOption((IOption)option);
}
internal void AddOption(IOption option)
{
_options.Add(option);
}
public void AddOption(IOption option)
{
_options.Add(option);
}

The cast and extra method hop appears unnecessary to me.

/// <inheritdoc />
public ValidationResult GetValidationResult(IOption option, ValidationContext context)
{
if (option.Values.Count > 1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validator seems to be unnecessary. As user of the API, I would expect this to be enforced already through using CommandOptionType.SingleValue.

.And.OnlyContain(o => string.IsNullOrEmpty(o.ShortName));
}

/*
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you delete commented-out code?

{
var app = Create<ShortNameOverride>();

using var assertionScope = new AssertionScope();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think usage of AssertionScope usually indicates a test is actually doing more than one thing. As a matter of preference, I prefer to have more and smaller test methods which test a single behavior.

}
}

private class PrivateSetterProgram
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, and the next 130 lines of code, appear to be copy/pasted from OptionAttributeTests. Can you delete instead of duplicating this content?

@natemcmaster
Copy link
Owner

@TheConstructor thank you for all of your effort on this feature. At this point, I am going to close this request. I have decided to put this project into maintenance mode. So, I won't be taking new features or any other major changes. I've posted my updated thoughts in #485.

Thanks again for the effort!

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

Successfully merging this pull request may close these issues.

2 participants