mirror of
https://github.com/kubernetes/node-problem-detector.git
synced 2026-08-23 22:26:27 +00:00
- Update github.com/sirupsen/logrus v1.9.0 -> v1.9.3 in test/go.mod to fix GHSA-4f99-4q7p-p3gh (High) - Update github.com/prometheus/prometheus v0.35.0 -> v0.311.3 to fix GHSA-vffh-x6r8-xx99 (Medium) - Run go mod tidy and go mod vendor to update vendor directory
371 lines
10 KiB
Go
371 lines
10 KiB
Go
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package mangling
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// NameMangler knows how to transform sentences or words into
|
|
// identifiers that are a better fit in contexts such as:
|
|
//
|
|
// - unexported or exported go variable identifiers
|
|
// - file names
|
|
// - camel cased identifiers
|
|
// - ...
|
|
//
|
|
// The [NameMangler] is safe for concurrent use, save for its [NameMangler.AddInitialisms] method,
|
|
// which is not.
|
|
//
|
|
// # Known limitations
|
|
//
|
|
// At this moment, the [NameMangler] doesn't play well with "all caps" text:
|
|
//
|
|
// unless every single upper-cased word is declared as an initialism, capitalized words would generally
|
|
// not be transformed with the expected result, e.g.
|
|
//
|
|
// ToFileName("THIS_IS_ALL_CAPS")
|
|
//
|
|
// yields the weird outcome
|
|
//
|
|
// "t_h_i_s_i_s_a_l_l_c_a_p_s"
|
|
type NameMangler struct {
|
|
options
|
|
|
|
index *indexOfInitialisms
|
|
|
|
splitter splitter
|
|
splitterWithPostSplit splitter
|
|
|
|
_ struct{}
|
|
}
|
|
|
|
// NewNameMangler builds a name mangler ready to convert strings.
|
|
//
|
|
// The default name mangler is configured with default common initialisms and all default options.
|
|
func NewNameMangler(opts ...Option) NameMangler {
|
|
m := NameMangler{
|
|
options: optionsWithDefaults(opts),
|
|
index: newIndexOfInitialisms(),
|
|
}
|
|
m.addInitialisms(m.commonInitialisms...)
|
|
|
|
// a splitter that returns matches lexemes as ready-to-assemble strings:
|
|
// details of the lexemes are redeemed.
|
|
m.splitter = newSplitter(
|
|
withInitialismsCache(&m.index.initialismsCache),
|
|
withReplaceFunc(m.replaceFunc),
|
|
)
|
|
|
|
// a splitter that returns matches lexemes ready for post-processing
|
|
m.splitterWithPostSplit = newSplitter(
|
|
withInitialismsCache(&m.index.initialismsCache),
|
|
withReplaceFunc(m.replaceFunc),
|
|
withPostSplitInitialismCheck,
|
|
)
|
|
|
|
return m
|
|
}
|
|
|
|
// AddInitialisms declares extra initialisms to the mangler.
|
|
//
|
|
// It declares extra words as "initialisms" (i.e. words that won't be camel cased or titled cased),
|
|
// on top of the existing list of common initialisms (such as ID, HTTP...).
|
|
//
|
|
// Added words must start with a (unicode) letter. If some don't, they are ignored.
|
|
// Added words are either fully capitalized or mixed-cased. Lower-case only words are considered capitalized.
|
|
//
|
|
// It is typically used just after initializing the [NameMangler].
|
|
//
|
|
// When all initialisms are known at the time the mangler is initialized, it is preferable to
|
|
// use [NewNameMangler] with the option [WithAdditionalInitialisms].
|
|
//
|
|
// Adding initialisms mutates the mangler and should not be carried out concurrently with other calls to the mangler.
|
|
func (m *NameMangler) AddInitialisms(words ...string) {
|
|
m.addInitialisms(words...)
|
|
}
|
|
|
|
// Initialisms renders the list of initialisms supported by this mangler.
|
|
func (m *NameMangler) Initialisms() []string {
|
|
return m.index.initialisms
|
|
}
|
|
|
|
// Camelize a single word.
|
|
//
|
|
// Example:
|
|
//
|
|
// - "HELLO" and "hello" become "Hello".
|
|
func (m NameMangler) Camelize(word string) string {
|
|
ru := []rune(word)
|
|
|
|
switch len(ru) {
|
|
case 0:
|
|
return ""
|
|
case 1:
|
|
return string(unicode.ToUpper(ru[0]))
|
|
default:
|
|
camelized := poolOfBuffers.BorrowBuffer(len(word))
|
|
camelized.Grow(len(word))
|
|
defer func() {
|
|
poolOfBuffers.RedeemBuffer(camelized)
|
|
}()
|
|
|
|
camelized.WriteRune(unicode.ToUpper(ru[0]))
|
|
for _, ru := range ru[1:] {
|
|
camelized.WriteRune(unicode.ToLower(ru))
|
|
}
|
|
|
|
return camelized.String()
|
|
}
|
|
}
|
|
|
|
// ToFileName generates a suitable snake-case file name from a sentence.
|
|
//
|
|
// It lower-cases everything with underscore (_) as a word separator.
|
|
//
|
|
// Examples:
|
|
//
|
|
// - "Hello, Swagger" becomes "hello_swagger"
|
|
// - "HelloSwagger" becomes "hello_swagger"
|
|
func (m NameMangler) ToFileName(name string) string {
|
|
inptr := m.split(name)
|
|
in := *inptr
|
|
out := make([]string, 0, len(in))
|
|
|
|
for _, w := range in {
|
|
out = append(out, lower(w))
|
|
}
|
|
poolOfStrings.RedeemStrings(inptr)
|
|
|
|
return strings.Join(out, "_")
|
|
}
|
|
|
|
// ToCommandName generates a suitable CLI command name from a sentence.
|
|
//
|
|
// It lower-cases everything with dash (-) as a word separator.
|
|
//
|
|
// Examples:
|
|
//
|
|
// - "Hello, Swagger" becomes "hello-swagger"
|
|
// - "HelloSwagger" becomes "hello-swagger"
|
|
func (m NameMangler) ToCommandName(name string) string {
|
|
inptr := m.split(name)
|
|
in := *inptr
|
|
out := make([]string, 0, len(in))
|
|
|
|
for _, w := range in {
|
|
out = append(out, lower(w))
|
|
}
|
|
poolOfStrings.RedeemStrings(inptr)
|
|
|
|
return strings.Join(out, "-")
|
|
}
|
|
|
|
// ToHumanNameLower represents a code name as a human-readable series of words.
|
|
//
|
|
// It lower-cases everything with blank space as a word separator.
|
|
//
|
|
// NOTE: parts recognized as initialisms just keep their original casing.
|
|
//
|
|
// Examples:
|
|
//
|
|
// - "Hello, Swagger" becomes "hello swagger"
|
|
// - "HelloSwagger" or "Hello-Swagger" become "hello swagger"
|
|
func (m NameMangler) ToHumanNameLower(name string) string {
|
|
s := m.splitterWithPostSplit
|
|
in := s.split(name)
|
|
out := make([]string, 0, len(*in))
|
|
|
|
for _, w := range *in {
|
|
if !w.IsInitialism() {
|
|
out = append(out, lower(w.GetOriginal()))
|
|
} else {
|
|
out = append(out, trim(w.GetOriginal()))
|
|
}
|
|
}
|
|
|
|
poolOfLexems.RedeemLexems(in)
|
|
|
|
return strings.Join(out, " ")
|
|
}
|
|
|
|
// ToHumanNameTitle represents a code name as a human-readable series of titleized words.
|
|
//
|
|
// It titleizes every word with blank space as a word separator.
|
|
//
|
|
// Examples:
|
|
//
|
|
// - "hello, Swagger" becomes "Hello Swagger"
|
|
// - "helloSwagger" becomes "Hello Swagger"
|
|
func (m NameMangler) ToHumanNameTitle(name string) string {
|
|
s := m.splitterWithPostSplit
|
|
in := s.split(name)
|
|
|
|
out := make([]string, 0, len(*in))
|
|
for _, w := range *in {
|
|
original := trim(w.GetOriginal())
|
|
if !w.IsInitialism() {
|
|
out = append(out, m.Camelize(original))
|
|
} else {
|
|
out = append(out, original)
|
|
}
|
|
}
|
|
poolOfLexems.RedeemLexems(in)
|
|
|
|
return strings.Join(out, " ")
|
|
}
|
|
|
|
// ToJSONName generates a camelized single-word version of a sentence.
|
|
//
|
|
// The output assembles every camelized word, but for the first word, which
|
|
// is lower-cased.
|
|
//
|
|
// Example:
|
|
//
|
|
// - "Hello_swagger" becomes "helloSwagger"
|
|
func (m NameMangler) ToJSONName(name string) string {
|
|
inptr := m.split(name)
|
|
in := *inptr
|
|
out := make([]string, 0, len(in))
|
|
|
|
for i, w := range in {
|
|
if i == 0 {
|
|
out = append(out, lower(w))
|
|
continue
|
|
}
|
|
out = append(out, m.Camelize(trim(w)))
|
|
}
|
|
|
|
poolOfStrings.RedeemStrings(inptr)
|
|
|
|
return strings.Join(out, "")
|
|
}
|
|
|
|
// ToVarName generates a legit unexported go variable name from a sentence.
|
|
//
|
|
// The generated name plays well with linters (see also [NameMangler.ToGoName]).
|
|
//
|
|
// Examples:
|
|
//
|
|
// - "Hello_swagger" becomes "helloSwagger"
|
|
// - "Http_server" becomes "httpServer"
|
|
//
|
|
// This name applies the same rules as [NameMangler.ToGoName] (legit exported variable), save the
|
|
// capitalization of the initial rune.
|
|
//
|
|
// Special case: when the initial part is a recognized as an initialism (like in the example above),
|
|
// the full part is lower-cased.
|
|
func (m NameMangler) ToVarName(name string) string {
|
|
return m.goIdentifier(name, false)
|
|
}
|
|
|
|
// ToGoName generates a legit exported go variable name from a sentence.
|
|
//
|
|
// The generated name plays well with most linters.
|
|
//
|
|
// ToGoName abides by the go "exported" symbol rule starting with an upper-case letter.
|
|
//
|
|
// Examples:
|
|
//
|
|
// - "hello_swagger" becomes "HelloSwagger"
|
|
// - "Http_server" becomes "HTTPServer"
|
|
//
|
|
// # Edge cases
|
|
//
|
|
// Whenever the first rune is not eligible to upper case, a special prefix is prepended to the resulting name.
|
|
// By default this is simply "X" and you may customize this behavior using the [WithGoNamePrefixFunc] option.
|
|
//
|
|
// This happens when the first rune is not a letter, e.g. a digit, or a symbol that has no word transliteration
|
|
// (see also [WithReplaceFunc] about symbol transliterations),
|
|
// as well as for most East Asian or Devanagari runes, for which there is no such concept as upper-case.
|
|
//
|
|
// # Linting
|
|
//
|
|
// [revive], the successor of golint is the reference linter.
|
|
//
|
|
// This means that [NameMangler.ToGoName] supports the initialisms that revive checks (see also [DefaultInitialisms]).
|
|
//
|
|
// At this moment, there is no attempt to transliterate unicode into ascii, meaning that some linters
|
|
// (e.g. asciicheck, gosmopolitan) may croak on go identifiers generated from unicode input.
|
|
//
|
|
// [revive]: https://github.com/mgechev/revive
|
|
func (m NameMangler) ToGoName(name string) string {
|
|
return m.goIdentifier(name, true)
|
|
}
|
|
|
|
func (m NameMangler) goIdentifier(name string, exported bool) string {
|
|
s := m.splitterWithPostSplit
|
|
lexems := s.split(name)
|
|
defer func() {
|
|
poolOfLexems.RedeemLexems(lexems)
|
|
}()
|
|
lexemes := *lexems
|
|
|
|
if len(lexemes) == 0 {
|
|
return ""
|
|
}
|
|
|
|
result := poolOfBuffers.BorrowBuffer(len(name))
|
|
defer func() {
|
|
poolOfBuffers.RedeemBuffer(result)
|
|
}()
|
|
|
|
firstPart := lexemes[0]
|
|
if !exported {
|
|
if ok := firstPart.WriteLower(result, true); !ok {
|
|
// NOTE: an initialism as the first part is lower-cased: no longer generates stuff like hTTPxyz.
|
|
//
|
|
// same prefixing rule applied to unexported variable as to an exported one, so that we have consistent
|
|
// names, whether the generated identifier is exported or not.
|
|
result.WriteString(strings.ToLower(m.prefixFunc()(name)))
|
|
result.WriteString(lexemes[0].GetOriginal())
|
|
}
|
|
} else {
|
|
if ok := firstPart.WriteTitleized(result, true); !ok {
|
|
// "repairs" a lexeme that doesn't start with a letter to become
|
|
// the start a legit go name. The current strategy is very crude and simply adds a fixed prefix,
|
|
// e.g. "X".
|
|
// For instance "1_sesame_street" would be split into lexemes ["1", "sesame", "street"] and
|
|
// the first one ("1") would result in something like "X1" (with the default prefix function).
|
|
//
|
|
// NOTE: no longer forcing the first part to be fully upper-cased
|
|
result.WriteString(m.prefixFunc()(name))
|
|
result.WriteString(lexemes[0].GetOriginal())
|
|
}
|
|
}
|
|
|
|
for _, lexem := range lexemes[1:] {
|
|
// NOTE: no longer forcing initialism parts to be fully upper-cased:
|
|
// * pluralized initialism preserve their trailing "s"
|
|
// * mixed-cased initialisms, such as IPv4, are preserved
|
|
if ok := lexem.WriteTitleized(result, false); !ok {
|
|
// it's not titleized: perhaps it's too short, perhaps the first rune is not a letter.
|
|
// write anyway
|
|
result.WriteString(lexem.GetOriginal())
|
|
}
|
|
}
|
|
|
|
return result.String()
|
|
}
|
|
|
|
func (m *NameMangler) addInitialisms(words ...string) {
|
|
m.index.add(words...)
|
|
m.index.buildCache()
|
|
}
|
|
|
|
// split calls the inner splitter.
|
|
func (m NameMangler) split(str string) *[]string {
|
|
s := m.splitter
|
|
lexems := s.split(str)
|
|
result := poolOfStrings.BorrowStrings()
|
|
|
|
for _, lexem := range *lexems {
|
|
*result = append(*result, lexem.GetOriginal())
|
|
}
|
|
poolOfLexems.RedeemLexems(lexems)
|
|
|
|
return result
|
|
}
|