forked from stripe/stripe-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add namespaces, resources and operations subcommands (stripe#87)
- Loading branch information
Showing
25 changed files
with
1,181 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,7 @@ To delete a charge: | |
RunE: gc.reqs.RunRequestsCmd, | ||
} | ||
|
||
gc.reqs.InitFlags() | ||
gc.reqs.InitFlags(true) | ||
|
||
return gc | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
//+build gen_resources | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"go/format" | ||
"io/ioutil" | ||
"strings" | ||
"text/template" | ||
|
||
"github.com/iancoleman/strcase" | ||
|
||
"github.com/stripe/stripe-cli/pkg/cmd/resource" | ||
"github.com/stripe/stripe-cli/pkg/spec" | ||
) | ||
|
||
type TemplateData struct { | ||
Namespaces map[string]*NamespaceData | ||
} | ||
|
||
type NamespaceData struct { | ||
Resources map[string]*ResourceData | ||
} | ||
|
||
type ResourceData struct { | ||
Operations map[string]*OperationData | ||
} | ||
|
||
type OperationData struct { | ||
Path string | ||
HTTPVerb string | ||
} | ||
|
||
const ( | ||
pathStripeSpec = "../../api/openapi-spec/spec3.sdk.json" | ||
|
||
pathTemplate = "resources_cmds.go.tpl" | ||
|
||
pathOutput = "resources_cmds.go" | ||
) | ||
|
||
func main() { | ||
// This is the script that generates the `resources.go` file from the | ||
// OpenAPI spec file. | ||
|
||
// Load the spec and prepare the template data | ||
templateData, err := getTemplateData() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Load the template with a custom function map | ||
tmpl := template.Must(template. | ||
// Note that the template name MUST match the file name | ||
New(pathTemplate). | ||
Funcs(template.FuncMap{ | ||
// The `ToCamel` function is used to turn snake_case strings to | ||
// CamelCase strings. The template uses this to form Go variable | ||
// names. | ||
"ToCamel": strcase.ToCamel, | ||
}). | ||
ParseFiles(pathTemplate)) | ||
|
||
// Execute the template | ||
var result bytes.Buffer | ||
err = tmpl.Execute(&result, templateData) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Format the output of the template execution | ||
formatted, err := format.Source(result.Bytes()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// Write the formatted source code to disk | ||
fmt.Printf("writing %s\n", pathOutput) | ||
err = ioutil.WriteFile(pathOutput, formatted, 0644) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func getTemplateData() (*TemplateData, error) { | ||
data := &TemplateData{ | ||
Namespaces: make(map[string]*NamespaceData), | ||
} | ||
|
||
// Load the JSON OpenAPI spec | ||
stripeAPI, err := spec.LoadSpec(pathStripeSpec) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Iterate over every resource schema | ||
for name, schema := range stripeAPI.Components.Schemas { | ||
// Skip resources that don't have any operations | ||
if schema.XStripeOperations == nil { | ||
continue | ||
} | ||
|
||
nsName, resName := parseSchemaName(name) | ||
|
||
// Iterate over every operation for the resource | ||
for _, op := range *schema.XStripeOperations { | ||
// We're only implementing "service" operations | ||
if op.MethodOn != "service" { | ||
continue | ||
} | ||
|
||
// If we haven't seen the namespace before, initialize it | ||
if _, ok := data.Namespaces[nsName]; !ok { | ||
data.Namespaces[nsName] = &NamespaceData{ | ||
Resources: make(map[string]*ResourceData), | ||
} | ||
} | ||
|
||
// If we haven't seen the resource before, initialize it | ||
resCmdName := resource.GetResourceCmdName(resName) | ||
if _, ok := data.Namespaces[nsName].Resources[resCmdName]; !ok { | ||
data.Namespaces[nsName].Resources[resCmdName] = &ResourceData{ | ||
Operations: make(map[string]*OperationData), | ||
} | ||
} | ||
|
||
// If we haven't seen the operation before, initialize it | ||
if _, ok := data.Namespaces[nsName].Resources[resCmdName].Operations[op.MethodName]; !ok { | ||
data.Namespaces[nsName].Resources[resCmdName].Operations[op.MethodName] = &OperationData{ | ||
Path: op.Path, | ||
HTTPVerb: string(op.Operation), | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
return data, nil | ||
} | ||
|
||
func parseSchemaName(name string) (string, string) { | ||
if strings.Contains(name, ".") { | ||
components := strings.SplitN(name, ".", 2) | ||
return components[0], components[1] | ||
} | ||
return "", name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,7 @@ To get 50 charges: | |
RunE: gc.reqs.RunRequestsCmd, | ||
} | ||
|
||
gc.reqs.InitFlags() | ||
gc.reqs.InitFlags(true) | ||
|
||
return gc | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package resource | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/stripe/stripe-cli/pkg/ansi" | ||
) | ||
|
||
// | ||
// Public types | ||
// | ||
|
||
// NamespaceCmd represents namespace commands. Namespace commands are top-level | ||
// commands that are simply containers for resource commands. | ||
// | ||
// Example of namespaces: `issuing`, `radar`, `terminal`. | ||
type NamespaceCmd struct { | ||
Cmd *cobra.Command | ||
Name string | ||
ResourceCmds map[string]*ResourceCmd | ||
} | ||
|
||
// | ||
// Public functions | ||
// | ||
|
||
// NewNamespaceCmd returns a new NamespaceCmd. | ||
func NewNamespaceCmd(rootCmd *cobra.Command, namespaceName string) *NamespaceCmd { | ||
cmd := &cobra.Command{ | ||
Use: namespaceName, | ||
Annotations: make(map[string]string), | ||
} | ||
cmd.SetUsageTemplate(namespaceUsageTemplate()) | ||
|
||
// For non-namespaced resources, we create a namespace command with the | ||
// empty string as its name so we can group the resource commands in its | ||
// ResourceCmds map, but we don't actually add the Cobra command as a | ||
// subcommand. | ||
if namespaceName != "" { | ||
rootCmd.AddCommand(cmd) | ||
rootCmd.Annotations[namespaceName] = "namespace" | ||
} | ||
|
||
return &NamespaceCmd{ | ||
Cmd: cmd, | ||
Name: namespaceName, | ||
ResourceCmds: make(map[string]*ResourceCmd), | ||
} | ||
} | ||
|
||
// | ||
// Private functions | ||
// | ||
|
||
func namespaceUsageTemplate() string { | ||
return fmt.Sprintf(`%s{{if .Runnable}} | ||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} | ||
{{.CommandPath}} <resource> <operation> [parameters...]{{end}}{{if gt (len .Aliases) 0}} | ||
%s | ||
{{.NameAndAliases}}{{end}}{{if .HasExample}} | ||
%s | ||
{{.Example}}{{end}}{{if .HasAvailableSubCommands}} | ||
%s{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} | ||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} | ||
%s | ||
{{WrappedLocalFlagUsages . | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} | ||
%s | ||
{{WrappedInheritedFlagUsages . | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} | ||
%s{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} | ||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} | ||
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} | ||
`, | ||
ansi.Bold("Usage:"), | ||
ansi.Bold("Aliases:"), | ||
ansi.Bold("Examples:"), | ||
ansi.Bold("Available Resources:"), | ||
ansi.Bold("Flags:"), | ||
ansi.Bold("Global Flags:"), | ||
ansi.Bold("Additional help topics:"), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package resource | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewNamespaceCmd_NonEmptyName(t *testing.T) { | ||
rootCmd := &cobra.Command{Annotations: make(map[string]string)} | ||
|
||
nsc := NewNamespaceCmd(rootCmd, "foo") | ||
|
||
assert.Equal(t, "foo", nsc.Name) | ||
assert.True(t, rootCmd.HasSubCommands()) | ||
val, ok := rootCmd.Annotations["foo"] | ||
assert.True(t, ok) | ||
assert.Equal(t, "namespace", val) | ||
assert.Contains(t, nsc.Cmd.UsageTemplate(), "Available Resources") | ||
} | ||
|
||
func TestNewNamespaceCmd_EmptyName(t *testing.T) { | ||
rootCmd := &cobra.Command{Annotations: make(map[string]string)} | ||
|
||
nsc := NewNamespaceCmd(rootCmd, "") | ||
|
||
assert.Equal(t, "", nsc.Name) | ||
assert.False(t, rootCmd.HasSubCommands()) | ||
_, ok := rootCmd.Annotations[""] | ||
assert.False(t, ok) | ||
assert.Contains(t, nsc.Cmd.UsageTemplate(), "Available Resources") | ||
} |
Oops, something went wrong.