dotnet/templating

sourceName replacement ("." -> "_")

javitosanchez opened this issue · 12 comments

I have a problem in the replacement you are doing in sourceName property. The "." replacement has been doing only inside files replacements (text), but not in file names.
My problem is that I'm using the sourceName to create a solution file:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlankProject", "BlankProject\BlankProject.csproj", "{7CF740F7-735F-48EA-8B7B-3FFA4902371C}"

After generation (sourceName = 'Blank.Project') I get:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blank_Project", "Blank_Project\Blank_Project.csproj", "{7CF740F7-735F-48EA-8B7B-3FFA4902371C}"

but the folder & project file name is Blank.Project\Blank.Project.csproj

Is there a way to not have this replacement?

Thanks!!

@javitosanchez can you change the source project name to include a "." to see if that fixes it? For example instead of the original project being named "BlankProject" try "Blank.Project" and then when creating the template try some different like "Contoso.Project".

I'm having the same issue. Is it possible not to replace underscores? It's quite common to create projects called My.Company.Business.Web.
Thanks!

@sayedihashimi, your solution works, but, do you have plans to change this behavior? It's quite strange to force having a "." in our sources...

@RobertVejvoda if you modify your original source project to have a . in the name I believe your scenario should work.

@javitosanchez

your solution works, but, do you have plans to change this behavior? It's quite strange to force having a "." in our sources...

You're not strictly required to modify your sources. It's possible to get the replacements to work w/o doing that but you would have to add a bunch of content into the template.json file. We have some special logic which determines how to translate the SourceName (namespace value) to the correct representation when it's transformed (i.e. replace special chars with _ or make all lowercase). It's a much harder task than it seems. To do that right we need a . in the value for SourceName. I think it's easier for template authors to just update project names to have a . (and I'll be including that info as a best practice in docs), but as I said it is possible to

This is due to a special behavior applied to the name parameter - it's the only parameter with multiple forms currently & unfortunately they're implicit and not overridable at the moment (but they will be when we address #141). This behavior should definitely be documented on the authoring section of the Wiki, but we'll start here:

Value forms (as a concept) take the value it replaces & applies a set of transforms to it. Those same transforms are applied to the user supplied value for the parameter and a mapping between the forms is established.

Ex.

For a "Replaces" value of "My.Application.0*" and a user supplied value of "My.App.1?", the following set of replacements is produced

Transform To Replace Replace With
(Identity) My.Application.0* My.App1?
Safe Filename My.Application.0_ My.App.1_
Safe Namespace My.Application._0_ My.App._1_
Safe Class Name My_Application__0_ My_App__1_

When any of the values in the To Replace column are found, they're replaced with the corresponding value from Replace With.

Let's take a look at what happens, if the values in the To Replace column aren't unique:

For a "Replaces" value of "BlankApp" and a user supplied value of "My.App.1?", the following set of replacements is produced

Transform To Replace Replace With
(Identity) BlankApp My.App1?
Safe Filename BlankApp My.App.1_
Safe Namespace BlankApp My.App._1_
Safe Class Name BlankApp My_App__1_

To make this case deterministic (and give the highest odds of successful generation), the replacement that ends up happening (for a set of replacements that would replace the same value) is the one that was specified last, in this case, it's:

Safe Class Name BlankApp My_App__1_

To relate this back to the issue at hand - sourceName is the value that gets plugged in to replaces on a special (hidden) parameter that gets bound to the user supplied name. This parameter has transforms similar to the previously mentioned ones applied to it (how it works specifically is here).

Unfortunately, this one's kind of by design with the Runnable Project Generator - to keep the project (or content, generally) that's being treated as a template in as close to running shape as possible, we need to infer what the right thing to do in each situation is. There are other ways we could have approached this (like requiring that each place the name could be used, it'd be surrounded by a comment indicating which form to replace it with - or not dealing with name forms at all, forcing each difference to be a fundamentally different string and requiring the configuration to be added to generate the appropriate replacement for each one of those strings (equivalent to how other template engines have approached the problem)), but all of the other options we came up with seemed to either violate the design of not requiring specific tokenization that breaks the content or was overly intrusive and not guaranteed to actually be supported by the content (surrounding usages with metadata).

When we start in earnest on #141, we'll be looking at the following scenarios very hard

  • Getting the behaviors that come in normally with name to be overridable
  • Making sure that all of the knobs that can be turned for other values with multiple forms are exposed

We're also thinking about what it might look like to specify forms that are particular to a certain file pattern (which would also work in the exact scenario mentioned - and address #368, possibly #381 too)

I think it's easier for template authors to just update project names to have a .

@sayedihashimi when you're creating a generic template for a whole solution this doesn't make sense at all. As an example, I'm creating a template that has:

MyTemplate
|-- MyTemplate.sln
|-- src
|    +-- MyTemplate
|         +-- MyTemplate.csproj
|-- test
|    +-- MyTemplate.Tests
|        +-- MyTemplate.Tests.csproj

A user of this template might be creating a package named MR.AspNetCore.ApiVersioning. I - as a template author - can't include dots to satisfy all the different naming strategies.

@mrahhal could you elaborate on this? Isn't it just changing what you're calling "MyTemplate" to be something more verbose (or even just throwing in a dot between "My" and "Template"):

My.Template
|-- My.Template.sln
|-- src
|    +-- My.Template
|         +-- My.Template.csproj
|-- test
|    +-- My.Template.Tests
|        +-- My.Template.Tests.csproj

The number of dots or special characters doesn't matter - it's essentially just a key for pattern matching. Ex. if you have a template with a name of My.Template and My_Template is found somewhere in the source text, per the grid above, it'll treat this as something to replace using the class name replacement. If it finds My.Template in text, it'll treat it as something to replace with the namespace replacement, if it finds My.Template in a filename, it'll treat it as something to replace using the filename replacement.

@mlorbetske didn't know including a single dot would enable this to work with all cases. Thanks!

With #561 merged, the remaining work here is to allow the overriding of the forms of the implicit name parameter by specifying it in config (already can be done) and setting the value forms for it explicitly.

#867 has the other piece to this

It looks like the referenced PR has been merged, please reopen if this is still an issue

Please have the CLI respond with a warning when passing in a name that is not in .NET class name format (ie. camelcase with first letter uppercase). Alternatively have it automatically convert any name passed as project name to a valid name. Cheers!