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

Add snake_case ValueForm support #8563

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/Value-Forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,13 @@ When the value say, `Test ©` is supplied as the value for the parameter `exampl
"identifier": "kebabCase"
}
}
```
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is targeting main, which right now is for .NET 10 development, so this would need to be updated. Once we merge this we can backport it to 9.0.200 pretty easily with a /backport command.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am open to backporting it to whatever versions are available. Technically it should be able to be backported as far back as .NET 5.0.300 since I patterned all of my code after the kebabCase just above it.

I am not sure how that is done though, and will require some assistance. This is my first contribution to this repo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point in time the only real options are:

  • 9.0.200 for February's release
  • 10.0.100 for next November's release

We don't generally add new features to an already-published release except for very special circumstances.

Once this PR is merged we'll take care of the back-porting, no worries :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9.0.200 would be fantastic but I can understand not doing it as well. I do have selfish motives for wanting to more easily snake_case my .proto file names in my template, but whatever timeline the dotnet team determines is fine by me. I'm just happy to contribute 😄

**`snakeCase`** - Converts the value to snake case using the casing rules of the invariant culture. Available since .NET 9.0.100.
```json
"forms": {
"snakeCase": {
"identifier": "snakeCase"
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
"valueSource": "pascalCasedName",
"replaces": "John Smith (kebab-case)",
"valueTransform": "kebabCase"
},
"snakeCasedName": {
"type": "derived",
"valueSource": "snakeCasedName",
"replaces": "John Smith (snake_case)",
"valueTransform": "snakeCase"
}
},
"forms" :{
Expand All @@ -57,8 +63,11 @@
"kebabCase": {
"identifier": "kebabCase"
},
"snakeCase": {
"identifier": "snakeCase"
},
"firtstLowerCase": {
"identifier": "firstLowerCaseInvariant"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See
Details

- A `derived` `type` with a value transformation is performed using [value forms](https://github.com/dotnet/templating/blob/main/docs/Value-Forms.md) (`ValueForms` type).
- The sample uses `replace`, `titleCase`, `kebabCase`, `firstLowerCaseInvariant` and `chain` value forms.
- The sample uses `replace`, `titleCase`, `kebabCase`, `snakeCase`, `firstLowerCaseInvariant` and `chain` value forms.
- More value forms can be found in [the source code](https://github.com/dotnet/templating/tree/main/src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/ValueForms).

Related
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,8 @@
"firstUpperCase",
"firstUpperCaseInvariant",
"titleCase",
"kebabCase"
"kebabCase",
"snakeCase"
]
}
}
Expand Down Expand Up @@ -1051,6 +1052,14 @@
"enum": ["kebabCase"]
}
}
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this! Post-PR can you also update the canonical schema at schemastore?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. Is there some documentation on this process that I can read up on? I have not contributed here before so want to make sure I am doing it correctly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no heavy process here - it's just that many tools (including VSCode) integrate easily with Schemastore so we also have the schema mirrored there at https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/template.json - you can submit changes to it same as you did here and tag me on the PR and I'll approve it. The one here in the repo is the 'most correct' one, but the one in Schemastore is the most tooling-friendly one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
"description": "Converts the value to snake case using the casing rules of the invariant culture.",
"properties": {
"identifier": {
"enum": ["snakeCase"]
}
}
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal static class ValueFormRegistry
new FirstUpperCaseInvariantValueFormFactory(),
new FirstLowerCaseInvariantValueFormFactory(),
new KebabCaseValueFormFactory(),
new SnakeCaseValueFormFactory(),
new TitleCaseValueFormFactory(),
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.RegularExpressions;

namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects.ValueForms
{
internal class SnakeCaseValueFormFactory : ActionableValueFormFactory
{
internal const string FormIdentifier = "snakeCase";

internal SnakeCaseValueFormFactory() : base(FormIdentifier) { }

protected override string Process(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}

Regex pattern = new(@"(?:\p{Lu}\p{M}*)?(?:\p{Ll}\p{M}*)+|(?:\p{Lu}\p{M}*)+(?!\p{Ll})|\p{N}+|[^\p{C}\p{P}\p{Z}]+|[\u2700-\u27BF]");
return string.Join("_", pattern.Matches(value).Cast<Match>().Select(m => m.Value)).ToLowerInvariant();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.TemplateEngine.Orchestrator.RunnableProjects.ValueForms;

namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests.ValueFormTests
{
public class SnakeCaseValueFormTests
{
[Theory]
[InlineData("I", "i")]
[InlineData("IO", "io")]
[InlineData("FileIO", "file_io")]
[InlineData("SignalR", "signal_r")]
[InlineData("IOStream", "io_stream")]
[InlineData("COMObject", "com_object")]
[InlineData("WebAPI", "web_api")]
[InlineData("XProjectX", "x_project_x")]
[InlineData("NextXXXProject", "next_xxx_project")]
[InlineData("NoNewProject", "no_new_project")]
[InlineData("NONewProject", "no_new_project")]
[InlineData("NewProjectName", "new_project_name")]
[InlineData("ABBREVIATIONAndSomeName", "abbreviation_and_some_name")]
[InlineData("NoNoNoNoNoNoNoName", "no_no_no_no_no_no_no_name")]
[InlineData("AnotherNewNewNewNewNewProjectName", "another_new_new_new_new_new_project_name")]
[InlineData("Param1TestValue", "param_1_test_value")]
[InlineData("Windows10", "windows_10")]
[InlineData("WindowsServer2016R2", "windows_server_2016_r_2")]
[InlineData("", "")]
[InlineData(";MyWord;", "my_word")]
[InlineData("My Word", "my_word")]
[InlineData("My Word", "my_word")]
[InlineData(";;;;;", "")]
[InlineData(" ", "")]
[InlineData("Simple TEXT_here", "simple_text_here")]
[InlineData("НоваяПеременная", "новая_переменная")]
public void SnakeCaseWorksAsExpected(string input, string expected)
{
IValueForm? model = new SnakeCaseValueFormFactory().Create("test");
string actual = model.Process(input, new Dictionary<string, IValueForm>());
Assert.Equal(expected, actual);
}

[Fact]
public void CanHandleNullValue()
{
IValueForm model = new SnakeCaseValueFormFactory().Create("test");
Assert.Throws<ArgumentNullException>(() => model.Process(null!, new Dictionary<string, IValueForm>()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
"kebab": {
"identifier": "kebabCase"
},
"snake": {
"identifier": "snakeCase"
},
"title": {
"identifier": "titleCase"
}
Expand Down
Loading