Open bilal-fazlani opened 4 years ago
Agreed, it would bring more clarity. The downside is taking on an external dependency for core functionality. I also am not sure how it will hold up with the new terminal features like virtual terminal sequences. Using those would provide a better format for the help for terminals that support it. I'd been holding off on touching help again until we better understood adoption and how we should expect to interact with them.
How will the template engine ensure text is properly aligned in columns? We generally have to loop through all the rows to get the max length of each field and pad with spaces.
How will the template engine ensure text is properly aligned in columns?
That will be an easy problem I think. Similar to asp.net mvc view models, we compute everything before hand and give dumb models to views to render. I can not be sure until I try though.
Agreed, it would bring more clarity. The downside is taking on an external dependency for core functionality. I also am not sure how it will hold up with the new terminal features like virtual terminal sequences. Using those would provide a better format for the help for terminals that support it. I'd been holding off on touching help again until we better understood adoption and how we should expect to interact with them.
yeah, I just created this issue for templated related discussions. even if we decide to do this change it can not be done suddenly. we need to consider the concerns you have mentioned. like being able to colour strings, and making sure alignment is proper, dependency etc.
Lets just use this issue for such discussions and spikes/explorations for now.
If we go this route with an external dependency, it should be a separate package like the other external dependencies. This way we can rev it independently.
It would also be great if there was a way for middleware to inject information into help. I haven't thought of a clean approach for that yet. The template would need to save a place for the data. The problem is that the middleware doesn't know what the template looks like and the template doesn't know what info the middleware wants to output.
What middlewares dont have to think about injecting info into templates... What if they are just concerned about creating data. Once a middleware decides that it is the final middleware, it passes that data to help rendering middleware with a template name.
Just some thoughts.
Yes, the middleware doesn't need to know about the template specifically. It needs to be able to specify some information to show and what entity that info belongs to. The command, subcommand, argument, etc.
I wonder if there are some generalized concepts about the help documentation to let the middleware give hints. For example, show before or after arguments. Show at top or bottom. This is a symbol to postfix to the end of an argument.
On Wed, Apr 1, 2020 at 9:55 AM Bilal notifications@github.com wrote:
What middlewares dont have to think about injecting info into templates... What if they are just concerned about creating data. Once a middleware decides that it is the final middleware, it passes that data to help rendering middleware with a template name.
Just some thoughts.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/bilal-fazlani/commanddotnet/issues/231#issuecomment-607123836, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAZHMP4LOKIY54QYC55JATRKL6QXANCNFSM4LX4EOPQ .
For example, show before or after arguments. Show at top or bottom.
With templates, I can think of following points
if
s inside a single templateThis is a symbol to postfix to the end of an argument.
I am not sure if I understand this correctly.
Also, just a thought - may it is worth exploring if we can create html template and then render it on console. Like how browsh or lynx does it? Using some library of course (if there is any). Not sure if that is possible, but if we find any library, then that can take care of colors, as well as table like padded alignments. But this is just too ambitious :P
For example, show before or after arguments. Show at top or bottom.
My point with this is that we have a bit of a race condition. A help template doesn't know what middleware is registered. A middleware doesn't know how to modify or append to the help output. Let's say we're adding a middleware component to support separated arguments. How does that middleware indicate the command supports separated arguments? It should update the Usage section, but it doesn't know the format of the section so it can't just modify it. And the help template doesn't know about this middleware. How do we solve this?
This is a symbol to postfix to the end of an argument.
I was thinking about middleware that may want to add some kind of indicator to an argument, like our "
Let's say we're adding a middleware component to support separated arguments. How does that middleware indicate the command supports separated arguments?
What about using A ViewModel? So our context class has one more property called view model. This view model has all properties required to render help. All middlewares which want to modify help, keep enriching this view model and keep passing it to next middleware until it finally reaches the help middleware where it gets passed to a view (template) and gets rendered.
In this case, our new middleware component adds relevant info into the view model.
I was thinking about middleware that may want to add some kind of indicator to an argument, like our "" or "(Multiple)". Perhaps, "!" for required or "*" for footnotes in extended help text.
So middleware don't decide how things get rendered. They just enrich information and its up to the template to decide how to render things.
For example, ViewModel contains a command and command contains 2 arguments. 1 non required other required. You can decide to a template which renders required args like this: agr1 !
or you can use a a template which renders required args like this: arg1: REQUIRED
The middleware just collect core domain cli data such as what is required, what is the type of argument, what is the description, etc. It doesn't decide how to display that data. Template decides that.
With this thought, a middleware can enrich the view model with footnote but it's the template that decide where to render that footnote and how.
I hope that makes sense..
I am sorry if that was incorrect, I have less knowledge about the framework than you now
What about using A ViewModel? So our context class has one more property called view model. This view model has all properties required to render help. All middlewares which want to modify help, keep enriching this view model and keep passing it to next middleware until it finally reaches the help middleware where it gets passed to a view (template) and gets rendered.
Yes, I was thinking something similar. It's just a matter of representing that model in a way that's as simple as possible while still being expressive enough.
You can decide to a template which renders required args like this: agr1 ! or you can use a a template which renders required args like this: arg1: REQUIRED
The gotcha here is that the template now has to understand "Required". Required is a contrived enough example that it's easy to assume a template should know about it. When we generalize the concept, required is just another feature. How does the template know what to do with "Random Feature H", maybe we'll call it Hamburger. The argument is totally Hamburger.
How would the Hamburger middleware represent itself in the template? Likely, the answer is to update the text of the ViewModel.
The gotcha here is that the template now has to understand "Required".
Yes. That is because this template is specifically designed for model "XYZ" and XYX has a property "Required"
How does the template know what to do with "Random Feature H"
That is the one and only job of template. As template developers, we decide what to do with model data and hardcode its presentation logic into the template
How would the Hamburger middleware represent itself in the template? Likely, the answer is to update the text of the ViewModel.
Yes. There are some best practices of doing this. For example it's ok to put bool IsHamburger
and int HamburgerPrice
properties in the model and assign them values. It's not idiomatic to put int DescriptionLeftOffset
properties in model and assign them values in the middleware.
That is the one and only job of template. As template developers, we decide what to do with model data and hardcode its presentation logic into the template
As a template developer, how do you model for data that you don't know about? What about data from custom middleware from an external package or defined in the application?
Yes. There are some best practices of doing this. For example it's ok to put bool IsHamburger and int HamburgerPrice properties in the model and assign them values. It's not idiomatic to put int DescriptionLeftOffset properties in model and assign them values in the middleware.
I agree with this. I'm probably overthinking this. I guess the best we can do is offer a default template and make it easy for users to update when they're adding other middleware that adds new properties to the view model.
As a template developer, how do you model for data that you don't know about? What about data from custom middleware from an external package or defined in the application?
This is a good question. If a person is introducing a middleware which wants to introduce new data to help, they would have to add their own template. But they also need a place where that data can be stored. The ViewModel can have a placeholder Dictionary<string, object> customData
for such scenarios. I agree that this is not the cleanest solution, but considering the number of people who might want do this, I think that solution should be fine. A more sophisticated solution might be based on generics but I am not sure if the outcome will justify the amount of engineering involved :P
To summarize:
CommandDotNet.Scriban
, CommandDotNet.DotLiquid
, CommandDotNet.RazerLite
...nice-to-have: users can template the help strings from attributes, like [Command(Description="lala {{ app.name }} lala")]
If, along the way we develop a good ViewModel, we can consider bringing that into CommandDotNet to be available for middleware to update and append to.
Another nice-to-have would be the ability to output all commands to markdown or man page files to be used as documentation for an application. Probably v2 of the package.
Right now, help generation is done via code. For small string generation it worked ok. But with time I feel it is becoming difficult to maintain the help generation code.
Templating engines are built specifically for this. You can look at a template such as this:
https://github.com/lunet-io/scriban
and know what is going to get rendered. This approach also makes it easy to intuitively change help text. The way tests are written it should not matter how help got rendered, so ideally tests should not get modified