Improve error handling in commands

Cobra, the CLI commander in use in Hugo, has some long awaited improvements in the error handling department.
This enables a more centralized error handling approach.

This commit introduces that by changing all the command funcs to `RunE`:

* The core part of the error logging, usage logging and `os.Exit(-1)` is now performed in one place and that one place only.
* The usage text is now only shown on invalid arguments etc. (user errors)

Fixes #1502
This commit is contained in:
Bjørn Erik Pedersen 2015-12-02 11:42:53 +01:00 committed by Anthony Fok
parent 6959b7fa80
commit 3f0f7eed68
17 changed files with 219 additions and 155 deletions

View file

@ -40,8 +40,44 @@ import (
"github.com/spf13/nitro"
"github.com/spf13/viper"
"gopkg.in/fsnotify.v1"
"regexp"
)
// userError is an error used to signal different error situations in command handling.
type commandError struct {
s string
userError bool
}
func (u commandError) Error() string {
return u.s
}
func (u commandError) isUserError() bool {
return u.userError
}
func newUserError(messages ...interface{}) commandError {
return commandError{s: fmt.Sprintln(messages...), userError: true}
}
func newSystemError(messages ...interface{}) commandError {
return commandError{s: fmt.Sprintln(messages...), userError: false}
}
// catch some of the obvious user errors from Cobra.
// We don't want to show the usage message for every error.
// The below may be to generic. Time will show.
var userErrorRegexp = regexp.MustCompile("argument|flag|shorthand")
func isUserError(err error) bool {
if cErr, ok := err.(commandError); ok && cErr.isUserError() {
return true
}
return userErrorRegexp.MatchString(err.Error())
}
//HugoCmd is Hugo's root command. Every other command attached to HugoCmd is a child command to it.
var HugoCmd = &cobra.Command{
Use: "hugo",
@ -52,10 +88,15 @@ Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go.
Complete documentation is available at http://gohugo.io/.`,
Run: func(cmd *cobra.Command, args []string) {
InitializeConfig()
RunE: func(cmd *cobra.Command, args []string) error {
if err := InitializeConfig(); err != nil {
return err
}
watchConfig()
build()
return build()
},
}
@ -68,9 +109,17 @@ var Source, CacheDir, Destination, Theme, BaseURL, CfgFile, LogFile, Editor stri
//Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
func Execute() {
HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
HugoCmd.SilenceUsage = true
AddCommands()
if err := HugoCmd.Execute(); err != nil {
// the err is already logged by Cobra
if c, err := HugoCmd.ExecuteC(); err != nil {
if isUserError(err) {
c.Println("")
c.Println(c.UsageString())
}
os.Exit(-1)
}
}
@ -184,7 +233,7 @@ func LoadDefaultSettings() {
}
// InitializeConfig initializes a config file with sensible default configuration flags.
func InitializeConfig() {
func InitializeConfig() error {
viper.SetConfigFile(CfgFile)
// See https://github.com/spf13/viper/issues/73#issuecomment-126970794
if Source == "" {
@ -195,9 +244,9 @@ func InitializeConfig() {
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
jww.ERROR.Println(err)
return newSystemError(err)
} else {
jww.ERROR.Println("Unable to locate Config file. Perhaps you need to create a new site. Run `hugo help new` for details", err)
return newSystemError("Unable to locate Config file. Perhaps you need to create a new site. Run `hugo help new` for details", err)
}
}
@ -320,7 +369,7 @@ func InitializeConfig() {
themeDir := helpers.GetThemeDir()
if themeDir != "" {
if _, err := os.Stat(themeDir); os.IsNotExist(err) {
jww.FATAL.Fatalln("Unable to find theme Directory:", themeDir)
return newSystemError("Unable to find theme Directory:", themeDir)
}
}
@ -330,6 +379,8 @@ func InitializeConfig() {
jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
helpers.HugoReleaseVersion(), minVersion)
}
return nil
}
func watchConfig() {
@ -344,23 +395,26 @@ func watchConfig() {
})
}
func build(watches ...bool) {
err := copyStatic()
if err != nil {
fmt.Println(err)
utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", helpers.AbsPathify(viper.GetString("PublishDir"))))
func build(watches ...bool) error {
if err := copyStatic(); err != nil {
return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("PublishDir")), err)
}
watch := false
if len(watches) > 0 && watches[0] {
watch = true
}
utils.StopOnErr(buildSite(BuildWatch || watch))
if err := buildSite(BuildWatch || watch); err != nil {
return fmt.Errorf("Error building site: %s", err)
}
if BuildWatch {
jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir")))
jww.FEEDBACK.Println("Press Ctrl+C to stop")
utils.CheckErr(NewWatcher(0))
}
return nil
}
func copyStatic() error {
@ -483,7 +537,6 @@ func NewWatcher(port int) error {
var wg sync.WaitGroup
if err != nil {
fmt.Println(err)
return err
}