diff --git a/.markdownlint.yml b/.markdownlint.yml index 7adb0d6..2be5c1d 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -3,10 +3,7 @@ # - https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml # MD013/line-length - Line length -MD013: - line_length: 120 - code_blocks: false - tables: false +MD013: false # MD033/no-inline-html - Inline HTML MD033: false diff --git a/Makefile b/Makefile index f8d39d2..f923f91 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ lint: ## Lint the app format: ## Format the app @bin/format.sh +.PHONY: run +run: ## Run the app + @bin/run.sh + .PHONY: test test: ## Test the app @bin/test.sh diff --git a/bin/run.sh b/bin/run.sh new file mode 100755 index 0000000..ebb10dc --- /dev/null +++ b/bin/run.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -o errexit -o errtrace -o nounset -o pipefail + +schemadoc gen --in ./schemas --out ./out -vv +markdownlint --fix out/ + +echo "[run] ✅" diff --git a/cspell.yml b/cspell.yml index 4a6a7a1..e1f95e1 100644 --- a/cspell.yml +++ b/cspell.yml @@ -25,6 +25,7 @@ ignorePaths: - go.mod - go.sum - "**/testdata/**" + - schemas/ ignoreRegExpList: - /import \([^)]+\)/g language: en @@ -52,6 +53,7 @@ words: - goldmark - gomod - goreleaser + - gosec - gpgconf - gpgsign - hadolint @@ -76,6 +78,7 @@ words: - signingkey - sigstore - simplejson + - stripmd - tablewriter - trimpath - twelvelabs diff --git a/go.mod b/go.mod index 5d49280..991ee7e 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,10 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tdewolff/minify/v2 v2.20.10 github.com/twelvelabs/termite v0.13.1 + github.com/writeas/go-strip-markdown v2.0.1+incompatible github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb github.com/yuin/goldmark v1.6.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -63,7 +65,6 @@ require ( golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 diff --git a/go.sum b/go.sum index 703e3c7..7eabe3a 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,8 @@ github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjA github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/twelvelabs/termite v0.13.1 h1:e5m67QY7MKJ7z7e3a59pQTNowptAL4FTCw7xhnPvhto= github.com/twelvelabs/termite v0.13.1/go.mod h1:2tjVjIRrhqyptjCeevwyNMN+mx25NS5lD1bxb9ka8JA= +github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= +github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/internal/cmd/gen.go b/internal/cmd/gen.go index d0fcffa..75d9884 100644 --- a/internal/cmd/gen.go +++ b/internal/cmd/gen.go @@ -15,6 +15,7 @@ import ( "github.com/twelvelabs/termite/fsutil" "github.com/twelvelabs/termite/render" "github.com/twelvelabs/termite/validate" + stripmd "github.com/writeas/go-strip-markdown" "github.com/twelvelabs/schemadoc/internal/core" "github.com/twelvelabs/schemadoc/internal/jsonschema" @@ -23,6 +24,9 @@ import ( func init() { render.FuncMap["toHTML"] = markdown.ToHTMLString + render.FuncMap["wrapCode"] = markdown.WrapCode + render.FuncMap["firstSentence"] = markdown.FirstSentence + render.FuncMap["stripMarkdown"] = stripmd.Strip } func NewGenCmd(app *core.App) *cobra.Command { @@ -56,7 +60,8 @@ type GenAction struct { InPath string `validate:"required"` OutDir string `validate:"required" default:"out"` - OutFile string `validate:"required" default:"{{ .EntityName }}.md"` + OutFile string `validate:"required" default:"{{ .EntityName | underscore }}.md"` + OutFileTpl render.Template SchemaPaths []string TemplatePath string } @@ -73,39 +78,51 @@ func (a *GenAction) Run(_ context.Context, _ []string) error { } context := jsonschema.NewContext() - scm, err := context.Get(path) + schema, err := context.Get(path) if err != nil { return err } + schema.GenPathTpl = a.OutFileTpl - var rendered string - if a.TemplatePath != "" { - rendered, err = render.File(a.TemplatePath, scm) - } else { - var tpl *template.Template - templatePath := "templates/markdown.tpl.md" - tpl, err = template.New(filepath.Base(templatePath)). - Funcs(render.FuncMap). - ParseFS(jsonschema.Templates, templatePath) - if err != nil { + if err := a.generateSchema(schema); err != nil { + return err + } + for _, subSchema := range schema.Definitions { + if err := a.generateSchema(subSchema); err != nil { return err } - buf := bytes.Buffer{} - err = tpl.Execute(&buf, scm) - rendered = buf.String() - } - if err != nil { - return err } + } - outFile, err := render.String(a.OutFile, scm) + return nil +} + +func (a *GenAction) generateSchema(schema *jsonschema.Schema) error { + var rendered string + var err error + + if a.TemplatePath != "" { + rendered, err = render.File(a.TemplatePath, schema) + } else { + var tpl *template.Template + templatePath := "templates/markdown.tpl.md" + tpl, err = template.New(filepath.Base(templatePath)). + Funcs(render.FuncMap). + ParseFS(jsonschema.Templates, templatePath) if err != nil { return err } - outPath := filepath.Join(a.OutDir, outFile) - if err := os.WriteFile(outPath, []byte(rendered), fsutil.DefaultFileMode); err != nil { - return err - } + buf := bytes.Buffer{} + err = tpl.Execute(&buf, schema) + rendered = buf.String() + } + if err != nil { + return err + } + + outPath := filepath.Join(a.OutDir, schema.GenPath()) + if err := os.WriteFile(outPath, []byte(rendered), 0644); err != nil { //nolint: gosec + return err } return nil @@ -142,6 +159,12 @@ func (a *GenAction) setup() error { return fmt.Errorf(`'--out': %w`, err) } + tpl, err := render.Compile(a.OutFile) + if err != nil { + return fmt.Errorf(`'--outfile': %w`, err) + } + a.OutFileTpl = *tpl + if a.TemplatePath != "" { info, err := os.Stat(a.TemplatePath) if err != nil { diff --git a/internal/jsonschema/schema.go b/internal/jsonschema/schema.go index 9f3d5e1..28a9202 100644 --- a/internal/jsonschema/schema.go +++ b/internal/jsonschema/schema.go @@ -10,6 +10,7 @@ import ( "github.com/gobuffalo/flect" "github.com/twelvelabs/termite/render" + "gopkg.in/yaml.v3" ) //go:embed templates/* @@ -138,7 +139,7 @@ func (s *Schema) DescriptionMarkdown() string { func (s *Schema) EntityName() string { if s.Title != "" { - return s.Title + return flect.Pascalize(s.Title) } if s.Key != "" { return flect.Pascalize(s.Key) @@ -186,7 +187,7 @@ func (s *Schema) EnumMarkdown() string { if idx < len(s.EnumDescriptions) { desc = s.EnumDescriptions[idx] } - item := " * `" + enum.String() + "`" + item := "- `" + enum.String() + "`" if desc != "" { item += ": " + desc } @@ -204,7 +205,7 @@ func (s *Schema) ExamplesMarkdown() string { items := []string{} for _, example := range s.Examples { - items = append(items, fmt.Sprintf(" * `%s`", example.String())) + items = append(items, fmt.Sprintf(" * `%s`", example.YAMLString())) } if len(items) == 0 { @@ -488,3 +489,25 @@ func (a Any) String() string { } return fmt.Sprintf("%v", a.value) } + +func (a Any) JSONString() string { + if a.value == nil { + return "" + } + serialized, err := json.Marshal(a.value) + if err != nil { + return err.Error() + } + return strings.TrimSpace(string(serialized)) +} + +func (a Any) YAMLString() string { + if a.value == nil { + return "" + } + serialized, err := yaml.Marshal(a.value) + if err != nil { + return err.Error() + } + return strings.TrimSpace(string(serialized)) +} diff --git a/internal/jsonschema/schema_test.go b/internal/jsonschema/schema_test.go index e152241..1eb3c0b 100644 --- a/internal/jsonschema/schema_test.go +++ b/internal/jsonschema/schema_test.go @@ -155,7 +155,7 @@ func TestSchema_EnumMarkdown(t *testing.T) { {"two"}, }, } - require.Equal(" * `one`\n * `two`", schema.EnumMarkdown()) + require.Equal("- `one`\n- `two`", schema.EnumMarkdown()) schema = Schema{ Enum: []Any{ @@ -167,7 +167,7 @@ func TestSchema_EnumMarkdown(t *testing.T) { "the second number", }, } - require.Equal(" * `one`: the first number\n * `two`: the second number", schema.EnumMarkdown()) + require.Equal("- `one`: the first number\n- `two`: the second number", schema.EnumMarkdown()) } func TestSchema_ExamplesMarkdown(t *testing.T) { diff --git a/internal/jsonschema/templates/markdown.tpl.md b/internal/jsonschema/templates/markdown.tpl.md index 934ed31..e29f39c 100644 --- a/internal/jsonschema/templates/markdown.tpl.md +++ b/internal/jsonschema/templates/markdown.tpl.md @@ -1,54 +1,92 @@ {{ define "ConstTpl" -}} -{{ if .String }}
Allowed value:
{{ .String }}
Allowed values:
{{ . | toHTML }}{{ end -}} +{{ if . -}} + +Allowed Values: + +{{ . }} + +{{ end -}} {{ end -}} {{ define "ExamplesTpl" -}} -{{ if . }}Examples:
{{ . | toHTML }}{{ end -}} +{{ if . -}} + +Examples: + +{{ range $example := . -}} + +```yaml +{{ $example.YAMLString }} +``` + +{{ end -}} +{{ end -}} {{ end -}} {{ define "PropertiesTpl" -}} {{ if . -}} -| Property | Type | Required | Default | Description | -| -------- | ---- | -------- | ------- | ----------- | +| Property | Type | Required | Enum | Default | Description | +| -------- | ---- | -------- | ---- | ------- | ----------- | {{ $propParent := . -}} {{ range $key, $prop := $propParent.Properties -}} -| `{{ $key }}` | {{ $prop.TypeInfoMarkdown }} | {{ if $propParent.RequiredKey $key }}✅{{ end }} | {{ $prop.Default }} | {{ $prop.DescriptionMarkdown | toHTML }}{{ template "ConstTpl" $prop.Const }}{{ template "EnumTpl" $prop.EnumMarkdown }}{{ template "ExamplesTpl" $prop.ExamplesMarkdown }} | +| [`{{ $key }}`](#{{ $key }}) | {{ $prop.TypeInfoMarkdown }} | {{ if $propParent.RequiredKey $key }}✅{{ else }}➖{{ end }} | {{ if $prop.Enum }}✅{{ else }}➖{{ end }} | {{ $prop.Default.JSONString | wrapCode | default "➖" }} | {{ $prop.DescriptionMarkdown | stripMarkdown | firstSentence | toHTML }} | {{ end -}} {{ end -}} {{ end -}} # {{ .EntityName }} -{{ .DescriptionMarkdown }} +{{ template "DescriptionTpl" .DescriptionMarkdown }} +{{ template "ConstTpl" .Const.String }} +{{ template "EnumTpl" .EnumMarkdown }} +{{ template "ExamplesTpl" .Examples }} -## {{ .EntityName }} Properties +{{ if .OneOf -}} -{{ template "PropertiesTpl" . }} -{{ range $key, $def := .Definitions -}} -{{ if $def.Enum }}{{ continue }}{{ end -}} +## Variants -## {{ $def.EntityName }} +{{ range $key, $schema := .OneOf -}} -{{ $def.DescriptionMarkdown }} +- [{{ $schema.EntityName }}]({{ $schema.EntityLink }}) +{{ end -}} +{{ end -}} +{{ if .Properties -}} -{{ if $def.Properties -}} +## Properties -### {{ $def.EntityName }} Properties +{{ template "PropertiesTpl" . }} -{{ template "PropertiesTpl" $def }} {{ end -}} +{{ $root := . -}} +{{ range $key, $prop := .Properties -}} -{{ if $def.OneOf -}} +### `{{ $key }}` -### {{ $def.EntityName }} Variants +| Type | Required | Enum | Default | +| ---- | -------- | ---- | ------- | +| {{ $prop.TypeInfoMarkdown }} | {{ if $root.RequiredKey $key }}✅{{ else }}➖{{ end }} | {{ if $prop.Enum }}✅{{ else }}➖{{ end }} | {{ $prop.Default.JSONString | wrapCode | default "➖" }} | -{{ range $key, $subSchema := $def.OneOf -}} +{{ template "DescriptionTpl" $prop.DescriptionMarkdown }} +{{ template "ConstTpl" $prop.Const.String }} +{{ template "EnumTpl" $prop.EnumMarkdown }} +{{ template "ExamplesTpl" $prop.Examples }} -- [{{ $subSchema.EntityName }}]({{ $subSchema.EntityLink }}) -{{ end }} -{{ end -}} {{ end -}} diff --git a/internal/markdown/markdown.go b/internal/markdown/markdown.go index d1dd218..7c6c494 100644 --- a/internal/markdown/markdown.go +++ b/internal/markdown/markdown.go @@ -3,6 +3,8 @@ package markdown import ( "bytes" "fmt" + "strings" + "unicode" "github.com/tdewolff/minify/v2" mhtml "github.com/tdewolff/minify/v2/html" @@ -58,3 +60,26 @@ func ToHTMLString(markdown string) (string, error) { buf, err := ToHTMLBytes([]byte(markdown)) return string(buf), err } + +func WrapCode(s string) string { + if s == "" { + return "" + } + return "`" + strings.TrimSpace(s) + "`" +} + +func FirstSentence(s string) string { + if s == "" { + return "" + } + sentence := strings.TrimSpace(strings.Split(s, ".")[0]) + + // Only append punctuation if needed. + runes := []rune(sentence) + lastRune := runes[len(runes)-1] + if !unicode.IsPunct(lastRune) { + sentence += "." + } + + return sentence +} diff --git a/internal/markdown/markdown_test.go b/internal/markdown/markdown_test.go index a05ef3e..366ff15 100644 --- a/internal/markdown/markdown_test.go +++ b/internal/markdown/markdown_test.go @@ -28,3 +28,20 @@ func TestToHTMLBytes(t *testing.T) { g.Assert(t, filename, []byte(htmlString)) } + +func TestWrapCode(t *testing.T) { + require := require.New(t) + + require.Equal("", WrapCode("")) + require.Equal("`foo`", WrapCode("foo")) + require.Equal("`foo`", WrapCode("\n\nfoo\n\n")) +} + +func TestFirstSentence(t *testing.T) { + require := require.New(t) + + require.Equal("", FirstSentence("")) + require.Equal("Hello there.", FirstSentence("Hello there")) + require.Equal("Hello there.", FirstSentence("Hello there. How are you?")) + require.Equal("How are you?", FirstSentence("How are you?")) +} diff --git a/schemas/stamp.schema.json b/schemas/stamp.schema.json index 3419dd9..1a93ec7 100644 --- a/schemas/stamp.schema.json +++ b/schemas/stamp.schema.json @@ -19,11 +19,12 @@ "Prepend to the destination content.", "Replace the destination.", "Delete the destination content." - ] + ], + "markdownDescription": "Determines what type of modification to perform.\n\nThe append/prepend behavior differs slightly depending on\nthe destination content type. Strings are concatenated,\nnumbers are added, and objects are recursively merged.\nArrays are concatenated by default, but that behavior can\nbe customized via the 'merge' enum.\n\nReplace and delete behave consistently across all types." }, "ConflictConfig": { "title": "ConflictConfig", - "description": "ConflictConfig determines what to do when destination paths already exist.", + "description": "Determines what to do when creating a new file and\nthe destination path already exists.\n\n_Only used in [CreateTask](create_task.md#createtask)._", "enum": [ "keep", "replace", @@ -34,25 +35,35 @@ "Keep the existing path.", "Replace the existing path.", "Prompt the user." - ] + ], + "markdownDescription": "Determines what to do when creating a new file and\nthe destination path already exists.\n\n_Only used in [CreateTask](create_task.md#createtask)._" }, "CreateTask": { "title": "CreateTask", "description": "Creates a new file in the destination directory.", + "required": [ + "dst", + "src", + "type" + ], "additionalProperties": false, "properties": { "dst": { - "$ref": "#/definitions/Destination" + "$ref": "#/definitions/Destination", + "title": "Destination" }, "each": { + "title": "Each", "description": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly.", "examples": [ "foo, bar, baz", "{{ .SomeList | join \",\" }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly." }, "if": { + "title": "If", "description": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean.", "default": "true", "examples": [ @@ -60,22 +71,27 @@ "true", "{{ .SomeBool }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean." }, "src": { - "$ref": "#/definitions/Source" + "$ref": "#/definitions/Source", + "title": "Source" }, "type": { + "title": "Type", "description": "Creates a new file in the destination directory.", "const": "create", - "type": "string" + "type": "string", + "markdownDescription": "Creates a new file in the destination directory." } }, - "type": "object" + "type": "object", + "markdownDescription": "Creates a new file in the destination directory." }, "DataType": { "title": "DataType", - "description": "DataType is the data type of a value.", + "description": "Specifies the data type of a value.", "enum": [ "bool", "int", @@ -85,30 +101,39 @@ ], "type": "string", "enumDescriptions": [ - "", - "", - "", - "", - "" - ] + "Boolean.", + "Integer.", + "Integer array/slice.", + "String.", + "String array/slice." + ], + "markdownDescription": "Specifies the data type of a value." }, "DeleteTask": { "title": "DeleteTask", "description": "Deletes a file in the destination directory.", + "required": [ + "dst", + "type" + ], "additionalProperties": false, "properties": { "dst": { - "$ref": "#/definitions/Destination" + "$ref": "#/definitions/Destination", + "title": "Destination" }, "each": { + "title": "Each", "description": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly.", "examples": [ "foo, bar, baz", "{{ .SomeList | join \",\" }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly." }, "if": { + "title": "If", "description": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean.", "default": "true", "examples": [ @@ -116,39 +141,51 @@ "true", "{{ .SomeBool }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean." }, "type": { + "title": "Type", "description": "Deletes a file in the destination directory.", "const": "delete", - "type": "string" + "type": "string", + "markdownDescription": "Deletes a file in the destination directory." } }, - "type": "object" + "type": "object", + "markdownDescription": "Deletes a file in the destination directory." }, "Destination": { "title": "Destination", "description": "The destination path.", + "required": [ + "path" + ], "additionalProperties": false, "properties": { "conflict": { "$ref": "#/definitions/ConflictConfig", + "title": "Conflict", "default": "prompt" }, "content_type": { + "title": "Content Type", "description": "An explicit content type. Inferred from the file extension by default.", "enum": [ "json", "yaml", "text" ], - "type": "string" + "type": "string", + "markdownDescription": "An explicit content type. Inferred from the file extension by default." }, "missing": { "$ref": "#/definitions/MissingConfig", + "title": "Missing", "default": "ignore" }, "mode": { + "title": "Mode", "description": "An optional [POSIX mode](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) to set on the file path.", "default": "0666", "examples": [ @@ -156,18 +193,22 @@ "{{ .ModeValue }}" ], "pattern": "\\{\\{(.*)\\}\\}|\\d{4}", - "type": "string" + "type": "string", + "markdownDescription": "An optional [POSIX mode](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) to set on the file path." }, "path": { + "title": "Path", "description": "The file path relative to the destination directory. Attempts to traverse outside the destination directory will raise a runtime error", - "type": "string" + "type": "string", + "markdownDescription": "The file path relative to the destination directory. Attempts to traverse outside the destination directory will raise a runtime error" } }, - "type": "object" + "type": "object", + "markdownDescription": "The destination path." }, "FileType": { "title": "FileType", - "description": "FileType specifies the content type of the destination path.", + "description": "Specifies the content type of the destination path.", "enum": [ "json", "yaml", @@ -178,7 +219,8 @@ "", "", "" - ] + ], + "markdownDescription": "Specifies the content type of the destination path." }, "GeneratorTask": { "title": "GeneratorTask", @@ -186,14 +228,17 @@ "additionalProperties": false, "properties": { "each": { + "title": "Each", "description": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly.", "examples": [ "foo, bar, baz", "{{ .SomeList | join \",\" }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly." }, "if": { + "title": "If", "description": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean.", "default": "true", "examples": [ @@ -201,7 +246,8 @@ "true", "{{ .SomeBool }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean." }, "name": { "type": "string" @@ -209,7 +255,8 @@ "type": { "description": "Executes another generator.", "const": "generator", - "type": "string" + "type": "string", + "markdownDescription": "Executes another generator." }, "values": { "default": {}, @@ -220,11 +267,12 @@ ] } }, - "type": "object" + "type": "object", + "markdownDescription": "Executes another generator." }, "InputMode": { "title": "InputMode", - "description": "InputMode determines whether the value is a flag or positional argument.", + "description": "Determines how the value is input on the command line.", "enum": [ "arg", "flag", @@ -232,14 +280,15 @@ ], "type": "string", "enumDescriptions": [ - "", - "", - "" - ] + "Passed as a positional argument.", + "Passed as a flag.", + "Read-only from the `default` value." + ], + "markdownDescription": "Determines how the value is input on the command line." }, "MatchSource": { "title": "MatchSource", - "description": "MatchSource determines how match patterns should be applied.", + "description": "Determines how regexp patterns should be applied.", "enum": [ "file", "line" @@ -248,7 +297,8 @@ "enumDescriptions": [ "Match the entire file.", "Match each line." - ] + ], + "markdownDescription": "Determines how regexp patterns should be applied." }, "MergeType": { "title": "MergeType", @@ -263,11 +313,12 @@ "Concatenate source and destination arrays.", "Add source array items if not present in the destination.", "Replace the destination with the source." - ] + ], + "markdownDescription": "Determines merge behavior for arrays - either when modifying them directly\nor when recursively merging objects containing arrays." }, "MissingConfig": { "title": "MissingConfig", - "description": "MissingConfig determines what to do when destination paths are missing.", + "description": "Determines what to do when updating an existing file and\nthe destination path is missing.\n\n_Only used in [UpdateTask](update_task.md#updatetask)._", "enum": [ "ignore", "touch", @@ -278,11 +329,12 @@ "Do nothing.", "Create an empty file.", "Raise an error." - ] + ], + "markdownDescription": "Determines what to do when updating an existing file and\nthe destination path is missing.\n\n_Only used in [UpdateTask](update_task.md#updatetask)._" }, "PromptConfig": { "title": "PromptConfig", - "description": "PromptConfig determines when a value should prompt.", + "description": "Determines when a value should prompt for input.", "enum": [ "always", "never", @@ -291,11 +343,12 @@ ], "type": "string", "enumDescriptions": [ - "", - "", - "", - "" - ] + "Always prompt.", + "Never prompt.", + "Only when input OR default is blank/zero.", + "Only when not explicitly set via CLI." + ], + "markdownDescription": "Determines when a value should prompt for input." }, "Source": { "title": "Source", @@ -308,7 +361,8 @@ { "$ref": "#/definitions/SourceWithPath" } - ] + ], + "markdownDescription": "The source path or inline content." }, "SourceWithContent": { "title": "Source Content", @@ -319,13 +373,27 @@ "additionalProperties": false, "properties": { "content": { - "description": "Inline content. Can be any type. String keys and/or values will be rendered as templates." + "title": "Content", + "description": "Inline content. Can be any type. String keys and/or values will be rendered as templates.", + "examples": [ + "{{ .ValueOne }}", + [ + "{{ .ValueOne }}", + "{{ .ValueTwo }}" + ], + { + "foo": "{{ .ValueOne }}" + } + ], + "markdownDescription": "Inline content. Can be any type. String keys and/or values will be rendered as templates." }, "content_type": { - "$ref": "#/definitions/FileType" + "$ref": "#/definitions/FileType", + "title": "Content Type" } }, - "type": "object" + "type": "object", + "markdownDescription": "The source content." }, "SourceWithPath": { "title": "Source Path", @@ -336,14 +404,18 @@ "additionalProperties": false, "properties": { "content_type": { - "$ref": "#/definitions/FileType" + "$ref": "#/definitions/FileType", + "title": "Content Type" }, "path": { - "description": "The file path relative to the source directory. Attempts to traverse outside the source directory will raise a runtime error.", - "type": "string" + "title": "Path", + "description": "The file path relative to the generator source directory (./\\_src). Attempts to traverse outside the source directory will raise a runtime error.", + "type": "string", + "markdownDescription": "The file path relative to the generator source directory (./\\_src). Attempts to traverse outside the source directory will raise a runtime error." } }, - "type": "object" + "type": "object", + "markdownDescription": "The source path." }, "TaskSchema": { "title": "Task", @@ -362,7 +434,8 @@ { "$ref": "#/definitions/UpdateTask" } - ] + ], + "markdownDescription": "A generator task." }, "UpdateAction": { "title": "Action", @@ -371,14 +444,17 @@ "properties": { "merge": { "$ref": "#/definitions/MergeType", + "title": "Merge", "default": "concat" }, "type": { "$ref": "#/definitions/Action", + "title": "Type", "default": "replace" } }, - "type": "object" + "type": "object", + "markdownDescription": "The action to perform on the destination." }, "UpdateMatch": { "title": "Match", @@ -386,44 +462,62 @@ "additionalProperties": false, "properties": { "default": { - "description": "A default value to use if the JSON path expression is not found." + "title": "Default", + "description": "A default value to use if the JSON path expression is not found.", + "markdownDescription": "A default value to use if the JSON path expression is not found." }, "pattern": { - "description": "A regexp or JSON path expression.", + "title": "Pattern", + "description": "A regexp (content_type: text) or JSON path expression (content_type: json, yaml). When empty, will match everything.", "default": "", - "type": "string" + "type": "string", + "markdownDescription": "A regexp (content_type: text) or JSON path expression (content_type: json, yaml). When empty, will match everything." }, "source": { "$ref": "#/definitions/MatchSource", + "title": "Source", "default": "line" } }, - "type": "object" + "type": "object", + "markdownDescription": "Target a subset of the destination to update." }, "UpdateTask": { "title": "UpdateTask", "description": "Updates a file in the destination directory.", + "required": [ + "dst", + "src", + "type" + ], "additionalProperties": false, "properties": { "action": { - "$ref": "#/definitions/UpdateAction" + "$ref": "#/definitions/UpdateAction", + "title": "Action" }, "description": { + "title": "Description", "description": "An optional description of what is being updated.", - "type": "string" + "type": "string", + "markdownDescription": "An optional description of what is being updated." }, "dst": { - "$ref": "#/definitions/Destination" + "$ref": "#/definitions/Destination", + "title": "Destination" }, "each": { + "title": "Each", "description": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly.", "examples": [ "foo, bar, baz", "{{ .SomeList | join \",\" }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Set to a comma separated value and the task will be executued once per-item. On each iteration, the `_Item` and `_Index` values will be set accordingly." }, "if": { + "title": "If", "description": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean.", "default": "true", "examples": [ @@ -431,21 +525,27 @@ "true", "{{ .SomeBool }}" ], - "type": "string" + "type": "string", + "markdownDescription": "Determines whether the task should be executed. The value must be [coercible](https://pkg.go.dev/strconv#ParseBool) to a boolean." }, "match": { - "$ref": "#/definitions/UpdateMatch" + "$ref": "#/definitions/UpdateMatch", + "title": "Match" }, "src": { - "$ref": "#/definitions/Source" + "$ref": "#/definitions/Source", + "title": "Source" }, "type": { + "title": "Type", "description": "Updates a file in the destination directory.", "const": "update", - "type": "string" + "type": "string", + "markdownDescription": "Updates a file in the destination directory." } }, - "type": "object" + "type": "object", + "markdownDescription": "Updates a file in the destination directory." }, "Value": { "title": "Value", @@ -496,21 +596,27 @@ "type": "string" } }, - "type": "object" + "type": "object", + "markdownDescription": "A generator input value." } }, "properties": { "description": { + "title": "Description", "description": "The generator description. The first line is shown when listing all generators. The full description is used when viewing generator help/usage text.", - "type": "string" + "type": "string", + "markdownDescription": "The generator description. The first line is shown when listing all generators. The full description is used when viewing generator help/usage text." }, "name": { + "title": "Name", "description": "The generator name.", "minLength": 1, "pattern": "^[\\w:_-]+$", - "type": "string" + "type": "string", + "markdownDescription": "The generator name." }, "tasks": { + "title": "Tasks", "description": "A list of generator tasks.", "items": { "$ref": "#/definitions/TaskSchema" @@ -518,9 +624,11 @@ "type": [ "array", "null" - ] + ], + "markdownDescription": "A list of generator tasks." }, "values": { + "title": "Values", "description": "A list of generator input values.", "items": { "$ref": "#/definitions/Value" @@ -528,8 +636,10 @@ "type": [ "array", "null" - ] + ], + "markdownDescription": "A list of generator input values." } }, - "type": "object" + "type": "object", + "markdownDescription": "Stamp generator metadata." }