diff options
Diffstat (limited to 'commands')
-rw-r--r-- | commands/commandeer.go | 177 | ||||
-rw-r--r-- | commands/hugo.go | 173 | ||||
-rw-r--r-- | commands/server.go | 68 |
3 files changed, 237 insertions, 181 deletions
diff --git a/commands/commandeer.go b/commands/commandeer.go index a69ce2084..e96c97814 100644 --- a/commands/commandeer.go +++ b/commands/commandeer.go @@ -14,6 +14,18 @@ package commands import ( + "os" + "path/filepath" + "sync" + + "github.com/spf13/cobra" + + "github.com/gohugoio/hugo/utils" + + "github.com/spf13/afero" + + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" @@ -23,11 +35,22 @@ import ( type commandeer struct { *deps.DepsCfg + + subCmdVs []*cobra.Command + pathSpec *helpers.PathSpec visitedURLs *types.EvictingStringQueue staticDirsConfig []*src.Dirs + // We watch these for changes. + configFiles []string + + doWithCommandeer func(c *commandeer) error + + // We can do this only once. + fsCreate sync.Once + serverPorts []int languages helpers.Languages @@ -65,16 +88,158 @@ func (c *commandeer) initFs(fs *hugofs.Fs) error { return nil } -func newCommandeer(cfg *deps.DepsCfg, running bool) (*commandeer, error) { +func newCommandeer(running bool, doWithCommandeer func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) { + + c := &commandeer{ + doWithCommandeer: doWithCommandeer, + subCmdVs: append([]*cobra.Command{hugoCmdV}, subCmdVs...), + visitedURLs: types.NewEvictingStringQueue(10)} + + return c, c.loadConfig(running) +} + +func (c *commandeer) loadConfig(running bool) error { + + if c.DepsCfg == nil { + c.DepsCfg = &deps.DepsCfg{} + } + + cfg := c.DepsCfg + c.configured = false cfg.Running = running - var languages helpers.Languages + var dir string + if source != "" { + dir, _ = filepath.Abs(source) + } else { + dir, _ = os.Getwd() + } + + var sourceFs afero.Fs = hugofs.Os + if c.DepsCfg.Fs != nil { + sourceFs = c.DepsCfg.Fs.Source + } + + config, configFiles, err := hugolib.LoadConfig(hugolib.ConfigSourceDescriptor{Fs: sourceFs, Path: source, WorkingDir: dir, Filename: cfgFile}) + if err != nil { + return err + } + + c.Cfg = config + c.configFiles = configFiles + + for _, cmdV := range c.subCmdVs { + c.initializeFlags(cmdV) + } + + if l, ok := c.Cfg.Get("languagesSorted").(helpers.Languages); ok { + c.languages = l + } - if l, ok := cfg.Cfg.Get("languagesSorted").(helpers.Languages); ok { - languages = l + if baseURL != "" { + config.Set("baseURL", baseURL) } - c := &commandeer{DepsCfg: cfg, languages: languages, visitedURLs: types.NewEvictingStringQueue(10)} + if c.doWithCommandeer != nil { + err = c.doWithCommandeer(c) + } + + if err != nil { + return err + } + + if len(disableKinds) > 0 { + c.Set("disableKinds", disableKinds) + } + + logger, err := createLogger(cfg.Cfg) + if err != nil { + return err + } + + cfg.Logger = logger + + config.Set("logI18nWarnings", logI18nWarnings) + + if theme != "" { + config.Set("theme", theme) + } + + if themesDir != "" { + config.Set("themesDir", themesDir) + } + + if destination != "" { + config.Set("publishDir", destination) + } + + config.Set("workingDir", dir) + + if contentDir != "" { + config.Set("contentDir", contentDir) + } + + if layoutDir != "" { + config.Set("layoutDir", layoutDir) + } + + if cacheDir != "" { + config.Set("cacheDir", cacheDir) + } + + createMemFs := config.GetBool("renderToMemory") + + if createMemFs { + // Rendering to memoryFS, publish to Root regardless of publishDir. + config.Set("publishDir", "/") + } + + c.fsCreate.Do(func() { + fs := hugofs.NewFrom(sourceFs, config) + + // Hugo writes the output to memory instead of the disk. + if createMemFs { + fs.Destination = new(afero.MemMapFs) + } + + err = c.initFs(fs) + }) + + if err != nil { + return err + } + + cacheDir = config.GetString("cacheDir") + if cacheDir != "" { + if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] { + cacheDir = cacheDir + helpers.FilePathSeparator + } + isDir, err := helpers.DirExists(cacheDir, sourceFs) + utils.CheckErr(cfg.Logger, err) + if !isDir { + mkdir(cacheDir) + } + config.Set("cacheDir", cacheDir) + } else { + config.Set("cacheDir", helpers.GetTempDir("hugo_cache", sourceFs)) + } + + cfg.Logger.INFO.Println("Using config file:", config.ConfigFileUsed()) + + themeDir := c.PathSpec().GetThemeDir() + if themeDir != "" { + if _, err := sourceFs.Stat(themeDir); os.IsNotExist(err) { + return newSystemError("Unable to find theme Directory:", themeDir) + } + } + + themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch(sourceFs) + + if themeVersionMismatch { + cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n", + helpers.CurrentHugoVersion.ReleaseVersion(), minVersion) + } + + return nil - return c, nil } diff --git a/commands/hugo.go b/commands/hugo.go index b041fad38..a5b2c8895 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -25,8 +25,6 @@ import ( "golang.org/x/sync/errgroup" - "github.com/gohugoio/hugo/hugofs" - "log" "os" "path/filepath" @@ -44,7 +42,6 @@ import ( "regexp" "github.com/fsnotify/fsnotify" - "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugolib" "github.com/gohugoio/hugo/livereload" @@ -55,7 +52,6 @@ import ( "github.com/spf13/fsync" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/nitro" - "github.com/spf13/viper" ) // Hugo represents the Hugo sites to build. This variable is exported as it @@ -142,10 +138,6 @@ Complete documentation is available at http://gohugo.io/.`, return err } - if buildWatch { - c.watchConfig() - } - return c.build() }, } @@ -301,129 +293,11 @@ func init() { // InitializeConfig initializes a config file with sensible default configuration flags. func InitializeConfig(running bool, doWithCommandeer func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) { - var cfg *deps.DepsCfg = &deps.DepsCfg{} - - // Init file systems. This may be changed at a later point. - osFs := hugofs.Os - - config, err := hugolib.LoadConfig(hugolib.ConfigSourceDescriptor{Fs: osFs, Src: source, Name: cfgFile}) - if err != nil { - return nil, err - } - - // Init file systems. This may be changed at a later point. - cfg.Cfg = config - - c, err := newCommandeer(cfg, running) - if err != nil { - return nil, err - } - - for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) { - c.initializeFlags(cmdV) - } - - if baseURL != "" { - config.Set("baseURL", baseURL) - } - - if doWithCommandeer != nil { - if err := doWithCommandeer(c); err != nil { - return nil, err - } - } - - if len(disableKinds) > 0 { - c.Set("disableKinds", disableKinds) - } - - logger, err := createLogger(cfg.Cfg) + c, err := newCommandeer(running, doWithCommandeer, subCmdVs...) if err != nil { return nil, err } - cfg.Logger = logger - - config.Set("logI18nWarnings", logI18nWarnings) - - if theme != "" { - config.Set("theme", theme) - } - - if themesDir != "" { - config.Set("themesDir", themesDir) - } - - if destination != "" { - config.Set("publishDir", destination) - } - - var dir string - if source != "" { - dir, _ = filepath.Abs(source) - } else { - dir, _ = os.Getwd() - } - config.Set("workingDir", dir) - - if contentDir != "" { - config.Set("contentDir", contentDir) - } - - if layoutDir != "" { - config.Set("layoutDir", layoutDir) - } - - if cacheDir != "" { - config.Set("cacheDir", cacheDir) - } - - fs := hugofs.NewFrom(osFs, config) - - // Hugo writes the output to memory instead of the disk. - // This is only used for benchmark testing. Cause the content is only visible - // in memory. - if config.GetBool("renderToMemory") { - fs.Destination = new(afero.MemMapFs) - // Rendering to memoryFS, publish to Root regardless of publishDir. - config.Set("publishDir", "/") - } - - cacheDir = config.GetString("cacheDir") - if cacheDir != "" { - if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] { - cacheDir = cacheDir + helpers.FilePathSeparator - } - isDir, err := helpers.DirExists(cacheDir, fs.Source) - utils.CheckErr(cfg.Logger, err) - if !isDir { - mkdir(cacheDir) - } - config.Set("cacheDir", cacheDir) - } else { - config.Set("cacheDir", helpers.GetTempDir("hugo_cache", fs.Source)) - } - - if err := c.initFs(fs); err != nil { - return nil, err - } - - cfg.Logger.INFO.Println("Using config file:", config.ConfigFileUsed()) - - themeDir := c.PathSpec().GetThemeDir() - if themeDir != "" { - if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) { - return nil, newSystemError("Unable to find theme Directory:", themeDir) - } - } - - themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch() - - if themeVersionMismatch { - cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n", - helpers.CurrentHugoVersion.ReleaseVersion(), minVersion) - } - return c, nil } @@ -524,20 +398,6 @@ If you need to set this configuration value from the command line, set it via an } } -func (c *commandeer) watchConfig() { - v := c.Cfg.(*viper.Viper) - v.WatchConfig() - v.OnConfigChange(func(e fsnotify.Event) { - c.Logger.FEEDBACK.Println("Config file changed:", e.Name) - // Force a full rebuild - utils.CheckErr(c.Logger, c.recreateAndBuildSites(true)) - if !c.Cfg.GetBool("disableLiveReload") { - // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized - livereload.ForceRefresh() - } - }) -} - func (c *commandeer) fullBuild() error { var ( g errgroup.Group @@ -942,6 +802,7 @@ func (c *commandeer) resetAndBuildSites() (err error) { func (c *commandeer) initSites() error { if Hugo != nil { + Hugo.Cfg = c.Cfg Hugo.Log.ResetLogCounters() return nil } @@ -1009,6 +870,15 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) { } } + // Identifies changes to config (config.toml) files. + configSet := make(map[string]bool) + + for _, configFile := range c.configFiles { + c.Logger.FEEDBACK.Println("Watching for config changes in", configFile) + watcher.Add(configFile) + configSet[configFile] = true + } + go func() { for { select { @@ -1021,6 +891,21 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) { // Special handling for symbolic links inside /content. filtered := []fsnotify.Event{} for _, ev := range evs { + if configSet[ev.Name] { + if ev.Op&fsnotify.Chmod == fsnotify.Chmod { + continue + } + // Config file changed. Need full rebuild. + if err := c.loadConfig(true); err != nil { + jww.ERROR.Println("Failed to reload config:", err) + } else if err := c.recreateAndBuildSites(true); err != nil { + jww.ERROR.Println(err) + } else if !buildWatch && !c.Cfg.GetBool("disableLiveReload") { + livereload.ForceRefresh() + } + break + } + // Check the most specific first, i.e. files. contentMapped := Hugo.ContentChanges.GetSymbolicLinkMappings(ev.Name) if len(contentMapped) > 0 { @@ -1212,7 +1097,7 @@ func pickOneWriteOrCreatePath(events []fsnotify.Event) string { // isThemeVsHugoVersionMismatch returns whether the current Hugo version is // less than the theme's min_version. -func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) { +func (c *commandeer) isThemeVsHugoVersionMismatch(fs afero.Fs) (mismatch bool, requiredMinVersion string) { if !c.PathSpec().ThemeSet() { return } @@ -1221,13 +1106,13 @@ func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinV path := filepath.Join(themeDir, "theme.toml") - exists, err := helpers.Exists(path, c.Fs.Source) + exists, err := helpers.Exists(path, fs) if err != nil || !exists { return } - b, err := afero.ReadFile(c.Fs.Source, path) + b, err := afero.ReadFile(fs, path) tomlMeta, err := parser.HandleTOMLMetaData(b) diff --git a/commands/server.go b/commands/server.go index 130ac18be..278ba7f37 100644 --- a/commands/server.go +++ b/commands/server.go @@ -24,6 +24,7 @@ import ( "runtime" "strconv" "strings" + "sync" "syscall" "time" @@ -111,12 +112,16 @@ func init() { } +var serverPorts []int + func server(cmd *cobra.Command, args []string) error { // If a Destination is provided via flag write to disk if destination != "" { renderToDisk = true } + var serverCfgInit sync.Once + cfgInit := func(c *commandeer) error { c.Set("renderToMemory", !renderToDisk) if cmd.Flags().Changed("navigateToChanged") { @@ -132,37 +137,42 @@ func server(cmd *cobra.Command, args []string) error { c.Set("watch", true) } - serverPorts := make([]int, 1) + var err error - if c.languages.IsMultihost() { - if !serverAppend { - return newSystemError("--appendPort=false not supported when in multihost mode") + // We can only do this once. + serverCfgInit.Do(func() { + serverPorts = make([]int, 1) + + if c.languages.IsMultihost() { + if !serverAppend { + err = newSystemError("--appendPort=false not supported when in multihost mode") + } + serverPorts = make([]int, len(c.languages)) } - serverPorts = make([]int, len(c.languages)) - } - currentServerPort := serverPort + currentServerPort := serverPort - for i := 0; i < len(serverPorts); i++ { - l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(currentServerPort))) - if err == nil { - l.Close() - serverPorts[i] = currentServerPort - } else { - if i == 0 && serverCmd.Flags().Changed("port") { - // port set explicitly by user -- he/she probably meant it! - return newSystemErrorF("Server startup failed: %s", err) - } - jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port") - sp, err := helpers.FindAvailablePort() - if err != nil { - return newSystemError("Unable to find alternative port to use:", err) + for i := 0; i < len(serverPorts); i++ { + l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(currentServerPort))) + if err == nil { + l.Close() + serverPorts[i] = currentServerPort + } else { + if i == 0 && serverCmd.Flags().Changed("port") { + // port set explicitly by user -- he/she probably meant it! + err = newSystemErrorF("Server startup failed: %s", err) + } + jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port") + sp, err := helpers.FindAvailablePort() + if err != nil { + err = newSystemError("Unable to find alternative port to use:", err) + } + serverPorts[i] = sp.Port } - serverPorts[i] = sp.Port - } - currentServerPort = serverPorts[i] + 1 - } + currentServerPort = serverPorts[i] + 1 + } + }) c.serverPorts = serverPorts @@ -184,7 +194,7 @@ func server(cmd *cobra.Command, args []string) error { baseURL, err := fixURL(language, baseURL, serverPort) if err != nil { - return err + return nil } if isMultiHost { language.Set("baseURL", baseURL) @@ -194,7 +204,7 @@ func server(cmd *cobra.Command, args []string) error { } } - return nil + return err } @@ -215,10 +225,6 @@ func server(cmd *cobra.Command, args []string) error { s.RegisterMediaTypes() } - if serverWatch { - c.watchConfig() - } - // Watch runs its own server as part of the routine if serverWatch { |