Use apiextension v1

- upgrade from apiextension v1beta1 to v1
 - generate yaml manifest for crd intead of applying it at runtime
  - users will have to apply the manifest with kubectl
 - kg and kgctl log an error if the crd is not present
 - now validation should actually work

Signed-off-by: leonnicolas <leonloechner@gmx.de>
This commit is contained in:
leonnicolas
2021-06-14 09:08:46 +02:00
parent e272d725a5
commit 36643b77b4
584 changed files with 50911 additions and 55838 deletions

View File

@@ -0,0 +1,23 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
// Package help contains utilities for actually writing out marker help.
//
// Namely, it contains a series of structs (and helpers for producing them)
// that represent a merged view of marker definition and help that can be used
// for consumption by the pretty subpackage (for terminal help) or serialized
// as JSON (e.g. for generating HTML help).
package help

View File

@@ -0,0 +1,30 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
// Package pretty contains utilities for formatting terminal help output,
// and a use of those to display marker help.
//
// Terminal Output
//
// The Span interface and Table struct allow you to construct tables with
// colored formatting, without causing ANSI formatting characters to mess up width
// calculations.
//
// Marker Help
//
// The MarkersSummary prints a summary table for marker help, while the MarkersDetails
// prints out more detailed information, with explainations of the individual marker fields.
package pretty

View File

@@ -0,0 +1,171 @@
package pretty
import (
"fmt"
"io"
"sigs.k8s.io/controller-tools/pkg/genall/help"
"github.com/fatih/color"
)
var (
headingStyle = Decoration(*color.New(color.Bold, color.Underline))
markerNameStyle = Decoration(*color.New(color.Bold))
fieldSummaryStyle = Decoration(*color.New(color.FgGreen, color.Italic))
markerTargetStyle = Decoration(*color.New(color.Faint))
fieldDetailStyle = Decoration(*color.New(color.Italic, color.FgGreen))
deprecatedStyle = Decoration(*color.New(color.CrossedOut))
)
// MarkersSummary returns a condensed summary of help for the given markers.
func MarkersSummary(groupName string, markers []help.MarkerDoc) Span {
out := new(SpanWriter)
out.Print(Text("\n"))
out.Print(headingStyle.Containing(Text(groupName)))
out.Print(Text("\n\n"))
table := &Table{Sizing: &TableCalculator{Padding: 2}}
for _, marker := range markers {
table.StartRow()
table.Column(MarkerSyntaxHelp(marker))
table.Column(markerTargetStyle.Containing(Text(marker.Target)))
summary := new(SpanWriter)
if marker.DeprecatedInFavorOf != nil && len(*marker.DeprecatedInFavorOf) > 0 {
summary.Print(markerNameStyle.Containing(Text("(use ")))
summary.Print(markerNameStyle.Containing(Text(*marker.DeprecatedInFavorOf)))
summary.Print(markerNameStyle.Containing(Text(") ")))
}
summary.Print(Text(marker.Summary))
table.Column(summary)
table.EndRow()
}
out.Print(table)
out.Print(Text("\n"))
return out
}
// MarkersDetails returns detailed help for the given markers, including detailed field help.
func MarkersDetails(fullDetail bool, groupName string, markers []help.MarkerDoc) Span {
out := new(SpanWriter)
out.Print(Line(headingStyle.Containing(Text(groupName))))
out.Print(Newlines(2))
for _, marker := range markers {
out.Print(Line(markerName(marker)))
out.Print(Text(" "))
out.Print(markerTargetStyle.Containing(Text(marker.Target)))
summary := new(SpanWriter)
if marker.DeprecatedInFavorOf != nil && len(*marker.DeprecatedInFavorOf) > 0 {
summary.Print(markerNameStyle.Containing(Text("(use ")))
summary.Print(markerNameStyle.Containing(Text(*marker.DeprecatedInFavorOf)))
summary.Print(markerNameStyle.Containing(Text(") ")))
}
summary.Print(Text(marker.Summary))
if !marker.AnonymousField() {
out.Print(Indented(1, Line(summary)))
if len(marker.Details) > 0 && fullDetail {
out.Print(Indented(1, Line(Text(marker.Details))))
}
}
if marker.AnonymousField() {
out.Print(Indented(1, Line(fieldDetailStyle.Containing(FieldSyntaxHelp(marker.Fields[0])))))
out.Print(Text(" "))
out.Print(summary)
if len(marker.Details) > 0 && fullDetail {
out.Print(Indented(2, Line(Text(marker.Details))))
}
out.Print(Newlines(1))
} else if !marker.Empty() {
out.Print(Newlines(1))
if fullDetail {
for _, arg := range marker.Fields {
out.Print(Indented(1, Line(fieldDetailStyle.Containing(FieldSyntaxHelp(arg)))))
out.Print(Indented(2, Line(Text(arg.Summary))))
if len(arg.Details) > 0 && fullDetail {
out.Print(Indented(2, Line(Text(arg.Details))))
out.Print(Newlines(1))
}
}
out.Print(Newlines(1))
} else {
table := &Table{Sizing: &TableCalculator{Padding: 2}}
for _, arg := range marker.Fields {
table.StartRow()
table.Column(fieldDetailStyle.Containing(FieldSyntaxHelp(arg)))
table.Column(Text(arg.Summary))
table.EndRow()
}
out.Print(Indented(1, table))
}
} else {
out.Print(Newlines(1))
}
}
return out
}
func FieldSyntaxHelp(arg help.FieldHelp) Span {
return fieldSyntaxHelp(arg, "")
}
// fieldSyntaxHelp prints the syntax help for a particular marker argument.
func fieldSyntaxHelp(arg help.FieldHelp, sep string) Span {
if arg.Optional {
return FromWriter(func(out io.Writer) error {
_, err := fmt.Fprintf(out, "[%s%s=<%s>]", sep, arg.Name, arg.TypeString())
return err
})
}
return FromWriter(func(out io.Writer) error {
_, err := fmt.Fprintf(out, "%s%s=<%s>", sep, arg.Name, arg.TypeString())
return err
})
}
// markerName returns a span containing just the appropriately-formatted marker name.
func markerName(def help.MarkerDoc) Span {
if def.DeprecatedInFavorOf != nil {
return deprecatedStyle.Containing(Text("+" + def.Name))
}
return markerNameStyle.Containing(Text("+" + def.Name))
}
// MarkerSyntaxHelp assembles syntax help for a given marker.
func MarkerSyntaxHelp(def help.MarkerDoc) Span {
out := new(SpanWriter)
out.Print(markerName(def))
if def.Empty() {
return out
}
sep := ":"
if def.AnonymousField() {
sep = ""
}
fieldStyle := fieldSummaryStyle
if def.DeprecatedInFavorOf != nil {
fieldStyle = deprecatedStyle
}
for _, arg := range def.Fields {
out.Print(fieldStyle.Containing(fieldSyntaxHelp(arg, sep)))
sep = ","
}
return out
}

View File

@@ -0,0 +1,304 @@
package pretty
import (
"bytes"
"fmt"
"io"
"github.com/fatih/color"
)
// NB(directxman12): this isn't particularly elegant, but it's also
// sufficiently simple as to be maintained here. Man (roff) would've
// probably worked, but it's not necessarily on Windows by default.
// Span is a chunk of content that is writable to an output, but knows how to
// calculate its apparent visual "width" on the terminal (not to be confused
// with the raw length, which may include zero-width coloring sequences).
type Span interface {
// VisualLength reports the "width" as perceived by the user on the terminal
// (i.e. widest line, ignoring ANSI escape characters).
VisualLength() int
// WriteTo writes the full span contents to the given writer.
WriteTo(io.Writer) error
}
// Table is a Span that writes its data in table form, with sizing controlled
// by the given table calculator. Rows are started with StartRow, followed by
// some calls to Column, followed by a call to EndRow. Once all rows are
// added, the table can be used as a Span.
type Table struct {
Sizing *TableCalculator
cellsByRow [][]Span
colSizes []int
}
// StartRow starts a new row.
// It must eventually be followed by EndRow.
func (t *Table) StartRow() {
t.cellsByRow = append(t.cellsByRow, []Span(nil))
}
// EndRow ends the currently started row.
func (t *Table) EndRow() {
lastRow := t.cellsByRow[len(t.cellsByRow)-1]
sizes := make([]int, len(lastRow))
for i, cell := range lastRow {
sizes[i] = cell.VisualLength()
}
t.Sizing.AddRowSizes(sizes...)
}
// Column adds the given span as a new column to the current row.
func (t *Table) Column(contents Span) {
currentRowInd := len(t.cellsByRow) - 1
t.cellsByRow[currentRowInd] = append(t.cellsByRow[currentRowInd], contents)
}
// SkipRow prints a span without having it contribute to the table calculation.
func (t *Table) SkipRow(contents Span) {
t.cellsByRow = append(t.cellsByRow, []Span{contents})
}
func (t *Table) WriteTo(out io.Writer) error {
if t.colSizes == nil {
t.colSizes = t.Sizing.ColumnWidths()
}
for _, cells := range t.cellsByRow {
currentPosition := 0
for colInd, cell := range cells {
colSize := t.colSizes[colInd]
diff := colSize - cell.VisualLength()
if err := cell.WriteTo(out); err != nil {
return err
}
if diff > 0 {
if err := writePadding(out, columnPadding, diff); err != nil {
return err
}
}
currentPosition += colSize
}
if _, err := fmt.Fprint(out, "\n"); err != nil {
return err
}
}
return nil
}
func (t *Table) VisualLength() int {
if t.colSizes == nil {
t.colSizes = t.Sizing.ColumnWidths()
}
res := 0
for _, colSize := range t.colSizes {
res += colSize
}
return res
}
// Text is a span that simply contains raw text. It's a good starting point.
type Text string
func (t Text) VisualLength() int { return len(t) }
func (t Text) WriteTo(w io.Writer) error {
_, err := w.Write([]byte(t))
return err
}
// indented is a span that indents all lines by the given number of tabs.
type indented struct {
Amount int
Content Span
}
func (i *indented) VisualLength() int { return i.Content.VisualLength() }
func (i *indented) WriteTo(w io.Writer) error {
var out bytes.Buffer
if err := i.Content.WriteTo(&out); err != nil {
return err
}
lines := bytes.Split(out.Bytes(), []byte("\n"))
for lineInd, line := range lines {
if lineInd != 0 {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
if len(line) == 0 {
continue
}
if err := writePadding(w, indentPadding, i.Amount); err != nil {
return err
}
if _, err := w.Write(line); err != nil {
return err
}
}
return nil
}
// Indented returns a span that indents all lines by the given number of tabs.
func Indented(amt int, content Span) Span {
return &indented{Amount: amt, Content: content}
}
// fromWriter is a span that takes content from a function expecting a Writer.
type fromWriter struct {
cache []byte
cacheError error
run func(io.Writer) error
}
func (f *fromWriter) VisualLength() int {
if f.cache == nil {
var buf bytes.Buffer
if err := f.run(&buf); err != nil {
f.cacheError = err
}
f.cache = buf.Bytes()
}
return len(f.cache)
}
func (f *fromWriter) WriteTo(w io.Writer) error {
if f.cache != nil {
if f.cacheError != nil {
return f.cacheError
}
_, err := w.Write(f.cache)
return err
}
return f.run(w)
}
// FromWriter returns a span that takes content from a function expecting a Writer.
func FromWriter(run func(io.Writer) error) Span {
return &fromWriter{run: run}
}
// Decoration represents a terminal decoration.
type Decoration color.Color
// Containing returns a Span that has the given decoration applied.
func (d Decoration) Containing(contents Span) Span {
return &decorated{
Contents: contents,
Attributes: color.Color(d),
}
}
// decorated is a span that has some terminal decoration applied.
type decorated struct {
Contents Span
Attributes color.Color
}
func (d *decorated) VisualLength() int { return d.Contents.VisualLength() }
func (d *decorated) WriteTo(w io.Writer) error {
oldOut := color.Output
color.Output = w
defer func() { color.Output = oldOut }()
d.Attributes.Set()
defer color.Unset()
return d.Contents.WriteTo(w)
}
// SpanWriter is a span that contains multiple sub-spans.
type SpanWriter struct {
contents []Span
}
func (m *SpanWriter) VisualLength() int {
res := 0
for _, span := range m.contents {
res += span.VisualLength()
}
return res
}
func (m *SpanWriter) WriteTo(w io.Writer) error {
for _, span := range m.contents {
if err := span.WriteTo(w); err != nil {
return err
}
}
return nil
}
// Print adds a new span to this SpanWriter.
func (m *SpanWriter) Print(s Span) {
m.contents = append(m.contents, s)
}
// lines is a span that adds some newlines, optionally followed by some content.
type lines struct {
content Span
amountBefore int
}
func (l *lines) VisualLength() int {
if l.content == nil {
return 0
}
return l.content.VisualLength()
}
func (l *lines) WriteTo(w io.Writer) error {
if err := writePadding(w, linesPadding, l.amountBefore); err != nil {
return err
}
if l.content != nil {
if err := l.content.WriteTo(w); err != nil {
return err
}
}
return nil
}
// Newlines returns a span just containing some newlines.
func Newlines(amt int) Span {
return &lines{amountBefore: amt}
}
// Line returns a span that emits a newline, followed by the given content.
func Line(content Span) Span {
return &lines{amountBefore: 1, content: content}
}
var (
columnPadding = []byte(" ")
indentPadding = []byte("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t")
linesPadding = []byte("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
)
// writePadding writes out padding of the given type in the given amount to the writer.
// Each byte in the padding buffer contributes 1 to the amount -- the padding being
// a buffer is just for efficiency.
func writePadding(out io.Writer, typ []byte, amt int) error {
if amt <= len(typ) {
_, err := out.Write(typ[:amt])
return err
}
num := amt / len(typ)
rem := amt % len(typ)
for i := 0; i < num; i++ {
if _, err := out.Write(typ); err != nil {
return err
}
}
if _, err := out.Write(typ[:rem]); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package pretty
// TableCalculator calculates column widths (with optional padding)
// for a table based on the maximum required column width.
type TableCalculator struct {
cellSizesByCol [][]int
Padding int
MaxWidth int
}
// AddRowSizes registers a new row with cells of the given sizes.
func (c *TableCalculator) AddRowSizes(cellSizes ...int) {
if len(cellSizes) > len(c.cellSizesByCol) {
for range cellSizes[len(c.cellSizesByCol):] {
c.cellSizesByCol = append(c.cellSizesByCol, []int(nil))
}
}
for i, size := range cellSizes {
c.cellSizesByCol[i] = append(c.cellSizesByCol[i], size)
}
}
// ColumnWidths calculates the appropriate column sizes given the
// previously registered rows.
func (c *TableCalculator) ColumnWidths() []int {
maxColWidths := make([]int, len(c.cellSizesByCol))
for colInd, cellSizes := range c.cellSizesByCol {
max := 0
for _, cellSize := range cellSizes {
if max < cellSize {
max = cellSize
}
}
maxColWidths[colInd] = max
}
actualMaxWidth := c.MaxWidth - c.Padding
for i, width := range maxColWidths {
if actualMaxWidth > 0 && width > actualMaxWidth {
maxColWidths[i] = actualMaxWidth
}
maxColWidths[i] += c.Padding
}
return maxColWidths
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package help
import (
"strings"
"sigs.k8s.io/controller-tools/pkg/markers"
)
// SortGroup knows how to sort and group marker definitions.
type SortGroup interface {
// Less is equivalent to the Less function from sort, and is used to sort the markers.
Less(*markers.Definition, *markers.Definition) bool
// Group returns the "group" that a given marker belongs to.
Group(*markers.Definition, *markers.DefinitionHelp) string
}
var (
// SortByCategory sorts the markers by name and groups them by their help category.
SortByCategory = sortByCategory{}
// SortByOption sorts by the generator that the option belongs to.
SortByOption = optionsSort{}
)
type sortByCategory struct{}
func (sortByCategory) Group(_ *markers.Definition, help *markers.DefinitionHelp) string {
if help == nil {
return ""
}
return help.Category
}
func (sortByCategory) Less(i, j *markers.Definition) bool {
return i.Name < j.Name
}
type optionsSort struct{}
func (optionsSort) Less(i, j *markers.Definition) bool {
iParts := strings.Split(i.Name, ":")
jParts := strings.Split(j.Name, ":")
iGen := ""
iRule := ""
jGen := ""
jRule := ""
switch len(iParts) {
case 1:
iGen = iParts[0]
// two means a default output rule, so ignore
case 2:
iRule = iParts[1]
case 3:
iGen = iParts[1]
iRule = iParts[2]
}
switch len(jParts) {
case 1:
jGen = jParts[0]
// two means a default output rule, so ignore
case 2:
jRule = jParts[1]
case 3:
jGen = jParts[1]
jRule = jParts[2]
}
if iGen != jGen {
return iGen > jGen
}
return iRule < jRule
}
func (optionsSort) Group(def *markers.Definition, _ *markers.DefinitionHelp) string {
parts := strings.Split(def.Name, ":")
switch len(parts) {
case 1:
if parts[0] == "paths" {
return "generic"
}
return "generators"
case 2:
return "output rules (optionally as output:<generator>:...)"
default:
return ""
// three means a marker-specific output rule, ignore
}
}

View File

@@ -0,0 +1,215 @@
/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package help
import (
"sort"
"strings"
"sigs.k8s.io/controller-tools/pkg/markers"
)
// DetailedHelp contains both a summary and further details.
type DetailedHelp struct {
// Summary contains a one-line description.
Summary string `json:"summary"`
// Details contains further information.
Details string `json:"details,omitempty"`
}
// Argument is the type data for a marker argument.
type Argument struct {
// Type is the data type of the argument (string, bool, int, slice, any, raw, invalid)
Type string `json:"type"`
// Optional marks this argument as optional.
Optional bool `json:"optional"`
// ItemType contains the type of the slice item, if this is a slice
ItemType *Argument `json:"itemType,omitempty"`
}
func (a Argument) typeString(out *strings.Builder) {
if a.Type == "slice" {
out.WriteString("[]")
a.ItemType.typeString(out)
return
}
out.WriteString(a.Type)
}
// TypeString returns a string roughly equivalent
// (but not identical) to the underlying Go type that
// this argument would parse to. It's mainly useful
// for user-friendly formatting of this argument (e.g.
// help strings).
func (a Argument) TypeString() string {
out := &strings.Builder{}
a.typeString(out)
return out.String()
}
// FieldHelp contains information required to print documentation for a marker field.
type FieldHelp struct {
// Name is the field name.
Name string `json:"name"`
// Argument is the type of the field.
Argument `json:",inline"`
// DetailedHelp contains the textual help for the field.
DetailedHelp `json:",inline"`
}
// MarkerDoc contains information required to print documentation for a marker.
type MarkerDoc struct {
// definition
// Name is the name of the marker.
Name string `json:"name"`
// Target is the target (field, package, type) of the marker.
Target string `json:"target"`
// help
// DetailedHelp is the textual help for the marker.
DetailedHelp `json:",inline"`
// Category is the general "category" that this marker belongs to.
Category string `json:"category"`
// DeprecatedInFavorOf marks that this marker shouldn't be used when
// non-nil. If also non-empty, another marker should be used instead.
DeprecatedInFavorOf *string `json:"deprecatedInFavorOf,omitempty"`
// Fields is the type and help data for each field of this marker.
Fields []FieldHelp `json:"fields,omitempty"`
}
// Empty checks if this marker has any arguments, returning true if not.
func (m MarkerDoc) Empty() bool {
return len(m.Fields) == 0
}
// AnonymousField chekcs if this is an single-valued marker
// (as opposed to having named fields).
func (m MarkerDoc) AnonymousField() bool {
return len(m.Fields) == 1 && m.Fields[0].Name == ""
}
// ForArgument returns the equivalent documentation for a marker argument.
func ForArgument(argRaw markers.Argument) Argument {
res := Argument{
Optional: argRaw.Optional,
}
if argRaw.ItemType != nil {
itemType := ForArgument(*argRaw.ItemType)
res.ItemType = &itemType
}
switch argRaw.Type {
case markers.IntType:
res.Type = "int"
case markers.StringType:
res.Type = "string"
case markers.BoolType:
res.Type = "bool"
case markers.AnyType:
res.Type = "any"
case markers.SliceType:
res.Type = "slice"
case markers.RawType:
res.Type = "raw"
case markers.InvalidType:
res.Type = "invalid"
}
return res
}
// ForDefinition returns the equivalent marker documentation for a given marker definition and spearate help.
func ForDefinition(defn *markers.Definition, maybeHelp *markers.DefinitionHelp) MarkerDoc {
var help markers.DefinitionHelp
if maybeHelp != nil {
help = *maybeHelp
}
res := MarkerDoc{
Name: defn.Name,
Category: help.Category,
DeprecatedInFavorOf: help.DeprecatedInFavorOf,
Target: defn.Target.String(),
DetailedHelp: DetailedHelp{Summary: help.Summary, Details: help.Details},
}
helpByField := help.FieldsHelp(defn)
// TODO(directxman12): deterministic ordering
for fieldName, fieldHelpRaw := range helpByField {
fieldInfo := defn.Fields[fieldName]
fieldHelp := FieldHelp{
Name: fieldName,
DetailedHelp: DetailedHelp{Summary: fieldHelpRaw.Summary, Details: fieldHelpRaw.Details},
Argument: ForArgument(fieldInfo),
}
res.Fields = append(res.Fields, fieldHelp)
}
sort.Slice(res.Fields, func(i, j int) bool { return res.Fields[i].Name < res.Fields[j].Name })
return res
}
// CategoryDoc contains help information for all markers in a Category.
type CategoryDoc struct {
Category string `json:"category"`
Markers []MarkerDoc `json:"markers"`
}
// ByCategory returns the marker help for markers in the given
// registry, grouped and sorted according to the given method.
func ByCategory(reg *markers.Registry, sorter SortGroup) []CategoryDoc {
groupedMarkers := make(map[string][]*markers.Definition)
for _, marker := range reg.AllDefinitions() {
group := sorter.Group(marker, reg.HelpFor(marker))
groupedMarkers[group] = append(groupedMarkers[group], marker)
}
allGroups := make([]string, 0, len(groupedMarkers))
for groupName := range groupedMarkers {
allGroups = append(allGroups, groupName)
}
sort.Strings(allGroups)
res := make([]CategoryDoc, len(allGroups))
for i, groupName := range allGroups {
markers := groupedMarkers[groupName]
sort.Slice(markers, func(i, j int) bool {
return sorter.Less(markers[i], markers[j])
})
markerDocs := make([]MarkerDoc, len(markers))
for i, marker := range markers {
markerDocs[i] = ForDefinition(marker, reg.HelpFor(marker))
}
res[i] = CategoryDoc{
Category: groupName,
Markers: markerDocs,
}
}
return res
}