-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/Azure/azure-dev into preflight
- Loading branch information
Showing
7 changed files
with
287 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -150,6 +150,7 @@ ignoreWords: | |
- tfstate | ||
- tfvars | ||
- traf | ||
- unmanage | ||
- useragent | ||
- versioncontrol | ||
- vmss | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,3 +40,4 @@ overrides: | |
- azdev | ||
- myimage | ||
- azureai | ||
- entra |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package repository | ||
|
||
import "strings" | ||
|
||
//cspell:disable | ||
|
||
// LabelName cleans up a string to be used as a RFC 1123 Label name. | ||
// It does not enforce the 63 character limit. | ||
// | ||
// RFC 1123 Label name: | ||
// - contain only lowercase alphanumeric characters or '-' | ||
// - start with an alphanumeric character | ||
// - end with an alphanumeric character | ||
// | ||
// Examples: | ||
// - myproject, MYPROJECT -> myproject | ||
// - myProject, myProjecT, MyProject, MyProjecT -> my-project | ||
// - my.project, My.Project, my-project, My-Project -> my-project | ||
func LabelName(name string) string { | ||
hasSeparator, n := cleanAlphaNumeric(name) | ||
if hasSeparator { | ||
return labelNameFromSeparators(n) | ||
} | ||
|
||
return labelNameFromCasing(name) | ||
} | ||
|
||
//cspell:enable | ||
|
||
// cleanAlphaNumeric removes non-alphanumeric characters from the name. | ||
// | ||
// It also returns whether the name uses word separators. | ||
func cleanAlphaNumeric(name string) (hasSeparator bool, cleaned string) { | ||
sb := strings.Builder{} | ||
hasSeparator = false | ||
for _, c := range name { | ||
if isAsciiAlphaNumeric(c) { | ||
sb.WriteRune(c) | ||
} else if isSeparator(c) { | ||
hasSeparator = true | ||
sb.WriteRune(c) | ||
} | ||
} | ||
|
||
return hasSeparator, sb.String() | ||
} | ||
|
||
func isAsciiAlphaNumeric(r rune) bool { | ||
return ('0' <= r && r <= '9') || ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') | ||
} | ||
|
||
func isSeparator(r rune) bool { | ||
return r == '-' || r == '_' || r == '.' | ||
} | ||
|
||
func lowerCase(r rune) rune { | ||
if 'A' <= r && r <= 'Z' { | ||
r += 'a' - 'A' | ||
} | ||
return r | ||
} | ||
|
||
// Converts camel-cased or Pascal-cased names into lower-cased dash-separated names. | ||
// Example: MyProject, myProject -> my-project | ||
func labelNameFromCasing(name string) string { | ||
result := strings.Builder{} | ||
// previously seen upper-case character | ||
prevUpperCase := -2 // -2 to avoid matching the first character | ||
|
||
for i, c := range name { | ||
if 'A' <= c && c <= 'Z' { | ||
if prevUpperCase == i-1 { // handle runs of upper-case word | ||
prevUpperCase = i | ||
result.WriteRune(lowerCase(c)) | ||
continue | ||
} | ||
|
||
if i > 0 && i != len(name)-1 { | ||
result.WriteRune('-') | ||
} | ||
|
||
prevUpperCase = i | ||
} | ||
|
||
if isAsciiAlphaNumeric(c) { | ||
result.WriteRune(lowerCase(c)) | ||
} | ||
} | ||
|
||
return result.String() | ||
} | ||
|
||
// Converts all word-separated names into lower-cased dash-separated names. | ||
// Examples: my.project, my_project, My-Project -> my-project | ||
func labelNameFromSeparators(name string) string { | ||
result := strings.Builder{} | ||
for i, c := range name { | ||
if isAsciiAlphaNumeric(c) { | ||
result.WriteRune(lowerCase(c)) | ||
} else if i > 0 && i != len(name)-1 && isSeparator(c) { | ||
result.WriteRune('-') | ||
} | ||
} | ||
|
||
return result.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package repository | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
//cspell:disable | ||
|
||
func TestLabelName(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
input string | ||
expected string | ||
}{ | ||
{"Lowercase", "myproject", "myproject"}, | ||
{"Uppercase", "MYPROJECT", "myproject"}, | ||
{"MixedCase", "myProject", "my-project"}, | ||
{"MixedCaseEnd", "myProjecT", "my-project"}, | ||
{"TitleCase", "MyProject", "my-project"}, | ||
{"TitleCaseEnd", "MyProjecT", "my-project"}, | ||
{"WithDot", "my.project", "my-project"}, | ||
{"WithDotTitleCase", "My.Project", "my-project"}, | ||
{"WithHyphen", "my-project", "my-project"}, | ||
{"WithHyphenTitleCase", "My-Project", "my-project"}, | ||
{"StartWithNumber", "1myproject", "1myproject"}, | ||
{"EndWithNumber", "myproject2", "myproject2"}, | ||
{"MixedWithNumbers", "my2Project3", "my2-project3"}, | ||
{"SpecialCharacters", "my_project!@#", "my-project"}, | ||
{"EmptyString", "", ""}, | ||
{"OnlySpecialCharacters", "@#$%^&*", ""}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
result := LabelName(tt.input) | ||
if result != tt.expected { | ||
t.Errorf("LabelName(%q) = %q, want %q", tt.input, result, tt.expected) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestLabelNameEdgeCases(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
input string | ||
expected string | ||
}{ | ||
{"SingleCharacter", "A", "a"}, | ||
{"TwoCharacters", "Ab", "ab"}, | ||
{"StartEndHyphens", "-abc-", "abc"}, | ||
{"LongString", | ||
"ThisIsOneVeryLongStringThatExceedsTheSixtyThreeCharacterLimitForRFC1123LabelNames", | ||
"this-is-one-very-long-string-that-exceeds-the-sixty-three-character-limit-for-rfc1123-label-names"}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
result := LabelName(tt.input) | ||
if result != tt.expected { | ||
t.Errorf("LabelName(%q) = %q, want %q", tt.input, result, tt.expected) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
//cspell:enable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters