Skip to content

Commit

Permalink
Shifting to Cobra
Browse files Browse the repository at this point in the history
I hit the issue urfave/cli#1950 with
urfave/cli, which makes it hard to Allow flags to come after arguments.

Hence, shifting to make use of Cobra as it allows a little more
flexibility.

Signed-off-by: Steve ESSO <[email protected]>
  • Loading branch information
stivesso committed Sep 1, 2024
1 parent a5f3e18 commit 61e9cb6
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 78 deletions.
143 changes: 76 additions & 67 deletions cmd/agent.go
Original file line number Diff line number Diff line change
@@ -1,94 +1,103 @@
package cmd

import (
"errors"
"fmt"
"github.com/VintageOps/structogqlgen/pkg/conversion"
"github.com/VintageOps/structogqlgen/pkg/load"
"github.com/urfave/cli/v2"
"github.com/spf13/cobra"
"log"
"os"
"strings"
)

type cmdOptions struct {
fNameContStruct string
fNamePathPkg string
printOpts conversion.PrettyPrintOptions
}

var requiredTagsFlag map[string]string
var opts cmdOptions

var rootCmd = &cobra.Command{
Use: "structogqlgen [path]",
Short: "Converts Golang structs into GraphQL types for gqlgen",
Long: `StructsToGqlGenTypes is a tool that helps automatically convert Golang structs into GraphQL types
that are readily usable with the popular GraphQL framework, gqlgen. It aims to reduce the boilerplate code
required to define GraphQL schemas manually, thus accelerating the development of GraphQL APIs in Go projects.`,
}

func init() {
// Define flags
rootCmd.PersistentFlags().StringVarP(&opts.fNameContStruct, "src", "s", "", "`SRC_PATH` is the required path to the source file containing the structs to import")
rootCmd.PersistentFlags().BoolVarP(&opts.printOpts.UseJsonTags, "use-json-tags", "j", false, "Use JSON Tag as field name when available. If not present, the field name will be used.")
rootCmd.PersistentFlags().StringVarP(&opts.printOpts.UseCustomTags, "use-custom-tags", "c", "", "Specify a custom tag to use as field name. This takes precedence over JSON tags.")

var tagFieldToIgnore string
rootCmd.PersistentFlags().StringVarP(&tagFieldToIgnore, "tags-value-ignored", "i", "-", "Specify a tag value that signals to ignore a field with this tag value."+
"Automatically set to '-' for JSON tags if not specified.")
opts.printOpts.TagFieldToIgnore = &tagFieldToIgnore

// required-tags checks to be done on the PersistentPreRunE
rootCmd.PersistentFlags().StringToStringVarP(&requiredTagsFlag, "required-tags", "r", nil, "If there is a tag that make a field required, specified that tag using the format `key=value`. e.g. validate=required")

}

func Execute() {
var opts cmdOptions
app := &cli.App{
Name: "structogqlgen",
Usage: "Converts Golang structs into GraphQL types that are readily usable with the popular GraphQL framework, gqlgen",
EnableBashCompletion: true,
HideHelpCommand: true,
Authors: []*cli.Author{
{Name: "VintageOps"},
},
Description: "StructsToGqlGenTypes is a tool that helps to automatically converts Golang structs into GraphQL types that are readily usable with the popular GraphQL framework, gqlgen.\n" +
"It aims to reduce the boilerplate code required to define GraphQL schemas manually, thus accelerating the development of GraphQL APIs in Go projects.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "src",
Usage: "`SRC_PATH` is the required path to the source file containing the structs to import (required)",
Destination: &opts.fNameContStruct,
Required: true,
Aliases: []string{"s"},
},
&cli.BoolFlag{
Name: "use-json-tags",
Usage: "Use JSON Tag as field name when available. If this is selected and a field has no Json tag, then the field name will be used.",
Destination: &opts.printOpts.UseJsonTags,
Aliases: []string{"j"},
},
&cli.StringFlag{
Name: "use-custom-tags",
Usage: "Specify a custom tag to use as field name. Specifying this takes precedence over JSON tags. If specifed and a field does not have this tag, the field name will be used",
Destination: &opts.printOpts.UseCustomTags,
Aliases: []string{"c"},
},
&cli.StringFlag{
Name: "tags-value-ignored",
Usage: "Specify a tag value that signal to ignore Field with tag having this value. When using json tags with use-json-tags option, if this not specified, it is automatically set to '-'",
Aliases: []string{"i"},
Action: func(context *cli.Context, s string) error {
// workaround as Destination: opts.printOpts.TagFieldToIgnore does not set the provided value
opts.printOpts.TagFieldToIgnore = &s
return nil
},
},
&cli.StringFlag{
Name: "required-tags",
Usage: "If there is a tag that make a field required, specified that tag using the format `key=value`. e.g. validate=required",
Aliases: []string{"r"},
Action: func(context *cli.Context, required string) error {
parts := strings.SplitN(required, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid format for required-tags, expected key=value")
}
opts.printOpts.RequireTags.Key = parts[0]
opts.printOpts.RequireTags.Val = parts[1]
return nil
},
},
},

// Check Argument that should mutually exclude
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
// Check mutual exclusion
if len(args) > 0 && opts.fNameContStruct != "" {
return errors.New("cannot use both positional argument and --src flag simultaneously")
}
if len(args) == 0 && opts.fNameContStruct == "" {
return errors.New("either a positional argument or --src flag must be provided")
}

if tagIgnored, _ := cmd.Flags().GetString("tags-value-ignored"); tagIgnored != "" {
opts.printOpts.TagFieldToIgnore = &tagIgnored
}

// Check on required tag key value
if requiredTagsFlag != nil && len(requiredTagsFlag) > 0 {
if len(requiredTagsFlag) > 1 {
return fmt.Errorf("invalid number of arguments for required-tags, expected only one key=value pair")
}
for k, v := range requiredTagsFlag {
opts.printOpts.RequireTags.Key = k
opts.printOpts.RequireTags.Val = v
}
}

return nil
}
app.Action = func(c *cli.Context) error {

// Define the Run
rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
opts.fNamePathPkg = args[0]
}
return printStructsAsGraphqlTypes(&opts)
}

err := app.Run(os.Args)
if err != nil {
// Execute the command
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}

// printStructsAsGraphqlTypes prints the GraphQL type definitions corresponding to the structs found in the provided source file.
// - load.GetStructsFromSourceFile function to find all structs defined in the source file.
// - buildTypeDefinitions function to build the GraphQL type definitions for each struct.
// - conversion.GqlPrettyPrint function to pretty print the GraphQL type definitions and prints the result.
func printStructsAsGraphqlTypes(opts *cmdOptions) error {
structsFound, err := load.GetStructsFromSourceFile(opts.fNameContStruct)
var err error
var structsFound []load.StructDiscovered

if opts.fNameContStruct != "" {
structsFound, err = load.GetStructsFromSourceFile(opts.fNameContStruct)
}

if opts.fNamePathPkg != "" {
structsFound, err = load.GetStructsFromPath(opts.fNamePathPkg)
}

if err != nil {
return err
}
Expand Down
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ go 1.22.1

require (
github.com/fatih/structtag v1.2.0
github.com/urfave/cli/v2 v2.27.1
github.com/spf13/cobra v1.8.1
golang.org/x/tools v0.24.0
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/sync v0.8.0 // indirect
)
22 changes: 15 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 61e9cb6

Please sign in to comment.