-
Notifications
You must be signed in to change notification settings - Fork 414
Description
I am building a command line app with 14+ options per command. The docs reccommend to use model binding if you have more than 8 options, but when i implemented the Binder, i became more and more unsure if i was doing this correctly, because there was just soo much boilerplate. Am i doing something wrong here? How can i properly handle 8+ arguments?
Excessive boilerplate example
You can see just the sheer amount of unnecessary boilerplate just passing around the options objects
public class ThumbnailOptionsBinder : BinderBase<ThumbnailOptions>
{
private readonly Option<TimeSpan> _startTimeOption;
private readonly Option<TimeSpan> _intervalOption;
private readonly Option<TimeSpan> _endTimeOption;
private readonly Option<string> _filenameOption;
private readonly Option<bool> _fastModeOption;
private readonly Option<string> _webVTTOption;
private readonly Option<string> _imagePathOption;
private readonly Option<int> _frameWidthOption;
private readonly Option<int> _frameHeightOption;
private readonly Option<int> _columnsOption;
private readonly Option<int> _rowsOption;
private readonly Option<int> _borderWidthOption;
private readonly Option<string> _backgroundGradientStartOption;
private readonly Option<string> _backgroundGradientEndOption;
public ThumbnailOptionsBinder(
Option<TimeSpan> startTimeOption,
Option<TimeSpan> intervalOption,
Option<TimeSpan> endTimeOption,
Option<string> filenameOption,
Option<bool> fastModeOption,
Option<string> webVTTOption,
Option<string> imagePathOption,
Option<int> frameWidthOption,
Option<int> frameHeightOption,
Option<int> columnsOption,
Option<int> rowsOption,
Option<int> borderWidthOption,
Option<string> backgroundGradientStartOption,
Option<string> backgroundGradientEndOption)
{
_startTimeOption = startTimeOption;
_intervalOption = intervalOption;
_endTimeOption = endTimeOption;
_filenameOption = filenameOption;
_fastModeOption = fastModeOption;
_webVTTOption = webVTTOption;
_imagePathOption = imagePathOption;
_frameWidthOption = frameWidthOption;
_frameHeightOption = frameHeightOption;
_columnsOption = columnsOption;
_rowsOption = rowsOption;
_borderWidthOption = borderWidthOption;
_backgroundGradientStartOption = backgroundGradientStartOption;
_backgroundGradientEndOption = backgroundGradientEndOption;
}
protected override ThumbnailOptions GetBoundValue(BindingContext bindingContext) =>
new ThumbnailOptions
{
StartTime = bindingContext.ParseResult.GetValueForOption(_startTimeOption),
Interval = bindingContext.ParseResult.GetValueForOption(_intervalOption),
EndTime = bindingContext.ParseResult.GetValueForOption(_endTimeOption),
Filename = bindingContext.ParseResult.GetValueForOption(_filenameOption),
FastMode = bindingContext.ParseResult.GetValueForOption(_fastModeOption),
WebVTT = bindingContext.ParseResult.GetValueForOption(_webVTTOption),
ImagePath = bindingContext.ParseResult.GetValueForOption(_imagePathOption),
FrameWidth = bindingContext.ParseResult.GetValueForOption(_frameWidthOption),
FrameHeight = bindingContext.ParseResult.GetValueForOption(_frameHeightOption),
Columns = bindingContext.ParseResult.GetValueForOption(_columnsOption),
Rows = bindingContext.ParseResult.GetValueForOption(_rowsOption),
BorderWidth = bindingContext.ParseResult.GetValueForOption(_borderWidthOption),
BackgroundGradientStart = bindingContext.ParseResult.GetValueForOption(_backgroundGradientStartOption),
BackgroundGradientEnd = bindingContext.ParseResult.GetValueForOption(_backgroundGradientEndOption),
// Add other rendering options here
};
}
class Program
{
static async Task<int> Main(string[] args)
{
var rootCommand = new RootCommand();
var startTimeOption = new Option<TimeSpan>("--start-time", () => TimeSpan.FromSeconds(60), "Start time");
var intervalOption = new Option<TimeSpan>("--interval", () => TimeSpan.FromSeconds(5), "Interval");
var endTimeOption = new Option<TimeSpan>("--end-time", () => TimeSpan.FromMinutes(4), "End time");
var filenameOption = new Option<string>("--filename", () => Path.GetFullPath("./thumbnail_systemdrawing.bmp"), "Output filename");
var fastModeOption = new Option<bool>("--fast-mode", "Use fast mode");
var webvttOption = new Option<string>("--webvtt", "WebVTT file");
var imagePathOption = new Option<string>("--image-path", "Image path for WebVTT");
var frameWidthOption = new Option<int>("--frame-width", () => 192, "Frame width");
var frameHeightOption = new Option<int>("--frame-height", () => 108, "Frame height");
var columnsOption = new Option<int>("--columns", () => 4, "Columns for tiling");
var rowsOption = new Option<int>("--rows", () => 4, "Rows for tiling");
var borderWidthOption = new Option<int>("--border-width", () => 8, "Border width");
var backgroundGradientStartOption = new Option<string>("--background-gradient-start", "Background gradient start color");
var backgroundGradientEndOption = new Option<string>("--background-gradient-end", "Background gradient end color");
rootCommand.Add(startTimeOption);
rootCommand.Add(intervalOption);
rootCommand.Add(endTimeOption);
rootCommand.Add(filenameOption);
rootCommand.Add(fastModeOption);
rootCommand.Add(webvttOption);
rootCommand.Add(imagePathOption);
rootCommand.Add(frameWidthOption);
rootCommand.Add(frameHeightOption);
rootCommand.Add(columnsOption);
rootCommand.Add(rowsOption);
rootCommand.Add(borderWidthOption);
rootCommand.Add(backgroundGradientStartOption);
rootCommand.Add(backgroundGradientEndOption);
var thumbnailOptionsBinder = new ThumbnailOptionsBinder(
startTimeOption,
intervalOption,
endTimeOption,
filenameOption,
fastModeOption,
webvttOption,
imagePathOption,
frameWidthOption,
frameHeightOption,
columnsOption,
rowsOption,
borderWidthOption,
backgroundGradientStartOption,
backgroundGradientEndOption
);
rootCommand.SetHandler(async (thumbnailOptions) =>
{
await Run(thumbnailOptions);
}, thumbnailOptionsBinder);
return await rootCommand.InvokeAsync(args);
}
}Problems of this code:
-
Excessive Code Redundancy: The constructor of the Binder and the subsequent setup of options in the Program class are excessively redundant. This violates the DRY (Don't Repeat Yourself) principle and makes the codebase unnecessarily complex.
-
Maintenance Nightmare: Any change or addition to the command line options requires modifications in multiple places, making maintenance a nightmare. Developers should not have to tediously update both the constructor and the Program class for each option.
-
Readability and Clarity: The readability of the code is compromised due to the extensive setup of options and bindings, making it hard for developers to quickly grasp the functionality.
Am i doing something wrong here? How can i properly handle 8+ arguments?
Is this really how the model binding should be used? If, yes, then I urge the maintainers to provide a more elegant and maintainable solution for setting up command line options and binders, as this much boilerplate just doesnt seem like the right way forward.
Thanks