feat: templates list command (#1134)

* feat: templates list command

* linter placation
This commit is contained in:
Luke Kingland 2022-08-10 13:37:58 -06:00 committed by GitHub
parent 849c2cd7a1
commit 2f8d82bec2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 324 additions and 4 deletions

View File

@ -27,13 +27,13 @@ DESCRIPTION
This includes embedded (included) language runtimes as well as any installed This includes embedded (included) language runtimes as well as any installed
via the 'repositories add' command. via the 'repositories add' command.
To specify a URI of a single, specific repository for which languages
should be displayed, use the --repository flag.
Installed repositories are by default located at ~/.func/repositories Installed repositories are by default located at ~/.func/repositories
($XDG_CONFIG_HOME/.func/repositories). This can be overridden with ($XDG_CONFIG_HOME/.func/repositories). This can be overridden with
$FUNC_REPOSITORIES_PATH. $FUNC_REPOSITORIES_PATH.
To specify a URI of a single, specific repository for which languages
should be displayed, use the --repository flag.
To see templates available for a given language, see the 'templates' command. To see templates available for a given language, see the 'templates' command.
@ -53,7 +53,7 @@ EXAMPLES
PreRunE: bindEnv("json", "repository"), PreRunE: bindEnv("json", "repository"),
} }
cmd.Flags().BoolP("json", "", false, "Set output to JSON format: $FUNC_JSON)") cmd.Flags().BoolP("json", "", false, "Set output to JSON format. (Env: $FUNC_JSON)")
cmd.Flags().StringP("repository", "r", "", "URI to a specific repository to consider (Env: $FUNC_REPOSITORY)") cmd.Flags().StringP("repository", "r", "", "URI to a specific repository to consider (Env: $FUNC_REPOSITORY)")
cmd.SetHelpFunc(defaultTemplatedHelp) cmd.SetHelpFunc(defaultTemplatedHelp)

View File

@ -99,6 +99,8 @@ EXAMPLES
cmd.AddCommand(NewCompletionCmd()) cmd.AddCommand(NewCompletionCmd())
cmd.AddCommand(NewVersionCmd(config.Version)) cmd.AddCommand(NewVersionCmd(config.Version))
cmd.AddCommand(NewLanguagesCmd(newClient)) cmd.AddCommand(NewLanguagesCmd(newClient))
cmd.AddCommand(NewTemplatesCmd(newClient))
cmd.AddCommand(NewLanguagesCmd(newClient))
// Help // Help
// Overridden to process the help text as a template and have // Overridden to process the help text as a template and have

176
cmd/templates.go Normal file
View File

@ -0,0 +1,176 @@
package cmd
import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/ory/viper"
"github.com/spf13/cobra"
fn "knative.dev/kn-plugin-func"
)
func NewTemplatesCmd(newClient ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "templates",
Short: "Templates",
Long: `
NAME
{{.Name}} templates - list available templates
SYNOPSIS
{{.Name}} templates [language] [--json] [-r|--repository]
DESCRIPTION
List all templates available, optionally for a specific language runtime.
To specify a URI of a single, specific repository for which templates
should be displayed, use the --repository flag.
Installed repositories are by default located at ~/.func/repositories
($XDG_CONFIG_HOME/.func/repositories). This can be overridden with
$FUNC_REPOSITORIES_PATH.
To see all available language runtimes, see the 'languages' command.
EXAMPLES
o Show a list of all available templates grouped by language runtime
$ {{.Name}} templates
o Show a list of all templates for the Go runtime
$ {{.Name}} templates go
o Return a list of all template runtimes in JSON output format
$ {{.Name}} templates --json
o Return Go templates in a specific repository
$ {{.Name}} templates go --repository=https://github.com/boson-project/func-templates
`,
SuggestFor: []string{"template", "templtaes", "templatse", "remplates",
"gemplates", "yemplates", "tenplates", "tekplates", "tejplates",
"temolates", "temllates", "temppates", "tempmates", "tempkates",
"templstes", "templztes", "templqtes", "templares", "templages", //nolint:misspell
"templayes", "templatee", "templatea", "templated", "templatew"},
PreRunE: bindEnv("json", "repository"),
}
cmd.Flags().BoolP("json", "", false, "Set output to JSON format. (Env: $FUNC_JSON)")
cmd.Flags().StringP("repository", "r", "", "URI to a specific repository to consider (Env: $FUNC_REPOSITORY)")
cmd.SetHelpFunc(defaultTemplatedHelp)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
return runTemplates(cmd, args, newClient)
}
return cmd
}
func runTemplates(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
// Gather config
cfg, err := newTemplatesConfig(newClient)
if err != nil {
return
}
// Client which will provide data
client, done := newClient(ClientConfig{Verbose: cfg.Verbose},
fn.WithRepository(cfg.Repository), // Use exactly this repo OR
fn.WithRepositoriesPath(cfg.RepositoriesPath)) // Path on disk to installed repos
defer done()
// For a singl language runtime
// -------------------
if len(args) == 1 {
templates, err := client.Templates().List(args[0])
if err != nil {
return err
}
if cfg.JSON {
s, err := json.MarshalIndent(templates, "", " ")
if err != nil {
return err
}
fmt.Fprintln(cmd.OutOrStdout(), string(s))
} else {
for _, template := range templates {
fmt.Fprintln(cmd.OutOrStdout(), template)
}
}
return nil
} else if len(args) > 1 {
return errors.New("unexpected extra arguments.")
}
// All language runtimes
// ------------
runtimes, err := client.Runtimes()
if err != nil {
return
}
if cfg.JSON {
// Gather into a single data structure for printing as json
templateMap := make(map[string][]string)
for _, runtime := range runtimes {
templates, err := client.Templates().List(runtime)
if err != nil {
return err
}
templateMap[runtime] = templates
}
s, err := json.MarshalIndent(templateMap, "", " ")
if err != nil {
return err
}
fmt.Fprintln(cmd.OutOrStdout(), string(s))
} else {
// print using a formatted writer (sorted)
builder := strings.Builder{}
writer := tabwriter.NewWriter(&builder, 0, 0, 3, ' ', 0)
fmt.Fprint(writer, "LANGUAGE\tTEMPLATE\n")
for _, runtime := range runtimes {
templates, err := client.Templates().List(runtime)
if err != nil {
return err
}
for _, template := range templates {
fmt.Fprintf(writer, "%v\t%v\n", runtime, template)
}
}
writer.Flush()
fmt.Fprint(cmd.OutOrStdout(), builder.String())
}
return
}
type templatesConfig struct {
Verbose bool
Repository string // Consider only a specific repository (URI)
RepositoriesPath string // Override location on disk of "installed" repos
JSON bool // output as JSON
}
func newTemplatesConfig(newClient ClientFactory) (cfg templatesConfig, err error) {
// Repositories Path
// Not exposed as a flag due to potential confusion with the more likely
// "repository" flag, but still available as an environment variable
repositoriesPath := os.Getenv("FUNC_REPOSITORIES_PATH")
if repositoriesPath == "" { // if no env var provided
repositoriesPath = fn.New().RepositoriesPath() // default to ~/.config/func/repositories
}
cfg = templatesConfig{
Verbose: viper.GetBool("verbose"),
Repository: viper.GetString("repository"),
RepositoriesPath: repositoriesPath,
JSON: viper.GetBool("json"),
}
return
}

142
cmd/templates_test.go Normal file
View File

@ -0,0 +1,142 @@
package cmd
import (
"testing"
fn "knative.dev/kn-plugin-func"
. "knative.dev/kn-plugin-func/testing"
)
// TestTemplates_Default ensures that the default behavior is listing all
// templates for all language runtimes.
func TestTemplates_Default(t *testing.T) {
defer WithEnvVar(t, "XDG_CONFIG_HOME", t.TempDir())() // ignore user-added
buf := piped(t) // gather output
cmd := NewTemplatesCmd(NewClientFactory(func() *fn.Client {
return fn.New()
}))
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
expected := `LANGUAGE TEMPLATE
go cloudevents
go http
node cloudevents
node http
python cloudevents
python http
quarkus cloudevents
quarkus http
rust cloudevents
rust http
springboot cloudevents
springboot http
typescript cloudevents
typescript http`
output := buf()
if output != expected {
t.Fatalf("expected:\n'%v'\n\ngot:\n'%v'\n", expected, output)
}
}
// TestTemplates_JSON ensures that listing templates respects the --json
// output format.
func TestTemplates_JSON(t *testing.T) {
defer WithEnvVar(t, "XDG_CONFIG_HOME", t.TempDir())() // ignore user-added
buf := piped(t) // gather output
cmd := NewTemplatesCmd(NewClientFactory(func() *fn.Client {
return fn.New()
}))
cmd.SetArgs([]string{"--json"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
expected := `{
"go": [
"cloudevents",
"http"
],
"node": [
"cloudevents",
"http"
],
"python": [
"cloudevents",
"http"
],
"quarkus": [
"cloudevents",
"http"
],
"rust": [
"cloudevents",
"http"
],
"springboot": [
"cloudevents",
"http"
],
"typescript": [
"cloudevents",
"http"
]
}`
output := buf()
for i, c := range expected {
if len(output) <= i {
t.Fatalf("output missing character(s) '%v', '%s' and later\n", i, string(c))
}
if rune(output[i]) != c {
t.Fatalf("Character at index %v expected '%s', got '%s'\n", i, string(c), string(output[i]))
}
}
if output != expected {
t.Fatalf("expected:\n%v\ngot:\n%v\n", expected, output)
}
}
// TestTemplates_ByLanguage ensures that the output is correctly filtered
// by language runtime when provided.
func TestTemplates_ByLanguage(t *testing.T) {
defer WithEnvVar(t, "XDG_CONFIG_HOME", t.TempDir())() // ignore user-added
cmd := NewTemplatesCmd(NewClientFactory(func() *fn.Client {
return fn.New()
}))
// Test plain text
buf := piped(t)
cmd.SetArgs([]string{"go"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
expected := `cloudevents
http`
output := buf()
if output != expected {
t.Fatalf("expected plain text:\n'%v'\ngot:\n'%v'\n", expected, output)
}
// Test JSON output
buf = piped(t)
cmd.SetArgs([]string{"go", "--json"})
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
expected = `[
"cloudevents",
"http"
]`
output = buf()
if output != expected {
t.Fatalf("expected JSON:\n'%v'\ngot:\n'%v'\n", expected, output)
}
}