diff --git a/cmd/root.go b/cmd/root.go index 29f2c91cf..1016ec7d4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -58,6 +58,7 @@ func Init(version, artifactArch string) { proxyInit() docsInit() apiInit() + templateInit() } func Execute(version, artifactArch string) { diff --git a/cmd/template.go b/cmd/template.go new file mode 100644 index 000000000..ca3f79be1 --- /dev/null +++ b/cmd/template.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "github.com/speakeasy-api/speakeasy/internal/template" + "github.com/spf13/cobra" +) + +var templateCmd = &cobra.Command{ + Use: "template", + Short: "executes your template to render an OpenAPI file.", + Args: cobra.NoArgs, + RunE: runTemplate, +} + +func templateInit() { + templateCmd.Flags().StringP("template", "t", "", "template file") + templateCmd.MarkFlagRequired("template") + templateCmd.Flags().StringP("values", "v", "", "values file") + templateCmd.MarkFlagRequired("values") + templateCmd.Flags().StringP("out", "o", "", "output location") + templateCmd.MarkFlagRequired("out") + + rootCmd.AddCommand(templateCmd) +} + +func runTemplate(cmd *cobra.Command, args []string) error { + templateLocation, err := cmd.Flags().GetString("template") + if err != nil { + return err + } + + valuesLocation, err := cmd.Flags().GetString("values") + if err != nil { + return err + } + + outLocation, err := cmd.Flags().GetString("out") + if err != nil { + return err + } + + return template.Execute(templateLocation, valuesLocation, outLocation) +} diff --git a/go.mod b/go.mod index ee707fef3..23822bbfd 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/pb33f/libopenapi v0.14.1 github.com/pkg/errors v0.9.1 github.com/sethvargo/go-githubactions v1.1.0 + github.com/speakeasy-api/easytemplate v0.8.0 github.com/speakeasy-api/openapi-generation/v2 v2.239.1 github.com/speakeasy-api/openapi-overlay v0.3.0 github.com/speakeasy-api/sdk-gen-config v1.6.1 @@ -145,7 +146,6 @@ require ( github.com/sethvargo/go-envconfig v0.9.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/speakeasy-api/easytemplate v0.8.0 // indirect github.com/speakeasy-api/speakeasy-go-sdk v1.8.0 // indirect github.com/speakeasy-api/speakeasy-schemas v1.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect diff --git a/go.sum b/go.sum index 48846e8df..7bf6ed245 100644 --- a/go.sum +++ b/go.sum @@ -570,10 +570,6 @@ github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIK github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/speakeasy-api/easytemplate v0.8.0 h1:Qr69X+SCkGmMGtL64p3SzoaRl7x8wm8mI83Jroj5X/g= github.com/speakeasy-api/easytemplate v0.8.0/go.mod h1:1X8lPuZh77bK7bxItR8OXcu0zRQXJ3Hv9AcQb28lwjg= -github.com/speakeasy-api/openapi-generation/v2 v2.237.3 h1:0lQV5pn6SjFwLnSYzbPn91skMnrnAQws38DdlLgdLbc= -github.com/speakeasy-api/openapi-generation/v2 v2.237.3/go.mod h1:Y1bJxyd+Bhm2x2gTwmFwhkpP2Y/0hh/NLh9GLxCp/QE= -github.com/speakeasy-api/openapi-generation/v2 v2.239.0 h1:lMZyq5DyIN7WPGqwWvexweofw4gWfc7Cw5DXtA27IzE= -github.com/speakeasy-api/openapi-generation/v2 v2.239.0/go.mod h1:Y1bJxyd+Bhm2x2gTwmFwhkpP2Y/0hh/NLh9GLxCp/QE= github.com/speakeasy-api/openapi-generation/v2 v2.239.1 h1:JOMLG2O8fwPgY4BWQ1c2JsM4Fwb2zCUW6O9ULalis0E= github.com/speakeasy-api/openapi-generation/v2 v2.239.1/go.mod h1:Y1bJxyd+Bhm2x2gTwmFwhkpP2Y/0hh/NLh9GLxCp/QE= github.com/speakeasy-api/openapi-overlay v0.3.0 h1:+5hIbDzyO7rD0ix9num8Y0bxbhwzRF28QluZ+dCuugA= diff --git a/internal/template/template.go b/internal/template/template.go new file mode 100644 index 000000000..1b8a082d3 --- /dev/null +++ b/internal/template/template.go @@ -0,0 +1,70 @@ +package template + +import ( + "fmt" + "github.com/Masterminds/sprig/v3" + "github.com/speakeasy-api/easytemplate" + "gopkg.in/yaml.v3" + "os" + "path/filepath" + "strings" +) + +func execute(templateLocation string, values interface{}) (string, error) { + funcMap := map[string]any{ + "fromYaml": func(str string) map[string]interface{} { + m := map[string]interface{}{} + + if err := yaml.Unmarshal([]byte(str), &m); err != nil { + panic(err) + } + return m + }, + "toYaml": func(v interface{}) string { + data, err := yaml.Marshal(v) + if err != nil { + panic(err) + } + return strings.TrimSuffix(string(data), "\n") + }, + } + // Bring in the standard go functions that most people know/use (indent, nindent, etc) + for k, v := range sprig.TxtFuncMap() { + funcMap[k] = v + } + e := easytemplate.New( + easytemplate.WithSearchLocations([]string{filepath.Dir(templateLocation)}), + easytemplate.WithTemplateFuncs(funcMap), + easytemplate.WithWriteFunc(func(outFile string, data []byte) error { + return fmt.Errorf("write function not available") + }), + ) + + created, err := e.RunTemplateString(templateLocation, values) + if err != nil { + return "", fmt.Errorf("Failed to execute template: %w", err) + } + return created, nil +} + +func Execute(templateFileLocation string, valuesFileLocation string, outputFileLocation string) error { + valuesString, err := os.ReadFile(valuesFileLocation) + if err != nil { + return fmt.Errorf("Failed to read values file: %w", err) + } + templateFileAbsPath, err := filepath.Abs(templateFileLocation) + if err != nil { + return fmt.Errorf("Failed to get absolute path of template file: %w", err) + } + values := make(map[string]interface{}) + if err = yaml.Unmarshal(valuesString, &values); err != nil { + return fmt.Errorf("Failed to unmarshal values file: %w", err) + } + + templated, err := execute(templateFileAbsPath, values) + if err != nil { + return fmt.Errorf("Failed to execute template: %w", err) + } + os.WriteFile(outputFileLocation, []byte(templated), 0644) + return nil +}