| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | package codegen | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"text/template" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"cuelang.org/go/cue/cuecontext" | 
					
						
							|  |  |  | 	"github.com/grafana/codejen" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/components/simplejson" | 
					
						
							| 
									
										
										
										
											2023-02-01 08:40:15 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/kindsys" | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	"github.com/grafana/thema/encoding/jsonschema" | 
					
						
							|  |  |  | 	"github.com/olekukonko/tablewriter" | 
					
						
							|  |  |  | 	"github.com/xeipuuv/gojsonpointer" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func DocsJenny(docsPath string) OneToOne { | 
					
						
							|  |  |  | 	return docsJenny{ | 
					
						
							|  |  |  | 		docsPath: docsPath, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type docsJenny struct { | 
					
						
							|  |  |  | 	docsPath string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (j docsJenny) JennyName() string { | 
					
						
							|  |  |  | 	return "DocsJenny" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 08:40:15 +08:00
										 |  |  | func (j docsJenny) Generate(kind kindsys.Kind) (*codejen.File, error) { | 
					
						
							|  |  |  | 	// TODO remove this once codejen catches nils https://github.com/grafana/codejen/issues/5
 | 
					
						
							|  |  |  | 	if kind == nil { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := jsonschema.GenerateSchema(kind.Lineage().Latest()) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("failed to generate json representation for the schema: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	b, err := cuecontext.New().BuildFile(f).MarshalJSON() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("failed to marshal schema value to json: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// We don't need entire json obj, only the value of components.schemas path
 | 
					
						
							|  |  |  | 	var obj struct { | 
					
						
							| 
									
										
										
										
											2023-01-23 07:58:49 +08:00
										 |  |  | 		Info struct { | 
					
						
							|  |  |  | 			Title string | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 		Components struct { | 
					
						
							|  |  |  | 			Schemas json.RawMessage | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-10 19:40:50 +08:00
										 |  |  | 	dec := json.NewDecoder(bytes.NewReader(b)) | 
					
						
							|  |  |  | 	dec.UseNumber() | 
					
						
							|  |  |  | 	err = dec.Decode(&obj) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("failed to unmarshal schema json: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// fixes the references between the types within a json after making components.schema.<types> the root of the json
 | 
					
						
							|  |  |  | 	kindJsonStr := strings.Replace(string(obj.Components.Schemas), "#/components/schemas/", "#/", -1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 08:40:15 +08:00
										 |  |  | 	kindProps := kind.Props().Common() | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	data := templateData{ | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 		KindName:        kindProps.Name, | 
					
						
							|  |  |  | 		KindVersion:     kind.Lineage().Latest().Version().String(), | 
					
						
							| 
									
										
										
										
											2023-02-07 18:02:05 +08:00
										 |  |  | 		KindMaturity:    fmt.Sprintf("[%s](../../../maturity/#%[1]s)", kindProps.Maturity), | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 		KindDescription: kindProps.Description, | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 		Markdown:        "{{ .Markdown }}", | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tmpl, err := makeTemplate(data, "docs.tmpl") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-23 07:58:49 +08:00
										 |  |  | 	doc, err := jsonToMarkdown([]byte(kindJsonStr), string(tmpl), obj.Info.Title) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-01-23 07:58:49 +08:00
										 |  |  | 		return nil, fmt.Errorf("failed to build markdown for kind %s: %v", kindProps.Name, err) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-23 07:58:49 +08:00
										 |  |  | 	return codejen.NewFile(filepath.Join(j.docsPath, strings.ToLower(kindProps.Name), "schema-reference.md"), doc, j), nil | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // makeTemplate pre-populates the template with the kind metadata
 | 
					
						
							|  |  |  | func makeTemplate(data templateData, tmpl string) ([]byte, error) { | 
					
						
							|  |  |  | 	buf := new(bytes.Buffer) | 
					
						
							|  |  |  | 	if err := tmpls.Lookup(tmpl).Execute(buf, data); err != nil { | 
					
						
							|  |  |  | 		return []byte{}, fmt.Errorf("failed to populate docs template with the kind metadata") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return buf.Bytes(), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type templateData struct { | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	KindName        string | 
					
						
							|  |  |  | 	KindVersion     string | 
					
						
							|  |  |  | 	KindMaturity    string | 
					
						
							|  |  |  | 	KindDescription string | 
					
						
							|  |  |  | 	Markdown        string | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // -------------------- JSON to Markdown conversion --------------------
 | 
					
						
							|  |  |  | // Copied from https://github.com/marcusolsson/json-schema-docs and slightly changed to fit the DocsJenny
 | 
					
						
							| 
									
										
										
										
											2023-02-10 19:40:50 +08:00
										 |  |  | type constraints struct { | 
					
						
							|  |  |  | 	Pattern          string      `json:"pattern"` | 
					
						
							|  |  |  | 	Maximum          json.Number `json:"maximum"` | 
					
						
							|  |  |  | 	ExclusiveMinimum bool        `json:"exclusiveMinimum"` | 
					
						
							|  |  |  | 	Minimum          json.Number `json:"minimum"` | 
					
						
							|  |  |  | 	ExclusiveMaximum bool        `json:"exclusiveMaximum"` | 
					
						
							|  |  |  | 	MinLength        uint        `json:"minLength"` | 
					
						
							|  |  |  | 	MaxLength        uint        `json:"maxLength"` | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | type schema struct { | 
					
						
							| 
									
										
										
										
											2023-02-10 19:40:50 +08:00
										 |  |  | 	constraints | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | 	ID                   string             `json:"$id,omitempty"` | 
					
						
							|  |  |  | 	Ref                  string             `json:"$ref,omitempty"` | 
					
						
							|  |  |  | 	Schema               string             `json:"$schema,omitempty"` | 
					
						
							|  |  |  | 	Title                string             `json:"title,omitempty"` | 
					
						
							|  |  |  | 	Description          string             `json:"description,omitempty"` | 
					
						
							|  |  |  | 	Required             []string           `json:"required,omitempty"` | 
					
						
							|  |  |  | 	Type                 PropertyTypes      `json:"type,omitempty"` | 
					
						
							|  |  |  | 	Properties           map[string]*schema `json:"properties,omitempty"` | 
					
						
							|  |  |  | 	Items                *schema            `json:"items,omitempty"` | 
					
						
							|  |  |  | 	Definitions          map[string]*schema `json:"definitions,omitempty"` | 
					
						
							|  |  |  | 	Enum                 []Any              `json:"enum"` | 
					
						
							|  |  |  | 	Default              any                `json:"default"` | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	AllOf                []*schema          `json:"allOf"` | 
					
						
							|  |  |  | 	AdditionalProperties *schema            `json:"additionalProperties"` | 
					
						
							|  |  |  | 	extends              []string           `json:"-"` | 
					
						
							|  |  |  | 	inheritedFrom        string             `json:"-"` | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func renderMapType(props *schema) string { | 
					
						
							|  |  |  | 	if props == nil { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if props.Type.HasType(PropertyTypeObject) { | 
					
						
							|  |  |  | 		name, anchor := propNameAndAnchor(props.Title, props.Title) | 
					
						
							|  |  |  | 		return fmt.Sprintf("[%s](#%s)", name, anchor) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if props.AdditionalProperties != nil { | 
					
						
							|  |  |  | 		return "map[string]" + renderMapType(props.AdditionalProperties) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if props.Items != nil { | 
					
						
							|  |  |  | 		return "[]" + renderMapType(props.Items) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var types []string | 
					
						
							|  |  |  | 	for _, t := range props.Type { | 
					
						
							|  |  |  | 		types = append(types, string(t)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return strings.Join(types, ", ") | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func jsonToMarkdown(jsonData []byte, tpl string, kindName string) ([]byte, error) { | 
					
						
							|  |  |  | 	sch, err := newSchema(jsonData, kindName) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return []byte{}, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t, err := template.New("markdown").Parse(tpl) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return []byte{}, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	buf := new(bytes.Buffer) | 
					
						
							|  |  |  | 	err = t.Execute(buf, sch) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return []byte{}, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return buf.Bytes(), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newSchema(b []byte, kindName string) (*schema, error) { | 
					
						
							|  |  |  | 	var data map[string]*schema | 
					
						
							|  |  |  | 	if err := json.Unmarshal(b, &data); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Needed for resolving in-schema references.
 | 
					
						
							|  |  |  | 	root, err := simplejson.NewJson(b) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return resolveSchema(data[kindName], root) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // resolveSchema recursively resolves schemas.
 | 
					
						
							|  |  |  | func resolveSchema(schem *schema, root *simplejson.Json) (*schema, error) { | 
					
						
							|  |  |  | 	for _, prop := range schem.Properties { | 
					
						
							|  |  |  | 		if prop.Ref != "" { | 
					
						
							|  |  |  | 			tmp, err := resolveReference(prop.Ref, root) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			*prop = *tmp | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		foo, err := resolveSchema(prop, root) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*prop = *foo | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if schem.Items != nil { | 
					
						
							|  |  |  | 		if schem.Items.Ref != "" { | 
					
						
							|  |  |  | 			tmp, err := resolveReference(schem.Items.Ref, root) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			*schem.Items = *tmp | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		foo, err := resolveSchema(schem.Items, root) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*schem.Items = *foo | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	if len(schem.AllOf) > 0 { | 
					
						
							|  |  |  | 		for idx, child := range schem.AllOf { | 
					
						
							|  |  |  | 			tmp, err := resolveSubSchema(schem, child, root) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			schem.AllOf[idx] = tmp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if len(tmp.Title) > 0 { | 
					
						
							|  |  |  | 				schem.extends = append(schem.extends, tmp.Title) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | 	if schem.AdditionalProperties != nil { | 
					
						
							|  |  |  | 		if schem.AdditionalProperties.Ref != "" { | 
					
						
							|  |  |  | 			tmp, err := resolveReference(schem.AdditionalProperties.Ref, root) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			*schem.AdditionalProperties = *tmp | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		foo, err := resolveSchema(schem.AdditionalProperties, root) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*schem.AdditionalProperties = *foo | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	return schem, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | func resolveSubSchema(parent, child *schema, root *simplejson.Json) (*schema, error) { | 
					
						
							|  |  |  | 	if child.Ref != "" { | 
					
						
							|  |  |  | 		tmp, err := resolveReference(child.Ref, root) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*child = *tmp | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(child.Required) > 0 { | 
					
						
							|  |  |  | 		parent.Required = append(parent.Required, child.Required...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	child, err := resolveSchema(child, root) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if parent.Properties == nil { | 
					
						
							|  |  |  | 		parent.Properties = make(map[string]*schema) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for k, v := range child.Properties { | 
					
						
							|  |  |  | 		prop := *v | 
					
						
							|  |  |  | 		prop.inheritedFrom = child.Title | 
					
						
							|  |  |  | 		parent.Properties[k] = &prop | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return child, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | // resolveReference loads a schema from a $ref.
 | 
					
						
							|  |  |  | // If ref contains a hashtag (#), the part after represents a in-schema reference.
 | 
					
						
							|  |  |  | func resolveReference(ref string, root *simplejson.Json) (*schema, error) { | 
					
						
							|  |  |  | 	i := strings.Index(ref, "#") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if i != 0 { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("not in-schema reference: %s", ref) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return resolveInSchemaReference(ref[i+1:], root) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func resolveInSchemaReference(ref string, root *simplejson.Json) (*schema, error) { | 
					
						
							|  |  |  | 	// in-schema reference
 | 
					
						
							|  |  |  | 	pointer, err := gojsonpointer.NewJsonPointer(ref) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	v, _, err := pointer.Get(root.MustMap()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var sch schema | 
					
						
							|  |  |  | 	b, err := json.Marshal(v) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := json.Unmarshal(b, &sch); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set the ref name as title
 | 
					
						
							|  |  |  | 	sch.Title = path.Base(ref) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &sch, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | type mdSection struct { | 
					
						
							|  |  |  | 	title       string | 
					
						
							|  |  |  | 	extends     string | 
					
						
							|  |  |  | 	description string | 
					
						
							|  |  |  | 	rows        [][]string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (md mdSection) write(w io.Writer) { | 
					
						
							|  |  |  | 	if md.title != "" { | 
					
						
							|  |  |  | 		fmt.Fprintf(w, "### %s\n", strings.Title(md.title)) | 
					
						
							|  |  |  | 		fmt.Fprintln(w) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if md.description != "" { | 
					
						
							|  |  |  | 		fmt.Fprintln(w, md.description) | 
					
						
							|  |  |  | 		fmt.Fprintln(w) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if md.extends != "" { | 
					
						
							|  |  |  | 		fmt.Fprintln(w, md.extends) | 
					
						
							|  |  |  | 		fmt.Fprintln(w) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	table := tablewriter.NewWriter(w) | 
					
						
							|  |  |  | 	table.SetHeader([]string{"Property", "Type", "Required", "Description"}) | 
					
						
							|  |  |  | 	table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) | 
					
						
							|  |  |  | 	table.SetCenterSeparator("|") | 
					
						
							|  |  |  | 	table.SetAutoFormatHeaders(false) | 
					
						
							|  |  |  | 	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) | 
					
						
							|  |  |  | 	table.SetAutoWrapText(false) | 
					
						
							|  |  |  | 	table.AppendBulk(md.rows) | 
					
						
							|  |  |  | 	table.Render() | 
					
						
							|  |  |  | 	fmt.Fprintln(w) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | // Markdown returns the Markdown representation of the schema.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // The level argument can be used to offset the heading levels. This can be
 | 
					
						
							|  |  |  | // useful if you want to add the schema under a subheading.
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | func (s *schema) Markdown() string { | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	buf := new(bytes.Buffer) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	for _, v := range s.sections() { | 
					
						
							|  |  |  | 		v.write(buf) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	return buf.String() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *schema) sections() []mdSection { | 
					
						
							|  |  |  | 	md := mdSection{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if s.AdditionalProperties == nil { | 
					
						
							|  |  |  | 		md.title = s.Title | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	md.description = s.Description | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	if len(s.extends) > 0 { | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 		md.extends = makeExtends(s.extends) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	md.rows = makeRows(s) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	sections := []mdSection{md} | 
					
						
							|  |  |  | 	for _, sch := range findDefinitions(s) { | 
					
						
							|  |  |  | 		for _, ss := range sch.sections() { | 
					
						
							|  |  |  | 			if !contains(sections, ss) { | 
					
						
							|  |  |  | 				sections = append(sections, ss) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	return sections | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | func contains(sl []mdSection, elem mdSection) bool { | 
					
						
							|  |  |  | 	for _, s := range sl { | 
					
						
							|  |  |  | 		if reflect.DeepEqual(s, elem) { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	return false | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | func makeExtends(from []string) string { | 
					
						
							|  |  |  | 	fromLinks := make([]string, 0, len(from)) | 
					
						
							|  |  |  | 	for _, f := range from { | 
					
						
							|  |  |  | 		fromLinks = append(fromLinks, fmt.Sprintf("[%s](#%s)", f, strings.ToLower(f))) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	return fmt.Sprintf("It extends %s.", strings.Join(fromLinks, " and ")) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func findDefinitions(s *schema) []*schema { | 
					
						
							|  |  |  | 	// Gather all properties of object type so that we can generate the
 | 
					
						
							|  |  |  | 	// properties for them recursively.
 | 
					
						
							|  |  |  | 	var objs []*schema | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | 	definition := func(k string, p *schema) { | 
					
						
							|  |  |  | 		if p.Type.HasType(PropertyTypeObject) && p.AdditionalProperties == nil { | 
					
						
							|  |  |  | 			// Use the identifier as the title.
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 			if len(p.Title) == 0 { | 
					
						
							|  |  |  | 				p.Title = k | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			objs = append(objs, p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// If the property is an array of objects, use the name of the array
 | 
					
						
							|  |  |  | 		// property as the title.
 | 
					
						
							|  |  |  | 		if p.Type.HasType(PropertyTypeArray) { | 
					
						
							|  |  |  | 			if p.Items != nil { | 
					
						
							|  |  |  | 				if p.Items.Type.HasType(PropertyTypeObject) { | 
					
						
							|  |  |  | 					if len(p.Items.Title) == 0 { | 
					
						
							|  |  |  | 						p.Items.Title = k | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					objs = append(objs, p.Items) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | 	for k, p := range s.Properties { | 
					
						
							|  |  |  | 		// If a property has AdditionalProperties, then it's a map
 | 
					
						
							|  |  |  | 		if p.AdditionalProperties != nil { | 
					
						
							|  |  |  | 			definition(k, p.AdditionalProperties) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		definition(k, p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	// This code could probably be unified with the one above
 | 
					
						
							|  |  |  | 	for _, child := range s.AllOf { | 
					
						
							|  |  |  | 		if child.Type.HasType(PropertyTypeObject) { | 
					
						
							|  |  |  | 			objs = append(objs, child) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if child.Type.HasType(PropertyTypeArray) { | 
					
						
							|  |  |  | 			if child.Items != nil { | 
					
						
							|  |  |  | 				if child.Items.Type.HasType(PropertyTypeObject) { | 
					
						
							|  |  |  | 					objs = append(objs, child.Items) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	// Sort the object schemas.
 | 
					
						
							|  |  |  | 	sort.Slice(objs, func(i, j int) bool { | 
					
						
							|  |  |  | 		return objs[i].Title < objs[j].Title | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return objs | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | func makeRows(s *schema) [][]string { | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	// Buffer all property rows so that we can sort them before printing them.
 | 
					
						
							| 
									
										
										
										
											2023-01-24 00:44:27 +08:00
										 |  |  | 	rows := make([][]string, 0, len(s.Properties)) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	for key, p := range s.Properties { | 
					
						
							| 
									
										
										
										
											2023-02-10 19:40:50 +08:00
										 |  |  | 		alias := propTypeAlias(p) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var typeStr string | 
					
						
							|  |  |  | 		if alias != "" { | 
					
						
							|  |  |  | 			typeStr = alias | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			typeStr = propTypeStr(key, p) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Emphasize required properties.
 | 
					
						
							|  |  |  | 		var required string | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | 		if in(s.Required, key) { | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 			required = "**Yes**" | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			required = "No" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 		var desc string | 
					
						
							|  |  |  | 		if p.inheritedFrom != "" { | 
					
						
							|  |  |  | 			desc = fmt.Sprintf("*(Inherited from [%s](#%s))*", p.inheritedFrom, strings.ToLower(p.inheritedFrom)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if p.Description != "" { | 
					
						
							|  |  |  | 			desc += "\n" + p.Description | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 		if len(p.Enum) > 0 { | 
					
						
							|  |  |  | 			vals := make([]string, 0, len(p.Enum)) | 
					
						
							|  |  |  | 			for _, e := range p.Enum { | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 				vals = append(vals, e.String()) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 			desc += "\nPossible values are: `" + strings.Join(vals, "`, `") + "`." | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 		if p.Default != nil { | 
					
						
							|  |  |  | 			desc += fmt.Sprintf(" Default: `%v`.", p.Default) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-10 19:40:50 +08:00
										 |  |  | 		// Render a constraint only if it's not a type alias https://cuelang.org/docs/references/spec/#predeclared-identifiers
 | 
					
						
							|  |  |  | 		if alias == "" { | 
					
						
							|  |  |  | 			desc += constraintDescr(p) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | 		rows = append(rows, []string{fmt.Sprintf("`%s`", key), typeStr, required, formatForTable(desc)}) | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 23:12:31 +08:00
										 |  |  | 	// Sort by the required column, then by the name column.
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | 	sort.Slice(rows, func(i, j int) bool { | 
					
						
							|  |  |  | 		if rows[i][2] < rows[j][2] { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if rows[i][2] > rows[j][2] { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return rows[i][0] < rows[j][0] | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2023-02-08 19:07:00 +08:00
										 |  |  | 	return rows | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-10 19:40:50 +08:00
										 |  |  | func propTypeAlias(prop *schema) string { | 
					
						
							|  |  |  | 	if prop.Minimum == "" || prop.Maximum == "" { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	min := prop.Minimum | 
					
						
							|  |  |  | 	max := prop.Maximum | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case min == "0" && max == "255": | 
					
						
							|  |  |  | 		return "uint8" | 
					
						
							|  |  |  | 	case min == "0" && max == "65535": | 
					
						
							|  |  |  | 		return "uint16" | 
					
						
							|  |  |  | 	case min == "0" && max == "4294967295": | 
					
						
							|  |  |  | 		return "uint32" | 
					
						
							|  |  |  | 	case min == "0" && max == "18446744073709551615": | 
					
						
							|  |  |  | 		return "uint64" | 
					
						
							|  |  |  | 	case min == "-128" && max == "127": | 
					
						
							|  |  |  | 		return "int8" | 
					
						
							|  |  |  | 	case min == "-32768" && max == "32767": | 
					
						
							|  |  |  | 		return "int16" | 
					
						
							|  |  |  | 	case min == "-2147483648" && max == "2147483647": | 
					
						
							|  |  |  | 		return "int32" | 
					
						
							|  |  |  | 	case min == "-9223372036854775808" && max == "9223372036854775807": | 
					
						
							|  |  |  | 		return "int64" | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func constraintDescr(prop *schema) string { | 
					
						
							|  |  |  | 	if prop.Minimum != "" && prop.Maximum != "" { | 
					
						
							|  |  |  | 		var left, right string | 
					
						
							|  |  |  | 		if prop.ExclusiveMinimum { | 
					
						
							|  |  |  | 			left = ">" + prop.Minimum.String() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			left = ">=" + prop.Minimum.String() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if prop.ExclusiveMaximum { | 
					
						
							|  |  |  | 			right = "<" + prop.Maximum.String() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			right = "<=" + prop.Maximum.String() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return fmt.Sprintf("\nConstraint: `%s & %s`.", left, right) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if prop.MinLength > 0 { | 
					
						
							|  |  |  | 		left := fmt.Sprintf(">=%v", prop.MinLength) | 
					
						
							|  |  |  | 		right := "" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if prop.MaxLength > 0 { | 
					
						
							|  |  |  | 			right = fmt.Sprintf(" && <=%v", prop.MaxLength) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return fmt.Sprintf("\nConstraint: `length %s`.", left+right) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if prop.Pattern != "" { | 
					
						
							|  |  |  | 		return fmt.Sprintf("\nConstraint: must match `%s`.", prop.Pattern) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 20:02:29 +08:00
										 |  |  | func propTypeStr(propName string, propValue *schema) string { | 
					
						
							|  |  |  | 	// If the property has AdditionalProperties, it is most likely a map type
 | 
					
						
							|  |  |  | 	if propValue.AdditionalProperties != nil { | 
					
						
							|  |  |  | 		mapValue := renderMapType(propValue.AdditionalProperties) | 
					
						
							|  |  |  | 		return "map[string]" + mapValue | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	propType := make([]string, 0, len(propValue.Type)) | 
					
						
							|  |  |  | 	// Generate relative links for objects and arrays of objects.
 | 
					
						
							|  |  |  | 	for _, pt := range propValue.Type { | 
					
						
							|  |  |  | 		switch pt { | 
					
						
							|  |  |  | 		case PropertyTypeObject: | 
					
						
							|  |  |  | 			name, anchor := propNameAndAnchor(propName, propValue.Title) | 
					
						
							|  |  |  | 			propType = append(propType, fmt.Sprintf("[%s](#%s)", name, anchor)) | 
					
						
							|  |  |  | 		case PropertyTypeArray: | 
					
						
							|  |  |  | 			if propValue.Items != nil { | 
					
						
							|  |  |  | 				for _, pi := range propValue.Items.Type { | 
					
						
							|  |  |  | 					if pi == PropertyTypeObject { | 
					
						
							|  |  |  | 						name, anchor := propNameAndAnchor(propName, propValue.Items.Title) | 
					
						
							|  |  |  | 						propType = append(propType, fmt.Sprintf("[%s](#%s)[]", name, anchor)) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						propType = append(propType, fmt.Sprintf("%s[]", pi)) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				propType = append(propType, string(pt)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			propType = append(propType, string(pt)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(propType) == 0 { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(propType) == 1 { | 
					
						
							|  |  |  | 		return propType[0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(propType) == 2 { | 
					
						
							|  |  |  | 		return strings.Join(propType, " or ") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return fmt.Sprintf("%s, or %s", strings.Join(propType[:len(propType)-1], ", "), propType[len(propType)-1]) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 20:55:40 +08:00
										 |  |  | func propNameAndAnchor(prop, title string) (string, string) { | 
					
						
							|  |  |  | 	if len(title) > 0 { | 
					
						
							|  |  |  | 		return title, strings.ToLower(title) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return string(PropertyTypeObject), strings.ToLower(prop) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // in returns true if a string slice contains a specific string.
 | 
					
						
							|  |  |  | func in(strs []string, str string) bool { | 
					
						
							|  |  |  | 	for _, s := range strs { | 
					
						
							|  |  |  | 		if s == str { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // formatForTable returns string usable in a Markdown table.
 | 
					
						
							|  |  |  | // It trims white spaces, replaces new lines and pipe characters.
 | 
					
						
							|  |  |  | func formatForTable(in string) string { | 
					
						
							|  |  |  | 	s := strings.TrimSpace(in) | 
					
						
							|  |  |  | 	s = strings.ReplaceAll(s, "\n", "<br/>") | 
					
						
							|  |  |  | 	s = strings.ReplaceAll(s, "|", "|") | 
					
						
							|  |  |  | 	return s | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type PropertyTypes []PropertyType | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (pts *PropertyTypes) HasType(pt PropertyType) bool { | 
					
						
							|  |  |  | 	for _, t := range *pts { | 
					
						
							|  |  |  | 		if t == pt { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (pts *PropertyTypes) UnmarshalJSON(data []byte) error { | 
					
						
							|  |  |  | 	var value interface{} | 
					
						
							|  |  |  | 	if err := json.Unmarshal(data, &value); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch val := value.(type) { | 
					
						
							|  |  |  | 	case string: | 
					
						
							|  |  |  | 		*pts = []PropertyType{PropertyType(val)} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	case []interface{}: | 
					
						
							|  |  |  | 		var pt []PropertyType | 
					
						
							|  |  |  | 		for _, t := range val { | 
					
						
							|  |  |  | 			s, ok := t.(string) | 
					
						
							|  |  |  | 			if !ok { | 
					
						
							|  |  |  | 				return errors.New("unsupported property type") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			pt = append(pt, PropertyType(s)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*pts = pt | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return errors.New("unsupported property type") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type PropertyType string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	PropertyTypeString  PropertyType = "string" | 
					
						
							|  |  |  | 	PropertyTypeNumber  PropertyType = "number" | 
					
						
							|  |  |  | 	PropertyTypeBoolean PropertyType = "boolean" | 
					
						
							|  |  |  | 	PropertyTypeObject  PropertyType = "object" | 
					
						
							|  |  |  | 	PropertyTypeArray   PropertyType = "array" | 
					
						
							|  |  |  | 	PropertyTypeNull    PropertyType = "null" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Any struct { | 
					
						
							|  |  |  | 	value interface{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (u *Any) UnmarshalJSON(data []byte) error { | 
					
						
							|  |  |  | 	if err := json.Unmarshal(data, &u.value); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (u *Any) String() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("%v", u.value) | 
					
						
							|  |  |  | } |