Skip to content

Commit

Permalink
ref: replace global state with bootstrap (#17)
Browse files Browse the repository at this point in the history
ref: move i18n into new internal package translate (#17)

ref: define a parameter set for root command (#17)

ref: replace init with bootstrap (#17)

ref: remove global state from widget command (#17)
  • Loading branch information
plastikfan committed Aug 1, 2022
1 parent dfdcdd0 commit d7800e0
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 80 deletions.
79 changes: 79 additions & 0 deletions src/app/command/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package command

import (
"fmt"
"os"

"github.com/snivilised/cobrass/src/assistant"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type bootstrap struct {
container *assistant.CobraContainer
}

func (b *bootstrap) execute() {
b.container = assistant.NewCobraContainer(
&cobra.Command{
Use: "main",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Version: fmt.Sprintf("'%v'", Version),
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
},
)

setupRootCommand(b.container)
BuildWidgetCommand(b.container)

configure(b.container)

root := b.container.Root()

err := root.Execute()
if err != nil {
os.Exit(1)
}
}

func configure(container *assistant.CobraContainer) {
// This is the functionality previously defined in initConfig, which was
// invoked as a result of it be passed into cobra.OnInitialize(). This
// approach was abandoned due to its reliance on global state and the init()
// function which is an anti-pattern.
//

// initConfig reads in config file and ENV variables if set.
paramSet := container.MustGetParamSet("root-ps").(*assistant.ParamSet[RootParameterSet])

if paramSet.Native.ConfigFile != "" {
// Use config file from the flag.
viper.SetConfigFile(paramSet.Native.ConfigFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)

// Search config in home directory with name ".arcadia" (without extension).
// NB: 'arcadia' should be renamed as appropriate
//
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(fmt.Sprintf(".%v", ApplicationName))
}

viper.AutomaticEnv() // read in environment variables that match

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
98 changes: 36 additions & 62 deletions src/app/command/root-cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,93 +6,67 @@ package command

import (
"fmt"
"os"

"github.com/snivilised/cobrass/src/assistant"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/text/language"
)

const AppEmoji = "🦄"
const ApplicationName = "arcadia"

var cfgFile string
var lang string

var Container = assistant.NewCobraContainer(
&cobra.Command{
Use: "main",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Version: fmt.Sprintf("'%v'", Version),
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
},
)
var rootCommand = Container.Root()

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCommand.Execute()
if err != nil {
os.Exit(1)
}
bs := bootstrap{}
bs.execute()
}

func init() {
cobra.OnInitialize(initConfig)
type RootParameterSet struct {
ConfigFile string
Language string
Toggle bool
}

func setupRootCommand(container *assistant.CobraContainer) {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.

bindToConfig := &cfgFile
const configFlagName = "config"
const defConfig = ""
const configUsage = "config file (default is $HOME/.arcadia.yml)"
rootCommand.PersistentFlags().StringVar(bindToConfig, configFlagName, defConfig, configUsage)
configUsage := fmt.Sprintf("config file (default is $HOME/.%v.yml", ApplicationName)

root := container.Root()
paramSet := assistant.NewParamSet[RootParameterSet](root)

paramSet.BindString(&assistant.FlagInfo{
Name: configFlagName,
Usage: configUsage,
Default: defConfig,
AlternativeFlagSet: root.PersistentFlags(),
}, &paramSet.Native.ConfigFile)

bindToLang := &lang
const langFlagName = "lang"
const defLang = "en-GB"
const langUsage = "lang defines the language"
rootCommand.PersistentFlags().StringVar(bindToLang, langFlagName, defLang, langUsage)

paramSet.BindValidatedString(&assistant.FlagInfo{
Name: langFlagName,
Usage: langUsage,
Default: defLang,
AlternativeFlagSet: root.PersistentFlags(),
}, &paramSet.Native.Language, func(value string) error {
_, err := language.Parse(value)
return err
})

// Cobra also supports local flags, which will only run
// when this action is called directly.
const toggleFlagName = "toggle"
const toggleShort = "t"
const toggleUsage = "Help message for toggle"
rootCommand.Flags().BoolP(toggleFlagName, toggleShort, false, toggleUsage)
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)

// Search config in home directory with name ".main" (without extension).
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".main")
}
const toggleUsage = "toggle Help message for toggle"

viper.AutomaticEnv() // read in environment variables that match
paramSet.BindBool(
assistant.NewFlagInfo(toggleUsage, toggleShort, false),
&paramSet.Native.Toggle,
)

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
container.MustRegisterParamSet("root-ps", paramSet)
}
40 changes: 24 additions & 16 deletions src/app/command/widget-cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,37 @@ const (
ScribbleFormatEn
)

var OutputFormatEnumInfo = assistant.NewEnumInfo(assistant.AcceptableEnumValues[OutputFormatEnum]{
XmlFormatEn: []string{"xml", "x"},
JsonFormatEn: []string{"json", "j"},
TextFormatEn: []string{"text", "tx"},
ScribbleFormatEn: []string{"scribble", "scribbler", "scr"},
})
var OutputFormatEn assistant.EnumValue[OutputFormatEnum]

type WidgetParameterSet struct {
Directory string
Format OutputFormatEnum
Concise bool
Pattern string
Threshold uint

// the following are supporting fields required for widget command
//
OutputFormatEnumInfo *assistant.EnumInfo[OutputFormatEnum]
OutputFormatEn assistant.EnumValue[OutputFormatEnum]
}

func init() {
func BuildWidgetCommand(container *assistant.CobraContainer) *cobra.Command {
// to test: arcadia widget -d ./some-existing-file -p "P?<date>" -t 30
//
widgetCommand := &cobra.Command{
Use: "widget",
Short: "widget sub command",
Long: "Long description of the widget command",
RunE: func(cmd *cobra.Command, args []string) error {
var appErr error = nil

ps := Container.MustGetParamSet("widget-ps").(*assistant.ParamSet[WidgetParameterSet])
ps := container.MustGetParamSet("widget-ps").(*assistant.ParamSet[WidgetParameterSet])

if err := ps.Validate(); err == nil {
native := ps.Native

// rebind enum into native member
//
native.Format = OutputFormatEn.Value()
native.Format = native.OutputFormatEn.Value()

// optionally invoke cross field validation
//
Expand Down Expand Up @@ -91,12 +90,20 @@ func init() {
},
)

OutputFormatEn = OutputFormatEnumInfo.NewValue()
paramSet.Native.OutputFormatEnumInfo = assistant.NewEnumInfo(assistant.AcceptableEnumValues[OutputFormatEnum]{
XmlFormatEn: []string{"xml", "x"},
JsonFormatEn: []string{"json", "j"},
TextFormatEn: []string{"text", "tx"},
ScribbleFormatEn: []string{"scribble", "scribbler", "scr"},
})

paramSet.Native.OutputFormatEn = paramSet.Native.OutputFormatEnumInfo.NewValue()

paramSet.BindValidatedEnum(
assistant.NewFlagInfo("format", "f", "xml"),
&OutputFormatEn.Source,
&paramSet.Native.OutputFormatEn.Source,
func(value string) error {
if OutputFormatEnumInfo.En(value) == XmlFormatEn {
if paramSet.Native.OutputFormatEnumInfo.En(value) == XmlFormatEn {
return nil
}
return fmt.Errorf("only xml format is currently supported, other formats available in future release")
Expand Down Expand Up @@ -134,7 +141,8 @@ func init() {
&paramSet.Native.Threshold,
lo, hi,
)
container.MustRegisterRootedCommand(widgetCommand)
container.MustRegisterParamSet("widget-ps", paramSet)

Container.MustRegisterRootedCommand(widgetCommand)
Container.MustRegisterParamSet("widget-ps", paramSet)
return widgetCommand
}
2 changes: 1 addition & 1 deletion src/app/main/i18n.go → src/internal/translate/i18n.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package translate

// import (
// "encoding/json"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main_test
package translate_test

import (
. "github.com/onsi/ginkgo/v2"
Expand Down
13 changes: 13 additions & 0 deletions src/internal/translate/translate_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package translate_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestTranslate(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Translate Suite")
}

0 comments on commit d7800e0

Please sign in to comment.