diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-01-04 18:24:36 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-05-16 18:01:29 +0200 |
commit | 241b21b0fd34d91fccb2ce69874110dceae6f926 (patch) | |
tree | d4e0118eac7e9c42f065815447a70805f8d6ad3e /commands/hugo.go | |
parent | 6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff) |
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code.
Also,
* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.
Closes #10896
Closes #10620
Diffstat (limited to 'commands/hugo.go')
-rw-r--r-- | commands/hugo.go | 1259 |
1 files changed, 0 insertions, 1259 deletions
diff --git a/commands/hugo.go b/commands/hugo.go deleted file mode 100644 index 1a35d1626..000000000 --- a/commands/hugo.go +++ /dev/null @@ -1,1259 +0,0 @@ -// Copyright 2019 The Hugo Authors. All rights reserved. -// -// 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 defines and implements command-line commands and flags -// used by Hugo. Commands and flags are implemented using Cobra. -package commands - -import ( - "context" - "fmt" - "io" - "os" - "os/signal" - "path/filepath" - "runtime" - "runtime/pprof" - "runtime/trace" - "strings" - "sync/atomic" - "syscall" - "time" - - "github.com/gohugoio/hugo/hugofs/files" - "github.com/gohugoio/hugo/tpl" - - "github.com/gohugoio/hugo/common/herrors" - "github.com/gohugoio/hugo/common/htime" - "github.com/gohugoio/hugo/common/types" - - "github.com/gohugoio/hugo/hugofs" - - "github.com/gohugoio/hugo/resources/page" - - "github.com/gohugoio/hugo/common/hugo" - "github.com/gohugoio/hugo/common/loggers" - "github.com/gohugoio/hugo/common/terminal" - - "github.com/gohugoio/hugo/hugolib/filesystems" - - "golang.org/x/sync/errgroup" - - "github.com/gohugoio/hugo/config" - - flag "github.com/spf13/pflag" - - "github.com/fsnotify/fsnotify" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/livereload" - "github.com/gohugoio/hugo/watcher" - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/spf13/fsync" - jww "github.com/spf13/jwalterweatherman" -) - -// The Response value from Execute. -type Response struct { - // The build Result will only be set in the hugo build command. - Result *hugolib.HugoSites - - // Err is set when the command failed to execute. - Err error - - // The command that was executed. - Cmd *cobra.Command -} - -// IsUserError returns true is the Response error is a user error rather than a -// system error. -func (r Response) IsUserError() bool { - return r.Err != nil && isUserError(r.Err) -} - -// Execute adds all child commands to the root command HugoCmd and sets flags appropriately. -// The args are usually filled with os.Args[1:]. -func Execute(args []string) Response { - hugoCmd := newCommandsBuilder().addAll().build() - cmd := hugoCmd.getCommand() - cmd.SetArgs(args) - - c, err := cmd.ExecuteC() - - var resp Response - - if c == cmd && hugoCmd.c != nil { - // Root command executed - resp.Result = hugoCmd.c.hugo() - } - - if err == nil { - errCount := int(loggers.GlobalErrorCounter.Count()) - if errCount > 0 { - err = fmt.Errorf("logged %d errors", errCount) - } else if resp.Result != nil { - errCount = resp.Result.NumLogErrors() - if errCount > 0 { - err = fmt.Errorf("logged %d errors", errCount) - } - } - - } - - resp.Err = err - resp.Cmd = c - - return resp -} - -// InitializeConfig initializes a config file with sensible default configuration flags. -func initializeConfig(mustHaveConfigFile, failOnInitErr, running bool, - h *hugoBuilderCommon, - f flagsToConfigHandler, - cfgInit func(c *commandeer) error) (*commandeer, error) { - c, err := newCommandeer(mustHaveConfigFile, failOnInitErr, running, h, f, cfgInit) - if err != nil { - return nil, err - } - - if h := c.hugoTry(); h != nil { - for _, s := range h.Sites { - s.RegisterMediaTypes() - } - } - - return c, nil -} - -func (c *commandeer) createLogger(cfg config.Provider) (loggers.Logger, error) { - var ( - logHandle = io.Discard - logThreshold = jww.LevelWarn - logFile = cfg.GetString("logFile") - outHandle = io.Discard - stdoutThreshold = jww.LevelWarn - ) - - if !c.h.quiet { - outHandle = os.Stdout - } - - if c.h.verboseLog || c.h.logging || (c.h.logFile != "") { - var err error - if logFile != "" { - logHandle, err = os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) - if err != nil { - return nil, newSystemError("Failed to open log file:", logFile, err) - } - } else { - logHandle, err = os.CreateTemp("", "hugo") - if err != nil { - return nil, newSystemError(err) - } - } - } else if !c.h.quiet && cfg.GetBool("verbose") { - stdoutThreshold = jww.LevelInfo - } - - if cfg.GetBool("debug") { - stdoutThreshold = jww.LevelDebug - } - - if c.h.verboseLog { - logThreshold = jww.LevelInfo - if cfg.GetBool("debug") { - logThreshold = jww.LevelDebug - } - } - - loggers.InitGlobalLogger(stdoutThreshold, logThreshold, outHandle, logHandle) - helpers.InitLoggers() - - return loggers.NewLogger(stdoutThreshold, logThreshold, outHandle, logHandle, c.running), nil -} - -func initializeFlags(cmd *cobra.Command, cfg config.Provider) { - persFlagKeys := []string{ - "debug", - "verbose", - "logFile", - // Moved from vars - } - flagKeys := []string{ - "cleanDestinationDir", - "buildDrafts", - "buildFuture", - "buildExpired", - "clock", - "uglyURLs", - "canonifyURLs", - "enableRobotsTXT", - "enableGitInfo", - "pluralizeListTitles", - "preserveTaxonomyNames", - "ignoreCache", - "forceSyncStatic", - "noTimes", - "noChmod", - "noBuildLock", - "ignoreVendorPaths", - "templateMetrics", - "templateMetricsHints", - - // Moved from vars. - "baseURL", - "buildWatch", - "cacheDir", - "cfgFile", - "confirm", - "contentDir", - "debug", - "destination", - "disableKinds", - "dryRun", - "force", - "gc", - "printI18nWarnings", - "printUnusedTemplates", - "invalidateCDN", - "layoutDir", - "logFile", - "maxDeletes", - "quiet", - "renderToMemory", - "source", - "target", - "theme", - "themesDir", - "verbose", - "verboseLog", - "workers", - "duplicateTargetPaths", - } - - for _, key := range persFlagKeys { - setValueFromFlag(cmd.PersistentFlags(), key, cfg, "", false) - } - for _, key := range flagKeys { - setValueFromFlag(cmd.Flags(), key, cfg, "", false) - } - - setValueFromFlag(cmd.Flags(), "minify", cfg, "minifyOutput", true) - - // Set some "config aliases" - setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false) - setValueFromFlag(cmd.Flags(), "printI18nWarnings", cfg, "logI18nWarnings", false) - setValueFromFlag(cmd.Flags(), "printPathWarnings", cfg, "logPathWarnings", false) - -} - -func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targetKey string, force bool) { - key = strings.TrimSpace(key) - if (force && flags.Lookup(key) != nil) || flags.Changed(key) { - f := flags.Lookup(key) - configKey := key - if targetKey != "" { - configKey = targetKey - } - // Gotta love this API. - switch f.Value.Type() { - case "bool": - bv, _ := flags.GetBool(key) - cfg.Set(configKey, bv) - case "string": - cfg.Set(configKey, f.Value.String()) - case "stringSlice": - bv, _ := flags.GetStringSlice(key) - cfg.Set(configKey, bv) - case "int": - iv, _ := flags.GetInt(key) - cfg.Set(configKey, iv) - default: - panic(fmt.Sprintf("update switch with %s", f.Value.Type())) - } - - } -} - -func (c *commandeer) fullBuild(noBuildLock bool) error { - var ( - g errgroup.Group - langCount map[string]uint64 - ) - - if !c.h.quiet { - fmt.Println("Start building sites … ") - fmt.Println(hugo.BuildVersionString()) - if terminal.IsTerminal(os.Stdout) { - defer func() { - fmt.Print(showCursor + clearLine) - }() - } - } - - copyStaticFunc := func() error { - cnt, err := c.copyStatic() - if err != nil { - return fmt.Errorf("Error copying static files: %w", err) - } - langCount = cnt - return nil - } - buildSitesFunc := func() error { - if err := c.buildSites(noBuildLock); err != nil { - return fmt.Errorf("Error building site: %w", err) - } - return nil - } - // Do not copy static files and build sites in parallel if cleanDestinationDir is enabled. - // This flag deletes all static resources in /public folder that are missing in /static, - // and it does so at the end of copyStatic() call. - if c.Cfg.GetBool("cleanDestinationDir") { - if err := copyStaticFunc(); err != nil { - return err - } - if err := buildSitesFunc(); err != nil { - return err - } - } else { - g.Go(copyStaticFunc) - g.Go(buildSitesFunc) - if err := g.Wait(); err != nil { - return err - } - } - - for _, s := range c.hugo().Sites { - s.ProcessingStats.Static = langCount[s.Language().Lang] - } - - if c.h.gc { - count, err := c.hugo().GC() - if err != nil { - return err - } - for _, s := range c.hugo().Sites { - // We have no way of knowing what site the garbage belonged to. - s.ProcessingStats.Cleaned = uint64(count) - } - } - - return nil -} - -func (c *commandeer) initCPUProfile() (func(), error) { - if c.h.cpuprofile == "" { - return nil, nil - } - - f, err := os.Create(c.h.cpuprofile) - if err != nil { - return nil, fmt.Errorf("failed to create CPU profile: %w", err) - } - if err := pprof.StartCPUProfile(f); err != nil { - return nil, fmt.Errorf("failed to start CPU profile: %w", err) - } - return func() { - pprof.StopCPUProfile() - f.Close() - }, nil -} - -func (c *commandeer) initMemProfile() { - if c.h.memprofile == "" { - return - } - - f, err := os.Create(c.h.memprofile) - if err != nil { - c.logger.Errorf("could not create memory profile: ", err) - } - defer f.Close() - runtime.GC() // get up-to-date statistics - if err := pprof.WriteHeapProfile(f); err != nil { - c.logger.Errorf("could not write memory profile: ", err) - } -} - -func (c *commandeer) initTraceProfile() (func(), error) { - if c.h.traceprofile == "" { - return nil, nil - } - - f, err := os.Create(c.h.traceprofile) - if err != nil { - return nil, fmt.Errorf("failed to create trace file: %w", err) - } - - if err := trace.Start(f); err != nil { - return nil, fmt.Errorf("failed to start trace: %w", err) - } - - return func() { - trace.Stop() - f.Close() - }, nil -} - -func (c *commandeer) initMutexProfile() (func(), error) { - if c.h.mutexprofile == "" { - return nil, nil - } - - f, err := os.Create(c.h.mutexprofile) - if err != nil { - return nil, err - } - - runtime.SetMutexProfileFraction(1) - - return func() { - pprof.Lookup("mutex").WriteTo(f, 0) - f.Close() - }, nil -} - -func (c *commandeer) initMemTicker() func() { - memticker := time.NewTicker(5 * time.Second) - quit := make(chan struct{}) - printMem := func() { - var m runtime.MemStats - runtime.ReadMemStats(&m) - fmt.Printf("\n\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\n\n", formatByteCount(m.Alloc), formatByteCount(m.TotalAlloc), formatByteCount(m.Sys), m.NumGC) - } - - go func() { - for { - select { - case <-memticker.C: - printMem() - case <-quit: - memticker.Stop() - printMem() - return - } - } - }() - - return func() { - close(quit) - } -} - -func (c *commandeer) initProfiling() (func(), error) { - stopCPUProf, err := c.initCPUProfile() - if err != nil { - return nil, err - } - - stopMutexProf, err := c.initMutexProfile() - if err != nil { - return nil, err - } - - stopTraceProf, err := c.initTraceProfile() - if err != nil { - return nil, err - } - - var stopMemTicker func() - if c.h.printm { - stopMemTicker = c.initMemTicker() - } - - return func() { - c.initMemProfile() - - if stopCPUProf != nil { - stopCPUProf() - } - if stopMutexProf != nil { - stopMutexProf() - } - - if stopTraceProf != nil { - stopTraceProf() - } - - if stopMemTicker != nil { - stopMemTicker() - } - }, nil -} - -func (c *commandeer) build() error { - stopProfiling, err := c.initProfiling() - if err != nil { - return err - } - - defer func() { - if stopProfiling != nil { - stopProfiling() - } - }() - - if err := c.fullBuild(false); err != nil { - return err - } - - if !c.h.quiet { - fmt.Println() - c.hugo().PrintProcessingStats(os.Stdout) - fmt.Println() - - hugofs.WalkFilesystems(c.publishDirFs, func(fs afero.Fs) bool { - if dfs, ok := fs.(hugofs.DuplicatesReporter); ok { - dupes := dfs.ReportDuplicates() - if dupes != "" { - c.logger.Warnln("Duplicate target paths:", dupes) - } - } - return false - }) - - unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates() - for _, unusedTemplate := range unusedTemplates { - c.logger.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename()) - } - } - - if c.h.buildWatch { - watchDirs, err := c.getDirList() - if err != nil { - return err - } - - baseWatchDir := c.Cfg.GetString("workingDir") - rootWatchDirs := getRootWatchDirsStr(baseWatchDir, watchDirs) - - c.logger.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs) - c.logger.Println("Press Ctrl+C to stop") - watcher, err := c.newWatcher(c.h.poll, watchDirs...) - checkErr(c.Logger, err) - defer watcher.Close() - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - - <-sigs - } - - return nil -} - -func (c *commandeer) serverBuild() error { - stopProfiling, err := c.initProfiling() - if err != nil { - return err - } - - defer func() { - if stopProfiling != nil { - stopProfiling() - } - }() - - if err := c.fullBuild(false); err != nil { - return err - } - - // TODO(bep) Feedback? - if !c.h.quiet { - fmt.Println() - c.hugo().PrintProcessingStats(os.Stdout) - fmt.Println() - } - - return nil -} - -func (c *commandeer) copyStatic() (map[string]uint64, error) { - m, err := c.doWithPublishDirs(c.copyStaticTo) - if err == nil || herrors.IsNotExist(err) { - return m, nil - } - return m, err -} - -func (c *commandeer) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesystem) (uint64, error)) (map[string]uint64, error) { - langCount := make(map[string]uint64) - - staticFilesystems := c.hugo().BaseFs.SourceFilesystems.Static - - if len(staticFilesystems) == 0 { - c.logger.Infoln("No static directories found to sync") - return langCount, nil - } - - for lang, fs := range staticFilesystems { - cnt, err := f(fs) - if err != nil { - return langCount, err - } - - if lang == "" { - // Not multihost - for _, l := range c.languages { - langCount[l.Lang] = cnt - } - } else { - langCount[lang] = cnt - } - } - - return langCount, nil -} - -type countingStatFs struct { - afero.Fs - statCounter uint64 -} - -func (fs *countingStatFs) Stat(name string) (os.FileInfo, error) { - f, err := fs.Fs.Stat(name) - if err == nil { - if !f.IsDir() { - atomic.AddUint64(&fs.statCounter, 1) - } - } - return f, err -} - -func chmodFilter(dst, src os.FileInfo) bool { - // Hugo publishes data from multiple sources, potentially - // with overlapping directory structures. We cannot sync permissions - // for directories as that would mean that we might end up with write-protected - // directories inside /public. - // One example of this would be syncing from the Go Module cache, - // which have 0555 directories. - return src.IsDir() -} - -func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) { - publishDir := helpers.FilePathSeparator - - if sourceFs.PublishFolder != "" { - publishDir = filepath.Join(publishDir, sourceFs.PublishFolder) - } - - fs := &countingStatFs{Fs: sourceFs.Fs} - - syncer := fsync.NewSyncer() - syncer.NoTimes = c.Cfg.GetBool("noTimes") - syncer.NoChmod = c.Cfg.GetBool("noChmod") - syncer.ChmodFilter = chmodFilter - syncer.SrcFs = fs - syncer.DestFs = c.Fs.PublishDirStatic - // Now that we are using a unionFs for the static directories - // We can effectively clean the publishDir on initial sync - syncer.Delete = c.Cfg.GetBool("cleanDestinationDir") - - if syncer.Delete { - c.logger.Infoln("removing all files from destination that don't exist in static dirs") - - syncer.DeleteFilter = func(f os.FileInfo) bool { - return f.IsDir() && strings.HasPrefix(f.Name(), ".") - } - } - c.logger.Infoln("syncing static files to", publishDir) - - // because we are using a baseFs (to get the union right). - // set sync src to root - err := syncer.Sync(publishDir, helpers.FilePathSeparator) - if err != nil { - return 0, err - } - - // Sync runs Stat 3 times for every source file (which sounds much) - numFiles := fs.statCounter / 3 - - return numFiles, err -} - -func (c *commandeer) firstPathSpec() *helpers.PathSpec { - return c.hugo().Sites[0].PathSpec -} - -func (c *commandeer) timeTrack(start time.Time, name string) { - // Note the use of time.Since here and time.Now in the callers. - // We have a htime.Sinnce, but that may be adjusted to the future, - // and that does not make sense here, esp. when used before the - // global Clock is initialized. - elapsed := time.Since(start) - c.logger.Printf("%s in %v ms", name, int(1000*elapsed.Seconds())) -} - -// getDirList provides NewWatcher() with a list of directories to watch for changes. -func (c *commandeer) getDirList() ([]string, error) { - var filenames []string - - walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error { - if err != nil { - c.logger.Errorln("walker: ", err) - return nil - } - - if fi.IsDir() { - if fi.Name() == ".git" || - fi.Name() == "node_modules" || fi.Name() == "bower_components" { - return filepath.SkipDir - } - - filenames = append(filenames, fi.Meta().Filename) - } - - return nil - } - - watchFiles := c.hugo().PathSpec.BaseFs.WatchDirs() - for _, fi := range watchFiles { - if !fi.IsDir() { - filenames = append(filenames, fi.Meta().Filename) - continue - } - - w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.logger, Info: fi, WalkFn: walkFn}) - if err := w.Walk(); err != nil { - c.logger.Errorln("walker: ", err) - } - } - - filenames = helpers.UniqueStringsSorted(filenames) - - return filenames, nil -} - -func (c *commandeer) buildSites(noBuildLock bool) (err error) { - return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: noBuildLock}) -} - -func (c *commandeer) handleBuildErr(err error, msg string) { - c.buildErr = err - c.logger.Errorln(msg + ": " + cleanErrorLog(err.Error())) -} - -func (c *commandeer) rebuildSites(events []fsnotify.Event) error { - if c.buildErr != nil { - ferrs := herrors.UnwrapFileErrorsWithErrorContext(c.buildErr) - for _, err := range ferrs { - events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write}) - } - } - c.buildErr = nil - visited := c.visitedURLs.PeekAllSet() - if c.fastRenderMode { - // Make sure we always render the home pages - for _, l := range c.languages { - langPath := c.hugo().PathSpec.GetLangSubDir(l.Lang) - if langPath != "" { - langPath = langPath + "/" - } - home := c.hugo().PathSpec.PrependBasePath("/"+langPath, false) - visited[home] = true - } - } - return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: visited, ErrRecovery: c.wasError}, events...) -} - -func (c *commandeer) partialReRender(urls ...string) error { - defer func() { - c.wasError = false - }() - c.buildErr = nil - visited := make(map[string]bool) - for _, url := range urls { - visited[url] = true - } - - // Note: We do not set NoBuildLock as the file lock is not acquired at this stage. - return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.wasError}) -} - -func (c *commandeer) fullRebuild(changeType string) { - if changeType == configChangeGoMod { - // go.mod may be changed during the build itself, and - // we really want to prevent superfluous builds. - if !c.fullRebuildSem.TryAcquire(1) { - return - } - c.fullRebuildSem.Release(1) - } - - c.fullRebuildSem.Acquire(context.Background(), 1) - - go func() { - defer c.fullRebuildSem.Release(1) - - c.printChangeDetected(changeType) - - defer func() { - // Allow any file system events to arrive back. - // This will block any rebuild on config changes for the - // duration of the sleep. - time.Sleep(2 * time.Second) - }() - - defer c.timeTrack(time.Now(), "Rebuilt") - - c.commandeerHugoState = newCommandeerHugoState() - err := c.loadConfig() - if err != nil { - // Set the processing on pause until the state is recovered. - c.paused = true - c.handleBuildErr(err, "Failed to reload config") - - } else { - c.paused = false - } - - if !c.paused { - _, err := c.copyStatic() - if err != nil { - c.logger.Errorln(err) - return - } - - err = c.buildSites(true) - if err != nil { - c.logger.Errorln(err) - } else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") { - livereload.ForceRefresh() - } - } - }() -} - -// newWatcher creates a new watcher to watch filesystem events. -func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*watcher.Batcher, error) { - if runtime.GOOS == "darwin" { - tweakLimit() - } - - staticSyncer, err := newStaticSyncer(c) - if err != nil { - return nil, err - } - - var pollInterval time.Duration - poll := pollIntervalStr != "" - if poll { - pollInterval, err = types.ToDurationE(pollIntervalStr) - if err != nil { - return nil, fmt.Errorf("invalid value for flag poll: %s", err) - } - c.logger.Printf("Use watcher with poll interval %v", pollInterval) - } - - if pollInterval == 0 { - pollInterval = 500 * time.Millisecond - } - - watcher, err := watcher.New(500*time.Millisecond, pollInterval, poll) - if err != nil { - return nil, err - } - - spec := c.hugo().Deps.SourceSpec - - for _, d := range dirList { - if d != "" { - if spec.IgnoreFile(d) { - continue - } - _ = watcher.Add(d) - } - } - - // Identifies changes to config (config.toml) files. - configSet := make(map[string]bool) - - c.logger.Println("Watching for config changes in", strings.Join(c.configFiles, ", ")) - for _, configFile := range c.configFiles { - watcher.Add(configFile) - configSet[configFile] = true - } - - go func() { - for { - select { - case evs := <-watcher.Events: - unlock, err := c.buildLock() - if err != nil { - c.logger.Errorln("Failed to acquire a build lock: %s", err) - return - } - c.handleEvents(watcher, staticSyncer, evs, configSet) - if c.showErrorInBrowser && c.errCount() > 0 { - // Need to reload browser to show the error - livereload.ForceRefresh() - } - unlock() - case err := <-watcher.Errors(): - if err != nil && !herrors.IsNotExist(err) { - c.logger.Errorln("Error while watching:", err) - } - } - } - }() - - return watcher, nil -} - -func (c *commandeer) printChangeDetected(typ string) { - msg := "\nChange" - if typ != "" { - msg += " of " + typ - } - msg += " detected, rebuilding site." - - c.logger.Println(msg) - const layout = "2006-01-02 15:04:05.000 -0700" - c.logger.Println(htime.Now().Format(layout)) -} - -const ( - configChangeConfig = "config file" - configChangeGoMod = "go.mod file" - configChangeGoWork = "go work file" -) - -func (c *commandeer) handleEvents(watcher *watcher.Batcher, - staticSyncer *staticSyncer, - evs []fsnotify.Event, - configSet map[string]bool) { - defer func() { - c.wasError = false - }() - - var isHandled bool - - for _, ev := range evs { - isConfig := configSet[ev.Name] - configChangeType := configChangeConfig - if isConfig { - if strings.Contains(ev.Name, "go.mod") { - configChangeType = configChangeGoMod - } - if strings.Contains(ev.Name, ".work") { - configChangeType = configChangeGoWork - } - } - if !isConfig { - // It may be one of the /config folders - dirname := filepath.Dir(ev.Name) - if dirname != "." && configSet[dirname] { - isConfig = true - } - } - - if isConfig { - isHandled = true - - if ev.Op&fsnotify.Chmod == fsnotify.Chmod { - continue - } - - if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename { - for _, configFile := range c.configFiles { - counter := 0 - for watcher.Add(configFile) != nil { - counter++ - if counter >= 100 { - break - } - time.Sleep(100 * time.Millisecond) - } - } - } - - // Config file(s) changed. Need full rebuild. - c.fullRebuild(configChangeType) - - return - } - } - - if isHandled { - return - } - - if c.paused { - // Wait for the server to get into a consistent state before - // we continue with processing. - return - } - - if len(evs) > 50 { - // This is probably a mass edit of the content dir. - // Schedule a full rebuild for when it slows down. - c.debounce(func() { - c.fullRebuild("") - }) - return - } - - c.logger.Infoln("Received System Events:", evs) - - staticEvents := []fsnotify.Event{} - dynamicEvents := []fsnotify.Event{} - - filtered := []fsnotify.Event{} - for _, ev := range evs { - if c.hugo().ShouldSkipFileChangeEvent(ev) { - continue - } - // Check the most specific first, i.e. files. - contentMapped := c.hugo().ContentChanges.GetSymbolicLinkMappings(ev.Name) - if len(contentMapped) > 0 { - for _, mapped := range contentMapped { - filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op}) - } - continue - } - - // Check for any symbolic directory mapping. - - dir, name := filepath.Split(ev.Name) - - contentMapped = c.hugo().ContentChanges.GetSymbolicLinkMappings(dir) - - if len(contentMapped) == 0 { - filtered = append(filtered, ev) - continue - } - - for _, mapped := range contentMapped { - mappedFilename := filepath.Join(mapped, name) - filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op}) - } - } - - evs = filtered - - for _, ev := range evs { - ext := filepath.Ext(ev.Name) - baseName := filepath.Base(ev.Name) - istemp := strings.HasSuffix(ext, "~") || - (ext == ".swp") || // vim - (ext == ".swx") || // vim - (ext == ".tmp") || // generic temp file - (ext == ".DS_Store") || // OSX Thumbn |