From fd6709c64e3c86692410b570767cd9d16da259f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BChnert?= Date: Sun, 7 Nov 2021 22:23:20 +0100 Subject: [PATCH] fix dialog and optional value handling // add examples and description to concept.json spec --- README.md | 50 +++++++++++++---- cmd/inputDialog.go | 114 +++++++++++++++++++++++++++++++++------ pkg/concepts/concepts.go | 6 ++- 3 files changed, 143 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ff76264..c88a9ce 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Mandatory Values ? instanceName (string) [? for help] ``` -As this is our first time rendering this, a dialog will pop up, asking us for values. +As this is our first time rendering this, a dialog will open up, asking us for values. So for now we will comply with what this pesky dialog wants. ``` @@ -132,7 +132,7 @@ will be reused, if this file is detected. You can even pass a specific file with ───────┴──────────────────────────────────────────────────────────────────── ``` -When we now execute our render command again, we will see that kable automatically +When we now run our render command again, we will see that kable automatically detects the `renderinfo.json` file in the output path, and reuses it's values. ``` @@ -232,7 +232,7 @@ When rendering via `kable render` a dialog will ask the user about the defined i Examples of how to define aforementioned inputs in your concept.json file: -``` +```json { "apiVersion": 1, "type": "jsonnet", @@ -243,19 +243,19 @@ Examples of how to define aforementioned inputs in your concept.json file: // HERE WE CAN DEFINE INPUTS "string": { - "type": "string", + "type": "string" }, "int": { - "type": "int", + "type": "int" }, "bool": { - "type": "bool", + "type": "bool" }, "map": { - "type": "map", + "type": "map" }, "selection": { @@ -264,11 +264,43 @@ Examples of how to define aforementioned inputs in your concept.json file: "Option 1", "Option 2" ] - }, + } }, - "optional": {...}, + "optional": { + + // ANOTHER SECTION OF INPUTS THAT ARE NOT MANDATORY + + "string": { + "type": "string" + }, + ... + } + } +} +``` + +There is the possibility to add a description and example to each required input. + +```json +{ + "apiVersion": 1, + "type": "jsonnet", + "metadata": { + ... + }, + "inputs": { + "mandatory": { + // HERE WE CAN DEFINE INPUTS + + "string": { + "type": "string", + "description": "This text describes the purpose of the input.", + "example": "examplevalue" + }, + ... } + } } ``` diff --git a/cmd/inputDialog.go b/cmd/inputDialog.go index 1f3af56..a8b80cc 100644 --- a/cmd/inputDialog.go +++ b/cmd/inputDialog.go @@ -4,7 +4,11 @@ import ( "encoding/json" "errors" "fmt" + "os" "sort" + "strings" + + "github.com/AlecAivazis/survey/v2/terminal" "github.com/fatih/color" @@ -13,6 +17,11 @@ import ( "github.com/redradrat/kable/pkg/concepts" ) +var ( + cursor *terminal.Cursor + hl = color.New(color.Bold, color.Underline).Sprintf +) + type InputDialog struct { inputs concepts.ConceptInputs } @@ -24,7 +33,7 @@ func NewInputDialog(inputs concepts.ConceptInputs) InputDialog { func (id InputDialog) RunInputDialog() (*concepts.RenderValues, error) { values := concepts.RenderValues{} if len(id.inputs.Mandatory) != 0 { - PrintMsg("Mandatory Values") + PrintMsg(hl("\nMandatory Values")) keys := getSortedMapKeys(id.inputs.Mandatory) for _, key := range keys { value, err := getValue(key, id.inputs.Mandatory[key]) @@ -36,17 +45,42 @@ func (id InputDialog) RunInputDialog() (*concepts.RenderValues, error) { } if len(id.inputs.Optional) != 0 { - PrintMsg("Optional Values") - keys := getSortedMapKeys(id.inputs.Optional) - for _, key := range keys { - value, err := getValue(key, id.inputs.Optional[key]) - if err != nil { - return nil, err + + optConfirm := false + optPrompt := &survey.Confirm{ + Message: "Provide values for optional inputs?", + } + if err := survey.AskOne(optPrompt, &optConfirm); err != nil { + return nil, err + } + erasePreviousLine() + + // Only go into optional values if the users want's to + if optConfirm { + PrintMsg(hl("\nOptional Values")) + keys := getSortedMapKeys(id.inputs.Optional) + for _, key := range keys { + valConfirm := false + valPrompt := &survey.Confirm{ + Message: fmt.Sprintf("Provide value for %s?", key), + Help: breakEvery60chars(id.inputs.Optional[key].Description), + } + if err := survey.AskOne(valPrompt, &valConfirm); err != nil { + return nil, err + } + if valConfirm { + erasePreviousLine() + value, err := getValue(key, id.inputs.Optional[key]) + if err != nil { + return nil, err + } + values[key] = value + } } - values[key] = value } } + fmt.Println() return &values, nil } @@ -61,14 +95,24 @@ func getSortedMapKeys(values map[string]concepts.InputType) []string { func getValue(name string, input concepts.InputType) (concepts.ValueType, error) { + var helpText string + if input.Description != "" { + helpText = helpText + breakEvery60chars(input.Description) + "\n\n" + } + if input.Example != "" { + helpText = helpText + "Example: " + input.Example + } + var value concepts.ValueType switch input.Type { case concepts.ConceptStringInputType: - + if helpText == "" { + helpText = "example: test" + } val := "" prompt := &survey.Input{ Message: name + color.CyanString(" (string)"), - Help: "example: test", + Help: helpText, } if err := survey.AskOne(prompt, &val); err != nil { return nil, err @@ -76,10 +120,13 @@ func getValue(name string, input concepts.InputType) (concepts.ValueType, error) value = concepts.StringValueType(val) case concepts.ConceptIntInputType: + if helpText == "" { + helpText = "example: 3" + } var val int prompt := &survey.Input{ Message: name + color.CyanString(" (integer)"), - Help: "example: 3", + Help: helpText, } if err := survey.AskOne(prompt, &val); err != nil { return nil, err @@ -90,6 +137,7 @@ func getValue(name string, input concepts.InputType) (concepts.ValueType, error) var val bool prompt := &survey.Confirm{ Message: name + color.CyanString(" (boolean)"), + Help: helpText, } if err := survey.AskOne(prompt, &val); err != nil { return nil, err @@ -97,11 +145,11 @@ func getValue(name string, input concepts.InputType) (concepts.ValueType, error) value = concepts.BoolValueType(val) case concepts.ConceptSelectionInputType: - - val := "" + var val string prompt := &survey.Select{ - Message: name, + Message: name + color.CyanString(" (select)"), Options: input.Options, + Help: helpText, } if err := survey.AskOne(prompt, &val); err != nil { return nil, err @@ -109,11 +157,13 @@ func getValue(name string, input concepts.InputType) (concepts.ValueType, error) value = concepts.StringValueType(val) case concepts.ConceptMapInputType: - + if helpText == "" { + helpText = "example: {'foo':'bar'}" + } val := "" prompt := &survey.Input{ Message: name + color.CyanString(" (map)"), - Help: "example: {'foo':'bar'}", + Help: helpText, } if err := survey.AskOne(prompt, &val); err != nil { return nil, err @@ -129,3 +179,35 @@ func getValue(name string, input concepts.InputType) (concepts.ValueType, error) } return value, nil } + +func breakEvery60chars(in string) string { + if len(in) <= 60 { + return in + } + var chunks []string + chunk := make([]rune, 60) + len := 0 + for _, r := range in { + chunk[len] = r + len++ + if len == 60 { + chunks = append(chunks, string(chunk)) + len = 0 + } + } + if len > 0 { + chunks = append(chunks, string(chunk[:len])) + } + return strings.Join(chunks, "\n") +} + +func erasePreviousLine() { + if cursor == nil { + cursor = &terminal.Cursor{ + In: os.Stdin, + Out: os.Stdout, + } + } + cursor.PreviousLine(1) + terminal.EraseLine(os.Stdout, terminal.ERASE_LINE_ALL) +} diff --git a/pkg/concepts/concepts.go b/pkg/concepts/concepts.go index a17b3e9..b78ee63 100644 --- a/pkg/concepts/concepts.go +++ b/pkg/concepts/concepts.go @@ -195,8 +195,10 @@ func (ci ConceptInputs) All() map[string]InputType { } type InputType struct { - Type InputTypeIdentifier `json:"type"` - Options []string `json:"options,omitempty"` + Type InputTypeIdentifier `json:"type"` + Description string `json:"description"` + Example string `json:"example"` + Options []string `json:"options,omitempty"` } type InputTypeIdentifier string