Compare commits

..

No commits in common. "main" and "v0.0.7" have entirely different histories.
main ... v0.0.7

50 changed files with 723 additions and 1607 deletions

148
.drone.yml 100644
View File

@ -0,0 +1,148 @@
---
kind: pipeline
name: compliance
platform:
os: linux
arch: arm64
trigger:
event:
- pull_request
steps:
- name: build
pull: always
image: golang:1.15
environment:
GOPROXY: https://goproxy.cn
commands:
- make test
- make build
- name: vet
pull: always
image: golang:1.15
environment:
GOPROXY: https://goproxy.cn
commands:
- make vet
depends_on:
- build
- name: lint
pull: always
image: golangci/golangci-lint:v1.32
commands:
- golangci-lint --timeout 5m run
depends_on:
- build
---
kind: pipeline
name: release-main
platform:
os: linux
arch: amd64
trigger:
event:
- push
branch:
- main
steps:
- name: build
pull: always
image: golang:1.15
environment:
GOPROXY: https://goproxy.cn
VERSION: ${DRONE_COMMIT}
commands:
- make build
- name: build-windows
pull: always
image: golang:1.15
environment:
GOPROXY: https://goproxy.cn
VERSION: ${DRONE_COMMIT}
GOOS: windows
commands:
- make build
- name: gitea-release
pull: always
image: jolheiser/drone-gitea-main:latest
environment:
GOPROXY: https://goproxy.cn
settings:
token:
from_secret: gitea_token
base: https://gitea.com
files:
- "tmpl"
- "tmpl.exe"
depends_on:
- build
- build-windows
---
kind: pipeline
name: release-tag
platform:
os: linux
arch: amd64
trigger:
ref:
include:
- refs/tags/*
exclude:
- refs/tags/latest
steps:
- name: build
pull: always
image: golang:1.15
environment:
GOPROXY: https://goproxy.cn
VERSION: ${DRONE_TAG}
commands:
- make build
- name: build-windows
pull: always
image: golang:1.15
environment:
GOPROXY: https://goproxy.cn
GOOS: windows
VERSION: ${DRONE_TAG}
commands:
- make build
- name: gitea-release
pull: always
image: plugins/gitea-release:1
environment:
GOPROXY: https://goproxy.cn
settings:
api_key:
from_secret: gitea_token
base_url: https://gitea.com
files:
- "tmpl"
- "tmpl.exe"
depends_on:
- build
- build-windows
- name: gitea-prune
pull: always
image: jolheiser/drone-gitea-prune
environment:
GOPROXY: https://goproxy.cn
settings:
token:
from_secret: gitea_token
base: https://gitea.com
depends_on:
- gitea-release

View File

@ -1,6 +0,0 @@
<!--
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,5 +3,4 @@
# Binaries
/tmpl
/tmpl.exe
dist/
/tmpl.exe

View File

@ -1,25 +0,0 @@
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

View File

@ -1,39 +0,0 @@
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,10 +11,6 @@ tmpl
[--source|-s]=[value]
```
# DESCRIPTION
Template automation
**Usage**:
```
@ -36,18 +32,6 @@ 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
@ -60,10 +44,6 @@ List templates in the registry
Remove a template
## restore
Restore missing templates
## save
Save a local template

111
DOCS.md 100644
View File

@ -0,0 +1,111 @@
# 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
**NOTE:** The template.toml file will be expanded, though not with the full power of the template itself.
The template.toml 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).
```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 = "$USER"
```
## 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`

10
Earthfile 100644
View File

@ -0,0 +1,10 @@
# To lint, install Earthly and run `earth +lint`
# This ensures the usage of the same version of golangci-lint
FROM golangci/golangci-lint:v1.32
WORKDIR /tmpl
lint:
COPY . .
RUN golangci-lint --timeout 5m run

128
FAQ.md
View File

@ -1,128 +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 `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.

22
Makefile 100644
View File

@ -0,0 +1,22 @@
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](FAQ.md)
[Project Docs/FAQs](DOCS.md)
## Examples
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).
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).
## License

View File

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

View File

@ -2,22 +2,20 @@ package cmd
import (
"fmt"
"os"
"path"
"strings"
"go.jolheiser.com/tmpl/env"
"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 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",
@ -31,33 +29,25 @@ var Download = &cli.Command{
}
func runDownload(ctx *cli.Context) error {
if ctx.NArg() < 1 {
if ctx.NArg() < 2 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
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 sourceFlag != "" {
if flags.Source != "" {
for _, s := range reg.Sources {
if strings.EqualFold(s.Name, sourceFlag) {
if strings.EqualFold(s.Name, flags.Source) {
source = s
break
}
}
if source == nil {
return fmt.Errorf("could not find source for %s", sourceFlag)
return fmt.Errorf("could not find source for %s", flags.Source)
}
}
@ -69,27 +59,11 @@ func runDownload(ctx *cli.Context) error {
cloneURL += ".git"
}
t, err := reg.DownloadTemplate(deriveName(ctx), cloneURL, ctx.String("branch"))
t, err := reg.DownloadTemplate(ctx.Args().Get(1), cloneURL, ctx.String("branch"))
if err != nil {
return err
}
log.Info().Msgf("Added new template %q", t.Name)
beaver.Infof("Added new template %s", 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())
}

View File

@ -1,92 +0,0 @@
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

@ -0,0 +1,7 @@
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("tmpl.yaml"); !os.IsNotExist(err) {
if _, err := os.Lstat("template.toml"); !os.IsNotExist(err) {
if err != nil {
return err
}
return errors.New("tmpl.yaml already detected, aborting initialization")
return errors.New("template.toml already detected, aborting initialization")
}
if fi, err := os.Lstat("template"); !os.IsNotExist(err) {
if err != nil {
@ -32,29 +32,25 @@ func runInit(_ *cli.Context) error {
return errors.New("template directory already detected, aborting initialization")
}
fi, err := os.Create("tmpl.yaml")
fi, err := os.Create("template.toml")
if err != nil {
return err
}
if _, err := fi.WriteString(initConfig); err != nil {
if _, err := fi.WriteString(comments); err != nil {
return err
}
if err := os.Mkdir("template", os.ModePerm); err != nil {
return err
}
log.Info().Msg("Template initialized!")
beaver.Info("Template initialized!")
return fi.Close()
}
var initConfig = `# tmpl.yaml
var comments = `# template.toml
# Write any template args here to prompt the user for, giving any defaults/options as applicable
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
[name]
prompt = "Project Name"
help = "The name to use in the project"
default = "tmpl"
`

View File

@ -1,17 +0,0 @@
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,27 +15,17 @@ var List = &cli.Command{
Name: "list",
Usage: "List templates in the registry",
Description: "List all usable templates currently downloaded in the registry",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "json",
Usage: "JSON format",
},
},
Action: runList,
Action: runList,
}
func runList(ctx *cli.Context) error {
reg, err := registry.Open(registryFlag)
func runList(_ *cli.Context) error {
reg, err := registry.Open(flags.Registry)
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\tLAST UPDATED\n"); err != nil {
if _, err := fmt.Fprintf(wr, "NAME\tURL\tLOCAL\tUPDATED\n"); err != nil {
return err
}
for _, t := range reg.Templates {
@ -45,7 +35,7 @@ func runList(ctx *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.LastUpdate.Format("01/02/2006")); err != nil {
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 {
return err
}
}

View File

@ -1,10 +1,11 @@
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{
@ -20,7 +21,7 @@ func runRemove(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
if err != nil {
return err
}
@ -29,6 +30,6 @@ func runRemove(ctx *cli.Context) error {
return err
}
log.Info().Msgf("Successfully removed %q", ctx.Args().First())
beaver.Infof("Successfully removed %s", ctx.Args().First())
return nil
}

View File

@ -1,38 +0,0 @@
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,10 +3,11 @@ 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{
@ -22,7 +23,7 @@ func runSave(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
if err != nil {
return err
}
@ -38,6 +39,6 @@ func runSave(ctx *cli.Context) error {
return err
}
log.Info().Msgf("Added new template %q", t.Name)
beaver.Infof("Added new template %s", 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,13 +29,7 @@ var (
Name: "list",
Usage: "List available sources",
Description: "List all available sources in the registry",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "json",
Usage: "JSON format",
},
},
Action: runSourceList,
Action: runSourceList,
}
SourceAdd = &cli.Command{
@ -55,16 +49,12 @@ var (
}
)
func runSourceList(ctx *cli.Context) error {
reg, err := registry.Open(registryFlag)
func runSourceList(_ *cli.Context) error {
reg, err := registry.Open(flags.Registry)
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
@ -82,7 +72,7 @@ func runSourceAdd(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
if err != nil {
return err
}
@ -92,7 +82,7 @@ func runSourceAdd(ctx *cli.Context) error {
return err
}
log.Info().Msgf("Added new source %q", s.Name)
beaver.Infof("Added new source %s", s.Name)
return nil
}
@ -101,7 +91,7 @@ func runSourceRemove(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
if err != nil {
return err
}
@ -110,6 +100,6 @@ func runSourceRemove(ctx *cli.Context) error {
return err
}
log.Info().Msgf("Successfully removed source for %q", ctx.Args().First())
beaver.Infof("Successfully removed source for %s", ctx.Args().First())
return nil
}

View File

@ -1,15 +1,11 @@
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{
@ -27,38 +23,24 @@ func runTest(ctx *cli.Context) error {
}
var errs []string
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))
}
if _, err := os.Lstat(filepath.Join(testPath, "template.toml")); err != nil {
errs = append(errs, "could not find template.toml")
}
fstat, err := os.Lstat(filepath.Join(testPath, "template"))
fi, err := os.Lstat(filepath.Join(testPath, "template"))
if err != nil {
errs = append(errs, "no template directory found")
}
if err == nil && !fstat.IsDir() {
if err == nil && !fi.IsDir() {
errs = append(errs, "template path is a file, not a directory")
}
if len(errs) > 0 {
for _, err := range errs {
log.Error().Msg(err)
beaver.Error(err)
}
return nil
}
log.Info().Msg("This is a valid tmpl template!")
beaver.Info("this is a valid tmpl template")
return nil
}

View File

@ -1,10 +1,11 @@
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{
@ -20,7 +21,7 @@ func runUpdate(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
if err != nil {
return err
}
@ -30,10 +31,19 @@ func runUpdate(ctx *cli.Context) error {
return err
}
if err := reg.UpdateTemplate(tmpl.Name); err != nil {
if err := reg.RemoveTemplate(tmpl.Name); err != nil {
return err
}
log.Info().Msgf("Successfully updated %q", tmpl.Name)
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)
return nil
}

View File

@ -1,11 +1,11 @@
package cmd
import (
"go.jolheiser.com/tmpl/env"
"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 Use = &cli.Command{
@ -21,10 +21,6 @@ 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,
@ -40,28 +36,20 @@ func runUse(ctx *cli.Context) error {
dest = ctx.Args().Get(1)
}
reg, err := registry.Open(registryFlag)
reg, err := registry.Open(flags.Registry)
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("accessible"), ctx.Bool("force")); err != nil {
if err := tmpl.Execute(dest, ctx.Bool("defaults"), ctx.Bool("force")); err != nil {
return err
}
log.Info().Msgf("Successfully executed %q", tmpl.Name)
beaver.Infof("Successfully executed %s", tmpl.Name)
return nil
}

View File

@ -1,54 +0,0 @@
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

@ -1,113 +0,0 @@
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
]

View File

@ -1,4 +1,3 @@
//go:build docs
// +build docs
package main
@ -11,7 +10,6 @@ import (
"go.jolheiser.com/tmpl/cmd"
)
//go:generate go run cli.go
func main() {
app := cmd.NewApp()
@ -30,7 +28,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)

50
env/env.go vendored
View File

@ -1,50 +0,0 @@
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)
}

View File

@ -1,26 +0,0 @@
{
"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
}

View File

@ -1,37 +0,0 @@
{
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,86 +1,13 @@
module go.jolheiser.com/tmpl
go 1.21
go 1.15
require (
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/AlecAivazis/survey/v2 v2.2.2
github.com/go-git/go-git/v5 v5.2.0
github.com/huandu/xstrings v1.3.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
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
)

314
go.sum
View File

@ -1,282 +1,140 @@
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/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/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/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/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/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/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/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
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/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 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
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.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/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/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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/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/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.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/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
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.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/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/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
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/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/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
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/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
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/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/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/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
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/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/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/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/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/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/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/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/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
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=
go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk=
go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g=
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-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/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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/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/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-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-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-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.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/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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,15 +5,14 @@ import (
"go.jolheiser.com/tmpl/cmd"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"go.jolheiser.com/beaver"
"go.jolheiser.com/beaver/color"
)
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 {
log.Fatal().Err(err).Msg("")
beaver.Fatal(err)
}
}

View File

@ -2,14 +2,6 @@ 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
}
@ -18,6 +10,11 @@ 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
}
@ -25,3 +22,8 @@ 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,7 +9,9 @@ import (
"github.com/huandu/xstrings"
)
var funcMap = map[string]any{
var funcMap = map[string]interface{}{
// String conversions
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
@ -19,10 +21,9 @@ var funcMap = map[string]any{
"camel": func(in string) string {
return xstrings.FirstRuneToLower(xstrings.ToCamelCase(in))
},
"trim_prefix": strings.TrimPrefix,
"trim_suffix": strings.TrimSuffix,
"replace": strings.ReplaceAll,
"env": os.Getenv,
// Other
"env": os.Getenv,
"sep": func() string {
return string(filepath.Separator)
},

View File

@ -2,130 +2,155 @@ package registry
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sort"
"text/template"
"go.jolheiser.com/tmpl/config"
"github.com/charmbracelet/huh"
"github.com/AlecAivazis/survey/v2"
"github.com/pelletier/go-toml"
)
func prompt(dir string, defaults, accessible bool) (templatePrompts, error) {
templatePath := filepath.Join(dir, "tmpl.yaml")
fi, err := os.Open(templatePath)
if err != nil {
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 {
return nil, err
}
defer fi.Close()
cfg, err := config.Load(fi)
templateBytes, err := ioutil.ReadFile(templatePath)
if err != nil {
return nil, err
}
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)
// Expand the template with environment variables
templateContents := os.ExpandEnv(string(templateBytes))
tree, err := toml.Load(templateContents)
if err != nil {
return nil, err
}
prompts := make(templatePrompts, len(tree.Keys()))
for idx, k := range tree.Keys() {
v := tree.Get(k)
obj, ok := v.(*toml.Tree)
if !ok {
prompts[idx] = templatePrompt{
Key: k,
Message: k,
Default: v,
}
continue
}
var p templatePrompt
if err := obj.Unmarshal(&p); err != nil {
return nil, err
}
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 {
// 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
}
// 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
}
// 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)
var p survey.Prompt
switch t := prompt.Default.(type) {
case []string:
p = &survey.Select{
Message: prompt.Message,
Options: t,
Help: prompt.Help,
}
case bool:
p = &survey.Confirm{
Message: prompt.Message,
Default: t,
Help: prompt.Help,
}
case string:
p = &survey.Input{
Message: prompt.Message,
Default: t,
Help: prompt.Help,
}
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:
def := os.ExpandEnv(prompt.Default)
f = huh.NewInput().
Title(prompt.Label).
Description(prompt.Help).
Key(prompt.ID).
Value(&def)
p = &survey.Input{
Message: prompt.Message,
Default: fmt.Sprintf("%v", t),
Help: prompt.Help,
}
}
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)
var a string
if err := survey.AskOne(p, &a); err != nil {
return nil, err
}
prompts[idx].Value = f.GetValue()
os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), fmt.Sprint(f.GetValue()))
prompts[idx].Value = a
}
return prompts, nil
}
type templatePrompt struct {
config.Prompt
Value any
}
type templatePrompts []templatePrompt
// ToMap converts a slice to templatePrompt into a suitable template context
func (t templatePrompts) ToMap() map[string]any {
m := make(map[string]any)
func (t templatePrompts) ToMap() map[string]interface{} {
m := make(map[string]interface{})
for _, p := range t {
m[p.ID] = p.Value
if p.Value != nil {
m[p.Key] = p.Value
continue
}
m[p.Key] = p.Default
}
return m
}
// ToFuncMap converts a slice of templatePrompt into a suitable template.FuncMap
func (t templatePrompts) ToFuncMap() template.FuncMap {
m := make(map[string]any)
m := make(map[string]interface{})
for k, v := range t.ToMap() {
vv := v // Enclosure
m[k] = func() any {
m[k] = func() interface{} {
return vv
}
}
return m
}
// Len is for sort.Sort
func (t templatePrompts) Len() int {
return len(t)
}
// Less is for sort.Sort
func (t templatePrompts) Less(i, j int) bool {
return t[i].Key > t[j].Key
}
// Swap is for sort.Sort
func (t templatePrompts) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}

View File

@ -2,6 +2,7 @@ package registry
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -10,14 +11,14 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/mholt/archiver/v3"
"gopkg.in/yaml.v3"
"github.com/pelletier/go-toml"
)
// Registry is a collection of Template
type Registry struct {
dir string
Sources []*Source `yaml:"sources"`
Templates []*Template `yaml:"templates"`
Sources []*Source `toml:"sources"`
Templates []*Template `toml:"templates"`
}
func (r *Registry) save() error {
@ -25,7 +26,7 @@ func (r *Registry) save() error {
if err != nil {
return err
}
if err := yaml.NewEncoder(fi).Encode(r); err != nil {
if err := toml.NewEncoder(fi).Encode(r); err != nil {
return err
}
return fi.Close()
@ -33,7 +34,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.yaml")
return filepath.Join(r.dir, "registry.toml")
}
// GetTemplate retrieves a Template from the Registry
@ -49,16 +50,12 @@ 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,
LastUpdate: time.Now(),
Created: time.Now(),
}
r.Templates = append(r.Templates, t)
@ -71,15 +68,11 @@ 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,
LastUpdate: time.Now(),
reg: r,
Name: name,
Path: path,
Created: time.Now(),
}
r.Templates = append(r.Templates, t)
@ -96,48 +89,16 @@ 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 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
return r.save()
}
// GetSource retrieves a Source from the Registry
@ -194,20 +155,11 @@ func Open(dir string) (*Registry, error) {
}
}
contents, err := os.ReadFile(reg.MetaFilePath())
tree, err := toml.LoadFile(reg.MetaFilePath())
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(contents, &reg); err != nil {
return nil, err
}
for _, tmpl := range reg.Templates {
tmpl.reg = &reg
}
return &reg, nil
return &reg, tree.Unmarshal(&reg)
}
func create(regFile string) error {
@ -222,7 +174,7 @@ func create(regFile string) error {
}
func download(cloneURL, branch, dest string) error {
tmp, err := os.MkdirTemp(os.TempDir(), "tmpl")
tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
if err != nil {
return err
}
@ -234,12 +186,11 @@ func download(cloneURL, branch, dest string) error {
ReferenceName: plumbing.NewBranchReferenceName(branch),
SingleBranch: true,
Depth: 1,
Progress: os.Stdout,
}); err != nil {
return err
}
// Remove .git
// RemoveTemplate .git
if err := os.RemoveAll(filepath.Join(tmp, ".git")); err != nil {
return err
}
@ -253,8 +204,9 @@ 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, "tmpl.yaml")); err != nil {
if _, err := os.Lstat(filepath.Join(source, "template.toml")); err != nil {
return err
}
fi, err := os.Lstat(filepath.Join(source, "template"))

View File

@ -1,23 +1,26 @@
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()
@ -27,6 +30,9 @@ 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)
}
@ -45,34 +51,37 @@ func TestTemplate(t *testing.T) {
}
func testSave(t *testing.T) {
assert := is.New(t)
_, err := reg.SaveTemplate("test", tmplDir)
assert.NoErr(err) // Should save template
if _, err := reg.SaveTemplate("test", tmplDir); err != nil {
t.Log("could not save template")
t.FailNow()
}
}
func testGet(t *testing.T) {
assert := is.New(t)
_, err := reg.GetTemplate("test")
assert.NoErr(err) // Should get template
if err != nil {
t.Logf("could not get template")
t.FailNow()
}
}
func testGetFail(t *testing.T) {
assert := is.New(t)
_, err := reg.GetTemplate("fail")
if !errors.As(err, &ErrTemplateNotFound{}) {
assert.Fail() // Template should not exist
if !IsErrTemplateNotFound(err) {
t.Logf("template should not exist")
t.FailNow()
}
}
func setupTemplate() {
var err error
tmplDir, err = os.MkdirTemp(os.TempDir(), "tmpl-setup")
tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
if err != nil {
panic(err)
}
// Template config
fi, err := os.Create(filepath.Join(tmplDir, "tmpl.yaml"))
fi, err := os.Create(filepath.Join(tmplDir, "template.toml"))
if err != nil {
panic(err)
}
@ -89,8 +98,7 @@ func setupTemplate() {
if err := os.MkdirAll(pkgPath, os.ModePerm); err != nil {
panic(err)
}
// .tmplkeep file
fi, err = os.Create(filepath.Join(pkgPath, ".tmplkeep"))
fi, err = os.Create(filepath.Join(pkgPath, ".keep"))
if err != nil {
panic(err)
}
@ -114,7 +122,7 @@ func setupTemplate() {
func setupRegistry() {
var err error
regDir, err = os.MkdirTemp(os.TempDir(), "tmpl-reg")
regDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
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 `yaml:"name"`
URL string `yaml:"url"`
Name string `toml:"name"`
URL string `toml:"url"`
}
// CloneURL constructs a URL suitable for cloning a repository

View File

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

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 `yaml:"-"`
Name string `yaml:"name"`
Path string `yaml:"path"`
Repository string `yaml:"repository"`
Branch string `yaml:"branch"`
LastUpdate time.Time `yaml:"last_update"`
reg *Registry `toml:"-"`
Name string `toml:"name"`
Path string `toml:"path"`
Repository string `toml:"repository"`
Branch string `toml:"branch"`
Created time.Time `toml:"created"`
}
// 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, accessible, overwrite bool) error {
tmp, err := os.MkdirTemp(os.TempDir(), "tmpl")
func (t *Template) Execute(dest string, defaults, overwrite bool) error {
tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
if err != nil {
return err
}
@ -45,12 +45,13 @@ func (t *Template) Execute(dest string, defaults, accessible, overwrite bool) er
return err
}
prompts, err := prompt(tmp, defaults, accessible)
prompts, err := prompt(tmp, defaults)
if err != nil {
return err
}
funcs := mergeMaps(funcMap, prompts.ToFuncMap(), sprig.TxtFuncMap())
funcs := mergeMaps(funcMap, prompts.ToFuncMap())
base := filepath.Join(tmp, "template")
return filepath.Walk(base, func(walkPath string, walkInfo os.FileInfo, walkErr error) error {
if walkErr != nil {
@ -61,12 +62,12 @@ func (t *Template) Execute(dest string, defaults, accessible, overwrite bool) er
return nil
}
contents, err := os.ReadFile(walkPath)
contents, err := ioutil.ReadFile(walkPath)
if err != nil {
return err
}
newDest := strings.TrimPrefix(walkPath, base+string(filepath.Separator))
newDest := strings.TrimPrefix(walkPath, base+"/")
newDest = filepath.Join(dest, newDest)
tmplDest, err := template.New("dest").Funcs(funcs).Parse(newDest)
@ -84,11 +85,6 @@ func (t *Template) Execute(dest string, defaults, accessible, overwrite bool) er
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
@ -116,8 +112,8 @@ func (t *Template) Execute(dest string, defaults, accessible, overwrite bool) er
})
}
func mergeMaps(maps ...map[string]any) map[string]any {
m := make(map[string]any)
func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{})
for _, mm := range maps {
for k, v := range mm {
m[k] = v

View File

@ -1,77 +1,89 @@
package registry
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/matryer/is"
)
var (
tmplContents = `{{title name}} (@{{username}}) {{if .bool}}{{.year}}{{end}} {{org}}`
tmplContents = `{{title name}} {{if .bool}}{{.year}}{{end}}`
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
name = "john olheiser"
[year]
default = ${TMPL_TEST} # 2020
[package]
default = "pkg"
[bool]
default = true
`
tmplGold = "John Olheiser (@jolheiser) 2020 jolheiser/org"
tmplGold = "John Olheiser 2020"
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
if err := os.Setenv("TMPL_TEST", "2020"); err != nil {
t.Logf("could not set environment: %v", err)
t.FailNow()
}
// Get template
tmpl, err := reg.GetTemplate("test")
assert.NoErr(err) // Should get template
if err != nil {
t.Logf("could not get template")
t.FailNow()
}
// Execute template
err = tmpl.Execute(destDir, true, true, true)
assert.NoErr(err) // Should execute template
if err := tmpl.Execute(destDir, true, true); err != nil {
t.Logf("could not execute template: %v\n", err)
t.FailNow()
}
// Check contents of file
testPath := filepath.Join(destDir, "TEST")
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
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()
}
// Check if directory was created
pkgPath := filepath.Join(destDir, "PKG")
_, 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
if _, err := os.Lstat(pkgPath); err != nil {
t.Logf("expected a directory at %s: %v\n", pkgPath, err)
t.FailNow()
}
// Change file to test non-overwrite
err = os.WriteFile(testPath, []byte(tmplNewGold), os.ModePerm)
assert.NoErr(err) // Writing file should succeed
if err := ioutil.WriteFile(testPath, []byte(tmplNewGold), os.ModePerm); err != nil {
t.Logf("could not write file: %v\n", err)
t.FailNow()
}
err = tmpl.Execute(destDir, true, true, false)
assert.NoErr(err) // Should execute template
if err := tmpl.Execute(destDir, true, false); err != nil {
t.Logf("could not execute template: %v\n", err)
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
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()
}
}

View File

@ -1,46 +0,0 @@
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
}
}

View File

@ -1,49 +0,0 @@
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

@ -1,42 +0,0 @@
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
}
})
}
}

View File

@ -1,20 +0,0 @@
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

View File

View File

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

View File

@ -1,75 +0,0 @@
{
"$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"
}
}
}
}
]
}
}
}
}