-
-
Notifications
You must be signed in to change notification settings - Fork 15
Support for trimming chars from generated slug's start/end. #175
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -214,4 +214,37 @@ Specifying the `DeniedCharactersRegex` option will disable the character removal | |
|
|
||
| This will limit the length of the generated slug to be a maximum of the number of chars given by the parameter. If the truncation happens in a way that a trailing `-` is left, it will be removed. | ||
|
|
||
| - Default value: `null` | ||
| - Default value: `null` | ||
|
|
||
| ### `TrimChars`, `TrimEndChars`, `TrimStartChars` | ||
|
|
||
| Trim options to remove characters from the input before replacements and character filtering. This is useful to prevent generated slug to start/end with certain characters like dot. | ||
|
|
||
| - Options | ||
| - `TrimChars` — characters to trim from both ends (behaves like `string.Trim(char[])`). | ||
| - `TrimEndChars` — characters to trim from the end (behaves like `string.TrimEnd(char[])`). | ||
| - `TrimStartChars` — characters to trim from the start (behaves like `string.TrimStart(char[])`). | ||
|
|
||
| - Default value: all three are empty arrays (no extra trimming). | ||
|
Comment on lines
+221
to
+228
|
||
|
|
||
| - Example: | ||
|
|
||
| ```csharp | ||
| var helper = new SlugHelper(new SlugHelperConfiguration | ||
| { | ||
| TrimChars = ['.'] | ||
| }); | ||
| Console.WriteLine(helper.GenerateSlug(".hello world.")); // "hello-world" | ||
|
|
||
| helper = new SlugHelper(new SlugHelperConfiguration | ||
| { | ||
| TrimEndChars = ['.'] | ||
| }); | ||
| Console.WriteLine(helper.GenerateSlug(".hello world.")); // ".hello-world" | ||
|
|
||
| helper = new SlugHelper(new SlugHelperConfiguration | ||
| { | ||
| TrimStartChars = ['.'] | ||
| }); | ||
| Console.WriteLine(helper.GenerateSlug(".hello world.")); // "hello-world." | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,6 +38,9 @@ public virtual string GenerateSlug(string inputString) | |||||||||
| normalizedInput = normalizedInput.Normalize(NormalizationForm.FormD); | ||||||||||
| normalizedInput = Config.TrimWhitespace ? normalizedInput.Trim() : normalizedInput; | ||||||||||
| normalizedInput = Config.ForceLowerCase ? normalizedInput.ToLowerInvariant() : normalizedInput; | ||||||||||
| normalizedInput = Config.TrimChars.Length != 0 ? normalizedInput.Trim(Config.TrimChars) : normalizedInput; | ||||||||||
|
||||||||||
| normalizedInput = Config.TrimChars.Length != 0 ? normalizedInput.Trim(Config.TrimChars) : normalizedInput; | |
| normalizedInput = Config.TrimChars.Length != 0 && Config.TrimEndChars.Length == 0 && Config.TrimStartChars.Length == 0 | |
| ? normalizedInput.Trim(Config.TrimChars) | |
| : normalizedInput; |
Copilot
AI
Feb 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trim operations are applied early in the pipeline, before string replacements and character filtering. This means they cannot trim characters that appear at the edges as a result of transformations. For example, if a string replacement adds a dot at the end, or if character filtering removes characters that expose trailing punctuation, the current implementation won't trim them. The MaximumLength feature (lines 92-100) sets a precedent by trimming trailing dashes AFTER all transformations. Consider moving these trim operations to after line 100 (before the return) to trim from the final slug, which would be more consistent and handle more use cases.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -61,6 +61,21 @@ public class SlugHelperConfiguration | |||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||
| public bool TrimWhitespace { get; set; } = true; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||
| /// List of characters to be trimmed (removed from both ends) from generated slug | ||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||
| public char[] TrimChars { get; set; } = []; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||
| /// List of characters to be trimmed from end of the generated slug | ||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||
| public char[] TrimEndChars { get; set; } = []; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||
| /// List of characters to be trimmed from start of the generated slug | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+75
|
||||||||||||||||||||||||||||||||||||||||||||||
| /// List of characters to be trimmed (removed from both ends) from generated slug | |
| /// </summary> | |
| public char[] TrimChars { get; set; } = []; | |
| /// <summary> | |
| /// List of characters to be trimmed from end of the generated slug | |
| /// </summary> | |
| public char[] TrimEndChars { get; set; } = []; | |
| /// <summary> | |
| /// List of characters to be trimmed from start of the generated slug | |
| /// List of characters to be trimmed (removed from both ends) from the input string before slug generation. | |
| /// </summary> | |
| public char[] TrimChars { get; set; } = []; | |
| /// <summary> | |
| /// List of characters to be trimmed from the end of the input string before slug generation. | |
| /// </summary> | |
| public char[] TrimEndChars { get; set; } = []; | |
| /// <summary> | |
| /// List of characters to be trimmed from the start of the input string before slug generation. |
ctolkien marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -512,6 +512,51 @@ public void TestNoTrimmingWithLeadingAndTrailingSpaces(ISlugHelper helper) | |
| Assert.Equal(expected, helper.GenerateSlug(original)); | ||
| } | ||
|
|
||
| [Theory] | ||
| [MemberData(nameof(GenerateStandardSluggers))] | ||
| public void TestTrimmingEndChars(ISlugHelper helper) | ||
| { | ||
| const string original = "hello._world._"; | ||
| const string expected = "hello._world"; | ||
|
|
||
| helper.Config = new SlugHelperConfiguration | ||
| { | ||
| TrimEndChars = [ '.', '_' ], | ||
| }; | ||
|
|
||
| Assert.Equal(expected, helper.GenerateSlug(original)); | ||
| } | ||
|
Comment on lines
+515
to
+528
|
||
|
|
||
| [Theory] | ||
| [MemberData(nameof(GenerateStandardSluggers))] | ||
| public void TestTrimmingStartChars(ISlugHelper helper) | ||
| { | ||
| const string original = "._hello._world"; | ||
| const string expected = "hello._world"; | ||
|
|
||
| helper.Config = new SlugHelperConfiguration | ||
| { | ||
| TrimStartChars = [ '.', '_' ], | ||
| }; | ||
|
|
||
| Assert.Equal(expected, helper.GenerateSlug(original)); | ||
| } | ||
|
|
||
ctolkien marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| [Theory] | ||
| [MemberData(nameof(GenerateStandardSluggers))] | ||
| public void TestTrimmingChars(ISlugHelper helper) | ||
| { | ||
| const string original = "._hello._world._"; | ||
| const string expected = "hello._world"; | ||
|
|
||
| helper.Config = new SlugHelperConfiguration | ||
| { | ||
| TrimChars = [ '.', '_' ], | ||
| }; | ||
|
|
||
| Assert.Equal(expected, helper.GenerateSlug(original)); | ||
| } | ||
|
|
||
| [Theory] | ||
| [MemberData(nameof(GenerateStandardSluggers))] | ||
| public void TestReplacementWithEmptyStringThenCollapsing(ISlugHelper helper) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar issue: "to prevent generated slug to start/end" should be either "to prevent the generated slug from starting/ending" or "to prevent generated slugs from starting/ending".