github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/commands/commands.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package commands
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"time"
    20  
    21  	"github.com/gohugoio/hugo/common/hugo"
    22  	"github.com/gohugoio/hugo/common/loggers"
    23  	hpaths "github.com/gohugoio/hugo/common/paths"
    24  	"github.com/gohugoio/hugo/config"
    25  	"github.com/gohugoio/hugo/helpers"
    26  	"github.com/spf13/cobra"
    27  )
    28  
    29  type commandsBuilder struct {
    30  	hugoBuilderCommon
    31  
    32  	commands []cmder
    33  }
    34  
    35  func newCommandsBuilder() *commandsBuilder {
    36  	return &commandsBuilder{}
    37  }
    38  
    39  func (b *commandsBuilder) addCommands(commands ...cmder) *commandsBuilder {
    40  	b.commands = append(b.commands, commands...)
    41  	return b
    42  }
    43  
    44  func (b *commandsBuilder) addAll() *commandsBuilder {
    45  	b.addCommands(
    46  		b.newServerCmd(),
    47  		newVersionCmd(),
    48  		newEnvCmd(),
    49  		b.newConfigCmd(),
    50  		b.newDeployCmd(),
    51  		b.newConvertCmd(),
    52  		b.newNewCmd(),
    53  		b.newListCmd(),
    54  		newImportCmd(),
    55  		newGenCmd(),
    56  		createReleaser(),
    57  		b.newModCmd(),
    58  	)
    59  
    60  	return b
    61  }
    62  
    63  func (b *commandsBuilder) build() *hugoCmd {
    64  	h := b.newHugoCmd()
    65  	addCommands(h.getCommand(), b.commands...)
    66  	return h
    67  }
    68  
    69  func addCommands(root *cobra.Command, commands ...cmder) {
    70  	for _, command := range commands {
    71  		cmd := command.getCommand()
    72  		if cmd == nil {
    73  			continue
    74  		}
    75  		root.AddCommand(cmd)
    76  	}
    77  }
    78  
    79  type baseCmd struct {
    80  	cmd *cobra.Command
    81  }
    82  
    83  var _ commandsBuilderGetter = (*baseBuilderCmd)(nil)
    84  
    85  // Used in tests.
    86  type commandsBuilderGetter interface {
    87  	getCommandsBuilder() *commandsBuilder
    88  }
    89  
    90  type baseBuilderCmd struct {
    91  	*baseCmd
    92  	*commandsBuilder
    93  }
    94  
    95  func (b *baseBuilderCmd) getCommandsBuilder() *commandsBuilder {
    96  	return b.commandsBuilder
    97  }
    98  
    99  func (c *baseCmd) getCommand() *cobra.Command {
   100  	return c.cmd
   101  }
   102  
   103  func newBaseCmd(cmd *cobra.Command) *baseCmd {
   104  	return &baseCmd{cmd: cmd}
   105  }
   106  
   107  func (b *commandsBuilder) newBuilderCmd(cmd *cobra.Command) *baseBuilderCmd {
   108  	bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
   109  	bcmd.hugoBuilderCommon.handleFlags(cmd)
   110  	return bcmd
   111  }
   112  
   113  func (b *commandsBuilder) newBuilderBasicCmd(cmd *cobra.Command) *baseBuilderCmd {
   114  	bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
   115  	bcmd.hugoBuilderCommon.handleCommonBuilderFlags(cmd)
   116  	return bcmd
   117  }
   118  
   119  func (c *baseCmd) flagsToConfig(cfg config.Provider) {
   120  	initializeFlags(c.cmd, cfg)
   121  }
   122  
   123  type hugoCmd struct {
   124  	*baseBuilderCmd
   125  
   126  	// Need to get the sites once built.
   127  	c *commandeer
   128  }
   129  
   130  var _ cmder = (*nilCommand)(nil)
   131  
   132  type nilCommand struct{}
   133  
   134  func (c *nilCommand) getCommand() *cobra.Command {
   135  	return nil
   136  }
   137  
   138  func (c *nilCommand) flagsToConfig(cfg config.Provider) {
   139  }
   140  
   141  func (b *commandsBuilder) newHugoCmd() *hugoCmd {
   142  	cc := &hugoCmd{}
   143  
   144  	cc.baseBuilderCmd = b.newBuilderCmd(&cobra.Command{
   145  		Use:   "hugo",
   146  		Short: "hugo builds your site",
   147  		Long: `hugo is the main command, used to build your Hugo site.
   148  
   149  Hugo is a Fast and Flexible Static Site Generator
   150  built with love by spf13 and friends in Go.
   151  
   152  Complete documentation is available at https://gohugo.io/.`,
   153  		RunE: func(cmd *cobra.Command, args []string) error {
   154  			defer cc.timeTrack(time.Now(), "Total")
   155  			cfgInit := func(c *commandeer) error {
   156  				if cc.buildWatch {
   157  					c.Set("disableLiveReload", true)
   158  				}
   159  				return nil
   160  			}
   161  
   162  			// prevent cobra printing error so it can be handled here (before the timeTrack prints)
   163  			cmd.SilenceErrors = true
   164  
   165  			c, err := initializeConfig(true, true, cc.buildWatch, &cc.hugoBuilderCommon, cc, cfgInit)
   166  			if err != nil {
   167  				cmd.PrintErrln("Error:", err.Error())
   168  				return err
   169  			}
   170  			cc.c = c
   171  
   172  			err = c.build()
   173  			if err != nil {
   174  				cmd.PrintErrln("Error:", err.Error())
   175  			}
   176  			return err
   177  		},
   178  	})
   179  
   180  	cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)")
   181  	cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir")
   182  	cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
   183  
   184  	// Set bash-completion
   185  	_ = cc.cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
   186  
   187  	cc.cmd.PersistentFlags().BoolVarP(&cc.verbose, "verbose", "v", false, "verbose output")
   188  	cc.cmd.PersistentFlags().BoolVarP(&cc.debug, "debug", "", false, "debug output")
   189  	cc.cmd.PersistentFlags().BoolVar(&cc.logging, "log", false, "enable Logging")
   190  	cc.cmd.PersistentFlags().StringVar(&cc.logFile, "logFile", "", "log File path (if set, logging enabled automatically)")
   191  	cc.cmd.PersistentFlags().BoolVar(&cc.verboseLog, "verboseLog", false, "verbose logging")
   192  
   193  	cc.cmd.Flags().BoolVarP(&cc.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
   194  
   195  	cc.cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
   196  
   197  	// Set bash-completion
   198  	_ = cc.cmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
   199  
   200  	cc.cmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
   201  	cc.cmd.SilenceUsage = true
   202  
   203  	return cc
   204  }
   205  
   206  type hugoBuilderCommon struct {
   207  	source      string
   208  	baseURL     string
   209  	environment string
   210  
   211  	buildWatch bool
   212  	poll       string
   213  	clock      string
   214  
   215  	gc bool
   216  
   217  	// Profile flags (for debugging of performance problems)
   218  	cpuprofile   string
   219  	memprofile   string
   220  	mutexprofile string
   221  	traceprofile string
   222  	printm       bool
   223  
   224  	// TODO(bep) var vs string
   225  	logging    bool
   226  	verbose    bool
   227  	verboseLog bool
   228  	debug      bool
   229  	quiet      bool
   230  
   231  	cfgFile string
   232  	cfgDir  string
   233  	logFile string
   234  }
   235  
   236  func (cc *hugoBuilderCommon) timeTrack(start time.Time, name string) {
   237  	if cc.quiet {
   238  		return
   239  	}
   240  	elapsed := time.Since(start)
   241  	fmt.Printf("%s in %v ms\n", name, int(1000*elapsed.Seconds()))
   242  }
   243  
   244  func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string {
   245  	if cc.cfgDir != "" {
   246  		return hpaths.AbsPathify(baseDir, cc.cfgDir)
   247  	}
   248  
   249  	if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found {
   250  		return hpaths.AbsPathify(baseDir, v)
   251  	}
   252  
   253  	return hpaths.AbsPathify(baseDir, "config")
   254  }
   255  
   256  func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {
   257  	if cc.environment != "" {
   258  		return cc.environment
   259  	}
   260  
   261  	if v, found := os.LookupEnv("HUGO_ENVIRONMENT"); found {
   262  		return v
   263  	}
   264  
   265  	//  Used by Netlify and Forestry
   266  	if v, found := os.LookupEnv("HUGO_ENV"); found {
   267  		return v
   268  	}
   269  
   270  	if isServer {
   271  		return hugo.EnvironmentDevelopment
   272  	}
   273  
   274  	return hugo.EnvironmentProduction
   275  }
   276  
   277  func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
   278  	cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
   279  	cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
   280  	cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
   281  	cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
   282  	cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
   283  	cmd.PersistentFlags().StringVar(&cc.clock, "clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
   284  }
   285  
   286  func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
   287  	cc.handleCommonBuilderFlags(cmd)
   288  	cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
   289  	cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
   290  	cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
   291  	cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
   292  	cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
   293  	cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
   294  	cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
   295  	cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
   296  	cmd.Flags().StringP("destination", "d", "", "filesystem path to write files to")
   297  	cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
   298  	cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. https://spf13.com/")
   299  	cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages")
   300  	cmd.Flags().BoolVar(&cc.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
   301  	cmd.Flags().StringVar(&cc.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
   302  	cmd.Flags().BoolVar(&loggers.PanicOnWarning, "panicOnWarning", false, "panic on first WARNING log")
   303  	cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
   304  	cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
   305  	cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.")
   306  	cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
   307  	cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
   308  	cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
   309  	cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
   310  	cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
   311  	cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
   312  	cmd.Flags().StringVarP(&cc.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
   313  	cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory profile to `file`")
   314  	cmd.Flags().BoolVarP(&cc.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")
   315  	cmd.Flags().StringVarP(&cc.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`")
   316  	cmd.Flags().StringVarP(&cc.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)")
   317  
   318  	// Hide these for now.
   319  	cmd.Flags().MarkHidden("profile-cpu")
   320  	cmd.Flags().MarkHidden("profile-mem")
   321  	cmd.Flags().MarkHidden("profile-mutex")
   322  
   323  	cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
   324  
   325  	cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
   326  
   327  	// Set bash-completion.
   328  	// Each flag must first be defined before using the SetAnnotation() call.
   329  	_ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
   330  	_ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
   331  	_ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
   332  	_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
   333  }
   334  
   335  func checkErr(logger loggers.Logger, err error, s ...string) {
   336  	if err == nil {
   337  		return
   338  	}
   339  	for _, message := range s {
   340  		logger.Errorln(message)
   341  	}
   342  	logger.Errorln(err)
   343  }