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
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)
},
}
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()
f, err := os.Create(cpuProfilefile)
if err != nil {
- panic(err)
+ return err
}
pprof.StartCPUProfile(f)
}
}
+ return nil
+
}
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()
+
},
}
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{
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]))
},
}
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]))
},
}
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]))
},
}
}
func convertContents(mark rune) (err error) {
- InitializeConfig()
+ if err := InitializeConfig(); err != nil {
+ return err
+ }
site := &hugolib.Site{}
if err := site.Initialise(); err != nil {
$ . /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
},
}
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
}
jww.FEEDBACK.Println("Generating Hugo command-line documentation in", gendocdir, "...")
cobra.GenMarkdownTreeCustom(cmd.Root(), gendocdir, prepender, linkHandler)
jww.FEEDBACK.Println("Done.")
+
+ return nil
},
}
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",
cmd.Root().GenManTree(header, genmandir)
jww.FEEDBACK.Println("Done.")
+
+ return nil
},
}
"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",
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()
+
},
}
//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)
}
}
}
// 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 == "" {
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)
}
}
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)
}
}
jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
helpers.HugoReleaseVersion(), minVersion)
}
+
+ return nil
}
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 {
var wg sync.WaitGroup
if err != nil {
- fmt.Println(err)
return err
}
+++ /dev/null
-// 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)
-}
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)
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)
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) {
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")
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
},
}
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 {
}
+ return nil
+
},
}
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 {
}
+ return nil
+
},
}
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
fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
}
}
+
+ return nil
},
}
If archetypes are provided in your theme or site, they will be used.`,
- Run: NewContent,
+ RunE: NewContent,
}
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{
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]
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 {
}
// 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]))
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) {
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 {
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 {
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
}
BaseURL, err := fixURL(BaseURL)
if err != nil {
- jww.ERROR.Fatal(err)
+ return err
}
viper.Set("BaseURL", BaseURL)
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 {
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) {
"github.com/spf13/cobra"
"github.com/spf13/hugo/parser"
- jww "github.com/spf13/jwalterweatherman"
)
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
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 {
} else {
fmt.Printf("Hugo Static Site Generator v%s-%s BuildDate: %s\n", helpers.HugoVersion(), strings.ToUpper(hugolib.CommitHash), hugolib.BuildDate)
}
+
+ return nil
},
}
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() {