github.com/Jeffail/benthos/v3@v3.65.0/internal/docs/component.go (about) 1 package docs 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "strings" 8 "text/template" 9 10 "github.com/Jeffail/benthos/v3/lib/util/config" 11 "gopkg.in/yaml.v3" 12 ) 13 14 // AnnotatedExample is an isolated example for a component. 15 type AnnotatedExample struct { 16 // A title for the example. 17 Title string `json:"title"` 18 19 // Summary of the example. 20 Summary string `json:"summary"` 21 22 // A config snippet to show. 23 Config string `json:"config"` 24 } 25 26 // Status of a component. 27 type Status string 28 29 // Component statuses. 30 var ( 31 StatusStable Status = "stable" 32 StatusBeta Status = "beta" 33 StatusExperimental Status = "experimental" 34 StatusDeprecated Status = "deprecated" 35 ) 36 37 // Type of a component. 38 type Type string 39 40 // Component types. 41 var ( 42 TypeBuffer Type = "buffer" 43 TypeCache Type = "cache" 44 TypeInput Type = "input" 45 TypeMetrics Type = "metrics" 46 TypeOutput Type = "output" 47 TypeProcessor Type = "processor" 48 TypeRateLimit Type = "rate_limit" 49 TypeTracer Type = "tracer" 50 ) 51 52 // Types returns a slice containing all component types. 53 func Types() []Type { 54 return []Type{ 55 TypeBuffer, 56 TypeCache, 57 TypeInput, 58 TypeMetrics, 59 TypeOutput, 60 TypeProcessor, 61 TypeRateLimit, 62 TypeTracer, 63 } 64 } 65 66 // ComponentSpec describes a Benthos component. 67 type ComponentSpec struct { 68 // Name of the component 69 Name string `json:"name"` 70 71 // Type of the component (input, output, etc) 72 Type Type `json:"type"` 73 74 // The status of the component. 75 Status Status `json:"status"` 76 77 // Plugin is true for all plugin components. 78 Plugin bool `json:"plugin"` 79 80 // Summary of the component (in markdown, must be short). 81 Summary string `json:"summary,omitempty"` 82 83 // Description of the component (in markdown). 84 Description string `json:"description,omitempty"` 85 86 // Categories that describe the purpose of the component. 87 Categories []string `json:"categories"` 88 89 // Footnotes of the component (in markdown). 90 Footnotes string `json:"footnotes,omitempty"` 91 92 // Examples demonstrating use cases for the component. 93 Examples []AnnotatedExample `json:"examples,omitempty"` 94 95 // A summary of each field in the component configuration. 96 Config FieldSpec `json:"config"` 97 98 // Version is the Benthos version this component was introduced. 99 Version string `json:"version,omitempty"` 100 } 101 102 type componentContext struct { 103 Name string 104 Type string 105 FrontMatterSummary string 106 Summary string 107 Description string 108 Categories string 109 Examples []AnnotatedExample 110 Fields []FieldSpecCtx 111 Footnotes string 112 CommonConfig string 113 AdvancedConfig string 114 Status string 115 Version string 116 } 117 118 var componentTemplate = FieldsTemplate(true) + `--- 119 title: {{.Name}} 120 type: {{.Type}} 121 status: {{.Status}} 122 {{if gt (len .FrontMatterSummary) 0 -}} 123 description: "{{.FrontMatterSummary}}" 124 {{end -}} 125 {{if gt (len .Categories) 0 -}} 126 categories: {{.Categories}} 127 {{end -}} 128 --- 129 130 <!-- 131 THIS FILE IS AUTOGENERATED! 132 133 To make changes please edit the contents of: 134 lib/{{.Type}}/{{.Name}}.go 135 --> 136 137 import Tabs from '@theme/Tabs'; 138 import TabItem from '@theme/TabItem'; 139 140 {{if eq .Status "beta" -}} 141 :::caution BETA 142 This component is mostly stable but breaking changes could still be made outside of major version releases if a fundamental problem with the component is found. 143 ::: 144 {{end -}} 145 {{if eq .Status "experimental" -}} 146 :::caution EXPERIMENTAL 147 This component is experimental and therefore subject to change or removal outside of major version releases. 148 ::: 149 {{end -}} 150 {{if eq .Status "deprecated" -}} 151 :::warning DEPRECATED 152 This component is deprecated and will be removed in the next major version release. Please consider moving onto [alternative components](#alternatives). 153 ::: 154 {{end -}} 155 156 {{if gt (len .Summary) 0 -}} 157 {{.Summary}} 158 {{end -}}{{if gt (len .Version) 0}} 159 Introduced in version {{.Version}}. 160 {{end}} 161 {{if eq .CommonConfig .AdvancedConfig -}} 162 ` + "```yaml" + ` 163 # Config fields, showing default values 164 {{.CommonConfig -}} 165 ` + "```" + ` 166 {{else}} 167 <Tabs defaultValue="common" values={{"{"}}[ 168 { label: 'Common', value: 'common', }, 169 { label: 'Advanced', value: 'advanced', }, 170 ]{{"}"}}> 171 172 <TabItem value="common"> 173 174 ` + "```yaml" + ` 175 # Common config fields, showing default values 176 {{.CommonConfig -}} 177 ` + "```" + ` 178 179 </TabItem> 180 <TabItem value="advanced"> 181 182 ` + "```yaml" + ` 183 # All config fields, showing default values 184 {{.AdvancedConfig -}} 185 ` + "```" + ` 186 187 </TabItem> 188 </Tabs> 189 {{end -}} 190 {{if gt (len .Description) 0}} 191 {{.Description}} 192 {{end}} 193 {{if and (le (len .Fields) 4) (gt (len .Fields) 0) -}} 194 ## Fields 195 196 {{template "field_docs" . -}} 197 {{end -}} 198 199 {{if gt (len .Examples) 0 -}} 200 ## Examples 201 202 <Tabs defaultValue="{{ (index .Examples 0).Title }}" values={{"{"}}[ 203 {{range $i, $example := .Examples -}} 204 { label: '{{$example.Title}}', value: '{{$example.Title}}', }, 205 {{end -}} 206 ]{{"}"}}> 207 208 {{range $i, $example := .Examples -}} 209 <TabItem value="{{$example.Title}}"> 210 211 {{if gt (len $example.Summary) 0 -}} 212 {{$example.Summary}} 213 {{end}} 214 {{if gt (len $example.Config) 0 -}} 215 ` + "```yaml" + `{{$example.Config}}` + "```" + ` 216 {{end}} 217 </TabItem> 218 {{end -}} 219 </Tabs> 220 221 {{end -}} 222 223 {{if gt (len .Fields) 4 -}} 224 ## Fields 225 226 {{template "field_docs" . -}} 227 {{end -}} 228 229 {{if gt (len .Footnotes) 0 -}} 230 {{.Footnotes}} 231 {{end}} 232 ` 233 234 func createOrderedConfig(t Type, rawExample interface{}, filter FieldFilter) (*yaml.Node, error) { 235 var newNode yaml.Node 236 if err := newNode.Encode(rawExample); err != nil { 237 return nil, err 238 } 239 240 if err := SanitiseYAML(t, &newNode, SanitiseConfig{ 241 RemoveTypeField: true, 242 Filter: filter, 243 ForExample: true, 244 }); err != nil { 245 return nil, err 246 } 247 248 return &newNode, nil 249 } 250 251 func genExampleConfigs(t Type, nest bool, fullConfigExample interface{}) (commonConfigStr, advConfigStr string, err error) { 252 var advConfig, commonConfig interface{} 253 if advConfig, err = createOrderedConfig(t, fullConfigExample, func(f FieldSpec) bool { 254 return !f.IsDeprecated 255 }); err != nil { 256 panic(err) 257 } 258 if commonConfig, err = createOrderedConfig(t, fullConfigExample, func(f FieldSpec) bool { 259 return !f.IsAdvanced && !f.IsDeprecated 260 }); err != nil { 261 panic(err) 262 } 263 264 if nest { 265 advConfig = map[string]interface{}{string(t): advConfig} 266 commonConfig = map[string]interface{}{string(t): commonConfig} 267 } 268 269 advancedConfigBytes, err := config.MarshalYAML(advConfig) 270 if err != nil { 271 panic(err) 272 } 273 commonConfigBytes, err := config.MarshalYAML(commonConfig) 274 if err != nil { 275 panic(err) 276 } 277 278 return string(commonConfigBytes), string(advancedConfigBytes), nil 279 } 280 281 // AsMarkdown renders the spec of a component, along with a full configuration 282 // example, into a markdown document. 283 func (c *ComponentSpec) AsMarkdown(nest bool, fullConfigExample interface{}) ([]byte, error) { 284 if strings.Contains(c.Summary, "\n\n") { 285 return nil, fmt.Errorf("%v component '%v' has a summary containing empty lines", c.Type, c.Name) 286 } 287 288 ctx := componentContext{ 289 Name: c.Name, 290 Type: string(c.Type), 291 Summary: c.Summary, 292 Description: c.Description, 293 Examples: c.Examples, 294 Footnotes: c.Footnotes, 295 Status: string(c.Status), 296 Version: c.Version, 297 } 298 if ctx.Status == "" { 299 ctx.Status = string(StatusStable) 300 } 301 302 if len(c.Categories) > 0 { 303 cats, _ := json.Marshal(c.Categories) 304 ctx.Categories = string(cats) 305 } 306 307 var err error 308 if ctx.CommonConfig, ctx.AdvancedConfig, err = genExampleConfigs(c.Type, nest, fullConfigExample); err != nil { 309 return nil, err 310 } 311 312 if len(c.Description) > 0 && c.Description[0] == '\n' { 313 ctx.Description = c.Description[1:] 314 } 315 if len(c.Footnotes) > 0 && c.Footnotes[0] == '\n' { 316 ctx.Footnotes = c.Footnotes[1:] 317 } 318 319 flattenedFields := c.Config.FlattenChildrenForDocs() 320 for _, v := range flattenedFields { 321 if v.Spec.Kind == KindMap { 322 v.Spec.Type = "object" 323 } else if v.Spec.Kind == KindArray { 324 v.Spec.Type = "array" 325 } else if v.Spec.Kind == Kind2DArray { 326 v.Spec.Type = "two-dimensional array" 327 } 328 v.Spec.Kind = KindScalar 329 ctx.Fields = append(ctx.Fields, v) 330 } 331 332 var buf bytes.Buffer 333 err = template.Must(template.New("component").Parse(componentTemplate)).Execute(&buf, ctx) 334 335 return buf.Bytes(), err 336 }