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

@ -28,9 +28,12 @@ var benchmark = &cobra.Command{
Short: "Benchmark hugo by building a site a number of times.", 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 Long: `Hugo can build a site many times over and analyze the running process
creating a benchmark.`, creating a benchmark.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
bench(cmd, args) 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") 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 != "" { if memProfilefile != "" {
f, err := os.Create(memProfilefile) f, err := os.Create(memProfilefile)
if err != nil { if err != nil {
panic(err) return err
} }
for i := 0; i < benchmarkTimes; i++ { for i := 0; i < benchmarkTimes; i++ {
_ = buildSite() _ = buildSite()
@ -62,7 +65,7 @@ func bench(cmd *cobra.Command, args []string) {
f, err := os.Create(cpuProfilefile) f, err := os.Create(cpuProfilefile)
if err != nil { if err != nil {
panic(err) return err
} }
pprof.StartCPUProfile(f) pprof.StartCPUProfile(f)
@ -72,4 +75,6 @@ func bench(cmd *cobra.Command, args []string) {
} }
} }
return nil
} }

View file

@ -23,9 +23,13 @@ var check = &cobra.Command{
Short: "Check content in the source directory", Short: "Check content in the source directory",
Long: `Hugo will perform some basic analysis on the content provided Long: `Hugo will perform some basic analysis on the content provided
and will give feedback.`, and will give feedback.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
site := hugolib.Site{} site := hugolib.Site{}
site.Analyze()
return site.Analyze()
}, },
} }

View file

@ -36,7 +36,7 @@ var convertCmd = &cobra.Command{
Long: `Convert your content (e.g. front matter) to different formats. Long: `Convert your content (e.g. front matter) to different formats.
See convert's subcommands toJSON, toTOML and toYAML for more information.`, See convert's subcommands toJSON, toTOML and toYAML for more information.`,
Run: nil, RunE: nil,
} }
var toJSONCmd = &cobra.Command{ var toJSONCmd = &cobra.Command{
@ -44,11 +44,8 @@ var toJSONCmd = &cobra.Command{
Short: "Convert front matter to JSON", Short: "Convert front matter to JSON",
Long: `toJSON converts all front matter in the content directory Long: `toJSON converts all front matter in the content directory
to use JSON for the front matter.`, to use JSON for the front matter.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
err := convertContents(rune([]byte(parser.JSON_LEAD)[0])) return convertContents(rune([]byte(parser.JSON_LEAD)[0]))
if err != nil {
jww.ERROR.Println(err)
}
}, },
} }
@ -57,11 +54,8 @@ var toTOMLCmd = &cobra.Command{
Short: "Convert front matter to TOML", Short: "Convert front matter to TOML",
Long: `toTOML converts all front matter in the content directory Long: `toTOML converts all front matter in the content directory
to use TOML for the front matter.`, to use TOML for the front matter.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
err := convertContents(rune([]byte(parser.TOML_LEAD)[0])) return convertContents(rune([]byte(parser.TOML_LEAD)[0]))
if err != nil {
jww.ERROR.Println(err)
}
}, },
} }
@ -70,11 +64,8 @@ var toYAMLCmd = &cobra.Command{
Short: "Convert front matter to YAML", Short: "Convert front matter to YAML",
Long: `toYAML converts all front matter in the content directory Long: `toYAML converts all front matter in the content directory
to use YAML for the front matter.`, to use YAML for the front matter.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
err := convertContents(rune([]byte(parser.YAML_LEAD)[0])) return convertContents(rune([]byte(parser.YAML_LEAD)[0]))
if err != nil {
jww.ERROR.Println(err)
}
}, },
} }
@ -87,7 +78,9 @@ func init() {
} }
func convertContents(mark rune) (err error) { func convertContents(mark rune) (err error) {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
site := &hugolib.Site{} site := &hugolib.Site{}
if err := site.Initialise(); err != nil { if err := site.Initialise(); err != nil {

View file

@ -31,16 +31,19 @@ or just source them in directly:
$ . /etc/bash_completion`, $ . /etc/bash_completion`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
if autocompleteType != "bash" { 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) err := cmd.Root().GenBashCompletionFile(autocompleteTarget)
if err != nil { if err != nil {
jww.FATAL.Fatalln("Failed to generate shell completion file:", err) return err
} else { } else {
jww.FEEDBACK.Println("Bash completion file for Hugo saved to", autocompleteTarget) jww.FEEDBACK.Println("Bash completion file for Hugo saved to", autocompleteTarget)
} }
return nil
}, },
} }

View 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 It creates one Markdown file per command with front matter suitable
for rendering in Hugo.`, 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) { if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
gendocdir += helpers.FilePathSeparator gendocdir += helpers.FilePathSeparator
} }
@ -55,6 +55,8 @@ for rendering in Hugo.`,
jww.FEEDBACK.Println("Generating Hugo command-line documentation in", gendocdir, "...") jww.FEEDBACK.Println("Generating Hugo command-line documentation in", gendocdir, "...")
cobra.GenMarkdownTreeCustom(cmd.Root(), gendocdir, prepender, linkHandler) cobra.GenMarkdownTreeCustom(cmd.Root(), gendocdir, prepender, linkHandler)
jww.FEEDBACK.Println("Done.") jww.FEEDBACK.Println("Done.")
return nil
}, },
} }

View file

@ -18,7 +18,7 @@ var genmanCmd = &cobra.Command{
command-line interface. By default, it creates the man page files command-line interface. By default, it creates the man page files
in the "man" directory under the current directory.`, 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{ header := &cobra.GenManHeader{
Section: "1", Section: "1",
Manual: "Hugo Manual", Manual: "Hugo Manual",
@ -37,6 +37,8 @@ in the "man" directory under the current directory.`,
cmd.Root().GenManTree(header, genmandir) cmd.Root().GenManTree(header, genmandir)
jww.FEEDBACK.Println("Done.") jww.FEEDBACK.Println("Done.")
return nil
}, },
} }

View file

@ -40,8 +40,44 @@ import (
"github.com/spf13/nitro" "github.com/spf13/nitro"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/fsnotify.v1" "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. //HugoCmd is Hugo's root command. Every other command attached to HugoCmd is a child command to it.
var HugoCmd = &cobra.Command{ var HugoCmd = &cobra.Command{
Use: "hugo", Use: "hugo",
@ -52,10 +88,15 @@ Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go. built with love by spf13 and friends in Go.
Complete documentation is available at http://gohugo.io/.`, Complete documentation is available at http://gohugo.io/.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
watchConfig() 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. //Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
func Execute() { func Execute() {
HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags) HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
HugoCmd.SilenceUsage = true
AddCommands() 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) os.Exit(-1)
} }
} }
@ -184,7 +233,7 @@ func LoadDefaultSettings() {
} }
// InitializeConfig initializes a config file with sensible default configuration flags. // InitializeConfig initializes a config file with sensible default configuration flags.
func InitializeConfig() { func InitializeConfig() error {
viper.SetConfigFile(CfgFile) viper.SetConfigFile(CfgFile)
// See https://github.com/spf13/viper/issues/73#issuecomment-126970794 // See https://github.com/spf13/viper/issues/73#issuecomment-126970794
if Source == "" { if Source == "" {
@ -195,9 +244,9 @@ func InitializeConfig() {
err := viper.ReadInConfig() err := viper.ReadInConfig()
if err != nil { if err != nil {
if _, ok := err.(viper.ConfigParseError); ok { if _, ok := err.(viper.ConfigParseError); ok {
jww.ERROR.Println(err) return newSystemError(err)
} else { } 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() themeDir := helpers.GetThemeDir()
if themeDir != "" { if themeDir != "" {
if _, err := os.Stat(themeDir); os.IsNotExist(err) { 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", jww.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
helpers.HugoReleaseVersion(), minVersion) helpers.HugoReleaseVersion(), minVersion)
} }
return nil
} }
func watchConfig() { func watchConfig() {
@ -344,23 +395,26 @@ func watchConfig() {
}) })
} }
func build(watches ...bool) { func build(watches ...bool) error {
err := copyStatic()
if err != nil { if err := copyStatic(); err != nil {
fmt.Println(err) return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("PublishDir")), err)
utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", helpers.AbsPathify(viper.GetString("PublishDir"))))
} }
watch := false watch := false
if len(watches) > 0 && watches[0] { if len(watches) > 0 && watches[0] {
watch = true watch = true
} }
utils.StopOnErr(buildSite(BuildWatch || watch)) if err := buildSite(BuildWatch || watch); err != nil {
return fmt.Errorf("Error building site: %s", err)
}
if BuildWatch { if BuildWatch {
jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir")))
jww.FEEDBACK.Println("Press Ctrl+C to stop") jww.FEEDBACK.Println("Press Ctrl+C to stop")
utils.CheckErr(NewWatcher(0)) utils.CheckErr(NewWatcher(0))
} }
return nil
} }
func copyStatic() error { func copyStatic() error {
@ -483,7 +537,6 @@ func NewWatcher(port int) error {
var wg sync.WaitGroup var wg sync.WaitGroup
if err != nil { if err != nil {
fmt.Println(err)
return err return err
} }

View file

@ -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)
}

View file

@ -35,34 +35,44 @@ import (
jww "github.com/spf13/jwalterweatherman" 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{ var importJekyllCmd = &cobra.Command{
Use: "jekyll", Use: "jekyll",
Short: "hugo import from Jekyll", Short: "hugo import from Jekyll",
Long: `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`.", 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.SetLogThreshold(jww.LevelTrace)
jww.SetStdoutThreshold(jww.LevelWarn) jww.SetStdoutThreshold(jww.LevelWarn)
if len(args) < 2 { if len(args) < 2 {
jww.ERROR.Println(`Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.") return newUserError(`Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
return
} }
jekyllRoot, err := filepath.Abs(filepath.Clean(args[0])) jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil { if err != nil {
jww.ERROR.Println("Path error:", args[0]) return newUserError("Path error:", args[0])
return
} }
targetDir, err := filepath.Abs(filepath.Clean(args[1])) targetDir, err := filepath.Abs(filepath.Clean(args[1]))
if err != nil { if err != nil {
jww.ERROR.Println("Path error:", args[1]) return newUserError("Path error:", args[1])
return
} }
createSiteFromJekyll(jekyllRoot, targetDir) createSiteFromJekyll(jekyllRoot, targetDir)
@ -82,8 +92,7 @@ func importFromJekyll(cmd *cobra.Command, args []string) {
relPath, err := filepath.Rel(jekyllRoot, path) relPath, err := filepath.Rel(jekyllRoot, path)
if err != nil { if err != nil {
jww.ERROR.Println("Get rel path error:", path) return newUserError("Get rel path error:", path)
return err
} }
relPath = filepath.ToSlash(relPath) relPath = filepath.ToSlash(relPath)
@ -106,13 +115,15 @@ func importFromJekyll(cmd *cobra.Command, args []string) {
err = filepath.Walk(jekyllRoot, callback) err = filepath.Walk(jekyllRoot, callback)
if err != nil { if err != nil {
fmt.Println(err) return err
} else { } else {
fmt.Println("Congratulations!", fileCount, "posts imported!") fmt.Println("Congratulations!", fileCount, "posts imported!")
fmt.Println("Now, start Hugo by yourself: \n" + fmt.Println("Now, start Hugo by yourself: \n" +
"$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove") "$ 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") fmt.Println("$ cd " + args[1] + "\n$ hugo server -w --theme=herring-cove")
} }
return nil
} }
func createSiteFromJekyll(jekyllRoot, targetDir string) { func createSiteFromJekyll(jekyllRoot, targetDir string) {

View file

@ -30,12 +30,13 @@ var limit = &cobra.Command{
Short: "Check system ulimit settings", Short: "Check system ulimit settings",
Long: `Hugo will inspect the current ulimit settings on the system. 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`, 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 var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil { 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("Current rLimit:", rLimit)
jww.FEEDBACK.Println("Attempting to increase limit") jww.FEEDBACK.Println("Attempting to increase limit")
@ -43,13 +44,15 @@ var limit = &cobra.Command{
rLimit.Cur = 999999 rLimit.Cur = 999999
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil { if err != nil {
jww.ERROR.Println("Error Setting rLimit ", err) return newSystemError("Error Setting rLimit ", err)
} }
err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil { if err != nil {
jww.ERROR.Println("Error Getting rLimit ", err) return newSystemError("Error Getting rLimit ", err)
} }
jww.FEEDBACK.Println("rLimit after change:", rLimit) jww.FEEDBACK.Println("rLimit after change:", rLimit)
return nil
}, },
} }

View file

@ -33,22 +33,25 @@ var listCmd = &cobra.Command{
Long: `Listing out various types of content. Long: `Listing out various types of content.
List requires a subcommand, e.g. ` + "`hugo list drafts`.", List requires a subcommand, e.g. ` + "`hugo list drafts`.",
Run: nil, RunE: nil,
} }
var listDraftsCmd = &cobra.Command{ var listDraftsCmd = &cobra.Command{
Use: "drafts", Use: "drafts",
Short: "List all drafts", Short: "List all drafts",
Long: `List all of the drafts in your content directory.`, 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) viper.Set("BuildDrafts", true)
site := &hugolib.Site{} site := &hugolib.Site{}
if err := site.Process(); err != nil { 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 { 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", Short: "List all posts dated in the future",
Long: `List all of the posts in your content directory which will be Long: `List all of the posts in your content directory which will be
posted in the future.`, 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) viper.Set("BuildFuture", true)
site := &hugolib.Site{} site := &hugolib.Site{}
if err := site.Process(); err != nil { 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 { for _, p := range site.Pages {
@ -84,5 +92,7 @@ posted in the future.`,
} }
return nil
}, },
} }

View file

@ -25,8 +25,11 @@ var config = &cobra.Command{
Use: "config", Use: "config",
Short: "Print the site configuration", Short: "Print the site configuration",
Long: `Print the site configuration, both default and custom settings.`, Long: `Print the site configuration, both default and custom settings.`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
allSettings := viper.AllSettings() allSettings := viper.AllSettings()
var separator string var separator string
@ -49,5 +52,7 @@ var config = &cobra.Command{
fmt.Printf("%s%s%+v\n", k, separator, allSettings[k]) fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
} }
} }
return nil
}, },
} }

View 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.`, If archetypes are provided in your theme or site, they will be used.`,
Run: NewContent, RunE: NewContent,
} }
var newSiteCmd = &cobra.Command{ var newSiteCmd = &cobra.Command{
@ -64,7 +64,7 @@ var newSiteCmd = &cobra.Command{
Long: `Create a new site in the provided directory. Long: `Create a new site in the provided directory.
The new site will have the correct structure, but no content or theme yet. The new site will have the correct structure, but no content or theme yet.
Use ` + "`hugo new [contentPath]`" + ` to create new content.`, Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
Run: NewSite, RunE: NewSite,
} }
var newThemeCmd = &cobra.Command{ 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 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 name to the copyright line in the license and adjust the theme.toml file
as you see fit.`, as you see fit.`,
Run: NewTheme, RunE: NewTheme,
} }
// NewContent adds new content to a Hugo site. // NewContent adds new content to a Hugo site.
func NewContent(cmd *cobra.Command, args []string) { func NewContent(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
if cmd.Flags().Lookup("format").Changed { if cmd.Flags().Lookup("format").Changed {
viper.Set("MetaDataFormat", configFormat) viper.Set("MetaDataFormat", configFormat)
} }
if len(args) < 1 { if len(args) < 1 {
cmd.Usage() return newUserError("path needs to be provided")
jww.FATAL.Fatalln("path needs to be provided")
} }
createpath := args[0] createpath := args[0]
@ -100,10 +101,8 @@ func NewContent(cmd *cobra.Command, args []string) {
kind = contentType kind = contentType
} }
err := create.NewContent(kind, createpath) return create.NewContent(kind, createpath)
if err != nil {
jww.ERROR.Println(err)
}
} }
func doNewSite(basepath string, force bool) error { 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. // 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 { if len(args) < 1 {
cmd.Usage() return newUserError("path needs to be provided")
jww.FATAL.Fatalln("path needs to be provided")
} }
createpath, err := filepath.Abs(filepath.Clean(args[0])) createpath, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil { if err != nil {
cmd.Usage() return newUserError(err)
jww.FATAL.Fatalln(err)
} }
forceNew, _ := cmd.Flags().GetBool("force") forceNew, _ := cmd.Flags().GetBool("force")
if err := doNewSite(createpath, forceNew); err != nil {
cmd.Usage() return doNewSite(createpath, forceNew)
jww.FATAL.Fatalln(err)
}
} }
// NewTheme creates a new Hugo theme. // NewTheme creates a new Hugo theme.
func NewTheme(cmd *cobra.Command, args []string) { func NewTheme(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
if len(args) < 1 { 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])) 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) err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), hugofs.SourceFs)
if err != nil { if err != nil {
jww.FATAL.Fatalln(err) return nil
} }
createThemeMD(createpath) createThemeMD(createpath)
return nil
} }
func mkdir(x ...string) { func mkdir(x ...string) {

View 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 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 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.`, of a second, you will be able to save and see your changes nearly instantly.`,
//Run: server, //RunE: server,
} }
type filesOnlyFs struct { type filesOnlyFs struct {
@ -90,10 +90,10 @@ func init() {
serverCmd.Flags().BoolVarP(&NoTimes, "noTimes", "", false, "Don't sync modification time of files") 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().String("memstats", "", "log memory usage to this file")
serverCmd.Flags().Int("meminterval", 100, "interval to poll memory usage (requires --memstats)") 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() InitializeConfig()
if cmd.Flags().Lookup("disableLiveReload").Changed { 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") jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort() sp, err := helpers.FindAvailablePort()
if err != nil { if err != nil {
jww.ERROR.Println("Unable to find alternative port to use") return newSystemError("Unable to find alternative port to use:", err)
jww.ERROR.Fatalln(err)
} }
serverPort = sp.Port serverPort = sp.Port
} }
@ -126,7 +125,7 @@ func server(cmd *cobra.Command, args []string) {
BaseURL, err := fixURL(BaseURL) BaseURL, err := fixURL(BaseURL)
if err != nil { if err != nil {
jww.ERROR.Fatal(err) return err
} }
viper.Set("BaseURL", BaseURL) viper.Set("BaseURL", BaseURL)
@ -146,7 +145,9 @@ func server(cmd *cobra.Command, args []string) {
viper.Set("PublishDir", "/") viper.Set("PublishDir", "/")
} }
build(serverWatch) if err := build(serverWatch); err != nil {
return err
}
// Watch runs its own server as part of the routine // Watch runs its own server as part of the routine
if serverWatch { 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) jww.FEEDBACK.Printf("Watching for changes in %s/{%s}\n", baseWatchDir, rootWatchDirs)
err := NewWatcher(serverPort) err := NewWatcher(serverPort)
if err != nil { if err != nil {
fmt.Println(err) return err
} }
} }
serve(serverPort) serve(serverPort)
return nil
} }
func serve(port int) { func serve(port int) {

View file

@ -20,7 +20,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/hugo/parser" "github.com/spf13/hugo/parser"
jww "github.com/spf13/jwalterweatherman"
) )
var undraftCmd = &cobra.Command{ var undraftCmd = &cobra.Command{
@ -29,53 +28,50 @@ var undraftCmd = &cobra.Command{
Long: `Undraft changes the content's draft status from 'True' to 'False' Long: `Undraft changes the content's draft status from 'True' to 'False'
and updates the date to the current date and time. and updates the date to the current date and time.
If the content's draft status is 'False', nothing is done.`, 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 // Publish publishes the specified content by setting its draft status
// to false and setting its publish date to now. If the specified content is // to false and setting its publish date to now. If the specified content is
// not a draft, it will log an error. // not a draft, it will log an error.
func Undraft(cmd *cobra.Command, args []string) { func Undraft(cmd *cobra.Command, args []string) error {
InitializeConfig() if err := InitializeConfig(); err != nil {
return err
}
if len(args) < 1 { if len(args) < 1 {
cmd.Usage() return newUserError("a piece of content needs to be specified")
jww.FATAL.Fatalln("a piece of content needs to be specified")
} }
location := args[0] location := args[0]
// open the file // open the file
f, err := os.Open(location) f, err := os.Open(location)
if err != nil { if err != nil {
jww.ERROR.Print(err) return err
return
} }
// get the page from file // get the page from file
p, err := parser.ReadFrom(f) p, err := parser.ReadFrom(f)
f.Close() f.Close()
if err != nil { if err != nil {
jww.ERROR.Print(err) return err
return
} }
w, err := undraftContent(p) w, err := undraftContent(p)
if err != nil { if err != nil {
jww.ERROR.Printf("an error occurred while undrafting %q: %s", location, err) return newSystemError("an error occurred while undrafting %q: %s", location, err)
return
} }
f, err = os.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644) f, err = os.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil { if err != nil {
jww.ERROR.Printf("%q not be undrafted due to error opening file to save changes: %q\n", location, err) return newSystemError("%q not be undrafted due to error opening file to save changes: %q\n", location, err)
return
} }
defer f.Close() defer f.Close()
_, err = w.WriteTo(f) _, err = w.WriteTo(f)
if err != nil { 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 // undraftContent: if the content is a draft, change its draft status to

View file

@ -32,7 +32,7 @@ var version = &cobra.Command{
Use: "version", Use: "version",
Short: "Print the version number of Hugo", Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's.`, 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 == "" { if hugolib.BuildDate == "" {
setBuildDate() // set the build date from executable's mdate setBuildDate() // set the build date from executable's mdate
} else { } else {
@ -43,6 +43,8 @@ var version = &cobra.Command{
} else { } else {
fmt.Printf("Hugo Static Site Generator v%s-%s BuildDate: %s\n", helpers.HugoVersion(), strings.ToUpper(hugolib.CommitHash), hugolib.BuildDate) fmt.Printf("Hugo Static Site Generator v%s-%s BuildDate: %s\n", helpers.HugoVersion(), strings.ToUpper(hugolib.CommitHash), hugolib.BuildDate)
} }
return nil
}, },
} }

View file

@ -254,9 +254,11 @@ func (s *Site) Build() (err error) {
return nil return nil
} }
func (s *Site) Analyze() { func (s *Site) Analyze() error {
s.Process() if err := s.Process(); err != nil {
s.ShowPlan(os.Stdout) return err
}
return s.ShowPlan(os.Stdout)
} }
func (s *Site) prepTemplates() { func (s *Site) prepTemplates() {