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

Prevent project name modifications #1066

Closed
dansiegel opened this issue Jul 11, 2017 · 13 comments
Closed

Prevent project name modifications #1066

dansiegel opened this issue Jul 11, 2017 · 13 comments

Comments

@dansiegel
Copy link
Contributor

Currently if you do dotnet new sometemplate -o Foo.Bar this will result in certain fields recieving like the namespace of files being renamed to Foo_Bar. For some replacements, this is acceptable, but for most like the namespace, this is highly undesirable. Is there some way to prevent this?

@seancpeters
Copy link
Contributor

If you're referring to some way to pass flags to an existing template to get certain replacements to not occur, unless the template author made some sort of provisions for it, then it can't be conditionally controlled without changing the template source.

For templates you're creating, there are a number of possibilities. If you want to post details on a specific scenario, we can talk through your options. But I might be able to provide enough general guidelines here for you to accomplish what you're trying to do. Let's use the EmptyWeb-CSharp 2.0 template as an example: https://github.com/dotnet/templating/tree/rel/vs2017/3-Preview3/template_feed/Microsoft.DotNet.Web.ProjectTemplates.2.0/content/EmptyWeb-CSharp

In template.json, this line is key to the type of content replacements you're referring to:

"sourceName": "Company.WebApplication1",

sourceName provides the literal string that will get replaced by the value of the -n input, or the -o input if the -n isn't provided (if invoking from the CLI). So with the invocation:
dotnet new web -o Foo.Bar
the value Company.WebApplication1 will be replaced by Foo.Bar for file names, and in file contents (as you noted with Foo_Bar sometimes the value gets modified to be appropriate for its use).

In the emptyweb template, the file Program.cs contains the line:
namespace Company.WebApplication1
...which matches the source name, and therefore gets replaced. To avoid this replacement, simply give the namespace a different value than the value of sourceName, and different from any other symbol value defined in your template. The emitted file on template creation will have the literal value for namespace which was in the source.

Caveat: replacement operations do not care about whitespace or word boundaries, so the namespace couldn't contain the substring Company.WebApplication1, or that part would get replaced.

If this isn't sufficient for your scenario, please let me know, and we can figure out a different approach.

@mlorbetske
Copy link
Contributor

Expanding on what @seancpeters mentioned:

If the values that are being replaced shouldn't be driven by the user input at all, the suggestion would be to change sourceName to not have a value that is a substring of that value.

If there's a particular occurrence that shouldn't be replaced, but most should be, then using flags to govern the behavior would be the suggestion.

For all other cases, there's a detailed breakdown of how name replacements happens in #402. Essentially, there ends up being a map between different transformations of what the user supplied for the name parameter and the string that will get replaced by that transformed value - duplicated source variations are addressed in a last in wins fashion. Generally, it comes down to how "well specified" sourceName is (whether any of the transformations that are applied it it result in duplication of the value of sourceName under a different transformation).

For example:
Given a source name of "foo", the transformed value that's safe for a namespace is also "foo", so anywhere "foo" is seen it'd get replaced with the namespace transformed version of the user input rather than the original name. In fact, it's also the same as the lower case namespace one, so everything would be replaced with the lower cased version of the namespace-safe transformed version of the user input. This process again holds through finding a safe class name transform of the sourceName value and the lower cased version as well.

Now, if we change to "Foo" for sourceName instead, it becomes a little more well specified, the class name variant is now used (because Foo is a safe class name without modification), but case variations are honored (because Foo != foo).

Now, if we throw in a dot, making sourceName "Foo.Bar", it's no longer a safe class name (as the dot prevents that), so we end up with all occurrences of "Foo.Bar" being replaced with the namespace safe transform on the user's name value and "Foo_Bar" being replaced with the safe class name transform applied to the user's value. This is the level of specificity we currently use in the templates in this repo (which is insufficient).

The next step would be to make sourceName not a valid namespace, but still agree with for file names - ex. "Foo.Bar Baz". This produces a namespace of Foo.Bar_Baz, so any occurrences of that string in the template would be replaced by the namespace safe version of the user input, while "Foo.Bar Baz" would be replaced with the exact value specified by the user (though it should instead be the safe file name version of it - @seancpeters did we do that as part of the unmerged PR for bringing name and value forms together?)

Sorry for any typos/incoherence/tone issues, wrote this up from my phone.

@sayedihashimi
Copy link
Member

Sorry for any typos/incoherence/tone issues, wrote this up from my phone.

That's a lot of typing for a phone!

@sayedihashimi
Copy link
Member

Here is a comment by @mlorbetske in which he discussed some of this previously #402 (comment)

@dansiegel
Copy link
Contributor Author

@seancpeters @mlorbetske super helpful! Thank you both!

@dansiegel
Copy link
Contributor Author

@mlorbetske I'm running into a slightly different variant of this now...

I have the project name as Company.Project.ModuleName, one of the files really should be named whatever ModuleName ends up as...

input Class/File name
Acme.Anvils.AuthModule AuthModule
Acme.AuthModule AuthModule
AuthModule AuthModule

any ideas?

@mlorbetske mlorbetske reopened this Aug 1, 2017
@mlorbetske
Copy link
Contributor

@seancpeters any thoughts on how to do about this?

@seancpeters
Copy link
Contributor

@dansiegel - we don't currently have a way to support this type of file renames. But we're currently designing a more robust system for file renaming in a lot of ways, including what you're asking for here. I'll keep this thread updated as we progress.

@seancpeters
Copy link
Contributor

seancpeters commented Aug 2, 2017

@dansiegel - This PR #1175 is the first step towards having more control over file renaming in templates. It does enough to satisfy your need in this thread. Once it's merged, you'll be able to setup template configuration such as:

{
...
  "symbols": {
    "app1Rename": {
      "type": "derived",  // the new parameter the
      "valueSource": "name",  // the name of the other symbol whose value will be used to derive this value
      "valueTransform": "ValueAfterLastDot",  // the name of the value form to apply to the source value
      "fileRename": "Application1",  // the filename string to be renamed by the value of this symbol (this is previously existing functionality for parameter symbols)
      "description": "A value derived from the 'name' param, used to rename Application1.cs"
    }
  }
...
  "forms": {
    "ValueAfterLastDot": {
      "identifier": "replace",
      "pattern": "^.*\\.(?=[^\\.]+$)",	// regex to match everything up to and including the final "."
      "replacement": ""  // replace it with empty string
    }
  }
...
}

A couple things to note:

  • Derived symbols values cannot be specified as inputs to template creation, they're strictly based on other symbols.
  • Derived symbols can also be used for content replacements within files, just like parameter type symbols. So, for example, if you wanted the value of 'app1Rename' to also replace the string 'foo' within file contents, just add to app1Rename's configuration: "replaces": "foo"
    "app1Rename": {
      "type": "derived",  // the new parameter the
      "valueSource": "name",  // the name of the other symbol whose value will be used to derive this value
      "valueTransform": "ValueAfterLastDot",  // the name of the value form to apply to the source value
      "fileRename": "Application1",  // the filename string to be renamed by the value of this symbol (this is previously existing functionality for parameter symbols)
      "description": "A value derived from the 'name' param, used to rename Application1.cs",
      "replaces": "foo"
    }

@seancpeters
Copy link
Contributor

I should clarify about the name symbol in the above post. When a template doesn't explicitly define a symbol called name, the sourceName is internally turned into a parameter symbol named "name". Since your template includes:
"sourceName": "Company.WebApplication1"
... the derived symbol is able to use that value via:
"valueSource": "name"

@dansiegel
Copy link
Contributor Author

will this be available in 2.0.0-pre3?

@seancpeters
Copy link
Contributor

@dansiegel - This won't be available for 2.0.0-pre3, it'll most likely be in the first 2.1.0 preview.

@seancpeters
Copy link
Contributor

@dansiegel - I've just merged the derived symbols code #1194 into dotnet:rel/2.1.0-preview1.

This test file contains an example that implements what we'd discussed earlier in this thread: https://github.com/dotnet/templating/pull/1194/files#diff-48edb1d14984859d5e1d2cca3a168db7
The definition of the symbol derivedRename and the corresponding form AfterLastDot are the interesting / relevant parts. The meanings of the pieces of the symbol definition remain as described previously.

This test file is quite similar to the previous, but also defines value forms on the resultant derived value:
https://github.com/dotnet/templating/pull/1194/files#diff-7861249ccf9ad06600cf8bc625897646
The derived symbol value still behaves in the same manner as the first test example. Then the

"forms": { 
        "global": [ "identity", "ChainAS" ] 
},

configuration is applied to the derived value, creating additional replacements in the same manner that value forms are used on parameter type symbols.

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

No branches or pull requests

4 participants