dnaka91 / cargo-hatch

Hatch new projects like a chick coming out of its egg
GNU Affero General Public License v3.0
7 stars 1 forks source link

Support dynamic defaults #8

Open ModProg opened 1 year ago

ModProg commented 1 year ago

I have a variable that is the project_name by default, but can be changed.

I'd like to be able to do something like: default = "{{project_name}}"

ModProg commented 1 year ago

For strings, this is relatively easy to implement, just call Tera::one_off on the defaults. For numbers/bools this could be done by calling parse on them.... but what to do with list/multilist...

dnaka91 commented 1 year ago

For a list this could just resolve to either the index or the concrete value. In case of multi-lists though, we'd need some kind of separator, which would work for indexes, but concrete values may conflict and then we have to handle escape sequences and what not.

Maybe we don't go all out on templates for the default values and rather do references only? So one input can reference the other one.

In addition, this builds a dependency chain, which would need to be evaluated (same problem in #10 actually). For starters, it's okay to just rely on Tera and abort with an error due to missing variables, with a nice info message about what value is missing (likely included in the Tera error already).

ModProg commented 1 year ago

well it should just be top to bottom. no need to over complicate.

ModProg commented 1 year ago

Maybe we don't go all out on templates for the default values and rather do references only? So one input can reference the other one.

I would like to be able to go a little further. But maybe restricting the scope to strings, bools, lists and numbers would be fine.

Or make the output for list and multi_list just the indexes, because then we can just do split(','), usize::parse().

dnaka91 commented 1 year ago

Another idea that just came to mind is, to reparse the input as TOML or JSON, so it would still be a string in the initial config, but the output is expected to be a valid array in one of the formats, so don't need a manual parsing for that.

Tera doesn't have a nice way of surrounding a string with quotes on each element of an array, but it has a json_encode function that could be used instead 🤔.

ModProg commented 1 year ago

json sounds like a good ide. the datatypes are almost identical to toml so should be fine!

dnaka91 commented 1 year ago

Sorry, but I'm holding off on this a little. Currently, there is already a fair amount of complexity involved in parsing the argument settings, as they require validation.

Implementing this would mean, we need yet another copy of those, for pre-processing the default values as templates, then further passing the result on to the final structs. Plus, I'm not super happy with this addition, although I can see its benefit. Don't want too much templating in every setting, and keep it somewhat simple, by keeping templates only in the condition field.

So I have to think about this a bit, how to solve it or if I even really want to add this.

ModProg commented 1 year ago

Totally understand your hesitation.

For now, having the ability to do references would be enough for me (technically I need to do a - to _ replacement, but I would be fine with skipping that).

The reason I was thinking the templating would be nicer is that it would be consistent with the other fields that allow referencing. (on top of being able to modify values e.g. CAPITALIZE/replacing illegal characters)

I could also just add a description to it: "(leave empty to use the default from the other value)" and do the default implementation in the actual template where I use the value. But I'd prefer to have it be integrated in there.

dnaka91 commented 1 year ago

I've come up with an idea of making technically everything a template. So it can basically be either the right value, or a string that's considered a template and then parsed back into the expected type, and the processing can continue as normal.

But it won't support referencing other arguments. That means, you get the base context, but not the data of any other arguments. The reason is, that the config is parsed upfront and validated, and I want to keep it that way.

To make it reference other values, I'd have to relax that validation and can't verify the config for being valid anymore, until the user inputs all prompts. And that I won't do.

It works for the condition field, because that is always a string and always has the same transformation from template string to boolean. Actually, you can already make the template fail later on by defining invalid templates in those fields. But that is slightly different, because it doesn't involve any extra validation.

dnaka91 commented 1 year ago

That means I'll have to come up with something else. Maybe not a full "everything can be a template" solution, but more scoped towards template arguments only.

Will let you know when I have a better idea for this.

ModProg commented 1 year ago

Understandable, the validation makes it difficult...

One idea would be to validate the default values i.e. run it with the default values for each argument, this would require everything to have a default value though if it was referenced, and would only validate partly.

What interests me is, what is the difference of an invalid template in a value and an invalid template in a file to you. For me these seem to result in similar errors.

ModProg commented 1 year ago

That means I'll have to come up with something else. Maybe not a full "everything can be a template" solution, but more scoped towards template arguments only.

What values did you think off on top of arguments?

Things like type/name would be overkill IMO

dnaka91 commented 1 year ago

Until further notice, I decided to not continue on this. Having conditions in the arguments is already a stretch in where I want to go with the project (or rather stay, really).

The thing is that I rather want to keep the project simple. That means the overall vision that I have with this project is to solve some pain points that I have with cargo-generate, but not much more, with a big focus on simplicity.

Biggest key point is, to have the Tera engine for templating, as I'm so used to it. And for the config I never planned anything too fancy, mostly static and simple.

Maybe at some point I decide differently, but for the moment I favor the current state of simplicity and rather focus on fixing any bugs or refine the current feature set, without introducing anything radical new.

That said, you're of course free to keep your own fork of it 👍

ModProg commented 1 year ago

That said, you're of course free to keep your own fork of it +1

I might look into it and see if I can implement this in a sane way.

But maybe one thing that could be done to elevate some of the pain is an optional: bool setting, that would make it a bit easier to fill in the default later while templating.

Currently, I have it set up like this:

[crate_name]
type = "string"
description = "Name of the rust crate (uses directory name by default)"
validator = "crate"
default = "crate_name"
{% macro default(value, place_holder, default) %}
    {%- if value == place_holder -%}
        {{default}}
    {%- else -%}
        {{value}}
    {%- endif -%}
{% endmacro default %}

{% macro crate_name() -%}
{{self::default(
        value = crate_name,
        place_holder = "crate_name",
        default = project_name | trim_end_matches(pat=".nvim") | trim_end_matches(pat=".vim")
        )}}
{%- endmacro crate_name%}

But with an optional argument, I could reduce this at least slightly to:

[crate_name]
type = "string"
description = "Name of the rust crate (uses directory name by default)"
validator = "crate"
optional = true
{% macro crate_name() -%}
{{crate_name | default(value=project_name | trim_end_matches(pat=".nvim") | trim_end_matches(pat=".vim"))}}
{%- endmacro crate_name%}