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:
422
vendor/sigs.k8s.io/controller-tools/pkg/markers/collect.go
generated
vendored
Normal file
422
vendor/sigs.k8s.io/controller-tools/pkg/markers/collect.go
generated
vendored
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
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 markers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"sigs.k8s.io/controller-tools/pkg/loader"
|
||||
)
|
||||
|
||||
// Collector collects and parses marker comments defined in the registry
|
||||
// from package source code. If no registry is provided, an empty one will
|
||||
// be initialized on the first call to MarkersInPackage.
|
||||
type Collector struct {
|
||||
*Registry
|
||||
|
||||
byPackage map[string]map[ast.Node]MarkerValues
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// MarkerValues are all the values for some set of markers.
|
||||
type MarkerValues map[string][]interface{}
|
||||
|
||||
// Get fetches the first value that for the given marker, returning
|
||||
// nil if no values are available.
|
||||
func (v MarkerValues) Get(name string) interface{} {
|
||||
vals := v[name]
|
||||
if len(vals) == 0 {
|
||||
return nil
|
||||
}
|
||||
return vals[0]
|
||||
}
|
||||
|
||||
func (c *Collector) init() {
|
||||
if c.Registry == nil {
|
||||
c.Registry = &Registry{}
|
||||
}
|
||||
if c.byPackage == nil {
|
||||
c.byPackage = make(map[string]map[ast.Node]MarkerValues)
|
||||
}
|
||||
}
|
||||
|
||||
// MarkersInPackage computes the marker values by node for the given package. Results
|
||||
// are cached by package ID, so this is safe to call repeatedly from different functions.
|
||||
// Each file in the package is treated as a distinct node.
|
||||
//
|
||||
// We consider a marker to be associated with a given AST node if either of the following are true:
|
||||
//
|
||||
// - it's in the Godoc for that AST node
|
||||
//
|
||||
// - it's in the closest non-godoc comment group above that node,
|
||||
// *and* that node is a type or field node, *and* [it's either
|
||||
// registered as type-level *or* it's not registered as being
|
||||
// package-level]
|
||||
//
|
||||
// - it's not in the Godoc of a node, doesn't meet the above criteria, and
|
||||
// isn't in a struct definition (in which case it's package-level)
|
||||
func (c *Collector) MarkersInPackage(pkg *loader.Package) (map[ast.Node]MarkerValues, error) {
|
||||
c.mu.Lock()
|
||||
c.init()
|
||||
if markers, exist := c.byPackage[pkg.ID]; exist {
|
||||
c.mu.Unlock()
|
||||
return markers, nil
|
||||
}
|
||||
// unlock early, it's ok if we do a bit extra work rather than locking while we're working
|
||||
c.mu.Unlock()
|
||||
|
||||
pkg.NeedSyntax()
|
||||
nodeMarkersRaw := c.associatePkgMarkers(pkg)
|
||||
markers, err := c.parseMarkersInPackage(nodeMarkersRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.byPackage[pkg.ID] = markers
|
||||
|
||||
return markers, nil
|
||||
}
|
||||
|
||||
// parseMarkersInPackage parses the given raw marker comments into output values using the registry.
|
||||
func (c *Collector) parseMarkersInPackage(nodeMarkersRaw map[ast.Node][]markerComment) (map[ast.Node]MarkerValues, error) {
|
||||
var errors []error
|
||||
nodeMarkerValues := make(map[ast.Node]MarkerValues)
|
||||
for node, markersRaw := range nodeMarkersRaw {
|
||||
var target TargetType
|
||||
switch node.(type) {
|
||||
case *ast.File:
|
||||
target = DescribesPackage
|
||||
case *ast.Field:
|
||||
target = DescribesField
|
||||
default:
|
||||
target = DescribesType
|
||||
}
|
||||
markerVals := make(map[string][]interface{})
|
||||
for _, markerRaw := range markersRaw {
|
||||
markerText := markerRaw.Text()
|
||||
def := c.Registry.Lookup(markerText, target)
|
||||
if def == nil {
|
||||
continue
|
||||
}
|
||||
val, err := def.Parse(markerText)
|
||||
if err != nil {
|
||||
errors = append(errors, loader.ErrFromNode(err, markerRaw))
|
||||
continue
|
||||
}
|
||||
markerVals[def.Name] = append(markerVals[def.Name], val)
|
||||
}
|
||||
nodeMarkerValues[node] = markerVals
|
||||
}
|
||||
|
||||
return nodeMarkerValues, loader.MaybeErrList(errors)
|
||||
}
|
||||
|
||||
// associatePkgMarkers associates markers with AST nodes in the given package.
|
||||
func (c *Collector) associatePkgMarkers(pkg *loader.Package) map[ast.Node][]markerComment {
|
||||
nodeMarkers := make(map[ast.Node][]markerComment)
|
||||
for _, file := range pkg.Syntax {
|
||||
fileNodeMarkers := c.associateFileMarkers(file)
|
||||
for node, markers := range fileNodeMarkers {
|
||||
nodeMarkers[node] = append(nodeMarkers[node], markers...)
|
||||
}
|
||||
}
|
||||
|
||||
return nodeMarkers
|
||||
}
|
||||
|
||||
// associateFileMarkers associates markers with AST nodes in the given file.
|
||||
func (c *Collector) associateFileMarkers(file *ast.File) map[ast.Node][]markerComment {
|
||||
// grab all the raw marker comments by node
|
||||
visitor := markerSubVisitor{
|
||||
collectPackageLevel: true,
|
||||
markerVisitor: &markerVisitor{
|
||||
nodeMarkers: make(map[ast.Node][]markerComment),
|
||||
allComments: file.Comments,
|
||||
},
|
||||
}
|
||||
ast.Walk(visitor, file)
|
||||
|
||||
// grab the last package-level comments at the end of the file (if any)
|
||||
lastFileMarkers := visitor.markersBetween(false, visitor.commentInd, len(visitor.allComments))
|
||||
visitor.pkgMarkers = append(visitor.pkgMarkers, lastFileMarkers...)
|
||||
|
||||
// figure out if any type-level markers are actually package-level markers
|
||||
for node, markers := range visitor.nodeMarkers {
|
||||
_, isType := node.(*ast.TypeSpec)
|
||||
if !isType {
|
||||
continue
|
||||
}
|
||||
endOfMarkers := 0
|
||||
for _, marker := range markers {
|
||||
if marker.fromGodoc {
|
||||
// markers from godoc are never package level
|
||||
markers[endOfMarkers] = marker
|
||||
endOfMarkers++
|
||||
continue
|
||||
}
|
||||
markerText := marker.Text()
|
||||
typeDef := c.Registry.Lookup(markerText, DescribesType)
|
||||
if typeDef != nil {
|
||||
// prefer assuming type-level markers
|
||||
markers[endOfMarkers] = marker
|
||||
endOfMarkers++
|
||||
continue
|
||||
}
|
||||
def := c.Registry.Lookup(markerText, DescribesPackage)
|
||||
if def == nil {
|
||||
// assume type-level unless proven otherwise
|
||||
markers[endOfMarkers] = marker
|
||||
endOfMarkers++
|
||||
continue
|
||||
}
|
||||
// it's package-level, since a package-level definition exists
|
||||
visitor.pkgMarkers = append(visitor.pkgMarkers, marker)
|
||||
}
|
||||
visitor.nodeMarkers[node] = markers[:endOfMarkers] // re-set after trimming the package markers
|
||||
}
|
||||
visitor.nodeMarkers[file] = visitor.pkgMarkers
|
||||
|
||||
return visitor.nodeMarkers
|
||||
}
|
||||
|
||||
// markerComment is an AST comment that contains a marker.
|
||||
// It may or may not be from a Godoc comment, which affects
|
||||
// marker re-associated (from type-level to package-level)
|
||||
type markerComment struct {
|
||||
*ast.Comment
|
||||
fromGodoc bool
|
||||
}
|
||||
|
||||
// Text returns the text of the marker, stripped of the comment
|
||||
// marker and leading spaces, as should be passed to Registry.Lookup
|
||||
// and Registry.Parse.
|
||||
func (c markerComment) Text() string {
|
||||
return strings.TrimSpace(c.Comment.Text[2:])
|
||||
}
|
||||
|
||||
// markerVisistor visits AST nodes, recording markers associated with each node.
|
||||
type markerVisitor struct {
|
||||
allComments []*ast.CommentGroup
|
||||
commentInd int
|
||||
|
||||
declComments []markerComment
|
||||
lastLineCommentGroup *ast.CommentGroup
|
||||
|
||||
pkgMarkers []markerComment
|
||||
nodeMarkers map[ast.Node][]markerComment
|
||||
}
|
||||
|
||||
// isMarkerComment checks that the given comment is a single-line (`//`)
|
||||
// comment and it's first non-space content is `+`.
|
||||
func isMarkerComment(comment string) bool {
|
||||
if comment[0:2] != "//" {
|
||||
return false
|
||||
}
|
||||
stripped := strings.TrimSpace(comment[2:])
|
||||
if len(stripped) < 1 || stripped[0] != '+' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// markersBetween grabs the markers between the given indicies in the list of all comments.
|
||||
func (v *markerVisitor) markersBetween(fromGodoc bool, start, end int) []markerComment {
|
||||
if start < 0 || end < 0 {
|
||||
return nil
|
||||
}
|
||||
var res []markerComment
|
||||
for i := start; i < end; i++ {
|
||||
commentGroup := v.allComments[i]
|
||||
for _, comment := range commentGroup.List {
|
||||
if !isMarkerComment(comment.Text) {
|
||||
continue
|
||||
}
|
||||
res = append(res, markerComment{Comment: comment, fromGodoc: fromGodoc})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type markerSubVisitor struct {
|
||||
*markerVisitor
|
||||
node ast.Node
|
||||
collectPackageLevel bool
|
||||
}
|
||||
|
||||
// Visit collects markers for each node in the AST, optionally
|
||||
// collecting unassociated markers as package-level.
|
||||
func (v markerSubVisitor) Visit(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
// end of the node, so we might need to advance comments beyond the end
|
||||
// of the block if we don't want to collect package-level markers in
|
||||
// this block.
|
||||
|
||||
if !v.collectPackageLevel {
|
||||
if v.commentInd < len(v.allComments) {
|
||||
lastCommentInd := v.commentInd
|
||||
nextGroup := v.allComments[lastCommentInd]
|
||||
for nextGroup.Pos() < v.node.End() {
|
||||
lastCommentInd++
|
||||
if lastCommentInd >= len(v.allComments) {
|
||||
// after the increment so our decrement below still makes sense
|
||||
break
|
||||
}
|
||||
nextGroup = v.allComments[lastCommentInd]
|
||||
}
|
||||
v.commentInd = lastCommentInd
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip comments on the same line as the previous node
|
||||
// making sure to double-check for the case where we've gone past the end of the comments
|
||||
// but still have to finish up typespec-gendecl association (see below).
|
||||
if v.lastLineCommentGroup != nil && v.commentInd < len(v.allComments) && v.lastLineCommentGroup.Pos() == v.allComments[v.commentInd].Pos() {
|
||||
v.commentInd++
|
||||
}
|
||||
|
||||
// stop visiting if there are no more comments in the file
|
||||
// NB(directxman12): we can't just stop immediately, because we
|
||||
// still need to check if there are typespecs associated with gendecls.
|
||||
var markerCommentBlock []markerComment
|
||||
var docCommentBlock []markerComment
|
||||
lastCommentInd := v.commentInd
|
||||
if v.commentInd < len(v.allComments) {
|
||||
// figure out the first comment after the node in question...
|
||||
nextGroup := v.allComments[lastCommentInd]
|
||||
for nextGroup.Pos() < node.Pos() {
|
||||
lastCommentInd++
|
||||
if lastCommentInd >= len(v.allComments) {
|
||||
// after the increment so our decrement below still makes sense
|
||||
break
|
||||
}
|
||||
nextGroup = v.allComments[lastCommentInd]
|
||||
}
|
||||
lastCommentInd-- // ...then decrement to get the last comment before the node in question
|
||||
|
||||
// figure out the godoc comment so we can deal with it separately
|
||||
var docGroup *ast.CommentGroup
|
||||
docGroup, v.lastLineCommentGroup = associatedCommentsFor(node)
|
||||
|
||||
// find the last comment group that's not godoc
|
||||
markerCommentInd := lastCommentInd
|
||||
if docGroup != nil && v.allComments[markerCommentInd].Pos() == docGroup.Pos() {
|
||||
markerCommentInd--
|
||||
}
|
||||
|
||||
// check if we have freestanding package markers,
|
||||
// and find the markers in our "closest non-godoc" comment block,
|
||||
// plus our godoc comment block
|
||||
if markerCommentInd >= v.commentInd {
|
||||
if v.collectPackageLevel {
|
||||
// assume anything between the comment ind and the marker ind (not including it)
|
||||
// are package-level
|
||||
v.pkgMarkers = append(v.pkgMarkers, v.markersBetween(false, v.commentInd, markerCommentInd)...)
|
||||
}
|
||||
markerCommentBlock = v.markersBetween(false, markerCommentInd, markerCommentInd+1)
|
||||
docCommentBlock = v.markersBetween(true, markerCommentInd+1, lastCommentInd+1)
|
||||
} else {
|
||||
docCommentBlock = v.markersBetween(true, markerCommentInd+1, lastCommentInd+1)
|
||||
}
|
||||
}
|
||||
|
||||
resVisitor := markerSubVisitor{
|
||||
collectPackageLevel: false, // don't collect package level by default
|
||||
markerVisitor: v.markerVisitor,
|
||||
node: node,
|
||||
}
|
||||
|
||||
// associate those markers with a node
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
// save the comments associated with the gen-decl if it's a single-line type decl
|
||||
if typedNode.Lparen != token.NoPos || typedNode.Tok != token.TYPE {
|
||||
// not a single-line type spec, treat them as free comments
|
||||
v.pkgMarkers = append(v.pkgMarkers, markerCommentBlock...)
|
||||
break
|
||||
}
|
||||
// save these, we'll need them when we encounter the actual type spec
|
||||
v.declComments = append(v.declComments, markerCommentBlock...)
|
||||
v.declComments = append(v.declComments, docCommentBlock...)
|
||||
case *ast.TypeSpec:
|
||||
// add in comments attributed to the gen-decl, if any,
|
||||
// as well as comments associated with the actual type
|
||||
v.nodeMarkers[node] = append(v.nodeMarkers[node], v.declComments...)
|
||||
v.nodeMarkers[node] = append(v.nodeMarkers[node], markerCommentBlock...)
|
||||
v.nodeMarkers[node] = append(v.nodeMarkers[node], docCommentBlock...)
|
||||
|
||||
v.declComments = nil
|
||||
v.collectPackageLevel = false // don't collect package-level inside type structs
|
||||
case *ast.Field:
|
||||
v.nodeMarkers[node] = append(v.nodeMarkers[node], markerCommentBlock...)
|
||||
v.nodeMarkers[node] = append(v.nodeMarkers[node], docCommentBlock...)
|
||||
case *ast.File:
|
||||
v.pkgMarkers = append(v.pkgMarkers, markerCommentBlock...)
|
||||
v.pkgMarkers = append(v.pkgMarkers, docCommentBlock...)
|
||||
|
||||
// collect markers in root file scope
|
||||
resVisitor.collectPackageLevel = true
|
||||
default:
|
||||
// assume markers before anything else are package-level markers,
|
||||
// *but* don't include any markers in godoc
|
||||
if v.collectPackageLevel {
|
||||
v.pkgMarkers = append(v.pkgMarkers, markerCommentBlock...)
|
||||
}
|
||||
}
|
||||
|
||||
// increment the comment ind so that we start at the right place for the next node
|
||||
v.commentInd = lastCommentInd + 1
|
||||
|
||||
return resVisitor
|
||||
|
||||
}
|
||||
|
||||
// associatedCommentsFor returns the doc comment group (if relevant and present) and end-of-line comment
|
||||
// (again if relevant and present) for the given AST node.
|
||||
func associatedCommentsFor(node ast.Node) (docGroup *ast.CommentGroup, lastLineCommentGroup *ast.CommentGroup) {
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.Field:
|
||||
docGroup = typedNode.Doc
|
||||
lastLineCommentGroup = typedNode.Comment
|
||||
case *ast.File:
|
||||
docGroup = typedNode.Doc
|
||||
case *ast.FuncDecl:
|
||||
docGroup = typedNode.Doc
|
||||
case *ast.GenDecl:
|
||||
docGroup = typedNode.Doc
|
||||
case *ast.ImportSpec:
|
||||
docGroup = typedNode.Doc
|
||||
lastLineCommentGroup = typedNode.Comment
|
||||
case *ast.TypeSpec:
|
||||
docGroup = typedNode.Doc
|
||||
lastLineCommentGroup = typedNode.Comment
|
||||
case *ast.ValueSpec:
|
||||
docGroup = typedNode.Doc
|
||||
lastLineCommentGroup = typedNode.Comment
|
||||
default:
|
||||
lastLineCommentGroup = nil
|
||||
}
|
||||
|
||||
return docGroup, lastLineCommentGroup
|
||||
}
|
113
vendor/sigs.k8s.io/controller-tools/pkg/markers/doc.go
generated
vendored
Normal file
113
vendor/sigs.k8s.io/controller-tools/pkg/markers/doc.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 markers contains utilities for defining and parsing "marker
|
||||
// comments", also occasionally called tag comments (we use the term marker to
|
||||
// avoid confusing with struct tags). Parsed result (output) values take the
|
||||
// form of Go values, much like the "encoding/json" package.
|
||||
//
|
||||
// Definitions and Parsing
|
||||
//
|
||||
// Markers are defined as structured Definitions which can be used to
|
||||
// consistently parse marker comments. A Definition contains an concrete
|
||||
// output type for the marker, which can be a simple type (like string), a
|
||||
// struct, or a wrapper type (useful for defining additional methods on marker
|
||||
// types).
|
||||
//
|
||||
// Markers take the general form
|
||||
//
|
||||
// +path:to:marker=val
|
||||
//
|
||||
// +path:to:marker:arg1=val,arg2=val2
|
||||
//
|
||||
// +path:to:marker
|
||||
//
|
||||
// Arguments may be ints, bools, strings, and slices. Ints and bool take their
|
||||
// standard form from Go. Strings may take any of their standard forms, or any
|
||||
// sequence of unquoted characters up until a `,` or `;` is encountered. Lists
|
||||
// take either of the following forms:
|
||||
//
|
||||
// val;val;val
|
||||
//
|
||||
// {val, val, val}
|
||||
//
|
||||
// Note that the first form will not properly parse nested slices, but is
|
||||
// generally convenient and is the form used in many existing markers.
|
||||
//
|
||||
// Each of those argument types maps to the corresponding go type. Pointers
|
||||
// mark optional fields (a struct tag, below, may also be used). The empty
|
||||
// interface will match any type.
|
||||
//
|
||||
// Struct fields may optionally be annotated with the `marker` struct tag. The
|
||||
// first argument is a name override. If it's left blank (or the tag isn't
|
||||
// present), the camelCase version of the name will be used. The only
|
||||
// additional argument defined is `optional`, which marks a field as optional
|
||||
// without using a pointer.
|
||||
//
|
||||
// All parsed values are unmarshalled into the output type. If any
|
||||
// non-optional fields aren't mentioned, an error will be raised unless
|
||||
// `Strict` is set to false.
|
||||
//
|
||||
// Registries and Lookup
|
||||
//
|
||||
// Definitions can be added to registries to facilitate lookups. Each
|
||||
// definition is marked as either describing a type, struct field, or package
|
||||
// (unassociated). The same marker name may be registered multiple times, as
|
||||
// long as each describes a different construct (type, field, or package).
|
||||
// Definitions can then be looked up by passing unparsed markers.
|
||||
//
|
||||
// Collection and Extraction
|
||||
//
|
||||
// Markers can be collected from a loader.Package using a Collector. The
|
||||
// Collector will read from a given Registry, collecting comments that look
|
||||
// like markers and parsing them if they match some definition on the registry.
|
||||
//
|
||||
// Markers are considered associated with a particular field or type if they
|
||||
// exist in the Godoc, or the closest non-godoc comment. Any other markers not
|
||||
// inside a some other block (e.g. a struct definition, interface definition,
|
||||
// etc) are considered package level. Markers in a "closest non-Go comment
|
||||
// block" may also be considered package level if registered as such and no
|
||||
// identical type-level definition exists.
|
||||
//
|
||||
// Like loader.Package, Collector's methods are idempotent and will not
|
||||
// reperform work.
|
||||
//
|
||||
// Traversal
|
||||
//
|
||||
// EachType function iterates over each type in a Package, providing
|
||||
// conveniently structured type and field information with marker values
|
||||
// associated.
|
||||
//
|
||||
// PackageMarkers can be used to fetch just package-level markers.
|
||||
//
|
||||
// Help
|
||||
//
|
||||
// Help can be defined for each marker using the DefinitionHelp struct. It's
|
||||
// mostly intended to be generated off of godocs using cmd/helpgen, which takes
|
||||
// the first line as summary (removing the type/field name), and considers the
|
||||
// rest as details. It looks for the
|
||||
//
|
||||
// +controllertools:generateHelp[:category=<string>]
|
||||
//
|
||||
// marker to start generation.
|
||||
//
|
||||
// If you can't use godoc-based generation for whatever reasons (e.g.
|
||||
// primitive-typed markers), you can use the SimpleHelp and DeprecatedHelp
|
||||
// helper functions to generate help structs.
|
||||
//
|
||||
// Help is then registered into a registry as associated with the actual
|
||||
// definition, and can then be later retrieved from the registry.
|
||||
package markers
|
81
vendor/sigs.k8s.io/controller-tools/pkg/markers/help.go
generated
vendored
Normal file
81
vendor/sigs.k8s.io/controller-tools/pkg/markers/help.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
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 markers
|
||||
|
||||
// You *probably* don't want to write these structs by hand
|
||||
// -- use cmd/helpgen if you can write Godoc, and {Simple,Deprecated}Help
|
||||
// otherwise.
|
||||
|
||||
// DetailedHelp contains brief help, as well as more details.
|
||||
// For the "full" help, join the two together.
|
||||
type DetailedHelp struct {
|
||||
Summary string
|
||||
Details string
|
||||
}
|
||||
|
||||
// DefinitionHelp contains overall help for a marker Definition,
|
||||
// as well as per-field help.
|
||||
type DefinitionHelp struct {
|
||||
// DetailedHelp contains the overall help for the marker.
|
||||
DetailedHelp
|
||||
// Category describes what kind of marker this is.
|
||||
Category string
|
||||
// DeprecatedInFavorOf marks the marker as deprecated.
|
||||
// If non-nil & empty, it's assumed to just mean deprecated permanently.
|
||||
// If non-empty, it's assumed to be a marker name.
|
||||
DeprecatedInFavorOf *string
|
||||
|
||||
// NB(directxman12): we make FieldHelp be in terms of the Go struct field
|
||||
// names so that we don't have to know the conversion or processing rules
|
||||
// for struct fields at compile-time for help generation.
|
||||
|
||||
// FieldHelp defines the per-field help for this marker, *in terms of the
|
||||
// go struct field names. Use the FieldsHelp method to map this to
|
||||
// marker argument names.
|
||||
FieldHelp map[string]DetailedHelp
|
||||
}
|
||||
|
||||
// FieldsHelp maps per-field help to the actual marker argument names from the
|
||||
// given definition.
|
||||
func (d *DefinitionHelp) FieldsHelp(def *Definition) map[string]DetailedHelp {
|
||||
fieldsHelp := make(map[string]DetailedHelp, len(def.FieldNames))
|
||||
for fieldName, argName := range def.FieldNames {
|
||||
fieldsHelp[fieldName] = d.FieldHelp[argName]
|
||||
}
|
||||
return fieldsHelp
|
||||
}
|
||||
|
||||
// SimpleHelp returns help that just has marker-level summary information
|
||||
// (e.g. for use with empty or primitive-typed markers, where Godoc-based
|
||||
// generation isn't possible).
|
||||
func SimpleHelp(category, summary string) *DefinitionHelp {
|
||||
return &DefinitionHelp{
|
||||
Category: category,
|
||||
DetailedHelp: DetailedHelp{Summary: summary},
|
||||
}
|
||||
}
|
||||
|
||||
// DeprecatedHelp returns simple help (a la SimpleHelp), except marked as
|
||||
// deprecated in favor of the given marker (or an empty string for just
|
||||
// deprecated).
|
||||
func DeprecatedHelp(inFavorOf, category, summary string) *DefinitionHelp {
|
||||
return &DefinitionHelp{
|
||||
Category: category,
|
||||
DetailedHelp: DetailedHelp{Summary: summary},
|
||||
DeprecatedInFavorOf: &inFavorOf,
|
||||
}
|
||||
}
|
923
vendor/sigs.k8s.io/controller-tools/pkg/markers/parse.go
generated
vendored
Normal file
923
vendor/sigs.k8s.io/controller-tools/pkg/markers/parse.go
generated
vendored
Normal file
@@ -0,0 +1,923 @@
|
||||
/*
|
||||
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 markers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
sc "text/scanner"
|
||||
"unicode"
|
||||
|
||||
"sigs.k8s.io/controller-tools/pkg/loader"
|
||||
)
|
||||
|
||||
// expect checks that the next token of the scanner is the given token, adding an error
|
||||
// to the scanner if not. It returns whether the token was as expected.
|
||||
func expect(scanner *sc.Scanner, expected rune, errDesc string) bool {
|
||||
tok := scanner.Scan()
|
||||
if tok != expected {
|
||||
scanner.Error(scanner, fmt.Sprintf("expected %s, got %q", errDesc, scanner.TokenText()))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// peekNoSpace is equivalent to scanner.Peek, except that it will consume intervening whitespace.
|
||||
func peekNoSpace(scanner *sc.Scanner) rune {
|
||||
hint := scanner.Peek()
|
||||
for ; hint <= rune(' ') && ((1<<uint64(hint))&scanner.Whitespace) != 0; hint = scanner.Peek() {
|
||||
scanner.Next() // skip the whitespace
|
||||
}
|
||||
return hint
|
||||
}
|
||||
|
||||
var (
|
||||
// interfaceType is a pre-computed reflect.Type representing the empty interface.
|
||||
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
rawArgsType = reflect.TypeOf((*RawArguments)(nil)).Elem()
|
||||
)
|
||||
|
||||
// lowerCamelCase converts PascalCase string to
|
||||
// a camelCase string (by lowering the first rune).
|
||||
func lowerCamelCase(in string) string {
|
||||
isFirst := true
|
||||
return strings.Map(func(inRune rune) rune {
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
return unicode.ToLower(inRune)
|
||||
}
|
||||
return inRune
|
||||
}, in)
|
||||
}
|
||||
|
||||
// RawArguments is a special type that can be used for a marker
|
||||
// to receive *all* raw, underparsed argument data for a marker.
|
||||
// You probably want to use `interface{}` to match any type instead.
|
||||
// Use *only* for legacy markers that don't follow Definition's normal
|
||||
// parsing logic. It should *not* be used as a field in a marker struct.
|
||||
type RawArguments []byte
|
||||
|
||||
// ArgumentType is the kind of a marker argument type.
|
||||
// It's roughly analogous to a subset of reflect.Kind, with
|
||||
// an extra "AnyType" to represent the empty interface.
|
||||
type ArgumentType int
|
||||
|
||||
const (
|
||||
// Invalid represents a type that can't be parsed, and should never be used.
|
||||
InvalidType ArgumentType = iota
|
||||
// IntType is an int
|
||||
IntType
|
||||
// StringType is a string
|
||||
StringType
|
||||
// BoolType is a bool
|
||||
BoolType
|
||||
// AnyType is the empty interface, and matches the rest of the content
|
||||
AnyType
|
||||
// SliceType is any slice constructed of the ArgumentTypes
|
||||
SliceType
|
||||
// MapType is any map constructed of string keys, and ArgumentType values.
|
||||
// Keys are strings, and it's common to see AnyType (non-uniform) values.
|
||||
MapType
|
||||
// RawType represents content that gets passed directly to the marker
|
||||
// without any parsing. It should *only* be used with anonymous markers.
|
||||
RawType
|
||||
)
|
||||
|
||||
// Argument is the type of a marker argument.
|
||||
type Argument struct {
|
||||
// Type is the type of this argument For non-scalar types (map and slice),
|
||||
// further information is specified in ItemType.
|
||||
Type ArgumentType
|
||||
// Optional indicates if this argument is optional.
|
||||
Optional bool
|
||||
// Pointer indicates if this argument was a pointer (this is really only
|
||||
// needed for deserialization, and should alway imply optional)
|
||||
Pointer bool
|
||||
|
||||
// ItemType is the type of the slice item for slices, and the value type
|
||||
// for maps.
|
||||
ItemType *Argument
|
||||
}
|
||||
|
||||
// typeString contains the internals of TypeString.
|
||||
func (a Argument) typeString(out *strings.Builder) {
|
||||
if a.Pointer {
|
||||
out.WriteRune('*')
|
||||
}
|
||||
|
||||
switch a.Type {
|
||||
case InvalidType:
|
||||
out.WriteString("<invalid>")
|
||||
case IntType:
|
||||
out.WriteString("int")
|
||||
case StringType:
|
||||
out.WriteString("string")
|
||||
case BoolType:
|
||||
out.WriteString("bool")
|
||||
case AnyType:
|
||||
out.WriteString("<any>")
|
||||
case SliceType:
|
||||
out.WriteString("[]")
|
||||
// arguments can't be non-pointer optional, so just call into typeString again.
|
||||
a.ItemType.typeString(out)
|
||||
case MapType:
|
||||
out.WriteString("map[string]")
|
||||
a.ItemType.typeString(out)
|
||||
case RawType:
|
||||
out.WriteString("<raw>")
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
func (a Argument) String() string {
|
||||
if a.Optional {
|
||||
return fmt.Sprintf("<optional arg %s>", a.TypeString())
|
||||
}
|
||||
return fmt.Sprintf("<arg %s>", a.TypeString())
|
||||
}
|
||||
|
||||
// castAndSet casts val to out's type if needed,
|
||||
// then sets out to val.
|
||||
func castAndSet(out, val reflect.Value) {
|
||||
outType := out.Type()
|
||||
if outType != val.Type() {
|
||||
val = val.Convert(outType)
|
||||
}
|
||||
out.Set(val)
|
||||
}
|
||||
|
||||
// makeSliceType makes a reflect.Type for a slice of the given type.
|
||||
// Useful for constructing the out value for when AnyType's guess returns a slice.
|
||||
func makeSliceType(itemType Argument) (reflect.Type, error) {
|
||||
var itemReflectedType reflect.Type
|
||||
switch itemType.Type {
|
||||
case IntType:
|
||||
itemReflectedType = reflect.TypeOf(int(0))
|
||||
case StringType:
|
||||
itemReflectedType = reflect.TypeOf("")
|
||||
case BoolType:
|
||||
itemReflectedType = reflect.TypeOf(false)
|
||||
case SliceType:
|
||||
subItemType, err := makeSliceType(*itemType.ItemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemReflectedType = subItemType
|
||||
case MapType:
|
||||
subItemType, err := makeMapType(*itemType.ItemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemReflectedType = subItemType
|
||||
// TODO(directxman12): support non-uniform slices? (probably not)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type when constructing guessed slice out: %v", itemType.Type)
|
||||
}
|
||||
|
||||
if itemType.Pointer {
|
||||
itemReflectedType = reflect.PtrTo(itemReflectedType)
|
||||
}
|
||||
|
||||
return reflect.SliceOf(itemReflectedType), nil
|
||||
}
|
||||
|
||||
// makeMapType makes a reflect.Type for a map of the given item type.
|
||||
// Useful for constructing the out value for when AnyType's guess returns a map.
|
||||
func makeMapType(itemType Argument) (reflect.Type, error) {
|
||||
var itemReflectedType reflect.Type
|
||||
switch itemType.Type {
|
||||
case IntType:
|
||||
itemReflectedType = reflect.TypeOf(int(0))
|
||||
case StringType:
|
||||
itemReflectedType = reflect.TypeOf("")
|
||||
case BoolType:
|
||||
itemReflectedType = reflect.TypeOf(false)
|
||||
case SliceType:
|
||||
subItemType, err := makeSliceType(*itemType.ItemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemReflectedType = subItemType
|
||||
// TODO(directxman12): support non-uniform slices? (probably not)
|
||||
case MapType:
|
||||
subItemType, err := makeMapType(*itemType.ItemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemReflectedType = subItemType
|
||||
case AnyType:
|
||||
// NB(directxman12): maps explicitly allow non-uniform item types, unlike slices at the moment
|
||||
itemReflectedType = interfaceType
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type when constructing guessed slice out: %v", itemType.Type)
|
||||
}
|
||||
|
||||
if itemType.Pointer {
|
||||
itemReflectedType = reflect.PtrTo(itemReflectedType)
|
||||
}
|
||||
|
||||
return reflect.MapOf(reflect.TypeOf(""), itemReflectedType), nil
|
||||
}
|
||||
|
||||
// guessType takes an educated guess about the type of the next field. If allowSlice
|
||||
// is false, it will not guess slices. It's less efficient than parsing with actual
|
||||
// type information, since we need to allocate to peek ahead full tokens, and the scanner
|
||||
// only allows peeking ahead one character.
|
||||
// Maps are *always* non-uniform (i.e. type the AnyType item type), since they're frequently
|
||||
// used to represent things like defaults for an object in JSON.
|
||||
func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
|
||||
if allowSlice {
|
||||
maybeItem := guessType(scanner, raw, false)
|
||||
|
||||
subRaw := raw[scanner.Pos().Offset:]
|
||||
subScanner := parserScanner(subRaw, scanner.Error)
|
||||
|
||||
var tok rune
|
||||
for tok = subScanner.Scan(); tok != ',' && tok != sc.EOF && tok != ';'; tok = subScanner.Scan() {
|
||||
// wait till we get something interesting
|
||||
}
|
||||
|
||||
// semicolon means it's a legacy slice
|
||||
if tok == ';' {
|
||||
return &Argument{
|
||||
Type: SliceType,
|
||||
ItemType: maybeItem,
|
||||
}
|
||||
}
|
||||
|
||||
return maybeItem
|
||||
}
|
||||
|
||||
// everything else needs a duplicate scanner to scan properly
|
||||
// (so we don't consume our scanner tokens until we actually
|
||||
// go to use this -- Go doesn't like scanners that can be rewound).
|
||||
subRaw := raw[scanner.Pos().Offset:]
|
||||
subScanner := parserScanner(subRaw, scanner.Error)
|
||||
|
||||
// skip whitespace
|
||||
hint := peekNoSpace(subScanner)
|
||||
|
||||
// first, try the easy case -- quoted strings strings
|
||||
switch hint {
|
||||
case '"', '\'', '`':
|
||||
return &Argument{Type: StringType}
|
||||
}
|
||||
|
||||
// next, check for slices or maps
|
||||
if hint == '{' {
|
||||
subScanner.Scan()
|
||||
|
||||
// TODO(directxman12): this can't guess at empty objects, but that's generally ok.
|
||||
// We'll cross that bridge when we get there.
|
||||
|
||||
// look ahead till we can figure out if this is a map or a slice
|
||||
firstElemType := guessType(subScanner, subRaw, false)
|
||||
if firstElemType.Type == StringType {
|
||||
// might be a map or slice, parse the string and check for colon
|
||||
// (blech, basically arbitrary look-ahead due to raw strings).
|
||||
var keyVal string // just ignore this
|
||||
(&Argument{Type: StringType}).parseString(subScanner, raw, reflect.Indirect(reflect.ValueOf(&keyVal)))
|
||||
|
||||
if subScanner.Scan() == ':' {
|
||||
// it's got a string followed by a colon -- it's a map
|
||||
return &Argument{
|
||||
Type: MapType,
|
||||
ItemType: &Argument{Type: AnyType},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// definitely a slice -- maps have to have string keys and have a value followed by a colon
|
||||
return &Argument{
|
||||
Type: SliceType,
|
||||
ItemType: firstElemType,
|
||||
}
|
||||
}
|
||||
|
||||
// then, bools...
|
||||
probablyString := false
|
||||
if hint == 't' || hint == 'f' {
|
||||
// maybe a bool
|
||||
if nextTok := subScanner.Scan(); nextTok == sc.Ident {
|
||||
switch subScanner.TokenText() {
|
||||
case "true", "false":
|
||||
// definitely a bool
|
||||
return &Argument{Type: BoolType}
|
||||
}
|
||||
// probably a string
|
||||
probablyString = true
|
||||
} else {
|
||||
// we shouldn't ever get here
|
||||
scanner.Error(scanner, fmt.Sprintf("got a token (%q) that looked like an ident, but was not", scanner.TokenText()))
|
||||
return &Argument{Type: InvalidType}
|
||||
}
|
||||
}
|
||||
|
||||
// then, integers...
|
||||
if !probablyString {
|
||||
nextTok := subScanner.Scan()
|
||||
if nextTok == '-' {
|
||||
nextTok = subScanner.Scan()
|
||||
}
|
||||
if nextTok == sc.Int {
|
||||
return &Argument{Type: IntType}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise assume bare strings
|
||||
return &Argument{Type: StringType}
|
||||
}
|
||||
|
||||
// parseString parses either of the two accepted string forms (quoted, or bare tokens).
|
||||
func (a *Argument) parseString(scanner *sc.Scanner, raw string, out reflect.Value) {
|
||||
// strings are a bit weird -- the "easy" case is quoted strings (tokenized as strings),
|
||||
// the "hard" case (present for backwards compat) is a bare sequence of tokens that aren't
|
||||
// a comma.
|
||||
tok := scanner.Scan()
|
||||
if tok == sc.String || tok == sc.RawString {
|
||||
// the easy case
|
||||
val, err := strconv.Unquote(scanner.TokenText())
|
||||
if err != nil {
|
||||
scanner.Error(scanner, fmt.Sprintf("unable to parse string: %v", err))
|
||||
return
|
||||
}
|
||||
castAndSet(out, reflect.ValueOf(val))
|
||||
return
|
||||
}
|
||||
|
||||
// the "hard" case -- bare tokens not including ',' (the argument
|
||||
// separator), ';' (the slice separator), ':' (the map separator), or '}'
|
||||
// (delimitted slice ender)
|
||||
startPos := scanner.Position.Offset
|
||||
for hint := peekNoSpace(scanner); hint != ',' && hint != ';' && hint != ':' && hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
|
||||
// skip this token
|
||||
scanner.Scan()
|
||||
}
|
||||
endPos := scanner.Position.Offset + len(scanner.TokenText())
|
||||
castAndSet(out, reflect.ValueOf(raw[startPos:endPos]))
|
||||
}
|
||||
|
||||
// parseSlice parses either of the two slice forms (curly-brace-delimitted and semicolon-separated).
|
||||
func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value) {
|
||||
// slices have two supported formats, like string:
|
||||
// - `{val, val, val}` (preferred)
|
||||
// - `val;val;val` (legacy)
|
||||
resSlice := reflect.Zero(out.Type())
|
||||
elem := reflect.Indirect(reflect.New(out.Type().Elem()))
|
||||
|
||||
// preferred case
|
||||
if peekNoSpace(scanner) == '{' {
|
||||
// NB(directxman12): supporting delimitted slices in bare slices
|
||||
// would require an extra look-ahead here :-/
|
||||
|
||||
scanner.Scan() // skip '{'
|
||||
for hint := peekNoSpace(scanner); hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
|
||||
a.ItemType.parse(scanner, raw, elem, true /* parsing a slice */)
|
||||
resSlice = reflect.Append(resSlice, elem)
|
||||
tok := peekNoSpace(scanner)
|
||||
if tok == '}' {
|
||||
break
|
||||
}
|
||||
if !expect(scanner, ',', "comma") {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !expect(scanner, '}', "close curly brace") {
|
||||
return
|
||||
}
|
||||
castAndSet(out, resSlice)
|
||||
return
|
||||
}
|
||||
|
||||
// legacy case
|
||||
for hint := peekNoSpace(scanner); hint != ',' && hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
|
||||
a.ItemType.parse(scanner, raw, elem, true /* parsing a slice */)
|
||||
resSlice = reflect.Append(resSlice, elem)
|
||||
tok := peekNoSpace(scanner)
|
||||
if tok == ',' || tok == '}' || tok == sc.EOF {
|
||||
break
|
||||
}
|
||||
scanner.Scan()
|
||||
if tok != ';' {
|
||||
scanner.Error(scanner, fmt.Sprintf("expected comma, got %q", scanner.TokenText()))
|
||||
return
|
||||
}
|
||||
}
|
||||
castAndSet(out, resSlice)
|
||||
}
|
||||
|
||||
// parseMap parses a map of the form {string: val, string: val, string: val}
|
||||
func (a *Argument) parseMap(scanner *sc.Scanner, raw string, out reflect.Value) {
|
||||
resMap := reflect.MakeMap(out.Type())
|
||||
elem := reflect.Indirect(reflect.New(out.Type().Elem()))
|
||||
key := reflect.Indirect(reflect.New(out.Type().Key()))
|
||||
|
||||
if !expect(scanner, '{', "open curly brace") {
|
||||
return
|
||||
}
|
||||
|
||||
for hint := peekNoSpace(scanner); hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
|
||||
a.parseString(scanner, raw, key)
|
||||
if !expect(scanner, ':', "colon") {
|
||||
return
|
||||
}
|
||||
a.ItemType.parse(scanner, raw, elem, false /* not in a slice */)
|
||||
resMap.SetMapIndex(key, elem)
|
||||
|
||||
if peekNoSpace(scanner) == '}' {
|
||||
break
|
||||
}
|
||||
if !expect(scanner, ',', "comma") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !expect(scanner, '}', "close curly brace") {
|
||||
return
|
||||
}
|
||||
|
||||
castAndSet(out, resMap)
|
||||
}
|
||||
|
||||
// parse functions like Parse, except that it allows passing down whether or not we're
|
||||
// already in a slice, to avoid duplicate legacy slice detection for AnyType
|
||||
func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inSlice bool) {
|
||||
// nolint:gocyclo
|
||||
if a.Type == InvalidType {
|
||||
scanner.Error(scanner, fmt.Sprintf("cannot parse invalid type"))
|
||||
return
|
||||
}
|
||||
if a.Pointer {
|
||||
out.Set(reflect.New(out.Type().Elem()))
|
||||
out = reflect.Indirect(out)
|
||||
}
|
||||
switch a.Type {
|
||||
case RawType:
|
||||
// raw consumes everything else
|
||||
castAndSet(out, reflect.ValueOf(raw[scanner.Pos().Offset:]))
|
||||
// consume everything else
|
||||
for tok := scanner.Scan(); tok != sc.EOF; tok = scanner.Scan() {
|
||||
}
|
||||
case IntType:
|
||||
nextChar := scanner.Peek()
|
||||
isNegative := false
|
||||
if nextChar == '-' {
|
||||
isNegative = true
|
||||
scanner.Scan() // eat the '-'
|
||||
}
|
||||
if !expect(scanner, sc.Int, "integer") {
|
||||
return
|
||||
}
|
||||
// TODO(directxman12): respect the size when parsing
|
||||
text := scanner.TokenText()
|
||||
if isNegative {
|
||||
text = "-" + text
|
||||
}
|
||||
val, err := strconv.Atoi(text)
|
||||
if err != nil {
|
||||
scanner.Error(scanner, fmt.Sprintf("unable to parse integer: %v", err))
|
||||
return
|
||||
}
|
||||
castAndSet(out, reflect.ValueOf(val))
|
||||
case StringType:
|
||||
// strings are a bit weird -- the "easy" case is quoted strings (tokenized as strings),
|
||||
// the "hard" case (present for backwards compat) is a bare sequence of tokens that aren't
|
||||
// a comma.
|
||||
a.parseString(scanner, raw, out)
|
||||
case BoolType:
|
||||
if !expect(scanner, sc.Ident, "true or false") {
|
||||
return
|
||||
}
|
||||
switch scanner.TokenText() {
|
||||
case "true":
|
||||
castAndSet(out, reflect.ValueOf(true))
|
||||
case "false":
|
||||
castAndSet(out, reflect.ValueOf(false))
|
||||
default:
|
||||
scanner.Error(scanner, fmt.Sprintf("expected true or false, got %q", scanner.TokenText()))
|
||||
return
|
||||
}
|
||||
case AnyType:
|
||||
guessedType := guessType(scanner, raw, !inSlice)
|
||||
newOut := out
|
||||
|
||||
// we need to be able to construct the right element types, below
|
||||
// in parse, so construct a concretely-typed value to use as "out"
|
||||
switch guessedType.Type {
|
||||
case SliceType:
|
||||
newType, err := makeSliceType(*guessedType.ItemType)
|
||||
if err != nil {
|
||||
scanner.Error(scanner, err.Error())
|
||||
return
|
||||
}
|
||||
newOut = reflect.Indirect(reflect.New(newType))
|
||||
case MapType:
|
||||
newType, err := makeMapType(*guessedType.ItemType)
|
||||
if err != nil {
|
||||
scanner.Error(scanner, err.Error())
|
||||
return
|
||||
}
|
||||
newOut = reflect.Indirect(reflect.New(newType))
|
||||
}
|
||||
if !newOut.CanSet() {
|
||||
panic("at the disco") // TODO(directxman12): this is left over from debugging -- it might need to be an error
|
||||
}
|
||||
guessedType.Parse(scanner, raw, newOut)
|
||||
castAndSet(out, newOut)
|
||||
case SliceType:
|
||||
// slices have two supported formats, like string:
|
||||
// - `{val, val, val}` (preferred)
|
||||
// - `val;val;val` (legacy)
|
||||
a.parseSlice(scanner, raw, out)
|
||||
case MapType:
|
||||
// maps are {string: val, string: val, string: val}
|
||||
a.parseMap(scanner, raw, out)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse attempts to consume the argument from the given scanner (based on the given
|
||||
// raw input as well for collecting ranges of content), and places the output value
|
||||
// in the given reflect.Value. Errors are reported via the given scanner.
|
||||
func (a *Argument) Parse(scanner *sc.Scanner, raw string, out reflect.Value) {
|
||||
a.parse(scanner, raw, out, false)
|
||||
}
|
||||
|
||||
// ArgumentFromType constructs an Argument by examining the given
|
||||
// raw reflect.Type. It can construct arguments from the Go types
|
||||
// corresponding to any of the types listed in ArgumentType.
|
||||
func ArgumentFromType(rawType reflect.Type) (Argument, error) {
|
||||
if rawType == rawArgsType {
|
||||
return Argument{
|
||||
Type: RawType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if rawType == interfaceType {
|
||||
return Argument{
|
||||
Type: AnyType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
arg := Argument{}
|
||||
if rawType.Kind() == reflect.Ptr {
|
||||
rawType = rawType.Elem()
|
||||
arg.Pointer = true
|
||||
arg.Optional = true
|
||||
}
|
||||
|
||||
switch rawType.Kind() {
|
||||
case reflect.String:
|
||||
arg.Type = StringType
|
||||
case reflect.Int, reflect.Int32: // NB(directxman12): all ints in kubernetes are int32, so explicitly support that
|
||||
arg.Type = IntType
|
||||
case reflect.Bool:
|
||||
arg.Type = BoolType
|
||||
case reflect.Slice:
|
||||
arg.Type = SliceType
|
||||
itemType, err := ArgumentFromType(rawType.Elem())
|
||||
if err != nil {
|
||||
return Argument{}, fmt.Errorf("bad slice item type: %w", err)
|
||||
}
|
||||
arg.ItemType = &itemType
|
||||
case reflect.Map:
|
||||
arg.Type = MapType
|
||||
if rawType.Key().Kind() != reflect.String {
|
||||
return Argument{}, fmt.Errorf("bad map key type: map keys must be strings")
|
||||
}
|
||||
itemType, err := ArgumentFromType(rawType.Elem())
|
||||
if err != nil {
|
||||
return Argument{}, fmt.Errorf("bad slice item type: %w", err)
|
||||
}
|
||||
arg.ItemType = &itemType
|
||||
default:
|
||||
return Argument{}, fmt.Errorf("type has unsupported kind %s", rawType.Kind())
|
||||
}
|
||||
|
||||
return arg, nil
|
||||
}
|
||||
|
||||
// TargetType describes which kind of node a given marker is associated with.
|
||||
type TargetType int
|
||||
|
||||
const (
|
||||
// DescribesPackage indicates that a marker is associated with a package.
|
||||
DescribesPackage TargetType = iota
|
||||
// DescribesType indicates that a marker is associated with a type declaration.
|
||||
DescribesType
|
||||
// DescribesField indicates that a marker is associated with a struct field.
|
||||
DescribesField
|
||||
)
|
||||
|
||||
func (t TargetType) String() string {
|
||||
switch t {
|
||||
case DescribesPackage:
|
||||
return "package"
|
||||
case DescribesType:
|
||||
return "type"
|
||||
case DescribesField:
|
||||
return "field"
|
||||
default:
|
||||
return "(unknown)"
|
||||
}
|
||||
}
|
||||
|
||||
// Definition is a parsed definition of a marker.
|
||||
type Definition struct {
|
||||
// Output is the deserialized Go type of the marker.
|
||||
Output reflect.Type
|
||||
// Name is the marker's name.
|
||||
Name string
|
||||
// Target indicates which kind of node this marker can be associated with.
|
||||
Target TargetType
|
||||
// Fields lists out the types of each field that this marker has, by
|
||||
// argument name as used in the marker (if the output type isn't a struct,
|
||||
// it'll have a single, blank field name). This only lists exported fields,
|
||||
// (as per reflection rules).
|
||||
Fields map[string]Argument
|
||||
// FieldNames maps argument names (as used in the marker) to struct field name
|
||||
// in the output type.
|
||||
FieldNames map[string]string
|
||||
// Strict indicates that this definition should error out when parsing if
|
||||
// not all non-optional fields were seen.
|
||||
Strict bool
|
||||
}
|
||||
|
||||
// AnonymousField indicates that the definition has one field,
|
||||
// (actually the original object), and thus the field
|
||||
// doesn't get named as part of the name.
|
||||
func (d *Definition) AnonymousField() bool {
|
||||
if len(d.Fields) != 1 {
|
||||
return false
|
||||
}
|
||||
_, hasAnonField := d.Fields[""]
|
||||
return hasAnonField
|
||||
}
|
||||
|
||||
// Empty indicates that this definition has no fields.
|
||||
func (d *Definition) Empty() bool {
|
||||
return len(d.Fields) == 0
|
||||
}
|
||||
|
||||
// argumentInfo returns information about an argument field as the marker parser's field loader
|
||||
// would see it. This can be useful if you have to interact with marker definition structs
|
||||
// externally (e.g. at compile time).
|
||||
func argumentInfo(fieldName string, tag reflect.StructTag) (argName string, optionalOpt bool) {
|
||||
argName = lowerCamelCase(fieldName)
|
||||
markerTag, tagSpecified := tag.Lookup("marker")
|
||||
markerTagParts := strings.Split(markerTag, ",")
|
||||
if tagSpecified && markerTagParts[0] != "" {
|
||||
// allow overriding to support legacy cases where we don't follow camelCase conventions
|
||||
argName = markerTagParts[0]
|
||||
}
|
||||
optionalOpt = false
|
||||
for _, tagOption := range markerTagParts[1:] {
|
||||
switch tagOption {
|
||||
case "optional":
|
||||
optionalOpt = true
|
||||
}
|
||||
}
|
||||
|
||||
return argName, optionalOpt
|
||||
}
|
||||
|
||||
// loadFields uses reflection to populate argument information from the Output type.
|
||||
func (d *Definition) loadFields() error {
|
||||
if d.Fields == nil {
|
||||
d.Fields = make(map[string]Argument)
|
||||
d.FieldNames = make(map[string]string)
|
||||
}
|
||||
if d.Output.Kind() != reflect.Struct {
|
||||
// anonymous field type
|
||||
argType, err := ArgumentFromType(d.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Fields[""] = argType
|
||||
d.FieldNames[""] = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < d.Output.NumField(); i++ {
|
||||
field := d.Output.Field(i)
|
||||
if field.PkgPath != "" {
|
||||
// as per the reflect package docs, pkgpath is empty for exported fields,
|
||||
// so non-empty package path means a private field, which we should skip
|
||||
continue
|
||||
}
|
||||
argName, optionalOpt := argumentInfo(field.Name, field.Tag)
|
||||
|
||||
argType, err := ArgumentFromType(field.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to extract type information for field %q: %w", field.Name, err)
|
||||
}
|
||||
|
||||
if argType.Type == RawType {
|
||||
return fmt.Errorf("RawArguments must be the direct type of a marker, and not a field")
|
||||
}
|
||||
|
||||
argType.Optional = optionalOpt || argType.Optional
|
||||
|
||||
d.Fields[argName] = argType
|
||||
d.FieldNames[argName] = field.Name
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parserScanner makes a new scanner appropriate for use in parsing definitions and arguments.
|
||||
func parserScanner(raw string, err func(*sc.Scanner, string)) *sc.Scanner {
|
||||
scanner := &sc.Scanner{}
|
||||
scanner.Init(bytes.NewBufferString(raw))
|
||||
scanner.Mode = sc.ScanIdents | sc.ScanInts | sc.ScanStrings | sc.ScanRawStrings | sc.SkipComments
|
||||
scanner.Error = err
|
||||
|
||||
return scanner
|
||||
}
|
||||
|
||||
// Parse uses the type information in this Definition to parse the given
|
||||
// raw marker in the form `+a:b:c=arg,d=arg` into an output object of the
|
||||
// type specified in the definition.
|
||||
func (d *Definition) Parse(rawMarker string) (interface{}, error) {
|
||||
name, anonName, fields := splitMarker(rawMarker)
|
||||
|
||||
out := reflect.Indirect(reflect.New(d.Output))
|
||||
|
||||
// if we're a not a struct or have no arguments, treat the full `a:b:c` as the name,
|
||||
// otherwise, treat `c` as a field name, and `a:b` as the marker name.
|
||||
if !d.AnonymousField() && !d.Empty() && len(anonName) >= len(name)+1 {
|
||||
fields = anonName[len(name)+1:] + "=" + fields
|
||||
}
|
||||
|
||||
var errs []error
|
||||
scanner := parserScanner(fields, func(scanner *sc.Scanner, msg string) {
|
||||
errs = append(errs, &ScannerError{Msg: msg, Pos: scanner.Position})
|
||||
})
|
||||
|
||||
// TODO(directxman12): strict parsing where we error out if certain fields aren't optional
|
||||
seen := make(map[string]struct{}, len(d.Fields))
|
||||
if d.AnonymousField() && scanner.Peek() != sc.EOF {
|
||||
// might still be a struct that something fiddled with, so double check
|
||||
structFieldName := d.FieldNames[""]
|
||||
outTarget := out
|
||||
if structFieldName != "" {
|
||||
// it's a struct field mapped to an anonymous marker
|
||||
outTarget = out.FieldByName(structFieldName)
|
||||
if !outTarget.CanSet() {
|
||||
scanner.Error(scanner, fmt.Sprintf("cannot set field %q (might not exist)", structFieldName))
|
||||
return out.Interface(), loader.MaybeErrList(errs)
|
||||
}
|
||||
}
|
||||
|
||||
// no need for trying to parse field names if we're not a struct
|
||||
field := d.Fields[""]
|
||||
field.Parse(scanner, fields, outTarget)
|
||||
seen[""] = struct{}{} // mark as seen for strict definitions
|
||||
} else if !d.Empty() && scanner.Peek() != sc.EOF {
|
||||
// if we expect *and* actually have arguments passed
|
||||
for {
|
||||
// parse the argument name
|
||||
if !expect(scanner, sc.Ident, "argument name") {
|
||||
break
|
||||
}
|
||||
argName := scanner.TokenText()
|
||||
if !expect(scanner, '=', "equals") {
|
||||
break
|
||||
}
|
||||
|
||||
// make sure we know the field
|
||||
fieldName, known := d.FieldNames[argName]
|
||||
if !known {
|
||||
scanner.Error(scanner, fmt.Sprintf("unknown argument %q", argName))
|
||||
break
|
||||
}
|
||||
fieldType, known := d.Fields[argName]
|
||||
if !known {
|
||||
scanner.Error(scanner, fmt.Sprintf("unknown argument %q", argName))
|
||||
break
|
||||
}
|
||||
seen[argName] = struct{}{} // mark as seen for strict definitions
|
||||
|
||||
// parse the field value
|
||||
fieldVal := out.FieldByName(fieldName)
|
||||
if !fieldVal.CanSet() {
|
||||
scanner.Error(scanner, fmt.Sprintf("cannot set field %q (might not exist)", fieldName))
|
||||
break
|
||||
}
|
||||
fieldType.Parse(scanner, fields, fieldVal)
|
||||
|
||||
if len(errs) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if scanner.Peek() == sc.EOF {
|
||||
break
|
||||
}
|
||||
if !expect(scanner, ',', "comma") {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tok := scanner.Scan(); tok != sc.EOF {
|
||||
scanner.Error(scanner, fmt.Sprintf("extra arguments provided: %q", fields[scanner.Position.Offset:]))
|
||||
}
|
||||
|
||||
if d.Strict {
|
||||
for argName, arg := range d.Fields {
|
||||
if _, wasSeen := seen[argName]; !wasSeen && !arg.Optional {
|
||||
scanner.Error(scanner, fmt.Sprintf("missing argument %q", argName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.Interface(), loader.MaybeErrList(errs)
|
||||
}
|
||||
|
||||
// MakeDefinition constructs a definition from a name, type, and the output type.
|
||||
// All such definitions are strict by default. If a struct is passed as the output
|
||||
// type, its public fields will automatically be populated into Fields (and similar
|
||||
// fields in Definition). Other values will have a single, empty-string-named Fields
|
||||
// entry.
|
||||
func MakeDefinition(name string, target TargetType, output interface{}) (*Definition, error) {
|
||||
def := &Definition{
|
||||
Name: name,
|
||||
Target: target,
|
||||
Output: reflect.TypeOf(output),
|
||||
Strict: true,
|
||||
}
|
||||
|
||||
if err := def.loadFields(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
// MakeAnyTypeDefinition constructs a definition for an output struct with a
|
||||
// field named `Value` of type `interface{}`. The argument to the marker will
|
||||
// be parsed as AnyType and assigned to the field named `Value`.
|
||||
func MakeAnyTypeDefinition(name string, target TargetType, output interface{}) (*Definition, error) {
|
||||
defn, err := MakeDefinition(name, target, output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defn.FieldNames = map[string]string{"": "Value"}
|
||||
defn.Fields = map[string]Argument{"": defn.Fields["value"]}
|
||||
return defn, nil
|
||||
}
|
||||
|
||||
// splitMarker takes a marker in the form of `+a:b:c=arg,d=arg` and splits it
|
||||
// into the name (`a:b`), the name if it's not a struct (`a:b:c`), and the parts
|
||||
// that are definitely fields (`arg,d=arg`).
|
||||
func splitMarker(raw string) (name string, anonymousName string, restFields string) {
|
||||
raw = raw[1:] // get rid of the leading '+'
|
||||
nameFieldParts := strings.SplitN(raw, "=", 2)
|
||||
if len(nameFieldParts) == 1 {
|
||||
return nameFieldParts[0], nameFieldParts[0], ""
|
||||
}
|
||||
anonymousName = nameFieldParts[0]
|
||||
name = anonymousName
|
||||
restFields = nameFieldParts[1]
|
||||
|
||||
nameParts := strings.Split(name, ":")
|
||||
if len(nameParts) > 1 {
|
||||
name = strings.Join(nameParts[:len(nameParts)-1], ":")
|
||||
}
|
||||
return name, anonymousName, restFields
|
||||
}
|
||||
|
||||
type ScannerError struct {
|
||||
Msg string
|
||||
Pos sc.Position
|
||||
}
|
||||
|
||||
func (e *ScannerError) Error() string {
|
||||
return fmt.Sprintf("%s (at %s)", e.Msg, e.Pos)
|
||||
}
|
153
vendor/sigs.k8s.io/controller-tools/pkg/markers/reg.go
generated
vendored
Normal file
153
vendor/sigs.k8s.io/controller-tools/pkg/markers/reg.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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 markers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Registry keeps track of registered definitions, and allows for easy lookup.
|
||||
// It's thread-safe, and the zero-value can be safely used.
|
||||
type Registry struct {
|
||||
forPkg map[string]*Definition
|
||||
forType map[string]*Definition
|
||||
forField map[string]*Definition
|
||||
helpFor map[*Definition]*DefinitionHelp
|
||||
|
||||
mu sync.RWMutex
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
func (r *Registry) init() {
|
||||
r.initOnce.Do(func() {
|
||||
if r.forPkg == nil {
|
||||
r.forPkg = make(map[string]*Definition)
|
||||
}
|
||||
if r.forType == nil {
|
||||
r.forType = make(map[string]*Definition)
|
||||
}
|
||||
if r.forField == nil {
|
||||
r.forField = make(map[string]*Definition)
|
||||
}
|
||||
if r.helpFor == nil {
|
||||
r.helpFor = make(map[*Definition]*DefinitionHelp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Define defines a new marker with the given name, target, and output type.
|
||||
// It's a shortcut around
|
||||
// r.Register(MakeDefinition(name, target, obj))
|
||||
func (r *Registry) Define(name string, target TargetType, obj interface{}) error {
|
||||
def, err := MakeDefinition(name, target, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Register(def)
|
||||
}
|
||||
|
||||
// Register registers the given marker definition with this registry for later lookup.
|
||||
func (r *Registry) Register(def *Definition) error {
|
||||
r.init()
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
switch def.Target {
|
||||
case DescribesPackage:
|
||||
r.forPkg[def.Name] = def
|
||||
case DescribesType:
|
||||
r.forType[def.Name] = def
|
||||
case DescribesField:
|
||||
r.forField[def.Name] = def
|
||||
default:
|
||||
return fmt.Errorf("unknown target type %v", def.Target)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddHelp stores the given help in the registry, marking it as associated with
|
||||
// the given definition.
|
||||
func (r *Registry) AddHelp(def *Definition, help *DefinitionHelp) {
|
||||
r.init()
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.helpFor[def] = help
|
||||
}
|
||||
|
||||
// Lookup fetches the definition corresponding to the given name and target type.
|
||||
func (r *Registry) Lookup(name string, target TargetType) *Definition {
|
||||
r.init()
|
||||
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
switch target {
|
||||
case DescribesPackage:
|
||||
return tryAnonLookup(name, r.forPkg)
|
||||
case DescribesType:
|
||||
return tryAnonLookup(name, r.forType)
|
||||
case DescribesField:
|
||||
return tryAnonLookup(name, r.forField)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// HelpFor fetches the help for a given definition, if present.
|
||||
func (r *Registry) HelpFor(def *Definition) *DefinitionHelp {
|
||||
r.init()
|
||||
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
return r.helpFor[def]
|
||||
}
|
||||
|
||||
// AllDefinitions returns all marker definitions known to this registry.
|
||||
func (r *Registry) AllDefinitions() []*Definition {
|
||||
res := make([]*Definition, 0, len(r.forPkg)+len(r.forType)+len(r.forField))
|
||||
for _, def := range r.forPkg {
|
||||
res = append(res, def)
|
||||
}
|
||||
for _, def := range r.forType {
|
||||
res = append(res, def)
|
||||
}
|
||||
for _, def := range r.forField {
|
||||
res = append(res, def)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// tryAnonLookup tries looking up the given marker as both an struct-based
|
||||
// marker and an anonymous marker, returning whichever format matches first,
|
||||
// preferring the longer (anonymous) name in case of conflicts.
|
||||
func tryAnonLookup(name string, defs map[string]*Definition) *Definition {
|
||||
// NB(directxman12): we look up anonymous names first to work with
|
||||
// legacy style marker definitions that have a namespaced approach
|
||||
// (e.g. deepcopy-gen, which uses `+k8s:deepcopy-gen=foo,bar` *and*
|
||||
// `+k8s.io:deepcopy-gen:interfaces=foo`).
|
||||
name, anonName, _ := splitMarker(name)
|
||||
if def, exists := defs[anonName]; exists {
|
||||
return def
|
||||
}
|
||||
|
||||
return defs[name]
|
||||
}
|
36
vendor/sigs.k8s.io/controller-tools/pkg/markers/regutil.go
generated
vendored
Normal file
36
vendor/sigs.k8s.io/controller-tools/pkg/markers/regutil.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 markers
|
||||
|
||||
// Must panics on errors creating definitions.
|
||||
func Must(def *Definition, err error) *Definition {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// RegisterAll attempts to register all definitions against the given registry,
|
||||
// stopping and returning if an error occurs.
|
||||
func RegisterAll(reg *Registry, defs ...*Definition) error {
|
||||
for _, def := range defs {
|
||||
if err := reg.Register(def); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
191
vendor/sigs.k8s.io/controller-tools/pkg/markers/zip.go
generated
vendored
Normal file
191
vendor/sigs.k8s.io/controller-tools/pkg/markers/zip.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
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 markers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/controller-tools/pkg/loader"
|
||||
)
|
||||
|
||||
// extractDoc extracts documentation from the given node, skipping markers
|
||||
// in the godoc and falling back to the decl if necessary (for single-line decls).
|
||||
func extractDoc(node ast.Node, decl *ast.GenDecl) string {
|
||||
var docs *ast.CommentGroup
|
||||
switch docced := node.(type) {
|
||||
case *ast.Field:
|
||||
docs = docced.Doc
|
||||
case *ast.File:
|
||||
docs = docced.Doc
|
||||
case *ast.GenDecl:
|
||||
docs = docced.Doc
|
||||
case *ast.TypeSpec:
|
||||
docs = docced.Doc
|
||||
// type Ident expr expressions get docs attached to the decl,
|
||||
// so check for that case (missing Lparen == single line type decl)
|
||||
if docs == nil && decl.Lparen == token.NoPos {
|
||||
docs = decl.Doc
|
||||
}
|
||||
}
|
||||
|
||||
if docs == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// filter out markers
|
||||
var outGroup ast.CommentGroup
|
||||
outGroup.List = make([]*ast.Comment, 0, len(docs.List))
|
||||
for _, comment := range docs.List {
|
||||
if isMarkerComment(comment.Text) {
|
||||
continue
|
||||
}
|
||||
outGroup.List = append(outGroup.List, comment)
|
||||
}
|
||||
|
||||
// split lines, and re-join together as a single
|
||||
// paragraph, respecting double-newlines as
|
||||
// paragraph markers.
|
||||
outLines := strings.Split(outGroup.Text(), "\n")
|
||||
if outLines[len(outLines)-1] == "" {
|
||||
// chop off the extraneous last part
|
||||
outLines = outLines[:len(outLines)-1]
|
||||
}
|
||||
// respect double-newline meaning actual newline
|
||||
for i, line := range outLines {
|
||||
if line == "" {
|
||||
outLines[i] = "\n"
|
||||
}
|
||||
}
|
||||
return strings.Join(outLines, " ")
|
||||
}
|
||||
|
||||
// PackageMarkers collects all the package-level marker values for the given package.
|
||||
func PackageMarkers(col *Collector, pkg *loader.Package) (MarkerValues, error) {
|
||||
markers, err := col.MarkersInPackage(pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make(MarkerValues)
|
||||
for _, file := range pkg.Syntax {
|
||||
fileMarkers := markers[file]
|
||||
for name, vals := range fileMarkers {
|
||||
res[name] = append(res[name], vals...)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FieldInfo contains marker values and commonly used information for a struct field.
|
||||
type FieldInfo struct {
|
||||
// Name is the name of the field (or "" for embedded fields)
|
||||
Name string
|
||||
// Doc is the Godoc of the field, pre-processed to remove markers and joine
|
||||
// single newlines together.
|
||||
Doc string
|
||||
// Tag struct tag associated with this field (or "" if non existed).
|
||||
Tag reflect.StructTag
|
||||
|
||||
// Markers are all registered markers associated with this field.
|
||||
Markers MarkerValues
|
||||
|
||||
// RawField is the raw, underlying field AST object that this field represents.
|
||||
RawField *ast.Field
|
||||
}
|
||||
|
||||
// TypeInfo contains marker values and commonly used information for a type declaration.
|
||||
type TypeInfo struct {
|
||||
// Name is the name of the type.
|
||||
Name string
|
||||
// Doc is the Godoc of the type, pre-processed to remove markers and joine
|
||||
// single newlines together.
|
||||
Doc string
|
||||
|
||||
// Markers are all registered markers associated with the type.
|
||||
Markers MarkerValues
|
||||
|
||||
// Fields are all the fields associated with the type, if it's a struct.
|
||||
// (if not, Fields will be nil).
|
||||
Fields []FieldInfo
|
||||
|
||||
// RawDecl contains the raw GenDecl that the type was declared as part of.
|
||||
RawDecl *ast.GenDecl
|
||||
// RawSpec contains the raw Spec that declared this type.
|
||||
RawSpec *ast.TypeSpec
|
||||
// RawFile contains the file in which this type was declared.
|
||||
RawFile *ast.File
|
||||
}
|
||||
|
||||
// TypeCallback is a callback called for each type declaration in a package.
|
||||
type TypeCallback func(info *TypeInfo)
|
||||
|
||||
// EachType collects all markers, then calls the given callback for each type declaration in a package.
|
||||
// Each individual spec is considered separate, so
|
||||
//
|
||||
// type (
|
||||
// Foo string
|
||||
// Bar int
|
||||
// Baz struct{}
|
||||
// )
|
||||
//
|
||||
// yields three calls to the callback.
|
||||
func EachType(col *Collector, pkg *loader.Package, cb TypeCallback) error {
|
||||
markers, err := col.MarkersInPackage(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loader.EachType(pkg, func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec) {
|
||||
var fields []FieldInfo
|
||||
if structSpec, isStruct := spec.Type.(*ast.StructType); isStruct {
|
||||
for _, field := range structSpec.Fields.List {
|
||||
for _, name := range field.Names {
|
||||
fields = append(fields, FieldInfo{
|
||||
Name: name.Name,
|
||||
Doc: extractDoc(field, nil),
|
||||
Tag: loader.ParseAstTag(field.Tag),
|
||||
Markers: markers[field],
|
||||
RawField: field,
|
||||
})
|
||||
}
|
||||
if field.Names == nil {
|
||||
fields = append(fields, FieldInfo{
|
||||
Doc: extractDoc(field, nil),
|
||||
Tag: loader.ParseAstTag(field.Tag),
|
||||
Markers: markers[field],
|
||||
RawField: field,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cb(&TypeInfo{
|
||||
Name: spec.Name.Name,
|
||||
Markers: markers[spec],
|
||||
Doc: extractDoc(spec, decl),
|
||||
Fields: fields,
|
||||
RawDecl: decl,
|
||||
RawSpec: spec,
|
||||
RawFile: file,
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user