Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion pkg/summon/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"maps"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -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
}
}
Expand Down
51 changes: 37 additions & 14 deletions pkg/summon/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -93,16 +103,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 {
Expand Down Expand Up @@ -178,18 +190,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)
}
}
}
Expand Down Expand Up @@ -369,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},
}
Expand Down Expand Up @@ -424,11 +447,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.
Expand Down
6 changes: 3 additions & 3 deletions pkg/summon/summon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}

Expand Down
8 changes: 5 additions & 3 deletions pkg/summon/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
}
Expand Down