From f73437dbd426caf065a9617f29d2bb1c7e553776 Mon Sep 17 00:00:00 2001 From: davidovich Date: Wed, 21 May 2025 20:41:31 +0100 Subject: [PATCH 1/4] misc fixes better errors --- README.md | 36 ++++++++++++++++++++++++++++++++++-- pkg/summon/options.go | 6 +++++- pkg/summon/run.go | 35 ++++++++++++++++++++++------------- pkg/summon/summon.go | 6 +++--- 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index dae947a..ae2226e 100755 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - [Build](#build) - [Install](#install) - [Use-cases](#use-cases) + - [Templated templates](#templated-templates) - [Makefile Library](#makefile-library) - [Templating](#templating) - [Running a Binary](#running-a-binary) @@ -27,7 +28,8 @@ - [`{{ .flag }}` field](#-flag--field) - [A Note on Completions](#a-note-on-completions) - [Removing the run subcommand](#removing-the-run-subcommand) - - [Dump the Data at a Location](#dump-the-data-at-a-location) + - [Dump All the Data at a Location](#dump-all-the-data-at-a-location) + - [Dump Dir of Data at Location](#dump-dir-of-data-at-location) - [Output a File to stdout](#output-a-file-to-stdout) - [Output a Template File Without Rendering](#output-a-template-file-without-rendering) - [List Summon Contents](#list-summon-contents) @@ -251,6 +253,26 @@ go install [your-go-repo-module]/summon@latest ## Use-cases +### Templated templates + +When using `summon` (command line or as a [template +function](#-summon--function)), summon will render all the templates in the +referenced asset content. This can be problematic if there are template-like +text in the source asset. You can disable rendering by modifying the source and +escaping it. + +i.e. + +```text/template +${{ not a go template }} +``` + +Can be escaped with backticks: + +```text/template +{{`${{ not a go template }}`}} +``` + ### Makefile Library In a makefile it can be useful to centralize certain libraries, notice how @@ -649,12 +671,22 @@ main command. In this mode, the `ls` subcommand to list embedded assets becomes a `--ls` flag. -### Dump the Data at a Location +### Dump All the Data at a Location ```bash summon --all --out .dir ``` +### Dump Dir of Data at Location + +```bash +summon a-dir a-destination +``` + +When copying a directory, the semantics are like `cp -r source dest`, meaning +that every file or directory present in `a-dir` will be copied recursively +in `a-destination`. + ### Output a File to stdout ```bash diff --git a/pkg/summon/options.go b/pkg/summon/options.go index c59c2b7..3171eb5 100644 --- a/pkg/summon/options.go +++ b/pkg/summon/options.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "maps" "github.com/spf13/cobra" @@ -171,7 +172,10 @@ func JSON(j *string) Option { if err != nil { return err } - opts.data = data + if opts.data == nil { + opts.data = map[string]interface{}{} + } + maps.Copy(opts.data, data) return nil } } diff --git a/pkg/summon/run.go b/pkg/summon/run.go index a3f6bdd..7e439c7 100644 --- a/pkg/summon/run.go +++ b/pkg/summon/run.go @@ -93,16 +93,18 @@ func (d *Driver) buildCmdArgs() ([]string, error) { } args := FlattenStrings(cmdSpec.args) - // Render and flatten arguments array of arrays to simple array - if cmdSpec.join != nil && *cmdSpec.join { - oneLine := strings.Join(args, " ") - args = []string{oneLine} - } + arguments, err := d.RenderArgs(args...) if err != nil { return nil, err } + // Render and flatten arguments array of arrays to simple array + if cmdSpec.join != nil && *cmdSpec.join { + oneLine := strings.Join(arguments, " ") + arguments = []string{oneLine} + } + // Render flags renderedFlags := []string{} for _, flag := range d.flagsToRender { @@ -178,18 +180,25 @@ func (d *Driver) RenderArgs(args ...string) ([]string, error) { } var renderedTargets = []string{rt} + + // We want to remove template introduced [] for slice render. + // The resulting list should be split by shell rules. + // The [] operator allows an empty param list or verbatim mode. if strings.HasPrefix(rt, "[") && strings.HasSuffix(rt, "]") { - inner := strings.Trim(rt, "[]") + inner := strings.TrimSuffix(strings.TrimPrefix(rt, "["), "]") if inner == "" { continue + } + // check if user wants to keep args list as one param (verbatim) + if strings.HasPrefix(inner, "[") && strings.HasSuffix(inner, "]") { + renderedTargets = []string{strings.TrimSuffix(strings.TrimPrefix(inner, "["), "]")} } else { - if inner == `""` { - renderedTargets = []string{""} - } else { + renderedTargets = []string{""} + if inner != `""` { renderedTargets, err = shlex.Split(inner, true) if err != nil { - return nil, err + return nil, fmt.Errorf("could not split args: %w", err) } } } @@ -424,11 +433,11 @@ func (d *Driver) addCmdSpec(root *cobra.Command, arg string, cmdSpec *commandSpe // help is requested. func (d *Driver) SetupRunArgs(root *cobra.Command) { // Summon needs to pass the help flag down to the proxied - // command, but cobra is very agressive in wanting to manage the help. + // command, but cobra is very aggressive in wanting to manage the help. // To workaround this, remove the help, but reintroduce it only if the user // defined a help for his command in the config file. If the help is removed, - // it can be positionned explicitely by the user with flagValue "help". - // Otherwize the help is reintroduced when calling the proxied command. It + // it can be positioned explicitly by the user with flagValue "help". + // Otherwise the help is reintroduced when calling the proxied command. It // is reinserted at the same position (before a recorded arg), if this arg // was not manipulated by a template rendering. In the latter case, help // is appended to the proxied command. diff --git a/pkg/summon/summon.go b/pkg/summon/summon.go index 2f005a2..b771e62 100644 --- a/pkg/summon/summon.go +++ b/pkg/summon/summon.go @@ -58,7 +58,7 @@ func (d *Driver) Summon(opts ...Option) (string, error) { embeddedFile, err := d.fs.Open(filename) if err != nil { - return "", err + return "", fmt.Errorf("could not open %s in store: %w", filename, err) } stat, err := embeddedFile.Stat() if err != nil { @@ -129,7 +129,7 @@ func (d *Driver) copyOneFile(embeddedFile fs.File, filename, root string) (strin summonedFile = filepath.Join(destination, filename) err := appFs.MkdirAll(filepath.Dir(summonedFile), os.ModePerm) if err != nil { - return "", err + return "", fmt.Errorf("could not create dir %s: %w", filepath.Dir(summonedFile), err) } outf, err := appFs.Create(summonedFile) @@ -151,7 +151,7 @@ func (d *Driver) copyOneFile(embeddedFile fs.File, filename, root string) (strin } else { rendered, err = d.renderTemplate(string(fileContent)) if err != nil { - return "", err + return "", fmt.Errorf("failed to render templates of file %s: %w", filepath.Join(root, filename), err) } } From bd723667d9fd77b98db2844252d1272fe43ef06e Mon Sep 17 00:00:00 2001 From: davidovich Date: Wed, 21 May 2025 21:29:30 +0100 Subject: [PATCH 2/4] fix help and missing subcommands --- pkg/summon/run.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/summon/run.go b/pkg/summon/run.go index 7e439c7..eb1e4b4 100644 --- a/pkg/summon/run.go +++ b/pkg/summon/run.go @@ -54,6 +54,16 @@ func (d *Driver) Run(opts ...Option) error { return err } + if len(cmdArgs) == 1 && cmdArgs[0] == d.opts.helpWanted.helpFlag { + // let cobra layer handle help + return fmt.Errorf("") + } + + if len(cmdArgs) == 0 { + _, ref := d.getCmdSpec() + return fmt.Errorf("missing params for command '%s'", ref) + } + cmd := d.execCommand(cmdArgs[0], cmdArgs[1:]...) if d.opts.debug || d.opts.dryrun { _, ref := d.getCmdSpec() @@ -378,8 +388,12 @@ func (d *Driver) addCmdSpec(root *cobra.Command, arg string, cmdSpec *commandSpe RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true - return d.Run(CobraCmd(cmd), + err := d.Run(CobraCmd(cmd), Args(extractUnknownArgs(cmd, d.opts.initialArgs, d.opts.args)...)) + if err != nil { + cmd.SilenceUsage = false + } + return err }, FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, } From 4bae842338d46fe16ddde610765571ba13250b57 Mon Sep 17 00:00:00 2001 From: davidovich Date: Thu, 22 May 2025 00:23:30 +0100 Subject: [PATCH 3/4] show error when template cannot be parsed --- pkg/summon/template.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/summon/template.go b/pkg/summon/template.go index a8c2fe8..2867cbb 100644 --- a/pkg/summon/template.go +++ b/pkg/summon/template.go @@ -50,7 +50,7 @@ func (d *Driver) renderTemplate(tmpl string) (string, error) { t, err = t.Parse(tmpl) if err != nil { - return tmpl, err + return tmpl, fmt.Errorf("could not parse template: '%s' error: %w", tmpl, err) } data := d.opts.data @@ -121,8 +121,8 @@ func summonFuncMap(d *Driver) template.FuncMap { }, "arg": func(index int, missingErrors ...string) (string, error) { missingError := strings.Join(missingErrors, " ") - if d.opts.args == nil { - return "", fmt.Errorf(missingError) + if len(d.opts.args) == 0 { + return "", fmt.Errorf("%s", missingError) } if index >= len(d.opts.args) { return "", fmt.Errorf("%s: index %v out of range, args: %s", missingError, index, d.opts.args) From fb0af0449841fdbc9b00ec65548bbe1d93df33b2 Mon Sep 17 00:00:00 2001 From: davidovich Date: Thu, 22 May 2025 13:26:24 +0100 Subject: [PATCH 4/4] support string slice in prompt --- pkg/summon/template.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/summon/template.go b/pkg/summon/template.go index 2867cbb..ca6536a 100644 --- a/pkg/summon/template.go +++ b/pkg/summon/template.go @@ -154,6 +154,8 @@ func summonFuncMap(d *Driver) template.FuncMap { for _, e := range t { selectors = append(selectors, e.(string)) } + case []string: + selectors = append(selectors, t...) default: return "", fmt.Errorf("last parameter should be a default value or a list of choices") }