Compare commits

...

23 Commits
v0.0.5 ... main

Author SHA1 Message Date
jolheiser 4281434739 feat: nix nushell completions (#32)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #32
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-12-22 19:44:44 +00:00
jolheiser 01944e1a3a feat: nix (#31)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #31
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-12-20 04:14:45 +00:00
jolheiser 44de3c2376 chore: survey -> huh (#30)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #30
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-12-20 04:07:47 +00:00
jolheiser 400fb64254 Prompt types (#29)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #29
2023-08-20 20:01:58 +00:00
jolheiser 2e05546c71 Nushell completions (#28)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #28
2023-08-20 17:39:15 +00:00
jolheiser 512bf7394d Derive branch name (#27)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #27
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-08-17 04:02:19 +00:00
jolheiser 4981a14e14 add sprig (#26)
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
Resolves #25

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: #26
2022-10-13 18:01:32 +00:00
jolheiser d02502078f Add options support (#24)
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
<!--
1. Did you add documentation?
2. Did you add tests?
3. Do you need to re-run formatting?
4. Do you need to re-run docs.go?
-->

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: #24
2022-06-15 14:59:21 +00:00
jolheiser c348255c37 Load env on download (#23)
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: #23
2022-06-14 20:20:51 +00:00
jolheiser 07ccb51584 Major changes (#22)
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
1. Switch from TOML to YAML (oof)
2. Add a JSON schema for templates (anti-oof)
3. Add functionality for prompt defaults to be based on earlier prompt answers
4. Add env commands and setting/loading for tmpl-specific env
5. Use a more helpful testing library

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: #22
2022-06-14 19:59:53 +00:00
jolheiser 4987bf8aed Add goreleaser (#21)
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
Reviewed-on: #21
2022-06-03 04:11:44 +00:00
jolheiser fe9dd61580 Fix gitea release plugin (#20)
continuous-integration/woodpecker the build was successful Details
Reviewed-on: #20
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2021-11-03 03:50:46 +00:00
jolheiser 1713f353f3 Fix token (#19)
continuous-integration/woodpecker the build failed Details
Reviewed-on: #19
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2021-11-02 03:28:30 +00:00
jolheiser d9564663db Update and move to woodpecker (#18)
continuous-integration/woodpecker the build failed Details
Reviewed-on: #18
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2021-11-02 03:22:27 +00:00
John Olheiser c173eee38c Explain .tmplkeep (#17)
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/17
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2021-01-03 11:29:37 +08:00
John Olheiser c497431a52 Add .tmplkeep (#16)
Resolves #15

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/16
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2021-01-03 11:06:32 +08:00
John Olheiser d4c47101ab Allow sourcing prompt answers from env variables (#14)
Fixes #12

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/14
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2020-12-29 11:21:41 +08:00
John Olheiser 19edb3a580 Add env and restore commands (#11)
Add env and restore commands

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Check for existance

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/11
Co-Authored-By: John Olheiser <john.olheiser@gmail.com>
Co-Committed-By: John Olheiser <john.olheiser@gmail.com>
2020-11-30 13:16:25 +08:00
John Olheiser 7121333290 Small tweaks (#10)
Small tweaks

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/10
Co-Authored-By: John Olheiser <john.olheiser@gmail.com>
Co-Committed-By: John Olheiser <john.olheiser@gmail.com>
2020-11-29 07:07:18 +08:00
John Olheiser 36832f93de Better defaults and prompts (#9)
Add true bools and better defaults, fix map/funcmap differences

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/9
Co-Authored-By: John Olheiser <john.olheiser@gmail.com>
Co-Committed-By: John Olheiser <john.olheiser@gmail.com>
2020-11-24 14:10:46 +08:00
John Olheiser 47ef9ea0be Fix CI (#8)
Update '.drone.yml'

Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/8
Co-Authored-By: John Olheiser <john.olheiser@gmail.com>
Co-Committed-By: John Olheiser <john.olheiser@gmail.com>
2020-11-24 07:28:10 +08:00
John Olheiser 6ffc50dc06 Expand the template.toml file (#7)
Expand the template.toml file

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/7
Co-Authored-By: John Olheiser <john.olheiser@gmail.com>
Co-Committed-By: John Olheiser <john.olheiser@gmail.com>
2020-11-23 13:02:16 +08:00
John Olheiser 6dc75436fc Add CI (#6)
Rename check to vet

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Parallel compliance

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Update lint and move back to arm64 for compliance

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Releases and tags

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Move checks to amd64

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Add CI

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/6
Co-Authored-By: John Olheiser <john.olheiser@gmail.com>
Co-Committed-By: John Olheiser <john.olheiser@gmail.com>
2020-11-23 12:40:45 +08:00
48 changed files with 1611 additions and 528 deletions

View File

@ -0,0 +1,6 @@
<!--
1. Did you add documentation?
2. Did you add tests?
3. Do you need to re-run formatting?
4. Do you need to re-run docs.go?
-->

3
.gitignore vendored
View File

@ -3,4 +3,5 @@
# Binaries
/tmpl
/tmpl.exe
/tmpl.exe
dist/

25
.goreleaser.yaml 100644
View File

@ -0,0 +1,25 @@
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
ldflags:
- "-s -w -X go.jolheiser.com/tmpl/cmd.Version={{.Version}}"
archives:
- replacements:
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
release:
gitea:
owner: jolheiser
name: tmpl
gitea_urls:
api: https://git.jojodev.com/api/v1/
download: https://git.jojodev.com

39
.woodpecker.yml 100644
View File

@ -0,0 +1,39 @@
clone:
git:
image: woodpeckerci/plugin-git
settings:
tags: true
pipeline:
compliance:
image: golang:1.18
commands:
- go test -race ./...
- go vet ./...
- go run github.com/rs/zerolog/cmd/lint@latest go.jolheiser.com/tmpl
when:
event: pull_request
build:
image: goreleaser/goreleaser
commands:
- goreleaser build --snapshot
when:
event: pull_request
release:
image: goreleaser/goreleaser
commands:
- goreleaser release
secrets: [ gitea_token ]
when:
event: tag
prune:
image: jolheiser/drone-gitea-prune
settings:
base: https://git.jojodev.com
token:
from_secret: gitea_token
when:
event: tag

20
CLI.md
View File

@ -11,6 +11,10 @@ tmpl
[--source|-s]=[value]
```
# DESCRIPTION
Template automation
**Usage**:
```
@ -32,6 +36,18 @@ Download a template
**--branch, -b**="": Branch to clone (default: main)
## env
Show tmpl environment variables
### set, add
Set a tmpl environment variable (stored plaintext)
### unset, delete
Unsets a tmpl environment variable
## init
Initialize a template
@ -44,6 +60,10 @@ List templates in the registry
Remove a template
## restore
Restore missing templates
## save
Save a local template

107
DOCS.md
View File

@ -1,107 +0,0 @@
# tmpl templates
This documentation aims to cover FAQs and setup.
## Setting up a template
A "valid" tmpl template only requires two things
1. A `template.toml` file in the root directory.
2. A `template` directory that serves as the "root" of the template.
## template.toml
```toml
# Key-value pairs can be simple
# The user will receive a basic prompt asking them to fill out the variable
project = "my-project"
# Extended properties MUST be added after any simple key-value pairs (due to how TOML works)
# The "key" is enclosed in braces
[author]
# prompt is what will be shown to prompt the user
prompt = "The name of the author of this project"
# help would be extra information (generally seen by giving '?' to a prompt)
help = "Who will be primarily writing this project"
# default is the "value" part of the simple pair. This could be a suggested value
default = "me"
```
## template directory
This directory contains any and all files that are part of the template.
Everything in this directory (including paths and file names!) will be executed as a [Go template](https://golang.org/pkg/text/template/).
See the [documentation](https://golang.org/pkg/text/template/) for every available possibility, but some basic examples are...
* A variable defined in template.toml (tmpl allows for keys to be called as a func or variable, whichever you prefer!)
* `{{project}}` or `{{.project}}`
* `{{author}}` or `{{.author}}`
* Conditionally including something
* `{{if eq project ""}} something... {{end}}`
### template helpers
For a full list, see [helper.go](registry/helper.go)
|Helper|Example|Output|
|-----|-----|-----|
|upper|`{{upper project}}`|`MY-PROJECT`|
|lower|`{{lower project}}`|`my-project`|
|title|`{{title project}}`|`My-Project`|
|snake|`{{snake project}}`|`my_project`|
|kebab|`{{kebab project}}`|`my-project`|
|pascal|`{{pascal project}}`|`MyProject`|
|camel|`{{camel project}}`|`myProject`|
|env|`{{env "USER"}}`|The current user|
|sep|`{{sep}}`|Filepath separator for current OS|
|time}|`{{time "01/02/2006"}}`|`11/21/2020` - The time according to the given [format](https://flaviocopes.com/go-date-time-format/)|
## Sources
tmpl was designed to work with any local or git-based template. Unfortunately, in contrast to boilr, this means
it cannot be used with `user/repo` notation out of the box.
However, you _can_ set up a source (and subsequent env variable) to make it easier to use your preferred source while
still allowing for others.
### Setting up a source
Let's set up a source for [Gitea](https://gitea.com)
```
tmpl source add https://gitea.com gitea
```
To use it, either pass it in with the `--source` flag
```
tmpl --source gitea download jolheiser/tmpls tmpls
```
Or set it as the env variable `TMPL_SOURCE`
## Using a different branch
By default, tmpl will want to use a branch called `main` in your repository.
If you are using another branch as your default, you can set it as the env variable `TMPL_BRANCH`
Alternatively, you can specify on the command-line with the `--branch` flag of the `download` command
```
tmpl --source gitea download --branch license jolheiser/tmpls license
```
The above command would download the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) template from `jolheiser/tmpls`
## Putting it all together
I realize that many users will be using GitHub, and most will likely still be using the `master` branch.
1. Set up a source for GitHub
1. `tmpl source add https://github.com github`
2. Set the env variable `TMPL_SOURCE` to `github`
2. Set the env variable `TMPL_BRANCH` to `master`
3. Happy templating! `tmpl download user/repo repo`

128
FAQ.md 100644
View File

@ -0,0 +1,128 @@
# tmpl templates
This documentation aims to cover FAQs and setup.
## Setting up a template
A "valid" tmpl template only requires two things
1. A `tmpl.yaml` file in the root directory.
2. A `template` directory that serves as the "root" of the template.
## tmpl.yaml
**NOTE:** The tmpl.yaml file will be expanded, though not with the full power of the template itself.
The tmpl.yaml file will only expand environment variables with syntax `$USER` or `${USER}`.
For full documentation on the syntax, see [os.ExpandEnv](https://golang.org/pkg/os/#ExpandEnv).
When using the `--defaults` flag, no prompts will be shown and only default values will be used.
As another alternative, any environment variable that matches a key will bypass the prompt.
For example, `author` would have the corresponding environment variable `TMPL_VAR_AUTHOR`.
```yaml
# tmpl.yaml
# Write any template args here to prompt the user for, giving any defaults/options as applicable
prompts:
- id: project # The unique ID for the prompt
label: Project Name # The prompt message/label
help: The name to use in the project # Optional help message for the prompt
default: tmpl # Prompt default
```
## template directory
This directory contains any and all files that are part of the template.
Everything in this directory (including paths and file names!) will be executed as a [Go template](https://golang.org/pkg/text/template/).
See the [documentation](https://golang.org/pkg/text/template/) for every available possibility, but some basic examples are...
* An id defined in `tmpl.yaml` (tmpl allows for keys to be called as a func or variable, whichever you prefer!)
* `{{project}}` or `{{.project}}`
* `{{author}}` or `{{.author}}`
* Conditionally including something
* `{{if eq project ""}} something... {{end}}`
### template helpers
For a full list, see [helper.go](registry/helper.go)
| Helper | Example | Output |
|-------------|------------------------------------|-------------------------------------------------------------------------------------------------------|
| upper | `{{upper project}}` | `MY-PROJECT` |
| lower | `{{lower project}}` | `my-project` |
| title | `{{title project}}` | `My-Project` |
| snake | `{{snake project}}` | `my_project` |
| kebab | `{{kebab project}}` | `my-project` |
| pascal | `{{pascal project}}` | `MyProject` |
| camel | `{{camel project}}` | `myProject` |
| env | `{{env "USER"}}` | The current user |
| sep | `{{sep}}` | Filepath separator for current OS |
| time | `{{time "01/02/2006"}}` | `11/21/2020` - The time according to the given [format](https://flaviocopes.com/go-date-time-format/) |
| trim_prefix | `{{trim_prefix "foobar" "foo"}}` | `bar` |
| trim_suffix | `{{trim_suffix "foobar" "bar"}}` | `foo` |
| replace | `{{replace "foobar" "bar" "baz"}}` | `foobaz` |
## Sources
tmpl was designed to work with any local or git-based template. Unfortunately, in contrast to boilr, this means
it cannot be used with `user/repo` notation out of the box.
However, you _can_ set up a source (and subsequent env variable) to make it easier to use your preferred source while
still allowing for others.
### Setting up a source
Let's set up a source for [Gitea](https://gitea.com)
```
tmpl source add https://gitea.com gitea
```
To use it, either pass it in with the `--source` flag
```
tmpl --source gitea download jolheiser/tmpls tmpls
```
Or set it as the env variable `TMPL_SOURCE`
## Using a different branch
By default, tmpl will want to use a branch called `main` in your repository.
If you are using another branch as your default, you can set it as the env variable `TMPL_BRANCH`
Alternatively, you can specify on the command-line with the `--branch` flag of the `download` command
```
tmpl --source gitea download --branch license jolheiser/tmpls license
```
The above command would download the [license](https://git.jojodev.com/jolheiser/tmpls/src/branch/license) template from `jolheiser/tmpls`
## Putting it all together
I realize that many users will be using GitHub, and most will likely still be using the `master` branch.
1. Set up a source for GitHub
1. `tmpl source add https://github.com github`
2. Set the env variable `TMPL_SOURCE` to `github`
2. Set the env variable `TMPL_BRANCH` to `master`
3. Happy templating! `tmpl download user/repo repo`
## Backup and Restore
1. The simplest solution is to make a copy of your `registry.yaml` (default: `~/.tmpl/registry.yaml`).
* Once in the new location, you will need to use `tmpl restore`.
2. Alternatively, you can copy/paste the entire registry (default: `~/.tmpl`) and skip the restore step.
## `.tmplkeep`
Perhaps you are familiar with `.gitkeep` and its unofficial status in git. Git does not like empty directories, so usually
a `.gitkeep` (or just `.keep`) file is added to retain the directory while keeping it effectively empty.
tmpl instead uses `.tmplkeep` files for this purpose. The difference is, tmpl will **not** create the `.tmplkeep` file
when the template is executed. This allows you to set up directory structures (for staging, examples, etc.) that
will *actually* be empty after execution.

View File

@ -1,22 +0,0 @@
GO ?= go
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
.PHONY: build
build:
$(GO) build -ldflags '-s -w -X "go.jolheiser.com/tmpl/cmd.Version=$(VERSION)"'
.PHONY: vet
vet:
$(GO) vet ./...
.PHONY: fmt
fmt:
$(GO) fmt ./...
.PHONY: test
test:
$(GO) test -race ./...
.PHONY: docs
docs:
$(GO) run docs.go

View File

@ -8,11 +8,11 @@ The two projects share many similarities, however other than general layout/stru
[CLI Docs](CLI.md)
[Project Docs/FAQs](DOCS.md)
[Project Docs/FAQs](FAQ.md)
## Examples
Check out the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) and [makefile](https://gitea.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://gitea.com/jolheiser/tmpls).
Check out the [license](https://git.jojodev.com/jolheiser/tmpls/src/branch/license) and [makefile](https://git.jojodev.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://git.jojodev.com/jolheiser/tmpls).
## License

View File

@ -1,3 +1,4 @@
//go:build docs
// +build docs
package main
@ -10,6 +11,7 @@ import (
"go.jolheiser.com/tmpl/cmd"
)
//go:generate go run cli.go
func main() {
app := cmd.NewApp()
@ -28,7 +30,7 @@ func main() {
md = md[strings.Index(md, "#"):]
// CLI is using real default instead of DefaultText
md = regexp.MustCompile(`[\/\w]+\.tmpl`).ReplaceAllString(md, "~/.tmpl")
md = regexp.MustCompile(`[\/\\:\w]+\.tmpl`).ReplaceAllString(md, "~/.tmpl")
if _, err := fi.WriteString(md); err != nil {
panic(err)

View File

@ -4,21 +4,23 @@ import (
"os"
"path/filepath"
"go.jolheiser.com/tmpl/cmd/flags"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var (
Version = "develop"
defaultDir string
registryFlag string
sourceFlag string
)
func init() {
home, err := os.UserHomeDir()
if err != nil {
beaver.Error("could not locate user's home directory, tmpl will use temp dir for registry")
log.Error().Msg("could not locate user's home directory, tmpl will use temp dir for registry")
defaultDir = filepath.Join(os.TempDir(), ".tmpl")
return
}
defaultDir = filepath.Join(home, ".tmpl")
@ -37,23 +39,25 @@ func NewApp() *cli.App {
Usage: "Registry directory of tmpl",
Value: defaultDir,
DefaultText: "~/.tmpl",
Destination: &flags.Registry,
Destination: &registryFlag,
EnvVars: []string{"TMPL_REGISTRY"},
},
&cli.StringFlag{
Name: "source",
Aliases: []string{"s"},
Usage: "Short-name source to use",
Destination: &flags.Source,
Destination: &sourceFlag,
EnvVars: []string{"TMPL_SOURCE"},
},
}
app.Commands = []*cli.Command{
Download,
Env,
Init,
List,
Remove,
Restore,
Save,
Source,
Test,

View File

@ -2,20 +2,22 @@ package cmd
import (
"fmt"
"os"
"path"
"strings"
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/env"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Download = &cli.Command{
Name: "download",
Usage: "Download a template",
Description: "Download a template and save it to the local registry",
ArgsUsage: "[repository URL] [name]",
ArgsUsage: "[repository URL] <name>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "branch",
@ -29,25 +31,33 @@ var Download = &cli.Command{
}
func runDownload(ctx *cli.Context) error {
if ctx.NArg() < 2 {
if ctx.NArg() < 1 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
e, err := env.Load(registryFlag)
if err != nil {
return err
}
if err := e.Set(); err != nil {
return err
}
var source *registry.Source
if flags.Source != "" {
if sourceFlag != "" {
for _, s := range reg.Sources {
if strings.EqualFold(s.Name, flags.Source) {
if strings.EqualFold(s.Name, sourceFlag) {
source = s
break
}
}
if source == nil {
return fmt.Errorf("could not find source for %s", flags.Source)
return fmt.Errorf("could not find source for %s", sourceFlag)
}
}
@ -59,11 +69,27 @@ func runDownload(ctx *cli.Context) error {
cloneURL += ".git"
}
t, err := reg.DownloadTemplate(ctx.Args().Get(1), cloneURL, ctx.String("branch"))
t, err := reg.DownloadTemplate(deriveName(ctx), cloneURL, ctx.String("branch"))
if err != nil {
return err
}
beaver.Infof("Added new template %s", t.Name)
log.Info().Msgf("Added new template %q", t.Name)
return nil
}
func deriveName(ctx *cli.Context) string {
if ctx.NArg() > 1 {
return ctx.Args().Get(1)
}
envBranch, envSet := os.LookupEnv("TMPL_BRANCH")
flagBranch, flagSet := ctx.String("branch"), ctx.IsSet("branch")
if flagSet {
if !envSet || envBranch != flagBranch {
return flagBranch
}
}
return path.Base(ctx.Args().First())
}

92
cmd/env.go 100644
View File

@ -0,0 +1,92 @@
package cmd
import (
"os"
"go.jolheiser.com/tmpl/env"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Env = &cli.Command{
Name: "env",
Usage: "Show tmpl environment variables",
Description: "Show tmpl environment variables and their configuration",
Action: runEnv,
Subcommands: []*cli.Command{
{
Name: "set",
Aliases: []string{"add"},
Usage: "Set a tmpl environment variable (stored plaintext)",
Description: "Set an environment variable that will be loaded specifically when running tmpl (stored plaintext)",
ArgsUsage: "[key] [value]",
Action: runEnvSet,
},
{
Name: "unset",
Aliases: []string{"delete"},
Usage: "Unsets a tmpl environment variable",
Description: "Unsets an environment variable previously set for tmpl",
ArgsUsage: "[key]",
Action: runEnvUnset,
},
},
}
func runEnv(_ *cli.Context) error {
// Source
log.Info().Str("TMPL_SOURCE", os.Getenv("TMPL_SOURCE")).Msg("system")
// Registry Path
log.Info().Str("TMPL_REGISTRY", os.Getenv("TMPL_REGISTRY")).Msg("system")
// Branch
log.Info().Str("TMPL_BRANCH", os.Getenv("TMPL_BRANCH")).Msg("system")
// Custom
e, err := env.Load(registryFlag)
if err != nil {
return err
}
for key, val := range e {
log.Info().Str(key, val).Msg("env.json")
}
return nil
}
func runEnvSet(ctx *cli.Context) error {
if ctx.NArg() < 2 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
e, err := env.Load(registryFlag)
if err != nil {
return err
}
key, val := ctx.Args().Get(0), ctx.Args().Get(1)
e[key] = val
if err := env.Save(registryFlag, e); err != nil {
return err
}
log.Info().Str(key, val).Msg("Successfully saved tmpl environment variable!")
return nil
}
func runEnvUnset(ctx *cli.Context) error {
if ctx.NArg() < 1 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
e, err := env.Load(registryFlag)
if err != nil {
return err
}
key := ctx.Args().First()
val := e[key]
delete(e, key)
if err := env.Save(registryFlag, e); err != nil {
return err
}
log.Info().Str(key, val).Msg("Successfully unset tmpl environment variable!")
return nil
}

View File

@ -1,7 +0,0 @@
package flags
var (
Debug bool
Registry string
Source string
)

View File

@ -4,8 +4,8 @@ import (
"errors"
"os"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Init = &cli.Command{
@ -16,11 +16,11 @@ var Init = &cli.Command{
}
func runInit(_ *cli.Context) error {
if _, err := os.Lstat("template.toml"); !os.IsNotExist(err) {
if _, err := os.Lstat("tmpl.yaml"); !os.IsNotExist(err) {
if err != nil {
return err
}
return errors.New("template.toml already detected, aborting initialization")
return errors.New("tmpl.yaml already detected, aborting initialization")
}
if fi, err := os.Lstat("template"); !os.IsNotExist(err) {
if err != nil {
@ -32,25 +32,29 @@ func runInit(_ *cli.Context) error {
return errors.New("template directory already detected, aborting initialization")
}
fi, err := os.Create("template.toml")
fi, err := os.Create("tmpl.yaml")
if err != nil {
return err
}
if _, err := fi.WriteString(comments); err != nil {
if _, err := fi.WriteString(initConfig); err != nil {
return err
}
if err := os.Mkdir("template", os.ModePerm); err != nil {
return err
}
beaver.Info("Template initialized!")
log.Info().Msg("Template initialized!")
return fi.Close()
}
var comments = `# template.toml
var initConfig = `# tmpl.yaml
# Write any template args here to prompt the user for, giving any defaults/options as applicable
[name]
prompt = "Project Name"
help = "The name to use in the project"
default = "tmpl"
prompts:
- id: name # The unique ID for the prompt
label: Project Name # (Optional) Prompt message/label, defaults to id
help: The name to use in the project # (Optional) Help message for the prompt
default: tmpl # (Optional) Prompt default
options: # (Optional) Set of options the user can choose from
- coolproject123
- ${USER}'s cool project
`

17
cmd/init_test.go 100644
View File

@ -0,0 +1,17 @@
package cmd
import (
"strings"
"testing"
"go.jolheiser.com/tmpl/schema"
"github.com/matryer/is"
)
func TestInitSchema(t *testing.T) {
assert := is.New(t)
err := schema.Lint(strings.NewReader(initConfig))
assert.NoErr(err) // Init config should conform to schema
}

View File

@ -1,11 +1,11 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
"github.com/urfave/cli/v2"
@ -15,17 +15,27 @@ var List = &cli.Command{
Name: "list",
Usage: "List templates in the registry",
Description: "List all usable templates currently downloaded in the registry",
Action: runList,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "json",
Usage: "JSON format",
},
},
Action: runList,
}
func runList(_ *cli.Context) error {
reg, err := registry.Open(flags.Registry)
func runList(ctx *cli.Context) error {
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
if ctx.Bool("json") {
return json.NewEncoder(os.Stdout).Encode(reg.Templates)
}
wr := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
if _, err := fmt.Fprintf(wr, "NAME\tURL\tLOCAL\tUPDATED\n"); err != nil {
if _, err := fmt.Fprintf(wr, "NAME\tURL\tLOCAL\tLAST UPDATED\n"); err != nil {
return err
}
for _, t := range reg.Templates {
@ -35,7 +45,7 @@ func runList(_ *cli.Context) error {
u = t.Path
local = true
}
if _, err := fmt.Fprintf(wr, "%s\t%s\t%t\t%s\n", t.Name, u, local, t.Created.Format("01/02/2006")); err != nil {
if _, err := fmt.Fprintf(wr, "%s\t%s\t%t\t%s\n", t.Name, u, local, t.LastUpdate.Format("01/02/2006")); err != nil {
return err
}
}

View File

@ -1,11 +1,10 @@
package cmd
import (
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Remove = &cli.Command{
@ -21,7 +20,7 @@ func runRemove(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
@ -30,6 +29,6 @@ func runRemove(ctx *cli.Context) error {
return err
}
beaver.Infof("Successfully removed %s", ctx.Args().First())
log.Info().Msgf("Successfully removed %q", ctx.Args().First())
return nil
}

38
cmd/restore.go 100644
View File

@ -0,0 +1,38 @@
package cmd
import (
"os"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Restore = &cli.Command{
Name: "restore",
Usage: "Restore missing templates",
Description: "Restore templates that are listed in the registry, but are missing archives",
Action: runRestore,
}
func runRestore(_ *cli.Context) error {
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
var num int
for _, tmpl := range reg.Templates {
if _, err := os.Lstat(tmpl.ArchivePath()); os.IsNotExist(err) {
log.Info().Msgf("Restoring %q...", tmpl.Name)
if err := reg.UpdateTemplate(tmpl.Name); err != nil {
return err
}
num++
}
}
log.Info().Int("count", num).Msgf("Restored templates.")
return nil
}

View File

@ -3,11 +3,10 @@ package cmd
import (
"path/filepath"
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Save = &cli.Command{
@ -23,7 +22,7 @@ func runSave(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
@ -39,6 +38,6 @@ func runSave(ctx *cli.Context) error {
return err
}
beaver.Infof("Added new template %s", t.Name)
log.Info().Msgf("Added new template %q", t.Name)
return nil
}

View File

@ -1,15 +1,15 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var (
@ -29,7 +29,13 @@ var (
Name: "list",
Usage: "List available sources",
Description: "List all available sources in the registry",
Action: runSourceList,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "json",
Usage: "JSON format",
},
},
Action: runSourceList,
}
SourceAdd = &cli.Command{
@ -49,12 +55,16 @@ var (
}
)
func runSourceList(_ *cli.Context) error {
reg, err := registry.Open(flags.Registry)
func runSourceList(ctx *cli.Context) error {
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
if ctx.Bool("json") {
return json.NewEncoder(os.Stdout).Encode(reg.Sources)
}
wr := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
if _, err := fmt.Fprintf(wr, "NAME\tURL\n"); err != nil {
return err
@ -72,7 +82,7 @@ func runSourceAdd(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
@ -82,7 +92,7 @@ func runSourceAdd(ctx *cli.Context) error {
return err
}
beaver.Infof("Added new source %s", s.Name)
log.Info().Msgf("Added new source %q", s.Name)
return nil
}
@ -91,7 +101,7 @@ func runSourceRemove(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
@ -100,6 +110,6 @@ func runSourceRemove(ctx *cli.Context) error {
return err
}
beaver.Infof("Successfully removed source for %s", ctx.Args().First())
log.Info().Msgf("Successfully removed source for %q", ctx.Args().First())
return nil
}

View File

@ -1,11 +1,15 @@
package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
"go.jolheiser.com/tmpl/schema"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Test = &cli.Command{
@ -23,24 +27,38 @@ func runTest(ctx *cli.Context) error {
}
var errs []string
if _, err := os.Lstat(filepath.Join(testPath, "template.toml")); err != nil {
errs = append(errs, "could not find template.toml")
fi, err := os.Open(filepath.Join(testPath, "tmpl.yaml"))
if err != nil {
errs = append(errs, fmt.Sprintf("could not open tmpl.yaml: %v", err))
}
defer fi.Close()
if err := schema.Lint(fi); err != nil {
var rerr schema.ResultErrors
if errors.As(err, &rerr) {
for _, re := range rerr {
errs = append(errs, fmt.Sprintf("%s: %s", re.Field(), re.Description()))
}
} else {
errs = append(errs, fmt.Sprintf("could not lint tmpl.yaml: %v", err))
}
}
fi, err := os.Lstat(filepath.Join(testPath, "template"))
fstat, err := os.Lstat(filepath.Join(testPath, "template"))
if err != nil {
errs = append(errs, "no template directory found")
}
if err == nil && !fi.IsDir() {
if err == nil && !fstat.IsDir() {
errs = append(errs, "template path is a file, not a directory")
}
if len(errs) > 0 {
for _, err := range errs {
beaver.Error(err)
log.Error().Msg(err)
}
return nil
}
beaver.Info("this is a valid tmpl template")
log.Info().Msg("This is a valid tmpl template!")
return nil
}

View File

@ -1,11 +1,10 @@
package cmd
import (
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Update = &cli.Command{
@ -21,7 +20,7 @@ func runUpdate(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
@ -31,19 +30,10 @@ func runUpdate(ctx *cli.Context) error {
return err
}
if err := reg.RemoveTemplate(tmpl.Name); err != nil {
if err := reg.UpdateTemplate(tmpl.Name); err != nil {
return err
}
if tmpl.Path != "" {
_, err = reg.SaveTemplate(tmpl.Name, tmpl.Path)
} else {
_, err = reg.DownloadTemplate(tmpl.Name, tmpl.Repository, tmpl.Branch)
}
if err != nil {
return err
}
beaver.Infof("Successfully updated %s", tmpl.Name)
log.Info().Msgf("Successfully updated %q", tmpl.Name)
return nil
}

View File

@ -1,11 +1,11 @@
package cmd
import (
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/env"
"go.jolheiser.com/tmpl/registry"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Use = &cli.Command{
@ -21,6 +21,10 @@ var Use = &cli.Command{
Name: "force",
Usage: "Overwrite existing files",
},
&cli.BoolFlag{
Name: "accessible",
Usage: "Prompt in accessible mode (one prompt at a time)",
},
},
ArgsUsage: "[name] [destination (default: \".\")]",
Action: runUse,
@ -36,20 +40,28 @@ func runUse(ctx *cli.Context) error {
dest = ctx.Args().Get(1)
}
reg, err := registry.Open(flags.Registry)
reg, err := registry.Open(registryFlag)
if err != nil {
return err
}
e, err := env.Load(registryFlag)
if err != nil {
return err
}
if err := e.Set(); err != nil {
return err
}
tmpl, err := reg.GetTemplate(ctx.Args().First())
if err != nil {
return err
}
if err := tmpl.Execute(dest, ctx.Bool("defaults"), ctx.Bool("force")); err != nil {
if err := tmpl.Execute(dest, ctx.Bool("defaults"), ctx.Bool("accessible"), ctx.Bool("force")); err != nil {
return err
}
beaver.Infof("Successfully executed %s", tmpl.Name)
log.Info().Msgf("Successfully executed %q", tmpl.Name)
return nil
}

54
config/config.go 100644
View File

@ -0,0 +1,54 @@
package config
import (
"fmt"
"io"
"os"
"strings"
"gopkg.in/yaml.v3"
)
// Config is a tmpl config
type Config struct {
Prompts []Prompt `yaml:"prompts"`
}
// PromptType is a type of prompt
type PromptType string
const (
PromptTypeInput PromptType = "input"
PromptTypeMultiline PromptType = "multi"
PromptTypeEditor PromptType = "editor"
PromptTypeConfirm PromptType = "confirm"
PromptTypeSelect PromptType = "select"
)
// Prompt is a tmpl prompt
type Prompt struct {
ID string `yaml:"id"`
Label string `yaml:"label"`
Help string `yaml:"help"`
Default string `yaml:"default"`
Options []string `yaml:"options"`
Type PromptType `yaml:"type"`
}
// Load loads a tmpl config
func Load(r io.Reader) (*Config, error) {
configBytes, err := io.ReadAll(r)
if err != nil {
return nil, err
}
configContents := os.Expand(string(configBytes), func(s string) string {
if strings.HasPrefix(s, "TMPL_PROMPT") {
return fmt.Sprintf("${%s}", s)
}
return os.Getenv(s)
})
var c Config
return &c, yaml.Unmarshal([]byte(configContents), &c)
}

View File

@ -0,0 +1,113 @@
def _tmpl_env_keys [] {
["TMPL_SOURCE", "TMPL_REGISTRY", "TMPL_BRANCH"]
}
def _tmpl_source_list [] {
^tmpl source list --json | from json | each { |it| { value: $it.Name, description: $it.URL } }
}
def _tmpl_template_list [] {
^tmpl list --json | from json | each { |it| { value: $it.Name, description: (if $it.Path != "" { $it.Path } else { $"($it.Repository)@($it.Branch)" }) } }
}
# Template automation
export extern "tmpl" [
--registry(-r): string # Registry directory of tmpl (default: ~/.tmpl) [$TMPL_REGISTRY]
--source(-s): string # Short-name source to use [$TMPL_SOURCE]
--help(-h): bool # Show help
--version(-v): bool # Show version
]
# Download a template
export extern "tmpl download" [
repo_url: string # Repository URL
name: string # Local name for template
--branch(-b): string # Branch to clone (default: "main") [$TMPL_BRANCH]
--help(-h): bool # Show help
]
# Manage tmpl environment variables
export extern "tmpl env" [
--help(-h): bool # Show help
]
# Set a tmpl environment variable
export extern "tmpl env set" [
key: string@"_tmpl_env_keys" # Env key
value: string # Env value
--help(-h): bool # Show help
]
# Unset a tmpl environment variable
export extern "tmpl env unset" [
key: string@"_tmpl_env_keys" # Env key
]
# Initialize a blank tmpl template
export extern "tmpl init" [
--help(-h): bool # Show help
]
# List all templates in registry
export extern "tmpl list" [
--json: bool # Output in JSON
--help(-h): bool # Show help
]
# Remove a template
export extern "tmpl remove" [
name: string # Name of the template to remove
--help(-h): bool #Show help
]
# Restore templates present in the registry, but missing archives
export extern "tmpl restore" [
--help(-h): # Show help
]
# Save a local template
export extern "tmpl save" [
path: string # Path to the local template
name: string # Name of the template
--help(-h): bool # Show help
]
# Work with tmpl sources
export extern "tmpl source" [
--help(-h): # Show help
]
# Add a tmpl source
export extern "tmpl source add" [
base_url: string # Base URL
name: string # Name
--help(-h): bool # Show help
]
# Remove a tmpl source
export extern "tmpl source remove" [
name: string@"_tmpl_source_list" # Source to remove
--help(-h): bool # Show help
]
# Test whether a directory is a valid tmpl template
export extern "tmpl test" [
path?: string # Path to test (default: ".")
--help(-h): bool # Show help
]
# Update a template
export extern "tmpl update" [
name: string@"_tmpl_template_list" # Template to update
--help(-h): # Show help
]
# Use a template
export extern "tmpl use" [
name: string@"_tmpl_template_list" # The template to execute
dest?: path # Destination for the template (default: ".")
--defaults: bool # Use template defaults
--force: bool # Overwrite existing files
--help(-h): bool # Show help
]

50
env/env.go vendored 100644
View File

@ -0,0 +1,50 @@
package env
import (
"encoding/json"
"errors"
"os"
"path/filepath"
)
// Env is tmpl environment variables
type Env map[string]string
// Set sets all environment variables from an Env
func (e Env) Set() error {
for key, val := range e {
if err := os.Setenv(key, val); err != nil {
return err
}
}
return nil
}
// Load loads an env from <path>/env.json
func Load(path string) (Env, error) {
p := filepath.Join(path, "env.json")
fi, err := os.Open(p)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return Env{}, nil
}
return nil, err
}
defer fi.Close()
var e Env
if err := json.NewDecoder(fi).Decode(&e); err != nil {
return nil, err
}
return e, nil
}
// Save saves an Env to <path>/env.json
func Save(path string, e Env) error {
p := filepath.Join(path, "env.json")
fi, err := os.Create(p)
if err != nil {
return err
}
return json.NewEncoder(fi).Encode(e)
}

26
flake.lock 100644
View File

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1703042410,
"narHash": "sha256-LKhDz1Lxp+QgL6UpuPCLkMAx6JvabwwyIoeoQd1A7Sc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "7dbe085eb6870dc0b729254c987f626c2a6e71d7",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

37
flake.nix 100644
View File

@ -0,0 +1,37 @@
{
description = "tmpl";
inputs.nixpkgs.url = "github:nixos/nixpkgs";
outputs = {
self,
nixpkgs,
}: let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
tmpl = pkgs.buildGoModule rec {
pname = "tmpl";
version = "0.4.0";
src = ./.;
vendorHash = "sha256-QNwzHC4fHLAhshOplKmMjRYa9sHNjBLdfBgANbs/iKk=";
ldflags = ["-s" "-w" "-X=go.jolheiser.com/tmpl/cmd.Version=${version}"];
postInstall = ''
mkdir -p $out/share
cp -vr ./contrib/tmpl-completions.nu $out/share/tmpl-completions.nu
'';
meta = with pkgs.lib; {
description = "";
homepage = "https://git.jojodev.com/jolheiser/tmpl";
license = licenses.mit;
maintainers = with maintainers; [jolheiser];
mainProgram = "tmpl";
};
};
in {
packages.x86_64-linux.default = tmpl;
};
}

87
go.mod
View File

@ -1,13 +1,86 @@
module go.jolheiser.com/tmpl
go 1.15
go 1.21
require (
github.com/AlecAivazis/survey/v2 v2.2.2
github.com/go-git/go-git/v5 v5.2.0
github.com/Masterminds/sprig/v3 v3.2.2
github.com/charmbracelet/huh v0.2.3
github.com/go-git/go-git/v5 v5.4.2
github.com/huandu/xstrings v1.3.2
github.com/mholt/archiver/v3 v3.5.0
github.com/pelletier/go-toml v1.8.1
github.com/urfave/cli/v2 v2.3.0
go.jolheiser.com/beaver v1.0.2
github.com/matryer/is v1.4.0
github.com/mholt/archiver/v3 v3.5.1
github.com/rs/zerolog v1.27.0
github.com/urfave/cli/v2 v2.8.1
github.com/xeipuuv/gojsonschema v1.2.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/bubbles v0.17.1 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/glamour v0.6.0 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.15.6 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/stretchr/testify v1.7.1 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yuin/goldmark v1.6.0 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

314
go.sum
View File

@ -1,140 +1,282 @@
github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY=
github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b h1:lcbBNuQhppsc7A5gjdHmdlqUqJfgGMylBdGyDs0j7G8=
github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
github.com/charmbracelet/huh v0.2.3 h1:fZaqnd/fiO7jlfcLqhP2iwpLt670IaHQfL/7Qu+fBm0=
github.com/charmbracelet/huh v0.2.3/go.mod h1:XmADLRnJs/Jqw7zIbi9BTss5gXbOkR6feyVoNAp19rA=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E=
github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
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/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk=
go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -5,14 +5,15 @@ import (
"go.jolheiser.com/tmpl/cmd"
"go.jolheiser.com/beaver"
"go.jolheiser.com/beaver/color"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
app := cmd.NewApp()
color.Fatal = color.Error // Easier to read, doesn't need to stand out as much in a CLI
if err := app.Run(os.Args); err != nil {
beaver.Fatal(err)
log.Fatal().Err(err).Msg("")
}
}

View File

@ -2,6 +2,14 @@ package registry
import "fmt"
type ErrTemplateExists struct {
Name string
}
func (e ErrTemplateExists) Error() string {
return fmt.Sprintf("template %s already exists", e.Name)
}
type ErrTemplateNotFound struct {
Name string
}
@ -10,11 +18,6 @@ func (e ErrTemplateNotFound) Error() string {
return fmt.Sprintf("template not found for %s", e.Name)
}
func IsErrTemplateNotFound(err error) bool {
_, ok := err.(ErrTemplateNotFound)
return ok
}
type ErrSourceNotFound struct {
Name string
}
@ -22,8 +25,3 @@ type ErrSourceNotFound struct {
func (e ErrSourceNotFound) Error() string {
return fmt.Sprintf("Source not found for %s", e.Name)
}
func IsErrSourceNotFound(err error) bool {
_, ok := err.(ErrSourceNotFound)
return ok
}

View File

@ -9,9 +9,7 @@ import (
"github.com/huandu/xstrings"
)
var funcMap = map[string]interface{}{
// String conversions
var funcMap = map[string]any{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
@ -21,9 +19,10 @@ var funcMap = map[string]interface{}{
"camel": func(in string) string {
return xstrings.FirstRuneToLower(xstrings.ToCamelCase(in))
},
// Other
"env": os.Getenv,
"trim_prefix": strings.TrimPrefix,
"trim_suffix": strings.TrimSuffix,
"replace": strings.ReplaceAll,
"env": os.Getenv,
"sep": func() string {
return string(filepath.Separator)
},

View File

@ -4,127 +4,128 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"text/template"
"github.com/AlecAivazis/survey/v2"
"github.com/pelletier/go-toml"
"go.jolheiser.com/tmpl/config"
"github.com/charmbracelet/huh"
)
type templatePrompt struct {
Key string `toml:"-"`
Value interface{} `toml:"-"`
Message string `toml:"prompt"`
Help string `toml:"help"`
Default interface{} `toml:"default"`
}
func prompt(dir string, defaults bool) (templatePrompts, error) {
templatePath := filepath.Join(dir, "template.toml")
if _, err := os.Lstat(templatePath); err != nil {
func prompt(dir string, defaults, accessible bool) (templatePrompts, error) {
templatePath := filepath.Join(dir, "tmpl.yaml")
fi, err := os.Open(templatePath)
if err != nil {
return nil, err
}
defer fi.Close()
tree, err := toml.LoadFile(templatePath)
cfg, err := config.Load(fi)
if err != nil {
return nil, err
}
prompts := make(templatePrompts, len(tree.Keys()))
for idx, k := range tree.Keys() {
v := tree.Get(k)
prompts := make(templatePrompts, 0, len(cfg.Prompts))
for _, prompt := range cfg.Prompts {
tp := templatePrompt{
Prompt: prompt,
}
if tp.Label == "" {
tp.Label = tp.ID
}
prompts = append(prompts, tp)
}
obj, ok := v.(*toml.Tree)
if !ok {
prompts[idx] = templatePrompt{
Key: k,
Message: k,
Default: v,
}
for idx, prompt := range prompts {
// Check for env variable
envKey := strings.ToUpper(prompt.ID)
if e, ok := os.LookupEnv(fmt.Sprintf("TMPL_VAR_%s", envKey)); ok {
prompts[idx].Value = e
os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), e)
continue
}
var p templatePrompt
if err := obj.Unmarshal(&p); err != nil {
return nil, err
// Check if we are using defaults
if defaults {
val := os.ExpandEnv(prompt.Default)
prompts[idx].Value = val
os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), val)
continue
}
p.Key = k
if p.Message == "" {
p.Message = p.Key
}
if p.Default == nil {
p.Default = ""
}
prompts[idx] = p
}
// Return early if we only want defaults
if defaults {
return prompts, nil
}
// Sort the prompts so they are consistent
sort.Sort(prompts)
for idx, prompt := range prompts {
var p survey.Prompt
switch t := prompt.Default.(type) {
case []string:
p = &survey.Select{
Message: prompt.Message,
Options: t,
Help: prompt.Help,
// Otherwise, prompt
var f huh.Field
switch prompt.Type {
case config.PromptTypeSelect:
opts := make([]huh.Option[string], 0, len(prompt.Options))
for idy, opt := range prompt.Options {
o := os.ExpandEnv(opt)
opts[idy] = huh.NewOption(o, o)
}
def := prompt.Default
f = huh.NewSelect[string]().
Title(prompt.Label).
Description(prompt.Help).
Options(opts...).
Key(prompt.ID).
Value(&def)
case config.PromptTypeConfirm:
def, _ := strconv.ParseBool(os.ExpandEnv(prompt.Default))
f = huh.NewConfirm().
Title(prompt.Label).
Description(prompt.Help).
Key(prompt.ID).
Value(&def)
case config.PromptTypeMultiline, config.PromptTypeEditor:
def := os.ExpandEnv(prompt.Default)
f = huh.NewText().
Title(prompt.Label).
Description(prompt.Help).
Key(prompt.ID).
Value(&def)
default:
p = &survey.Input{
Message: prompt.Message,
Default: fmt.Sprintf("%v", t),
Help: prompt.Help,
}
def := os.ExpandEnv(prompt.Default)
f = huh.NewInput().
Title(prompt.Label).
Description(prompt.Help).
Key(prompt.ID).
Value(&def)
}
var a string
if err := survey.AskOne(p, &a); err != nil {
return nil, err
if err := huh.NewForm(huh.NewGroup(f)).WithAccessible(accessible).WithTheme(huh.ThemeCatppuccin()).Run(); err != nil {
return nil, fmt.Errorf("could not run field: %w", err)
}
prompts[idx].Value = a
prompts[idx].Value = f.GetValue()
os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), fmt.Sprint(f.GetValue()))
}
return prompts, nil
}
type templatePrompt struct {
config.Prompt
Value any
}
type templatePrompts []templatePrompt
func (t templatePrompts) ToMap() map[string]interface{} {
m := make(map[string]interface{})
// ToMap converts a slice to templatePrompt into a suitable template context
func (t templatePrompts) ToMap() map[string]any {
m := make(map[string]any)
for _, p := range t {
if p.Value != nil {
m[p.Key] = p.Value
continue
}
m[p.Key] = p.Default
m[p.ID] = p.Value
}
return m
}
// ToFuncMap converts a slice of templatePrompt into a suitable template.FuncMap
func (t templatePrompts) ToFuncMap() template.FuncMap {
m := make(map[string]interface{})
m := make(map[string]any)
for k, v := range t.ToMap() {
vv := v // Enclosure
m[k] = func() string {
return fmt.Sprintf("%v", vv)
m[k] = func() any {
return vv
}
}
return m
}
func (t templatePrompts) Len() int {
return len(t)
}
func (t templatePrompts) Less(i, j int) bool {
return t[i].Key > t[j].Key
}
func (t templatePrompts) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}

View File

@ -2,7 +2,6 @@ package registry
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -11,14 +10,14 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/mholt/archiver/v3"
"github.com/pelletier/go-toml"
"gopkg.in/yaml.v3"
)
// Registry is a collection of Template
type Registry struct {
dir string
Sources []*Source `toml:"sources"`
Templates []*Template `toml:"templates"`
Sources []*Source `yaml:"sources"`
Templates []*Template `yaml:"templates"`
}
func (r *Registry) save() error {
@ -26,7 +25,7 @@ func (r *Registry) save() error {
if err != nil {
return err
}
if err := toml.NewEncoder(fi).Encode(r); err != nil {
if err := yaml.NewEncoder(fi).Encode(r); err != nil {
return err
}
return fi.Close()
@ -34,7 +33,7 @@ func (r *Registry) save() error {
// MetaFilePath is the path to the Registry meta-file
func (r *Registry) MetaFilePath() string {
return filepath.Join(r.dir, "registry.toml")
return filepath.Join(r.dir, "registry.yaml")
}
// GetTemplate retrieves a Template from the Registry
@ -50,12 +49,16 @@ func (r *Registry) GetTemplate(name string) (*Template, error) {
// DownloadTemplate downloads and adds a new Template to the Registry
func (r *Registry) DownloadTemplate(name, repo, branch string) (*Template, error) {
if _, err := r.GetTemplate(name); err == nil {
return nil, ErrTemplateExists{Name: name}
}
t := &Template{
reg: r,
Name: name,
Repository: repo,
Branch: branch,
Created: time.Now(),
LastUpdate: time.Now(),
}
r.Templates = append(r.Templates, t)
@ -68,11 +71,15 @@ func (r *Registry) DownloadTemplate(name, repo, branch string) (*Template, error
// SaveTemplate saves a local Template to the Registry
func (r *Registry) SaveTemplate(name, path string) (*Template, error) {
if _, err := r.GetTemplate(name); err == nil {
return nil, ErrTemplateExists{Name: name}
}
t := &Template{
reg: r,
Name: name,
Path: path,
Created: time.Now(),
reg: r,
Name: name,
Path: path,
LastUpdate: time.Now(),
}
r.Templates = append(r.Templates, t)
@ -89,16 +96,48 @@ func (r *Registry) RemoveTemplate(name string) error {
if err != nil {
return err
}
for idx, t := range r.Templates {
if strings.EqualFold(name, t.Name) {
r.Templates = append(r.Templates[:idx], r.Templates[idx+1:]...)
if err := os.Remove(t.ArchivePath()); err != nil {
return err
}
return r.save()
}
}
return r.save()
return nil
}
// UpdateTemplate updates the Template on disk and in meta
func (r *Registry) UpdateTemplate(name string) error {
_, err := r.GetTemplate(name)
if err != nil {
return err
}
for idx, t := range r.Templates {
if strings.EqualFold(name, t.Name) {
// If the path doesn't exist, we are replacing it regardless
if err := os.Remove(t.ArchivePath()); err != nil && !os.IsNotExist(err) {
return err
}
// Cut it out of the template list so we don't get a duplicate
r.Templates = append(r.Templates[:idx], r.Templates[idx+1:]...)
// If path exists, it is local
if t.Path != "" {
_, err = r.SaveTemplate(t.Name, t.Path)
} else {
_, err = r.DownloadTemplate(t.Name, t.Repository, t.Branch)
}
return err
}
}
return nil
}
// GetSource retrieves a Source from the Registry
@ -155,11 +194,20 @@ func Open(dir string) (*Registry, error) {
}
}
tree, err := toml.LoadFile(reg.MetaFilePath())
contents, err := os.ReadFile(reg.MetaFilePath())
if err != nil {
return nil, err
}
return &reg, tree.Unmarshal(&reg)
if err := yaml.Unmarshal(contents, &reg); err != nil {
return nil, err
}
for _, tmpl := range reg.Templates {
tmpl.reg = &reg
}
return &reg, nil
}
func create(regFile string) error {
@ -174,7 +222,7 @@ func create(regFile string) error {
}
func download(cloneURL, branch, dest string) error {
tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
tmp, err := os.MkdirTemp(os.TempDir(), "tmpl")
if err != nil {
return err
}
@ -186,11 +234,12 @@ func download(cloneURL, branch, dest string) error {
ReferenceName: plumbing.NewBranchReferenceName(branch),
SingleBranch: true,
Depth: 1,
Progress: os.Stdout,
}); err != nil {
return err
}
// RemoveTemplate .git
// Remove .git
if err := os.RemoveAll(filepath.Join(tmp, ".git")); err != nil {
return err
}
@ -204,9 +253,8 @@ func download(cloneURL, branch, dest string) error {
}
func save(source, dest string) error {
// Make sure it's a valid template
if _, err := os.Lstat(filepath.Join(source, "template.toml")); err != nil {
if _, err := os.Lstat(filepath.Join(source, "tmpl.yaml")); err != nil {
return err
}
fi, err := os.Lstat(filepath.Join(source, "template"))

View File

@ -1,26 +1,23 @@
package registry
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/matryer/is"
)
var (
tmplDir string
regDir string
destDir string
reg *Registry
)
func TestMain(m *testing.M) {
var err error
destDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
if err != nil {
panic(err)
}
// Set up template
setupTemplate()
@ -30,9 +27,6 @@ func TestMain(m *testing.M) {
status := m.Run()
if err = os.RemoveAll(destDir); err != nil {
fmt.Printf("could not clean up temp directory %s\n", destDir)
}
if err = os.RemoveAll(tmplDir); err != nil {
fmt.Printf("could not clean up temp directory %s\n", tmplDir)
}
@ -51,37 +45,34 @@ func TestTemplate(t *testing.T) {
}
func testSave(t *testing.T) {
if _, err := reg.SaveTemplate("test", tmplDir); err != nil {
t.Log("could not save template")
t.FailNow()
}
assert := is.New(t)
_, err := reg.SaveTemplate("test", tmplDir)
assert.NoErr(err) // Should save template
}
func testGet(t *testing.T) {
assert := is.New(t)
_, err := reg.GetTemplate("test")
if err != nil {
t.Logf("could not get template")
t.FailNow()
}
assert.NoErr(err) // Should get template
}
func testGetFail(t *testing.T) {
assert := is.New(t)
_, err := reg.GetTemplate("fail")
if !IsErrTemplateNotFound(err) {
t.Logf("template should not exist")
t.FailNow()
if !errors.As(err, &ErrTemplateNotFound{}) {
assert.Fail() // Template should not exist
}
}
func setupTemplate() {
var err error
tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
tmplDir, err = os.MkdirTemp(os.TempDir(), "tmpl-setup")
if err != nil {
panic(err)
}
// Template config
fi, err := os.Create(filepath.Join(tmplDir, "template.toml"))
fi, err := os.Create(filepath.Join(tmplDir, "tmpl.yaml"))
if err != nil {
panic(err)
}
@ -98,7 +89,8 @@ func setupTemplate() {
if err := os.MkdirAll(pkgPath, os.ModePerm); err != nil {
panic(err)
}
fi, err = os.Create(filepath.Join(pkgPath, ".keep"))
// .tmplkeep file
fi, err = os.Create(filepath.Join(pkgPath, ".tmplkeep"))
if err != nil {
panic(err)
}
@ -122,7 +114,7 @@ func setupTemplate() {
func setupRegistry() {
var err error
regDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
regDir, err = os.MkdirTemp(os.TempDir(), "tmpl-reg")
if err != nil {
panic(err)
}

View File

@ -5,8 +5,8 @@ import "fmt"
// Source is a quick way to specify a git source
// e.g. Gitea, GitHub, etc.
type Source struct {
Name string `toml:"name"`
URL string `toml:"url"`
Name string `yaml:"name"`
URL string `yaml:"url"`
}
// CloneURL constructs a URL suitable for cloning a repository

View File

@ -1,11 +1,14 @@
package registry
import (
"strings"
"testing"
"github.com/matryer/is"
)
func TestSource(t *testing.T) {
assert := is.New(t)
tt := []struct {
Name string
Source *Source
@ -38,10 +41,7 @@ func TestSource(t *testing.T) {
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
cloneURL := tc.Source.CloneURL(namespace)
if !strings.EqualFold(tc.CloneURL, cloneURL) {
t.Logf("incorrect clone URL:\n\tExpected: %s\n\tGot: %s\n", tc.CloneURL, cloneURL)
t.Fail()
}
assert.Equal(tc.CloneURL, cloneURL) // Clone URLs should match
})
}
}

View File

@ -3,24 +3,24 @@ package registry
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/Masterminds/sprig/v3"
"github.com/mholt/archiver/v3"
)
// Template is a tmpl project
type Template struct {
reg *Registry `toml:"-"`
Name string `toml:"name"`
Path string `toml:"path"`
Repository string `toml:"repository"`
Branch string `toml:"branch"`
Created time.Time `toml:"created"`
reg *Registry `yaml:"-"`
Name string `yaml:"name"`
Path string `yaml:"path"`
Repository string `yaml:"repository"`
Branch string `yaml:"branch"`
LastUpdate time.Time `yaml:"last_update"`
}
// ArchiveName is the name given to the archive for this Template
@ -34,8 +34,8 @@ func (t *Template) ArchivePath() string {
}
// Execute runs the Template and copies to dest
func (t *Template) Execute(dest string, defaults, overwrite bool) error {
tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
func (t *Template) Execute(dest string, defaults, accessible, overwrite bool) error {
tmp, err := os.MkdirTemp(os.TempDir(), "tmpl")
if err != nil {
return err
}
@ -45,13 +45,12 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error {
return err
}
prompts, err := prompt(tmp, defaults)
prompts, err := prompt(tmp, defaults, accessible)
if err != nil {
return err
}
funcs := mergeMaps(funcMap, prompts.ToFuncMap())
funcs := mergeMaps(funcMap, prompts.ToFuncMap(), sprig.TxtFuncMap())
base := filepath.Join(tmp, "template")
return filepath.Walk(base, func(walkPath string, walkInfo os.FileInfo, walkErr error) error {
if walkErr != nil {
@ -62,12 +61,12 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error {
return nil
}
contents, err := ioutil.ReadFile(walkPath)
contents, err := os.ReadFile(walkPath)
if err != nil {
return err
}
newDest := strings.TrimPrefix(walkPath, base+"/")
newDest := strings.TrimPrefix(walkPath, base+string(filepath.Separator))
newDest = filepath.Join(dest, newDest)
tmplDest, err := template.New("dest").Funcs(funcs).Parse(newDest)
@ -85,6 +84,11 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error {
return err
}
// Skip .tmplkeep files, after creating the directory structure
if strings.EqualFold(walkInfo.Name(), ".tmplkeep") {
return nil
}
oldFi, err := os.Lstat(walkPath)
if err != nil {
return err
@ -112,8 +116,8 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error {
})
}
func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{})
func mergeMaps(maps ...map[string]any) map[string]any {
m := make(map[string]any)
for _, mm := range maps {
for k, v := range mm {
m[k] = v

View File

@ -1,78 +1,77 @@
package registry
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/matryer/is"
)
var (
tmplContents = `{{title name}} {{.year}}`
tmplTemplate = `name = "john olheiser"
[year]
default = 2020
[package]
default = "pkg"`
tmplGold = "John Olheiser 2020"
tmplContents = `{{title name}} (@{{username}}) {{if .bool}}{{.year}}{{end}} {{org}}`
tmplTemplate = `
prompts:
- id: name
default: john olheiser
- id: year
default: ${TMPL_TEST} # 2020
- id: package
default: pkg
- id: bool
default: true
- id: username
default: username
- id: org
default: ${TMPL_PROMPT_USERNAME}/org
`
tmplGold = "John Olheiser (@jolheiser) 2020 jolheiser/org"
tmplNewGold = "DO NOT OVERWRITE!"
)
func testExecute(t *testing.T) {
assert := is.New(t)
destDir := t.TempDir()
// Set environment variable
err := os.Setenv("TMPL_TEST", "2020")
assert.NoErr(err) // Should set TMPL_TEST env
err = os.Setenv("TMPL_VAR_USERNAME", "jolheiser")
assert.NoErr(err) // Should set TMPL_VAR_USERNAME env
// Get template
tmpl, err := reg.GetTemplate("test")
if err != nil {
t.Logf("could not get template")
t.FailNow()
}
assert.NoErr(err) // Should get template
// Execute template
if err := tmpl.Execute(destDir, true, true); err != nil {
t.Logf("could not execute template: %v\n", err)
t.FailNow()
}
err = tmpl.Execute(destDir, true, true, true)
assert.NoErr(err) // Should execute template
// Check contents of file
testPath := filepath.Join(destDir, "TEST")
contents, err := ioutil.ReadFile(testPath)
if err != nil {
t.Logf("could not read file: %v\n", err)
t.FailNow()
}
if string(contents) != tmplGold {
t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplGold, string(contents))
t.FailNow()
}
contents, err := os.ReadFile(testPath)
assert.NoErr(err) // Should be able to read TEST file
assert.Equal(string(contents), tmplGold) // Template should match golden file
// Check if directory was created
pkgPath := filepath.Join(destDir, "PKG")
if _, err := os.Lstat(pkgPath); err != nil {
t.Logf("expected a directory at %s: %v\n", pkgPath, err)
t.FailNow()
}
_, err = os.Lstat(pkgPath)
assert.NoErr(err) // PKG directory should exist
// Check for .tmplkeep
tmplKeep := filepath.Join(pkgPath, ".tmplkeep")
_, err = os.Lstat(tmplKeep)
assert.True(err != nil) // .tmplkeep file should NOT be retained
// Change file to test non-overwrite
if err := ioutil.WriteFile(testPath, []byte(tmplNewGold), os.ModePerm); err != nil {
t.Logf("could not write file: %v\n", err)
t.FailNow()
}
err = os.WriteFile(testPath, []byte(tmplNewGold), os.ModePerm)
assert.NoErr(err) // Writing file should succeed
if err := tmpl.Execute(destDir, true, false); err != nil {
t.Logf("could not execute template: %v\n", err)
t.FailNow()
}
err = tmpl.Execute(destDir, true, true, false)
assert.NoErr(err) // Should execute template
contents, err = ioutil.ReadFile(testPath)
if err != nil {
t.Logf("could not read file: %v\n", err)
t.FailNow()
}
if string(contents) != tmplNewGold {
t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplNewGold, string(contents))
t.FailNow()
}
contents, err = os.ReadFile(testPath)
assert.NoErr(err) // Should be able to read file
assert.Equal(string(contents), tmplNewGold) // Template should match new golden file
}

46
schema/convert.go 100644
View File

@ -0,0 +1,46 @@
package schema
import (
"fmt"
"gopkg.in/yaml.v3"
)
// Unmarshal YAML to map[string]any instead of map[any]any.
func Unmarshal(in []byte, out any) error {
var res any
if err := yaml.Unmarshal(in, &res); err != nil {
return err
}
*out.(*any) = mapValue(res)
return nil
}
func mapSlice(in []any) []any {
res := make([]any, len(in))
for i, v := range in {
res[i] = mapValue(v)
}
return res
}
func mapMap(in map[any]any) map[string]any {
res := make(map[string]any)
for k, v := range in {
res[fmt.Sprintf("%v", k)] = mapValue(v)
}
return res
}
func mapValue(v any) any {
switch v := v.(type) {
case []any:
return mapSlice(v)
case map[any]any:
return mapMap(v)
default:
return v
}
}

49
schema/schema.go 100644
View File

@ -0,0 +1,49 @@
package schema
import (
_ "embed"
"fmt"
"io"
"strings"
"github.com/xeipuuv/gojsonschema"
)
var (
//go:embed tmpl.json
schema []byte
schemaLoader = gojsonschema.NewBytesLoader(schema)
)
// Lint is for linting a recipe against the schema
func Lint(r io.Reader) error {
data, err := io.ReadAll(r)
if err != nil {
return err
}
var m any
if err := Unmarshal(data, &m); err != nil {
return err
}
sourceLoader := gojsonschema.NewGoLoader(m)
result, err := gojsonschema.Validate(schemaLoader, sourceLoader)
if err != nil {
return err
}
if len(result.Errors()) > 0 {
return ResultErrors(result.Errors())
}
return nil
}
// ResultErrors is a slice of gojsonschema.ResultError that implements error
type ResultErrors []gojsonschema.ResultError
// Error implements error
func (r ResultErrors) Error() string {
errs := make([]string, 0, len(r))
for _, re := range r {
errs = append(errs, fmt.Sprintf("%s: %s", re.Field(), re.Description()))
}
return strings.Join(errs, " | ")
}

View File

@ -0,0 +1,42 @@
package schema
import (
"embed"
"errors"
"fmt"
"testing"
"github.com/matryer/is"
)
//go:embed testdata
var testdata embed.FS
func TestSchema(t *testing.T) {
tt := []struct {
Name string
NumErr int
}{
{Name: "good", NumErr: 0},
{Name: "bad", NumErr: 10},
{Name: "empty", NumErr: 1},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
assert := is.New(t)
fi, err := testdata.Open(fmt.Sprintf("testdata/%s.yaml", tc.Name))
assert.NoErr(err) // Should open test file
err = Lint(fi)
if tc.NumErr > 0 {
var rerrs ResultErrors
assert.True(errors.As(err, &rerrs)) // Error should be ResultErrors
assert.True(len(rerrs) == tc.NumErr) // Number of errors should match test case
} else {
assert.NoErr(err) // Good schemas shouldn't return errors
}
})
}
}

20
schema/testdata/bad.yaml vendored 100644
View File

@ -0,0 +1,20 @@
prompts:
- label: Bar
default: baz
help: |
This is a foobar!
options:
- "1"
- bonk
- "false"
- id: test
label: 1234
- id: test123
options: []
- label: 1234
default: false
help: # nil
options:
- 1
- 2
- true

0
schema/testdata/empty.yaml vendored 100644
View File

10
schema/testdata/good.yaml vendored 100644
View File

@ -0,0 +1,10 @@
prompts:
- id: foo
label: Bar
default: baz
help: |
This is a foobar!
options:
- "1"
- bonk
- "false"

75
schema/tmpl.json 100644
View File

@ -0,0 +1,75 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.jojodev.com/jolheiser/tmpl/raw/branch/main/schema/tmpl.json",
"title": "tmpl template",
"description": "A template for tmpl",
"type": "object",
"required": [
"prompts"
],
"additionalProperties": false,
"properties": {
"prompts": {
"description": "Template prompts",
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"id"
],
"additionalProperties": false,
"properties": {
"id": {
"description": "The unique prompt ID",
"type": "string"
},
"label": {
"description": "A label to show instead of the ID when prompting",
"type": "string"
},
"help": {
"description": "A help message for more information on a prompt",
"type": "string"
},
"default": {
"description": "A default value for the prompt",
"type": "string"
},
"options": {
"description": "A set of options for this prompt",
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"type": {
"description": "The type of prompt",
"type": "string",
"enum": ["input", "multi", "select", "confirm", "editor"]
}
},
"anyOf": [
{
"properties": {
"type": {
"const": "select"
}
},
"required": ["options"]
},
{
"properties": {
"type": {
"not": {
"const": "select"
}
}
}
}
]
}
}
}
}