mirror of
https://github.com/kubernetes/node-problem-detector.git
synced 2026-08-28 01:47:20 +00:00
Fix Grype CVEs: update logrus and prometheus/prometheus
- 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
This commit is contained in:
committed by
Ciprian Hacman
parent
255c6e602c
commit
97bb2fbb44
+90
@@ -0,0 +1,90 @@
|
||||
# Benchmarking name mangling utilities
|
||||
|
||||
```bash
|
||||
go test -bench XXX -run XXX -benchtime 30s
|
||||
```
|
||||
|
||||
## Benchmarks at `b3e7a5386f996177e4808f11acb2aa93a0f660df`
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
|
||||
BenchmarkToXXXName/ToGoName-4 862623 44101 ns/op 10450 B/op 732 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-4 853656 40728 ns/op 10468 B/op 734 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-4 1268312 27813 ns/op 9785 B/op 617 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-4 1276322 27903 ns/op 9785 B/op 617 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-4 895334 40354 ns/op 10472 B/op 731 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-4 882441 40678 ns/op 10566 B/op 749 allocs/op
|
||||
```
|
||||
|
||||
## Benchmarks after PR #79
|
||||
|
||||
~ x10 performance improvement and ~ /100 memory allocations.
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
|
||||
BenchmarkToXXXName/ToGoName-4 9595830 3991 ns/op 42 B/op 5 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-4 9194276 3984 ns/op 62 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-4 17002711 2123 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-4 16772926 2111 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-4 9788331 3749 ns/op 92 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-4 9188260 3941 ns/op 104 B/op 6 allocs/op
|
||||
```
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
BenchmarkToXXXName/ToGoName-16 18527378 1972 ns/op 42 B/op 5 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-16 15552692 2093 ns/op 62 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-16 32161176 1117 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-16 32256634 1137 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-16 18599661 1946 ns/op 92 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-16 17581353 2054 ns/op 105 B/op 6 allocs/op
|
||||
```
|
||||
|
||||
## Benchmarks at `d7d2d1b895f5b6747afaff312dd2a402e69e818b`
|
||||
|
||||
go1.24
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
BenchmarkToXXXName/ToGoName-16 19757858 1881 ns/op 42 B/op 5 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-16 17494111 2094 ns/op 74 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-16 28161226 1492 ns/op 158 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-16 23787333 1489 ns/op 158 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-16 17537257 2030 ns/op 103 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-16 16977453 2156 ns/op 105 B/op 6 allocs/op
|
||||
```
|
||||
|
||||
## Benchmarks after PR #106
|
||||
|
||||
Moving the scope of everything down to a struct allowed to reduce a bit garbage and pooling.
|
||||
|
||||
On top of that, ToGoName (and thus ToVarName) have been subject to a minor optimization, removing a few allocations.
|
||||
|
||||
Overall timings improve by ~ -10%.
|
||||
|
||||
go1.24
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag/mangling
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
BenchmarkToXXXName/ToGoName-16 22496130 1618 ns/op 31 B/op 3 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-16 22538068 1618 ns/op 33 B/op 3 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-16 27722977 1236 ns/op 105 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-16 27967395 1258 ns/op 105 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-16 18587901 1917 ns/op 103 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-16 17193208 2019 ns/op 108 B/op 7 allocs/op
|
||||
```
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package mangling provides name mangling capabilities.
|
||||
//
|
||||
// Name mangling is an important stage when generating code:
|
||||
// it helps construct safe program identifiers that abide by the language rules
|
||||
// and play along with linters.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// Suppose we get an object name taken from an API spec: "json_object",
|
||||
//
|
||||
// We may generate a legit go type name using [NameMangler.ToGoName]: "JsonObject".
|
||||
//
|
||||
// We may then locate this type in a source file named using [NameMangler.ToFileName]: "json_object.go".
|
||||
//
|
||||
// The methods exposed by the NameMangler are used to generate code in many different contexts, such as:
|
||||
//
|
||||
// - generating exported or unexported go identifiers from a JSON schema or an API spec
|
||||
// - generating file names
|
||||
// - generating human-readable comments for types and variables
|
||||
// - generating JSON-like API identifiers from go code
|
||||
// - ...
|
||||
package mangling
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// DefaultInitialisms returns all the initialisms configured by default for this package.
|
||||
//
|
||||
// # Motivation
|
||||
//
|
||||
// Common initialisms are acronyms for which the ordinary camel-casing rules are altered and
|
||||
// for which we retain the original case.
|
||||
//
|
||||
// This is largely specific to the go naming conventions enforced by golint (now revive).
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// In go, "id" is a good-looking identifier, but "Id" is not and "ID" is preferred
|
||||
// (notice that this stems only from conventions: the go compiler accepts all of these).
|
||||
//
|
||||
// Similarly, we may use "http", but not "Http". In this case, "HTTP" is preferred.
|
||||
//
|
||||
// # Reference and customization
|
||||
//
|
||||
// The default list of these casing-style exceptions is taken from the [github.com/mgechev/revive] linter for go:
|
||||
// https://github.com/mgechev/revive/blob/master/lint/name.go#L93
|
||||
//
|
||||
// There are a few additions to the original list, such as IPv4, IPv6 and OAI ("OpenAPI").
|
||||
//
|
||||
// For these additions, "IPv4" would be preferred to "Ipv4" or "IPV4", and "OAI" to "Oai"
|
||||
//
|
||||
// You may redefine this list entirely using the mangler option [WithInitialisms], or simply add extra definitions
|
||||
// using [WithAdditionalInitialisms].
|
||||
//
|
||||
// # Mixed-case and plurals
|
||||
//
|
||||
// Notice that initialisms are not necessarily fully upper-cased: a mixed-case initialism indicates the preferred casing.
|
||||
//
|
||||
// Obviously, lower-case only initialisms do not make a lot of sense: if lower-case only initialisms are added,
|
||||
// they will be considered fully capitalized.
|
||||
//
|
||||
// Plural forms use mixed case like "IDs". And so do values like "IPv4" or "IPv6".
|
||||
//
|
||||
// The [NameMangler] automatically detects simple plurals for words such as "IDs" or "APIs",
|
||||
// so you don't need to configure these variants.
|
||||
//
|
||||
// At this moment, it doesn't support pluralization of terms that ends with an 's' (or 'S'), since there is
|
||||
// no clear consensus on whether a word like DNS should be pluralized as DNSes or remain invariant.
|
||||
// The [NameMangler] consider those invariant. Therefore DNSs or DNSes are not recognized as plurals for DNS.
|
||||
//
|
||||
// Besids, we don't want to support pluralization of terms which would otherwise conflict with another one,
|
||||
// like "HTTPs" vs "HTTPS". All these should be considered invariant. Hence: "Https" matches "HTTPS" and
|
||||
// "HTTPSS" is "HTTPS" followed by "S".
|
||||
func DefaultInitialisms() []string {
|
||||
return []string{
|
||||
"ACL",
|
||||
"API",
|
||||
"ASCII",
|
||||
"CPU",
|
||||
"CSS",
|
||||
"DNS",
|
||||
"EOF",
|
||||
"GUID",
|
||||
"HTML",
|
||||
"HTTPS",
|
||||
"HTTP",
|
||||
"ID",
|
||||
"IP",
|
||||
"IPv4", // prefer the mixed case outcome IPv4 over the capitalized IPV4
|
||||
"IPv6", // prefer the mixed case outcome IPv6 over the capitalized IPV6
|
||||
"JSON",
|
||||
"LHS",
|
||||
"OAI",
|
||||
"QPS",
|
||||
"RAM",
|
||||
"RHS",
|
||||
"RPC",
|
||||
"SLA",
|
||||
"SMTP",
|
||||
"SQL",
|
||||
"SSH",
|
||||
"TCP",
|
||||
"TLS",
|
||||
"TTL",
|
||||
"UDP",
|
||||
"UI",
|
||||
"UID",
|
||||
"UUID",
|
||||
"URI",
|
||||
"URL",
|
||||
"UTF8",
|
||||
"VM",
|
||||
"XML",
|
||||
"XMPP",
|
||||
"XSRF",
|
||||
"XSS",
|
||||
}
|
||||
}
|
||||
|
||||
type indexOfInitialisms struct {
|
||||
initialismsCache
|
||||
|
||||
index map[string]struct{}
|
||||
}
|
||||
|
||||
func newIndexOfInitialisms() *indexOfInitialisms {
|
||||
return &indexOfInitialisms{
|
||||
index: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) add(words ...string) *indexOfInitialisms {
|
||||
for _, word := range words {
|
||||
// sanitization of injected words: trimmed from blanks, and must start with a letter
|
||||
trimmed := strings.TrimSpace(word)
|
||||
|
||||
firstRune, _ := utf8.DecodeRuneInString(trimmed)
|
||||
if !unicode.IsLetter(firstRune) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Initialisms are case-sensitive. This means that we support mixed-case words.
|
||||
// However, if specified as a lower-case string, the initialism should be fully capitalized.
|
||||
if trimmed == strings.ToLower(trimmed) {
|
||||
m.index[strings.ToUpper(trimmed)] = struct{}{}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
m.index[trimmed] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) sorted() []string {
|
||||
result := make([]string, 0, len(m.index))
|
||||
for k := range m.index {
|
||||
result = append(result, k)
|
||||
}
|
||||
sort.Sort(sort.Reverse(byInitialism(result)))
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) buildCache() {
|
||||
m.build(m.sorted(), m.pluralForm)
|
||||
}
|
||||
|
||||
// initialismsCache caches all needed pre-computed and converted initialism entries,
|
||||
// in the desired resolution order.
|
||||
type initialismsCache struct {
|
||||
initialisms []string
|
||||
initialismsRunes [][]rune
|
||||
initialismsUpperCased [][]rune // initialisms cached in their trimmed, upper-cased version
|
||||
initialismsPluralForm []pluralForm
|
||||
}
|
||||
|
||||
func (c *initialismsCache) build(in []string, pluralfunc func(string) pluralForm) {
|
||||
c.initialisms = in
|
||||
c.initialismsRunes = asRunes(c.initialisms)
|
||||
c.initialismsUpperCased = asUpperCased(c.initialisms)
|
||||
c.initialismsPluralForm = asPluralForms(c.initialisms, pluralfunc)
|
||||
}
|
||||
|
||||
// pluralForm denotes the kind of pluralization to be used for initialisms.
|
||||
//
|
||||
// At this moment, initialisms are either invariant or follow a simple plural form with an
|
||||
// extra (lower case) "s".
|
||||
type pluralForm uint8
|
||||
|
||||
const (
|
||||
notPlural pluralForm = iota
|
||||
invariantPlural
|
||||
simplePlural
|
||||
)
|
||||
|
||||
func (f pluralForm) String() string {
|
||||
switch f {
|
||||
case notPlural:
|
||||
return "notPlural"
|
||||
case invariantPlural:
|
||||
return "invariantPlural"
|
||||
case simplePlural:
|
||||
return "simplePlural"
|
||||
default:
|
||||
return "<unknown>"
|
||||
}
|
||||
}
|
||||
|
||||
// pluralForm indicates how we want to pluralize a given initialism.
|
||||
//
|
||||
// Besides configured invariant forms (like HTTP and HTTPS),
|
||||
// an initialism is normally pluralized by adding a single 's', like in IDs.
|
||||
//
|
||||
// Initialisms ending with an 'S' or an 's' are configured as invariant (we don't
|
||||
// support plural forms like CSSes or DNSes, however the mechanism could be extended to
|
||||
// do just that).
|
||||
func (m *indexOfInitialisms) pluralForm(key string) pluralForm {
|
||||
if _, ok := m.index[key]; !ok {
|
||||
return notPlural
|
||||
}
|
||||
|
||||
if strings.HasSuffix(strings.ToUpper(key), "S") {
|
||||
return invariantPlural
|
||||
}
|
||||
|
||||
if _, ok := m.index[key+"s"]; ok {
|
||||
return invariantPlural
|
||||
}
|
||||
|
||||
if _, ok := m.index[key+"S"]; ok {
|
||||
return invariantPlural
|
||||
}
|
||||
|
||||
return simplePlural
|
||||
}
|
||||
|
||||
type byInitialism []string
|
||||
|
||||
func (s byInitialism) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s byInitialism) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less specifies the order in which initialisms are prioritized:
|
||||
// 1. match longest first
|
||||
// 2. when equal length, match in reverse lexicographical order, lower case match comes first
|
||||
func (s byInitialism) Less(i, j int) bool {
|
||||
if len(s[i]) != len(s[j]) {
|
||||
return len(s[i]) < len(s[j])
|
||||
}
|
||||
|
||||
return s[i] < s[j]
|
||||
}
|
||||
|
||||
func asRunes(in []string) [][]rune {
|
||||
out := make([][]rune, len(in))
|
||||
for i, initialism := range in {
|
||||
out[i] = []rune(initialism)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func asUpperCased(in []string) [][]rune {
|
||||
out := make([][]rune, len(in))
|
||||
|
||||
for i, initialism := range in {
|
||||
out[i] = []rune(upper(trim(initialism)))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// asPluralForms bakes an index of pluralization support.
|
||||
func asPluralForms(in []string, pluralFunc func(string) pluralForm) []pluralForm {
|
||||
out := make([]pluralForm, len(in))
|
||||
for i, initialism := range in {
|
||||
out[i] = pluralFunc(initialism)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type (
|
||||
lexemKind uint8
|
||||
|
||||
nameLexem struct {
|
||||
original string
|
||||
matchedInitialism string
|
||||
kind lexemKind
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
lexemKindCasualName lexemKind = iota
|
||||
lexemKindInitialismName
|
||||
)
|
||||
|
||||
func newInitialismNameLexem(original, matchedInitialism string) nameLexem {
|
||||
return nameLexem{
|
||||
kind: lexemKindInitialismName,
|
||||
original: original,
|
||||
matchedInitialism: matchedInitialism,
|
||||
}
|
||||
}
|
||||
|
||||
func newCasualNameLexem(original string) nameLexem {
|
||||
return nameLexem{
|
||||
kind: lexemKindCasualName,
|
||||
original: trim(original), // TODO: save on calls to trim
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTitleized writes the titleized lexeme to a bytes.Buffer.
|
||||
//
|
||||
// If the first letter cannot be capitalized, it doesn't write anything and return false,
|
||||
// so the caller may attempt some workaround strategy.
|
||||
func (l nameLexem) WriteTitleized(w *bytes.Buffer, alwaysUpper bool) bool {
|
||||
if l.kind == lexemKindInitialismName {
|
||||
w.WriteString(l.matchedInitialism)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if len(l.original) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(l.original) == 1 {
|
||||
// identifier is too short: casing will depend on the context
|
||||
firstByte := l.original[0]
|
||||
switch {
|
||||
case 'A' <= firstByte && firstByte <= 'Z':
|
||||
// safe
|
||||
w.WriteByte(firstByte)
|
||||
|
||||
return true
|
||||
case alwaysUpper && 'a' <= firstByte && firstByte <= 'z':
|
||||
w.WriteByte(firstByte - 'a' + 'A')
|
||||
|
||||
return true
|
||||
default:
|
||||
|
||||
// not a letter: skip and let the caller decide
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if firstByte := l.original[0]; firstByte < utf8.RuneSelf {
|
||||
// ASCII
|
||||
switch {
|
||||
case 'A' <= firstByte && firstByte <= 'Z':
|
||||
// already an upper case letter
|
||||
w.WriteString(l.original)
|
||||
|
||||
return true
|
||||
case 'a' <= firstByte && firstByte <= 'z':
|
||||
w.WriteByte(firstByte - 'a' + 'A')
|
||||
w.WriteString(l.original[1:])
|
||||
|
||||
return true
|
||||
default:
|
||||
// not a good candidate: doesn't start with a letter
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// unicode
|
||||
firstRune, idx := utf8.DecodeRuneInString(l.original)
|
||||
if !unicode.IsLetter(firstRune) || !unicode.IsUpper(unicode.ToUpper(firstRune)) {
|
||||
// not a good candidate: doesn't start with a letter
|
||||
// or a rune for which case doesn't make sense (e.g. East-Asian runes etc)
|
||||
return false
|
||||
}
|
||||
|
||||
rest := l.original[idx:]
|
||||
w.WriteRune(unicode.ToUpper(firstRune))
|
||||
w.WriteString(strings.ToLower(rest))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteLower is like write titleized but it writes a lower-case version of the lexeme.
|
||||
//
|
||||
// Similarly, there is no writing if the casing of the first rune doesn't make sense.
|
||||
func (l nameLexem) WriteLower(w *bytes.Buffer, alwaysLower bool) bool {
|
||||
if l.kind == lexemKindInitialismName {
|
||||
w.WriteString(lower(l.matchedInitialism))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if len(l.original) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(l.original) == 1 {
|
||||
// identifier is too short: casing will depend on the context
|
||||
firstByte := l.original[0]
|
||||
switch {
|
||||
case 'a' <= firstByte && firstByte <= 'z':
|
||||
// safe
|
||||
w.WriteByte(firstByte)
|
||||
|
||||
return true
|
||||
case alwaysLower && 'A' <= firstByte && firstByte <= 'Z':
|
||||
w.WriteByte(firstByte - 'A' + 'a')
|
||||
|
||||
return true
|
||||
default:
|
||||
|
||||
// not a letter: skip and let the caller decide
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if firstByte := l.original[0]; firstByte < utf8.RuneSelf {
|
||||
// ASCII
|
||||
switch {
|
||||
case 'a' <= firstByte && firstByte <= 'z':
|
||||
// already a lower case letter
|
||||
w.WriteString(l.original)
|
||||
|
||||
return true
|
||||
case 'A' <= firstByte && firstByte <= 'Z':
|
||||
w.WriteByte(firstByte - 'A' + 'a')
|
||||
w.WriteString(l.original[1:])
|
||||
|
||||
return true
|
||||
default:
|
||||
// not a good candidate: doesn't start with a letter
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// unicode
|
||||
firstRune, idx := utf8.DecodeRuneInString(l.original)
|
||||
if !unicode.IsLetter(firstRune) || !unicode.IsLower(unicode.ToLower(firstRune)) {
|
||||
// not a good candidate: doesn't start with a letter
|
||||
// or a rune for which case doesn't make sense (e.g. East-Asian runes etc)
|
||||
return false
|
||||
}
|
||||
|
||||
rest := l.original[idx:]
|
||||
w.WriteRune(unicode.ToLower(firstRune))
|
||||
w.WriteString(rest)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (l nameLexem) GetOriginal() string {
|
||||
return l.original
|
||||
}
|
||||
|
||||
func (l nameLexem) IsInitialism() bool {
|
||||
return l.kind == lexemKindInitialismName
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
// 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
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
type (
|
||||
// PrefixFunc defines a safeguard rule (that may depend on the input string), to prefix
|
||||
// a generated go name (in [NameMangler.ToGoName] and [NameMangler.ToVarName]).
|
||||
//
|
||||
// See [NameMangler.ToGoName] for more about which edge cases the prefix function covers.
|
||||
PrefixFunc func(string) string
|
||||
|
||||
// ReplaceFunc is a transliteration function to replace special runes by a word.
|
||||
ReplaceFunc func(r rune) (string, bool)
|
||||
|
||||
// Option to configure a [NameMangler].
|
||||
Option func(*options)
|
||||
|
||||
options struct {
|
||||
commonInitialisms []string
|
||||
|
||||
goNamePrefixFunc PrefixFunc
|
||||
goNamePrefixFuncPtr *PrefixFunc
|
||||
replaceFunc func(r rune) (string, bool)
|
||||
}
|
||||
)
|
||||
|
||||
func (o *options) prefixFunc() PrefixFunc {
|
||||
if o.goNamePrefixFuncPtr != nil && *o.goNamePrefixFuncPtr != nil {
|
||||
return *o.goNamePrefixFuncPtr
|
||||
}
|
||||
|
||||
return o.goNamePrefixFunc
|
||||
}
|
||||
|
||||
// WithGoNamePrefixFunc overrides the default prefix rule to safeguard generated go names.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// This helps convert "123" into "{prefix}123" (a very crude strategy indeed, but it works).
|
||||
//
|
||||
// See [github.com/go-swagger/go-swagger/generator.DefaultFuncMap] for an example.
|
||||
//
|
||||
// The prefix function is assumed to return a string that starts with an upper case letter.
|
||||
//
|
||||
// The default is to prefix with "X".
|
||||
//
|
||||
// See [NameMangler.ToGoName] for more about which edge cases the prefix function covers.
|
||||
func WithGoNamePrefixFunc(fn PrefixFunc) Option {
|
||||
return func(o *options) {
|
||||
o.goNamePrefixFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithGoNamePrefixFuncPtr is like [WithGoNamePrefixFunc] but it specifies a pointer to a function.
|
||||
//
|
||||
// [WithGoNamePrefixFunc] should be preferred in most situations. This option should only serve the
|
||||
// purpose of handling special situations where the prefix function is not an internal variable
|
||||
// (e.g. an exported package global).
|
||||
//
|
||||
// [WithGoNamePrefixFuncPtr] supersedes [WithGoNamePrefixFunc] if it also specified.
|
||||
//
|
||||
// If the provided pointer is nil or points to a nil value, this option has no effect.
|
||||
//
|
||||
// The caller should ensure that no undesirable concurrent changes are applied to the function pointed to.
|
||||
func WithGoNamePrefixFuncPtr(ptr *PrefixFunc) Option {
|
||||
return func(o *options) {
|
||||
o.goNamePrefixFuncPtr = ptr
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitialisms declares the initialisms this mangler supports.
|
||||
//
|
||||
// This supersedes any pre-loaded defaults (see [DefaultInitialisms] for more about what initialisms are).
|
||||
//
|
||||
// It declares words to be recognized as "initialisms" (i.e. words that won't be camel cased or titled cased).
|
||||
//
|
||||
// Words must start with a (unicode) letter. If some don't, they are ignored.
|
||||
// Words are either fully capitalized or mixed-cased. Lower-case only words are considered capitalized.
|
||||
func WithInitialisms(words ...string) Option {
|
||||
return func(o *options) {
|
||||
o.commonInitialisms = words
|
||||
}
|
||||
}
|
||||
|
||||
// WithAdditionalInitialisms adds new initialisms to the currently supported list (see [DefaultInitialisms]).
|
||||
//
|
||||
// The same sanitization rules apply as those described for [WithInitialisms].
|
||||
func WithAdditionalInitialisms(words ...string) Option {
|
||||
return func(o *options) {
|
||||
o.commonInitialisms = append(o.commonInitialisms, words...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithReplaceFunc specifies a custom transliteration function instead of the default.
|
||||
//
|
||||
// The default translates the following characters into words as follows:
|
||||
//
|
||||
// - '@' -> 'At'
|
||||
// - '&' -> 'And'
|
||||
// - '|' -> 'Pipe'
|
||||
// - '$' -> 'Dollar'
|
||||
// - '!' -> 'Bang'
|
||||
//
|
||||
// Notice that the outcome of a transliteration should always be titleized.
|
||||
func WithReplaceFunc(fn ReplaceFunc) Option {
|
||||
return func(o *options) {
|
||||
o.replaceFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
func defaultPrefixFunc(_ string) string {
|
||||
return "X"
|
||||
}
|
||||
|
||||
// defaultReplaceTable finds a word representation for special characters.
|
||||
func defaultReplaceTable(r rune) (string, bool) {
|
||||
switch r {
|
||||
case '@':
|
||||
return "At ", true
|
||||
case '&':
|
||||
return "And ", true
|
||||
case '|':
|
||||
return "Pipe ", true
|
||||
case '$':
|
||||
return "Dollar ", true
|
||||
case '!':
|
||||
return "Bang ", true
|
||||
case '-':
|
||||
return "", true
|
||||
case '_':
|
||||
return "", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func optionsWithDefaults(opts []Option) options {
|
||||
o := options{
|
||||
commonInitialisms: DefaultInitialisms(),
|
||||
goNamePrefixFunc: defaultPrefixFunc,
|
||||
replaceFunc: defaultReplaceTable,
|
||||
}
|
||||
|
||||
for _, apply := range opts {
|
||||
apply(&o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const maxAllocMatches = 8
|
||||
|
||||
type (
|
||||
// memory pools of temporary objects.
|
||||
//
|
||||
// These are used to recycle temporarily allocated objects
|
||||
// and relieve the GC from undue pressure.
|
||||
|
||||
matchesPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
buffersPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
lexemsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
stringsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// poolOfMatches holds temporary slices for recycling during the initialism match process
|
||||
poolOfMatches = matchesPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := make(initialismMatches, 0, maxAllocMatches)
|
||||
|
||||
return &s
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
poolOfBuffers = buffersPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
poolOfLexems = lexemsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := make([]nameLexem, 0, maxAllocMatches)
|
||||
|
||||
return &s
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
poolOfStrings = stringsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := make([]string, 0, maxAllocMatches)
|
||||
|
||||
return &s
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (p matchesPool) BorrowMatches() *initialismMatches {
|
||||
s := p.Get().(*initialismMatches)
|
||||
*s = (*s)[:0] // reset slice, keep allocated capacity
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p buffersPool) BorrowBuffer(size int) *bytes.Buffer {
|
||||
s := p.Get().(*bytes.Buffer)
|
||||
s.Reset()
|
||||
|
||||
if s.Cap() < size {
|
||||
s.Grow(size)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p lexemsPool) BorrowLexems() *[]nameLexem {
|
||||
s := p.Get().(*[]nameLexem)
|
||||
*s = (*s)[:0] // reset slice, keep allocated capacity
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p stringsPool) BorrowStrings() *[]string {
|
||||
s := p.Get().(*[]string)
|
||||
*s = (*s)[:0] // reset slice, keep allocated capacity
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p matchesPool) RedeemMatches(s *initialismMatches) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p buffersPool) RedeemBuffer(s *bytes.Buffer) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p lexemsPool) RedeemLexems(s *[]nameLexem) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p stringsPool) RedeemStrings(s *[]string) {
|
||||
p.Put(s)
|
||||
}
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type splitterOption func(*splitter)
|
||||
|
||||
// withPostSplitInitialismCheck allows to catch initialisms after main split process
|
||||
func withPostSplitInitialismCheck(s *splitter) {
|
||||
s.postSplitInitialismCheck = true
|
||||
}
|
||||
|
||||
func withReplaceFunc(fn ReplaceFunc) func(*splitter) {
|
||||
return func(s *splitter) {
|
||||
s.replaceFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
func withInitialismsCache(c *initialismsCache) splitterOption {
|
||||
return func(s *splitter) {
|
||||
s.initialismsCache = c
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
initialismMatch struct {
|
||||
body []rune
|
||||
start, end int
|
||||
complete bool
|
||||
hasPlural pluralForm
|
||||
}
|
||||
initialismMatches []initialismMatch
|
||||
)
|
||||
|
||||
// String representation of a match, e.g. for debugging.
|
||||
func (m initialismMatch) String() string {
|
||||
return fmt.Sprintf("{body: %s (%d), start: %d, end; %d, complete: %t, hasPlural: %v}",
|
||||
string(m.body), len(m.body), m.start, m.end, m.complete, m.hasPlural,
|
||||
)
|
||||
}
|
||||
|
||||
func (m initialismMatch) isZero() bool {
|
||||
return m.start == 0 && m.end == 0
|
||||
}
|
||||
|
||||
type splitter struct {
|
||||
*initialismsCache
|
||||
|
||||
postSplitInitialismCheck bool
|
||||
replaceFunc ReplaceFunc
|
||||
}
|
||||
|
||||
func newSplitter(options ...splitterOption) splitter {
|
||||
var s splitter
|
||||
|
||||
for _, option := range options {
|
||||
option(&s)
|
||||
}
|
||||
|
||||
if s.replaceFunc == nil {
|
||||
s.replaceFunc = defaultReplaceTable
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s splitter) split(name string) *[]nameLexem {
|
||||
nameRunes := []rune(name)
|
||||
matches := s.gatherInitialismMatches(nameRunes)
|
||||
if matches == nil {
|
||||
return poolOfLexems.BorrowLexems()
|
||||
}
|
||||
|
||||
return s.mapMatchesToNameLexems(nameRunes, matches)
|
||||
}
|
||||
|
||||
func (s splitter) gatherInitialismMatches(nameRunes []rune) *initialismMatches {
|
||||
matches := poolOfMatches.BorrowMatches()
|
||||
const minLenInitialism = 1
|
||||
if len(nameRunes) < minLenInitialism+1 {
|
||||
// can't match initialism with 0 or 1 rune
|
||||
return matches
|
||||
}
|
||||
|
||||
// first iteration
|
||||
s.findMatches(matches, nameRunes, nameRunes[0], 0)
|
||||
|
||||
for i, currentRune := range nameRunes[1:] {
|
||||
currentRunePosition := i + 1
|
||||
// recycle allocations as we loop over runes
|
||||
// with such recycling, only 2 slices should be allocated per call
|
||||
// instead of o(n).
|
||||
//
|
||||
// BorrowMatches always yields slices with zero length (with some capacity)
|
||||
newMatches := poolOfMatches.BorrowMatches()
|
||||
|
||||
// check current initialism matches
|
||||
for _, match := range *matches {
|
||||
if keepCompleteMatch := match.complete; keepCompleteMatch {
|
||||
// the match is already complete: keep it then move on to the next match
|
||||
*newMatches = append(*newMatches, match)
|
||||
continue
|
||||
}
|
||||
|
||||
if currentRunePosition-match.start == len(match.body) {
|
||||
// unmatched: skip
|
||||
continue
|
||||
}
|
||||
|
||||
// 1. by construction of the matches, we can't have currentRunePosition - match.start < 0
|
||||
// because matches have been computed with their start <= currentRunePosition in the previous
|
||||
// iterations.
|
||||
// 2. by construction of the matches, we can't have currentRunePosition - match.start >= len(match.body)
|
||||
|
||||
currentMatchRune := match.body[currentRunePosition-match.start]
|
||||
if currentMatchRune != currentRune {
|
||||
// failed match, discard it then move on to the next match
|
||||
continue
|
||||
}
|
||||
|
||||
// try to complete the current match
|
||||
if currentRunePosition-match.start == len(match.body)-1 {
|
||||
// we are close: the next step is to check the symbol ahead
|
||||
// if it is a lowercase letter, then it is not the end of match
|
||||
// but the beginning of the next word.
|
||||
//
|
||||
// NOTE(fredbi): this heuristic sometimes leads to counterintuitive splits and
|
||||
// perhaps (not sure yet) we should check against case _alternance_.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// In the current version, in the sentence "IDS initialism", "ID" is recognized as an initialism,
|
||||
// leading to a split like "id_s_initialism" (or IDSInitialism),
|
||||
// whereas in the sentence "IDx initialism", it is not and produces something like
|
||||
// "i_d_x_initialism" (or IDxInitialism). The generated file name is not great.
|
||||
//
|
||||
// Both go identifiers are tolerated by linters.
|
||||
//
|
||||
// Notice that the slightly different input "IDs initialism" is correctly detected
|
||||
// as a pluralized initialism and produces something like "ids_initialism" (or IDsInitialism).
|
||||
|
||||
if currentRunePosition < len(nameRunes)-1 { // when before the last rune
|
||||
nextRune := nameRunes[currentRunePosition+1]
|
||||
|
||||
// recognize a plural form for this initialism (only simple english pluralization is supported).
|
||||
if nextRune == 's' && match.hasPlural == simplePlural {
|
||||
// detected a pluralized initialism
|
||||
match.body = append(match.body, nextRune)
|
||||
lookAhead := currentRunePosition + 1
|
||||
if lookAhead < len(nameRunes)-1 {
|
||||
nextRune = nameRunes[lookAhead+1]
|
||||
if newWord := unicode.IsLower(nextRune); newWord {
|
||||
// it is the start of a new word.
|
||||
// Match is only partial and the initialism is not recognized:
|
||||
// move on to the next match, but do not advance the rune position
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// this is a pluralized match: keep it
|
||||
currentRunePosition++
|
||||
match.complete = true
|
||||
match.hasPlural = simplePlural
|
||||
match.end = currentRunePosition
|
||||
*newMatches = append(*newMatches, match)
|
||||
|
||||
// match is complete: keep it then move on to the next match
|
||||
continue
|
||||
}
|
||||
|
||||
// other cases
|
||||
// example: invariant plural such as "TLS"
|
||||
if newWord := unicode.IsLower(nextRune); newWord {
|
||||
// it is the start of a new word
|
||||
// Match is only partial and the initialism is not recognized : move on
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
match.complete = true
|
||||
match.end = currentRunePosition
|
||||
}
|
||||
|
||||
// append the ongoing matching attempt: it is not necessarily complete, but was successful so far.
|
||||
// Let's see if it still matches on the next rune.
|
||||
*newMatches = append(*newMatches, match)
|
||||
}
|
||||
|
||||
s.findMatches(newMatches, nameRunes, currentRune, currentRunePosition)
|
||||
|
||||
poolOfMatches.RedeemMatches(matches)
|
||||
matches = newMatches
|
||||
}
|
||||
|
||||
// it is up to the caller to redeem this last slice
|
||||
return matches
|
||||
}
|
||||
|
||||
func (s splitter) findMatches(newMatches *initialismMatches, nameRunes []rune, currentRune rune, currentRunePosition int) {
|
||||
// check for new initialism matches, based on the first character
|
||||
for i, r := range s.initialismsRunes {
|
||||
if r[0] != currentRune {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentRunePosition+len(r) > len(nameRunes) {
|
||||
continue // not eligible: would spilll over the initial string
|
||||
}
|
||||
|
||||
// possible matches: all initialisms starting with the current rune and that can fit the given string (nameRunes)
|
||||
*newMatches = append(*newMatches, initialismMatch{
|
||||
start: currentRunePosition,
|
||||
body: r,
|
||||
complete: false,
|
||||
hasPlural: s.initialismsPluralForm[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s splitter) mapMatchesToNameLexems(nameRunes []rune, matches *initialismMatches) *[]nameLexem {
|
||||
nameLexems := poolOfLexems.BorrowLexems()
|
||||
|
||||
var lastAcceptedMatch initialismMatch
|
||||
for _, match := range *matches {
|
||||
if !match.complete {
|
||||
continue
|
||||
}
|
||||
|
||||
if firstMatch := lastAcceptedMatch.isZero(); firstMatch {
|
||||
s.appendBrokenDownCasualString(nameLexems, nameRunes[:match.start])
|
||||
*nameLexems = append(*nameLexems, s.breakInitialism(string(match.body)))
|
||||
|
||||
lastAcceptedMatch = match
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if overlappedMatch := match.start <= lastAcceptedMatch.end; overlappedMatch {
|
||||
continue
|
||||
}
|
||||
|
||||
middle := nameRunes[lastAcceptedMatch.end+1 : match.start]
|
||||
s.appendBrokenDownCasualString(nameLexems, middle)
|
||||
*nameLexems = append(*nameLexems, s.breakInitialism(string(match.body)))
|
||||
|
||||
lastAcceptedMatch = match
|
||||
}
|
||||
|
||||
// we have not found any accepted matches
|
||||
if lastAcceptedMatch.isZero() {
|
||||
*nameLexems = (*nameLexems)[:0]
|
||||
s.appendBrokenDownCasualString(nameLexems, nameRunes)
|
||||
} else if lastAcceptedMatch.end+1 != len(nameRunes) {
|
||||
rest := nameRunes[lastAcceptedMatch.end+1:]
|
||||
s.appendBrokenDownCasualString(nameLexems, rest)
|
||||
}
|
||||
|
||||
poolOfMatches.RedeemMatches(matches)
|
||||
|
||||
return nameLexems
|
||||
}
|
||||
|
||||
func (s splitter) breakInitialism(original string) nameLexem {
|
||||
return newInitialismNameLexem(original, original)
|
||||
}
|
||||
|
||||
func (s splitter) appendBrokenDownCasualString(segments *[]nameLexem, str []rune) {
|
||||
currentSegment := poolOfBuffers.BorrowBuffer(len(str)) // unlike strings.Builder, bytes.Buffer initial storage can reused
|
||||
defer func() {
|
||||
poolOfBuffers.RedeemBuffer(currentSegment)
|
||||
}()
|
||||
|
||||
addCasualNameLexem := func(original string) {
|
||||
*segments = append(*segments, newCasualNameLexem(original))
|
||||
}
|
||||
|
||||
addInitialismNameLexem := func(original, match string) {
|
||||
*segments = append(*segments, newInitialismNameLexem(original, match))
|
||||
}
|
||||
|
||||
var addNameLexem func(string)
|
||||
if s.postSplitInitialismCheck {
|
||||
addNameLexem = func(original string) {
|
||||
for i := range s.initialisms {
|
||||
if isEqualFoldIgnoreSpace(s.initialismsUpperCased[i], original) {
|
||||
addInitialismNameLexem(original, s.initialisms[i])
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addCasualNameLexem(original)
|
||||
}
|
||||
} else {
|
||||
addNameLexem = addCasualNameLexem
|
||||
}
|
||||
|
||||
// NOTE: (performance). The few remaining non-amortized allocations
|
||||
// lay in the code below: using String() forces
|
||||
for _, rn := range str {
|
||||
if replace, found := s.replaceFunc(rn); found {
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
currentSegment.Reset()
|
||||
}
|
||||
|
||||
if replace != "" {
|
||||
addNameLexem(replace)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) {
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
currentSegment.Reset()
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsUpper(rn) {
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
}
|
||||
currentSegment.Reset()
|
||||
}
|
||||
|
||||
currentSegment.WriteRune(rn)
|
||||
}
|
||||
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// hackStringBytes returns the (unsafe) underlying bytes slice of a string.
|
||||
func hackStringBytes(str string) []byte {
|
||||
return unsafe.Slice(unsafe.StringData(str), len(str))
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mangling
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Removes leading whitespaces
|
||||
func trim(str string) string { return strings.TrimSpace(str) }
|
||||
|
||||
// upper is strings.ToUpper() combined with trim
|
||||
func upper(str string) string {
|
||||
return strings.ToUpper(trim(str))
|
||||
}
|
||||
|
||||
// lower is strings.ToLower() combined with trim
|
||||
func lower(str string) string {
|
||||
return strings.ToLower(trim(str))
|
||||
}
|
||||
|
||||
// isEqualFoldIgnoreSpace is the same as strings.EqualFold, but
|
||||
// it ignores leading and trailing blank spaces in the compared
|
||||
// string.
|
||||
//
|
||||
// base is assumed to be composed of upper-cased runes, and be already
|
||||
// trimmed.
|
||||
//
|
||||
// This code is heavily inspired from strings.EqualFold.
|
||||
func isEqualFoldIgnoreSpace(base []rune, str string) bool {
|
||||
var i, baseIndex int
|
||||
// equivalent to b := []byte(str), but without data copy
|
||||
b := hackStringBytes(str)
|
||||
|
||||
for i < len(b) {
|
||||
if c := b[i]; c < utf8.RuneSelf {
|
||||
// fast path for ASCII
|
||||
if c != ' ' && c != '\t' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// unicode case
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if !unicode.IsSpace(r) {
|
||||
break
|
||||
}
|
||||
i += size
|
||||
}
|
||||
|
||||
if i >= len(b) {
|
||||
return len(base) == 0
|
||||
}
|
||||
|
||||
for _, baseRune := range base {
|
||||
if i >= len(b) {
|
||||
break
|
||||
}
|
||||
|
||||
if c := b[i]; c < utf8.RuneSelf {
|
||||
// single byte rune case (ASCII)
|
||||
if baseRune >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
|
||||
baseChar := byte(baseRune)
|
||||
if c != baseChar && ((c < 'a') || (c > 'z') || (c-'a'+'A' != baseChar)) {
|
||||
return false
|
||||
}
|
||||
|
||||
baseIndex++
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// unicode case
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if unicode.ToUpper(r) != baseRune {
|
||||
return false
|
||||
}
|
||||
baseIndex++
|
||||
i += size
|
||||
}
|
||||
|
||||
if baseIndex != len(base) {
|
||||
return false
|
||||
}
|
||||
|
||||
// all passed: now we should only have blanks
|
||||
for i < len(b) {
|
||||
if c := b[i]; c < utf8.RuneSelf {
|
||||
// fast path for ASCII
|
||||
if c != ' ' && c != '\t' {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// unicode case
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if !unicode.IsSpace(r) {
|
||||
return false
|
||||
}
|
||||
|
||||
i += size
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user