Improve error handling in commands
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 2 Dec 2015 10:42:53 +0000 (11:42 +0100)
committerAnthony Fok <foka@debian.org>
Wed, 2 Dec 2015 14:07:05 +0000 (07:07 -0700)
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

17 files changed:
commands/benchmark.go
commands/check.go
commands/convert.go
commands/genautocomplete.go
commands/gendoc.go
commands/genman.go
commands/hugo.go
commands/import.go [deleted file]
commands/import_jekyll.go
commands/limit_darwin.go
commands/list.go
commands/list_config.go
commands/new.go
commands/server.go
commands/undraft.go
commands/version.go
hugolib/site.go

index a55ed75355d605ccd73ca8fbc03e387067764b1e..530bf3906ce56f8f1f78e473c729fc3dfe9bbf27 100644 (file)
@@ -28,9 +28,12 @@ var benchmark = &cobra.Command{
        Short: "Benchmark hugo by building a site a number of times.",
        Long: `Hugo can build a site many times over and analyze the running process
 creating a benchmark.`,
-       Run: func(cmd *cobra.Command, args []string) {
-               InitializeConfig()
-               bench(cmd, args)
+       RunE: func(cmd *cobra.Command, args []string) error {
+               if err := InitializeConfig(); err != nil {
+                       return err
+               }
+
+               return bench(cmd, args)
        },
 }
 
@@ -41,13 +44,13 @@ func init() {
        benchmark.Flags().IntVarP(&benchmarkTimes, "count", "n", 13, "number of times to build the site")
 }
 
-func bench(cmd *cobra.Command, args []string) {
+func bench(cmd *cobra.Command, args []string) error {
 
        if memProfilefile != "" {
                f, err := os.Create(memProfilefile)
 
                if err != nil {
-                       panic(err)
+                       return err
                }
                for i := 0; i < benchmarkTimes; i++ {
                        _ = buildSite()
@@ -62,7 +65,7 @@ func bench(cmd *cobra.Command, args []string) {
                f, err := os.Create(cpuProfilefile)
 
                if err != nil {
-                       panic(err)
+                       return err
                }
 
                pprof.StartCPUProfile(f)
@@ -72,4 +75,6 @@ func bench(cmd *cobra.Command, args []string) {
                }
        }
 
+       return nil
+
 }
index 0c8b3ba79105f657754d834ec2319d281dbef037..f176022460db7ffafcd22a4ea70fca5dd77bada4 100644 (file)
@@ -23,9 +23,13 @@ var check = &cobra.Command{
        Short: "Check content in the source directory",
        Long: `Hugo will perform some basic analysis on the content provided
 and will give feedback.`,
-       Run: func(cmd *cobra.Command, args []string) {
-               InitializeConfig()
+       RunE: func(cmd *cobra.Command, args []string) error {
+               if err := InitializeConfig(); err != nil {
+                       return err
+               }
                site := hugolib.Site{}
-               site.Analyze()
+
+               return site.Analyze()
+
        },
 }
index 9f7076d7be96c80d7b9898f7c6fa80daf9c3d143..3e66ba2bf716d2596cd7d497c8ef0a085fa7c48d 100644 (file)
@@ -36,7 +36,7 @@ var convertCmd = &cobra.Command{
        Long: `Convert your content (e.g. front matter) to different formats.
 
 See convert's subcommands toJSON, toTOML and toYAML for more information.`,
-       Run: nil,
+       RunE: nil,
 }
 
 var toJSONCmd = &cobra.Command{
@@ -44,11 +44,8 @@ var toJSONCmd = &cobra.Command{
        Short: "Convert front matter to JSON",
        Long: `toJSON converts all front matter in the content directory
 to use JSON for the front matter.`,
-       Run: func(cmd *cobra.Command, args []string) {
-               err := convertContents(rune([]byte(parser.JSON_LEAD)[0]))
-               if err != nil {
-                       jww.ERROR.Println(err)
-               }
+       RunE: func(cmd *cobra.Command, args []string) error {
+               return convertContents(rune([]byte(parser.JSON_LEAD)[0]))
        },
 }
 
@@ -57,11 +54,8 @@ var toTOMLCmd = &cobra.Command{
        Short: "Convert front matter to TOML",
        Long: `toTOML converts all front matter in the content directory
 to use TOML for the front matter.`,
-       Run: func(cmd *cobra.Command, args []string) {
-               err := convertContents(rune([]byte(parser.TOML_LEAD)[0]))
-               if err != nil {
-                       jww.ERROR.Println(err)
-               }
+       RunE: func(cmd *cobra.Command, args []string) error {
+               return convertContents(rune([]byte(parser.TOML_LEAD)[0]))
        },
 }
 
@@ -70,11 +64,8 @@ var toYAMLCmd = &cobra.Command{
        Short: "Convert front matter to YAML",
        Long: `toYAML converts all front matter in the content directory
 to use YAML for the front matter.`,
-       Run: func(cmd *cobra.Command, args []string) {
-               err := convertContents(rune([]byte(parser.YAML_LEAD)[0]))
-               if err != nil {
-                       jww.ERROR.Println(err)
-               }
+       RunE: func(cmd *cobra.Command, args []string) error {
+               return convertContents(rune([]byte(parser.YAML_LEAD)[0]))
        },
 }
 
@@ -87,7 +78,9 @@ func init() {
 }
 
 func convertContents(mark rune) (err error) {
-       InitializeConfig()
+       if err := InitializeConfig(); err != nil {
+               return err
+       }
        site := &hugolib.Site{}
 
        if err := site.Initialise(); err != nil {
index b7f74fd89ddc84e3452999e10cf5952572a240ea..54dd7563b69ed88426188377705257742a638783 100644 (file)
@@ -31,16 +31,19 @@ or just source them in directly:
 
        $ . /etc/bash_completion`,
 
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
                if autocompleteType != "bash" {
-                       jww.FATAL.Fatalln("Only Bash is supported for now")
+                       return newUserError("Only Bash is supported for now")
                }
+
                err := cmd.Root().GenBashCompletionFile(autocompleteTarget)
+
                if err != nil {
-                       jww.FATAL.Fatalln("Failed to generate shell completion file:", err)
+                       return err
                } else {
                        jww.FEEDBACK.Println("Bash completion file for Hugo saved to", autocompleteTarget)
                }
+               return nil
        },
 }
 
index 9589bffa4550af94de48302425fa6591f2aeaea1..44d09fb50d1ad41244775a0a7e5b206ecd3482b9 100644 (file)
@@ -32,7 +32,7 @@ of Hugo's command-line interface for http://gohugo.io/.
 It creates one Markdown file per command with front matter suitable
 for rendering in Hugo.`,
 
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
                if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
                        gendocdir += helpers.FilePathSeparator
                }
@@ -55,6 +55,8 @@ for rendering in Hugo.`,
                jww.FEEDBACK.Println("Generating Hugo command-line documentation in", gendocdir, "...")
                cobra.GenMarkdownTreeCustom(cmd.Root(), gendocdir, prepender, linkHandler)
                jww.FEEDBACK.Println("Done.")
+
+               return nil
        },
 }
 
index 68a98a46d29d1554f2f89e3ce7a47754923f67c0..a842b49b8df6eef5fe148e831c4d6da63202d5d3 100644 (file)
@@ -18,7 +18,7 @@ var genmanCmd = &cobra.Command{
 command-line interface.  By default, it creates the man page files
 in the "man" directory under the current directory.`,
 
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
                header := &cobra.GenManHeader{
                        Section: "1",
                        Manual:  "Hugo Manual",
@@ -37,6 +37,8 @@ in the "man" directory under the current directory.`,
                cmd.Root().GenManTree(header, genmandir)
 
                jww.FEEDBACK.Println("Done.")
+
+               return nil
        },
 }
 
index 2a72f535ef0e841be1d5d4ccd10d658b9c2454cd..6724b794d97bb843d38f68afd3335e140a17ffcd 100644 (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
        }
 
diff --git a/commands/import.go b/commands/import.go
deleted file mode 100644 (file)
index 830d970..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright © 2015 Steve Francia <spf@spf13.com>.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
-       "github.com/spf13/cobra"
-)
-
-var importCmd = &cobra.Command{
-       Use:   "import",
-       Short: "Import your site from others.",
-       Long: `Import your site from other web site generators like Jekyll.
-
-Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
-       Run: nil,
-}
-
-func init() {
-       importCmd.AddCommand(importJekyllCmd)
-}
index 38d352671e2f065d7c80d9590da51389de69be55..00893f8540f869eb06d7222e5298236d3c7370f8 100644 (file)
@@ -35,34 +35,44 @@ import (
        jww "github.com/spf13/jwalterweatherman"
 )
 
+func init() {
+       importCmd.AddCommand(importJekyllCmd)
+}
+
+var importCmd = &cobra.Command{
+       Use:   "import",
+       Short: "Import your site from others.",
+       Long: `Import your site from other web site generators like Jekyll.
+
+Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
+       RunE: nil,
+}
+
 var importJekyllCmd = &cobra.Command{
        Use:   "jekyll",
        Short: "hugo import from Jekyll",
        Long: `hugo import from Jekyll.
 
 Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
-       Run: importFromJekyll,
+       RunE: importFromJekyll,
 }
 
-func importFromJekyll(cmd *cobra.Command, args []string) {
+func importFromJekyll(cmd *cobra.Command, args []string) error {
        jww.SetLogThreshold(jww.LevelTrace)
        jww.SetStdoutThreshold(jww.LevelWarn)
 
        if len(args) < 2 {
-               jww.ERROR.Println(`Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
-               return
+               return newUserError(`Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
        }
 
        jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
        if err != nil {
-               jww.ERROR.Println("Path error:", args[0])
-               return
+               return newUserError("Path error:", args[0])
        }
 
        targetDir, err := filepath.Abs(filepath.Clean(args[1]))
        if err != nil {
-               jww.ERROR.Println("Path error:", args[1])
-               return
+               return newUserError("Path error:", args[1])
        }
 
        createSiteFromJekyll(jekyllRoot, targetDir)
@@ -82,8 +92,7 @@ func importFromJekyll(cmd *cobra.Command, args []string) {
 
                relPath, err := filepath.Rel(jekyllRoot, path)
                if err != nil {
-                       jww.ERROR.Println("Get rel path error:", path)
-                       return err
+                       return newUserError("Get rel path error:", path)
                }
 
                relPath = filepath.ToSlash(relPath)
@@ -106,13 +115,15 @@ func importFromJekyll(cmd *cobra.Command, args []string) {
        err = filepath.Walk(jekyllRoot, callback)
 
        if err != nil {
-               fmt.Println(err)
+               return err
        } else {
                fmt.Println("Congratulations!", fileCount, "posts imported!")
                fmt.Println("Now, start Hugo by yourself: \n" +
                        "$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove")
                fmt.Println("$ cd " + args[1] + "\n$ hugo server -w --theme=herring-cove")
        }
+
+       return nil
 }
 
 func createSiteFromJekyll(jekyllRoot, targetDir string) {
index 2745050736e1bbbc1fa44bc5eca8aee308d34005..ab8dba0f7b3129c052ad429556b9a0205cc5c859 100644 (file)
@@ -30,12 +30,13 @@ var limit = &cobra.Command{
        Short: "Check system ulimit settings",
        Long: `Hugo will inspect the current ulimit settings on the system.
     This is primarily to ensure that Hugo can watch enough files on some OSs`,
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
                var rLimit syscall.Rlimit
                err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
                if err != nil {
-                       jww.ERROR.Println("Error Getting Rlimit ", err)
+                       return newSystemError("Error Getting Rlimit ", err)
                }
+
                jww.FEEDBACK.Println("Current rLimit:", rLimit)
 
                jww.FEEDBACK.Println("Attempting to increase limit")
@@ -43,13 +44,15 @@ var limit = &cobra.Command{
                rLimit.Cur = 999999
                err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
                if err != nil {
-                       jww.ERROR.Println("Error Setting rLimit ", err)
+                       return newSystemError("Error Setting rLimit ", err)
                }
                err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
                if err != nil {
-                       jww.ERROR.Println("Error Getting rLimit ", err)
+                       return newSystemError("Error Getting rLimit ", err)
                }
                jww.FEEDBACK.Println("rLimit after change:", rLimit)
+
+               return nil
        },
 }
 
index 2e85a876533bb43f646c2e18e736b8ffa25ea7ad..b3af0ef63a34cb77c363d0241c136df4bd570d97 100644 (file)
@@ -33,22 +33,25 @@ var listCmd = &cobra.Command{
        Long: `Listing out various types of content.
 
 List requires a subcommand, e.g. ` + "`hugo list drafts`.",
-       Run: nil,
+       RunE: nil,
 }
 
 var listDraftsCmd = &cobra.Command{
        Use:   "drafts",
        Short: "List all drafts",
        Long:  `List all of the drafts in your content directory.`,
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
+
+               if err := InitializeConfig(); err != nil {
+                       return err
+               }
 
-               InitializeConfig()
                viper.Set("BuildDrafts", true)
 
                site := &hugolib.Site{}
 
                if err := site.Process(); err != nil {
-                       fmt.Println("Error Processing Source Content", err)
+                       return newSystemError("Error Processing Source Content", err)
                }
 
                for _, p := range site.Pages {
@@ -58,6 +61,8 @@ var listDraftsCmd = &cobra.Command{
 
                }
 
+               return nil
+
        },
 }
 
@@ -66,15 +71,18 @@ var listFutureCmd = &cobra.Command{
        Short: "List all posts dated in the future",
        Long: `List all of the posts in your content directory which will be
 posted in the future.`,
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
+
+               if err := InitializeConfig(); err != nil {
+                       return err
+               }
 
-               InitializeConfig()
                viper.Set("BuildFuture", true)
 
                site := &hugolib.Site{}
 
                if err := site.Process(); err != nil {
-                       fmt.Println("Error Processing Source Content", err)
+                       return newSystemError("Error Processing Source Content", err)
                }
 
                for _, p := range site.Pages {
@@ -84,5 +92,7 @@ posted in the future.`,
 
                }
 
+               return nil
+
        },
 }
index 9520fdbc95c323474b66acf5e9cec9aebe818134..d71d04a670c873549e08c5b6b5cc11f7841cab3b 100644 (file)
@@ -25,8 +25,11 @@ var config = &cobra.Command{
        Use:   "config",
        Short: "Print the site configuration",
        Long:  `Print the site configuration, both default and custom settings.`,
-       Run: func(cmd *cobra.Command, args []string) {
-               InitializeConfig()
+       RunE: func(cmd *cobra.Command, args []string) error {
+               if err := InitializeConfig(); err != nil {
+                       return err
+               }
+
                allSettings := viper.AllSettings()
 
                var separator string
@@ -49,5 +52,7 @@ var config = &cobra.Command{
                                fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
                        }
                }
+
+               return nil
        },
 }
index f2c362ac36c9d9380480739d4364b04ebbd956a0..579d12a52a99b8ae6b85df9b17ed64b80f05e5ec 100644 (file)
@@ -55,7 +55,7 @@ You can also specify the kind with ` + "`-k KIND`" + `.
 
 If archetypes are provided in your theme or site, they will be used.`,
 
-       Run: NewContent,
+       RunE: NewContent,
 }
 
 var newSiteCmd = &cobra.Command{
@@ -64,7 +64,7 @@ var newSiteCmd = &cobra.Command{
        Long: `Create a new site in the provided directory.
 The new site will have the correct structure, but no content or theme yet.
 Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
-       Run: NewSite,
+       RunE: NewSite,
 }
 
 var newThemeCmd = &cobra.Command{
@@ -74,20 +74,21 @@ var newThemeCmd = &cobra.Command{
 New theme is a skeleton. Please add content to the touched files. Add your
 name to the copyright line in the license and adjust the theme.toml file
 as you see fit.`,
-       Run: NewTheme,
+       RunE: NewTheme,
 }
 
 // NewContent adds new content to a Hugo site.
-func NewContent(cmd *cobra.Command, args []string) {
-       InitializeConfig()
+func NewContent(cmd *cobra.Command, args []string) error {
+       if err := InitializeConfig(); err != nil {
+               return err
+       }
 
        if cmd.Flags().Lookup("format").Changed {
                viper.Set("MetaDataFormat", configFormat)
        }
 
        if len(args) < 1 {
-               cmd.Usage()
-               jww.FATAL.Fatalln("path needs to be provided")
+               return newUserError("path needs to be provided")
        }
 
        createpath := args[0]
@@ -100,10 +101,8 @@ func NewContent(cmd *cobra.Command, args []string) {
                kind = contentType
        }
 
-       err := create.NewContent(kind, createpath)
-       if err != nil {
-               jww.ERROR.Println(err)
-       }
+       return create.NewContent(kind, createpath)
+
 }
 
 func doNewSite(basepath string, force bool) error {
@@ -146,32 +145,31 @@ func doNewSite(basepath string, force bool) error {
 }
 
 // NewSite creates a new hugo site and initializes a structured Hugo directory.
-func NewSite(cmd *cobra.Command, args []string) {
+func NewSite(cmd *cobra.Command, args []string) error {
        if len(args) < 1 {
-               cmd.Usage()
-               jww.FATAL.Fatalln("path needs to be provided")
+               return newUserError("path needs to be provided")
        }
 
        createpath, err := filepath.Abs(filepath.Clean(args[0]))
        if err != nil {
-               cmd.Usage()
-               jww.FATAL.Fatalln(err)
+               return newUserError(err)
        }
 
        forceNew, _ := cmd.Flags().GetBool("force")
-       if err := doNewSite(createpath, forceNew); err != nil {
-               cmd.Usage()
-               jww.FATAL.Fatalln(err)
-       }
+
+       return doNewSite(createpath, forceNew)
+
 }
 
 // NewTheme creates a new Hugo theme.
-func NewTheme(cmd *cobra.Command, args []string) {
-       InitializeConfig()
+func NewTheme(cmd *cobra.Command, args []string) error {
+       if err := InitializeConfig(); err != nil {
+               return err
+       }
 
        if len(args) < 1 {
-               cmd.Usage()
-               jww.FATAL.Fatalln("theme name needs to be provided")
+
+               return newUserError("theme name needs to be provided")
        }
 
        createpath := helpers.AbsPathify(filepath.Join("themes", args[0]))
@@ -229,10 +227,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
        err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), hugofs.SourceFs)
        if err != nil {
-               jww.FATAL.Fatalln(err)
+               return nil
        }
 
        createThemeMD(createpath)
+
+       return nil
 }
 
 func mkdir(x ...string) {
index 8d69304c3dd5bfe63b17ba6e450c5f558b6fb488..378637b6cc0a8aeb906d8fa9916b9db10a35e31d 100644 (file)
@@ -57,7 +57,7 @@ By default hugo will also watch your files for any changes you make and
 automatically rebuild the site. It will then live reload any open browser pages
 and push the latest content to them. As most Hugo sites are built in a fraction
 of a second, you will be able to save and see your changes nearly instantly.`,
-       //Run: server,
+       //RunE: server,
 }
 
 type filesOnlyFs struct {
@@ -90,10 +90,10 @@ func init() {
        serverCmd.Flags().BoolVarP(&NoTimes, "noTimes", "", false, "Don't sync modification time of files")
        serverCmd.Flags().String("memstats", "", "log memory usage to this file")
        serverCmd.Flags().Int("meminterval", 100, "interval to poll memory usage (requires --memstats)")
-       serverCmd.Run = server
+       serverCmd.RunE = server
 }
 
-func server(cmd *cobra.Command, args []string) {
+func server(cmd *cobra.Command, args []string) error {
        InitializeConfig()
 
        if cmd.Flags().Lookup("disableLiveReload").Changed {
@@ -116,8 +116,7 @@ func server(cmd *cobra.Command, args []string) {
                jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
                sp, err := helpers.FindAvailablePort()
                if err != nil {
-                       jww.ERROR.Println("Unable to find alternative port to use")
-                       jww.ERROR.Fatalln(err)
+                       return newSystemError("Unable to find alternative port to use:", err)
                }
                serverPort = sp.Port
        }
@@ -126,7 +125,7 @@ func server(cmd *cobra.Command, args []string) {
 
        BaseURL, err := fixURL(BaseURL)
        if err != nil {
-               jww.ERROR.Fatal(err)
+               return err
        }
        viper.Set("BaseURL", BaseURL)
 
@@ -146,7 +145,9 @@ func server(cmd *cobra.Command, args []string) {
                viper.Set("PublishDir", "/")
        }
 
-       build(serverWatch)
+       if err := build(serverWatch); err != nil {
+               return err
+       }
 
        // Watch runs its own server as part of the routine
        if serverWatch {
@@ -160,12 +161,15 @@ func server(cmd *cobra.Command, args []string) {
 
                jww.FEEDBACK.Printf("Watching for changes in %s/{%s}\n", baseWatchDir, rootWatchDirs)
                err := NewWatcher(serverPort)
+
                if err != nil {
-                       fmt.Println(err)
+                       return err
                }
        }
 
        serve(serverPort)
+
+       return nil
 }
 
 func serve(port int) {
index 00772935680f36f54e1e3e55efa84eb11614f478..89b56c95e5ca5f59dd43d8db2f34bb14f220b302 100644 (file)
@@ -20,7 +20,6 @@ import (
 
        "github.com/spf13/cobra"
        "github.com/spf13/hugo/parser"
-       jww "github.com/spf13/jwalterweatherman"
 )
 
 var undraftCmd = &cobra.Command{
@@ -29,53 +28,50 @@ var undraftCmd = &cobra.Command{
        Long: `Undraft changes the content's draft status from 'True' to 'False'
 and updates the date to the current date and time.
 If the content's draft status is 'False', nothing is done.`,
-       Run: Undraft,
+       RunE: Undraft,
 }
 
 // Publish publishes the specified content by setting its draft status
 // to false and setting its publish date to now. If the specified content is
 // not a draft, it will log an error.
-func Undraft(cmd *cobra.Command, args []string) {
-       InitializeConfig()
+func Undraft(cmd *cobra.Command, args []string) error {
+       if err := InitializeConfig(); err != nil {
+               return err
+       }
 
        if len(args) < 1 {
-               cmd.Usage()
-               jww.FATAL.Fatalln("a piece of content needs to be specified")
+               return newUserError("a piece of content needs to be specified")
        }
 
        location := args[0]
        // open the file
        f, err := os.Open(location)
        if err != nil {
-               jww.ERROR.Print(err)
-               return
+               return err
        }
 
        // get the page from file
        p, err := parser.ReadFrom(f)
        f.Close()
        if err != nil {
-               jww.ERROR.Print(err)
-               return
+               return err
        }
 
        w, err := undraftContent(p)
        if err != nil {
-               jww.ERROR.Printf("an error occurred while undrafting %q: %s", location, err)
-               return
+               return newSystemError("an error occurred while undrafting %q: %s", location, err)
        }
 
        f, err = os.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
        if err != nil {
-               jww.ERROR.Printf("%q not be undrafted due to error opening file to save changes: %q\n", location, err)
-               return
+               return newSystemError("%q not be undrafted due to error opening file to save changes: %q\n", location, err)
        }
        defer f.Close()
        _, err = w.WriteTo(f)
        if err != nil {
-               jww.ERROR.Printf("%q not be undrafted due to save error: %q\n", location, err)
+               return newSystemError("%q not be undrafted due to save error: %q\n", location, err)
        }
-       return
+       return nil
 }
 
 // undraftContent: if the content is a draft, change its draft status to
index f775f2e33b72e179e9805a9e29fa910beb781d3d..56c134bc58ba606721c5d69589013bbc692d4af4 100644 (file)
@@ -32,7 +32,7 @@ var version = &cobra.Command{
        Use:   "version",
        Short: "Print the version number of Hugo",
        Long:  `All software has versions. This is Hugo's.`,
-       Run: func(cmd *cobra.Command, args []string) {
+       RunE: func(cmd *cobra.Command, args []string) error {
                if hugolib.BuildDate == "" {
                        setBuildDate() // set the build date from executable's mdate
                } else {
@@ -43,6 +43,8 @@ var version = &cobra.Command{
                } else {
                        fmt.Printf("Hugo Static Site Generator v%s-%s BuildDate: %s\n", helpers.HugoVersion(), strings.ToUpper(hugolib.CommitHash), hugolib.BuildDate)
                }
+
+               return nil
        },
 }
 
index a8f0599bb8396c1a4978a51cf2c4c6ecfb5ab9f1..8cdfa44f2bf14ff0a8d1386847c0148e882fde36 100644 (file)
@@ -254,9 +254,11 @@ func (s *Site) Build() (err error) {
        return nil
 }
 
-func (s *Site) Analyze() {
-       s.Process()
-       s.ShowPlan(os.Stdout)
+func (s *Site) Analyze() error {
+       if err := s.Process(); err != nil {
+               return err
+       }
+       return s.ShowPlan(os.Stdout)
 }
 
 func (s *Site) prepTemplates() {