Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'commands')
-rw-r--r--commands/commandeer.go28
-rw-r--r--commands/commands.go13
-rw-r--r--commands/convert.go5
-rw-r--r--commands/hugo.go314
-rw-r--r--commands/import_jekyll.go71
-rw-r--r--commands/list_test.go1
-rw-r--r--commands/mod.go189
-rw-r--r--commands/new.go6
-rw-r--r--commands/server.go21
-rw-r--r--commands/static_syncer.go1
10 files changed, 374 insertions, 275 deletions
diff --git a/commands/commandeer.go b/commands/commandeer.go
index 8c9da53b9..015bf3678 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -16,6 +16,11 @@ package commands
import (
"bytes"
"errors"
+ "sync"
+
+ "golang.org/x/sync/semaphore"
+
+ "github.com/gohugoio/hugo/modules"
"io/ioutil"
@@ -27,8 +32,6 @@ import (
"os"
"path/filepath"
"regexp"
- "strings"
- "sync"
"time"
"github.com/gohugoio/hugo/common/loggers"
@@ -88,6 +91,8 @@ type commandeer struct {
configured bool
paused bool
+ fullRebuildSem *semaphore.Weighted
+
// Any error from the last build.
buildErr error
}
@@ -153,6 +158,7 @@ func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f fla
doWithCommandeer: doWithCommandeer,
visitedURLs: types.NewEvictingStringQueue(10),
debounce: rebuildDebouncer,
+ fullRebuildSem: semaphore.NewWeighted(1),
// This will be replaced later, but we need something to log to before the configuration is read.
logger: loggers.NewLogger(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, running),
}
@@ -282,6 +288,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
WorkingDir: dir,
Filename: c.h.cfgFile,
AbsConfigDir: c.h.getConfigDir(dir),
+ Environ: os.Environ(),
Environment: environment},
doWithCommandeer,
doWithConfig)
@@ -290,7 +297,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
if mustHaveConfigFile {
return err
}
- if err != hugolib.ErrNoConfigFile {
+ if err != hugolib.ErrNoConfigFile && !modules.IsNotExist(err) {
return err
}
@@ -388,21 +395,6 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
cfg.Logger.INFO.Println("Using config file:", config.ConfigFileUsed())
- themeDir := c.hugo.PathSpec.GetFirstThemeDir()
- if themeDir != "" {
- if _, err := sourceFs.Stat(themeDir); os.IsNotExist(err) {
- return newSystemError("Unable to find theme Directory:", themeDir)
- }
- }
-
- dir, themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch(sourceFs)
-
- if themeVersionMismatch {
- name := filepath.Base(dir)
- cfg.Logger.ERROR.Printf("%s theme does not support Hugo version %s. Minimum version required is %s\n",
- strings.ToUpper(name), hugo.CurrentVersion.ReleaseVersion(), minVersion)
- }
-
return nil
}
diff --git a/commands/commands.go b/commands/commands.go
index 51bfb4763..3096e1fe0 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -54,6 +54,7 @@ func (b *commandsBuilder) addAll() *commandsBuilder {
newImportCmd(),
newGenCmd(),
createReleaser(),
+ b.newModCmd(),
)
return b
@@ -243,20 +244,26 @@ func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {
return hugo.EnvironmentProduction
}
+func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
+ cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
+ cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
+ cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
+ cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
+ cmd.PersistentFlags().BoolP("ignoreVendor", "", false, "ignores any _vendor directory")
+}
+
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
+ cc.handleCommonBuilderFlags(cmd)
cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
- cmd.Flags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
- cmd.Flags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
cmd.Flags().StringP("destination", "d", "", "filesystem path to write files to")
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
- cmd.Flags().StringP("themesDir", "", "", "filesystem path to themes directory")
cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. http://spf13.com/")
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date and author info to the pages")
cmd.Flags().BoolVar(&cc.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
diff --git a/commands/convert.go b/commands/convert.go
index d0a46a641..e4ff1ac61 100644
--- a/commands/convert.go
+++ b/commands/convert.go
@@ -30,7 +30,6 @@ import (
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/parser/pageparser"
- src "github.com/gohugoio/hugo/source"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/hugolib"
@@ -152,8 +151,8 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
site.Log.INFO.Println("Attempting to convert", p.File().Filename())
- f, _ := p.File().(src.ReadableFile)
- file, err := f.Open()
+ f := p.File()
+ file, err := f.FileInfo().Meta().Open()
if err != nil {
site.Log.ERROR.Println(errMsg)
file.Close()
diff --git a/commands/hugo.go b/commands/hugo.go
index 07f2b95a2..36d3812eb 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -16,19 +16,18 @@
package commands
import (
+ "context"
"fmt"
"io/ioutil"
"os/signal"
"runtime/pprof"
"runtime/trace"
- "sort"
"sync/atomic"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/resources/page"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/common/herrors"
@@ -49,7 +48,6 @@ import (
"github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/parser/metadecoders"
flag "github.com/spf13/pflag"
"github.com/fsnotify/fsnotify"
@@ -196,6 +194,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
"forceSyncStatic",
"noTimes",
"noChmod",
+ "ignoreVendor",
"templateMetrics",
"templateMetricsHints",
@@ -291,6 +290,7 @@ func ifTerminal(s string) string {
}
func (c *commandeer) fullBuild() error {
+
var (
g errgroup.Group
langCount map[string]uint64
@@ -309,13 +309,9 @@ func (c *commandeer) fullBuild() error {
cnt, err := c.copyStatic()
if err != nil {
- if !os.IsNotExist(err) {
- return errors.Wrap(err, "Error copying static files")
- }
- c.logger.INFO.Println("No Static directory found")
+ return errors.Wrap(err, "Error copying static files")
}
langCount = cnt
- langCount = cnt
return nil
}
buildSitesFunc := func() error {
@@ -503,7 +499,11 @@ func (c *commandeer) build() error {
if err != nil {
return err
}
- c.logger.FEEDBACK.Println("Watching for changes in", c.hugo.PathSpec.AbsPathify(c.Cfg.GetString("contentDir")))
+
+ baseWatchDir := c.Cfg.GetString("workingDir")
+ rootWatchDirs := getRootWatchDirsStr(baseWatchDir, watchDirs)
+
+ c.logger.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
c.logger.FEEDBACK.Println("Press Ctrl+C to stop")
watcher, err := c.newWatcher(watchDirs...)
checkErr(c.Logger, err)
@@ -547,7 +547,11 @@ func (c *commandeer) serverBuild() error {
}
func (c *commandeer) copyStatic() (map[string]uint64, error) {
- return c.doWithPublishDirs(c.copyStaticTo)
+ m, err := c.doWithPublishDirs(c.copyStaticTo)
+ if err == nil || os.IsNotExist(err) {
+ return m, nil
+ }
+ return m, err
}
func (c *commandeer) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesystem) (uint64, error)) (map[string]uint64, error) {
@@ -566,6 +570,7 @@ func (c *commandeer) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesy
if err != nil {
return langCount, err
}
+
if lang == "" {
// Not multihost
for _, l := range c.languages {
@@ -594,6 +599,16 @@ func (fs *countingStatFs) Stat(name string) (os.FileInfo, error) {
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 := c.hugo.PathSpec.PublishDir
// If root, remove the second '/'
@@ -610,6 +625,7 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
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.Destination
// Now that we are using a unionFs for the static directories
@@ -652,120 +668,39 @@ func (c *commandeer) timeTrack(start time.Time, name string) {
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *commandeer) getDirList() ([]string, error) {
- var a []string
-
- // To handle nested symlinked content dirs
- var seen = make(map[string]bool)
- var nested []string
-
- newWalker := func(allowSymbolicDirs bool) func(path string, fi os.FileInfo, err error) error {
- return func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- if os.IsNotExist(err) {
- return nil
- }
-
- c.logger.ERROR.Println("Walker: ", err)
- return nil
- }
+ var dirnames []string
- // Skip .git directories.
- // Related to https://github.com/gohugoio/hugo/issues/3468.
- if fi.Name() == ".git" {
- return nil
- }
-
- if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
- link, err := filepath.EvalSymlinks(path)
- if err != nil {
- c.logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
- return nil
- }
- linkfi, err := helpers.LstatIfPossible(c.Fs.Source, link)
- if err != nil {
- c.logger.ERROR.Printf("Cannot stat %q: %s", link, err)
- return nil
- }
- if !allowSymbolicDirs && !linkfi.Mode().IsRegular() {
- c.logger.ERROR.Printf("Symbolic links for directories not supported, skipping %q", path)
- return nil
- }
-
- if allowSymbolicDirs && linkfi.IsDir() {
- // afero.Walk will not walk symbolic links, so wee need to do it.
- if !seen[path] {
- seen[path] = true
- nested = append(nested, path)
- }
- return nil
- }
-
- fi = linkfi
- }
-
- if fi.IsDir() {
- if fi.Name() == ".git" ||
- fi.Name() == "node_modules" || fi.Name() == "bower_components" {
- return filepath.SkipDir
- }
- a = append(a, path)
- }
+ walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
+ if err != nil {
+ c.logger.ERROR.Println("walker: ", err)
return nil
}
- }
-
- symLinkWalker := newWalker(true)
- regularWalker := newWalker(false)
-
- // SymbolicWalk will log anny ERRORs
- // Also note that the Dirnames fetched below will contain any relevant theme
- // directories.
- for _, contentDir := range c.hugo.PathSpec.BaseFs.Content.Dirnames {
- _ = helpers.SymbolicWalk(c.Fs.Source, contentDir, symLinkWalker)
- }
-
- for _, staticDir := range c.hugo.PathSpec.BaseFs.Data.Dirnames {
- _ = helpers.SymbolicWalk(c.Fs.Source, staticDir, regularWalker)
- }
- for _, staticDir := range c.hugo.PathSpec.BaseFs.I18n.Dirnames {
- _ = helpers.SymbolicWalk(c.Fs.Source, staticDir, regularWalker)
- }
-
- for _, staticDir := range c.hugo.PathSpec.BaseFs.Layouts.Dirnames {
- _ = helpers.SymbolicWalk(c.Fs.Source, staticDir, regularWalker)
- }
+ if fi.IsDir() {
+ if fi.Name() == ".git" ||
+ fi.Name() == "node_modules" || fi.Name() == "bower_components" {
+ return filepath.SkipDir
+ }
- for _, staticFilesystem := range c.hugo.PathSpec.BaseFs.Static {
- for _, staticDir := range staticFilesystem.Dirnames {
- _ = helpers.SymbolicWalk(c.Fs.Source, staticDir, regularWalker)
+ dirnames = append(dirnames, fi.Meta().Filename())
}
- }
- for _, assetDir := range c.hugo.PathSpec.BaseFs.Assets.Dirnames {
- _ = helpers.SymbolicWalk(c.Fs.Source, assetDir, regularWalker)
- }
-
- if len(nested) > 0 {
- for {
+ return nil
- toWalk := nested
- nested = nested[:0]
+ }
- for _, d := range toWalk {
- _ = helpers.SymbolicWalk(c.Fs.Source, d, symLinkWalker)
- }
+ watchDirs := c.hugo.PathSpec.BaseFs.WatchDirs()
+ for _, watchDir := range watchDirs {
- if len(nested) == 0 {
- break
- }
+ w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.logger, Info: watchDir, WalkFn: walkFn})
+ if err := w.Walk(); err != nil {
+ c.logger.ERROR.Println("walker: ", err)
}
}
- a = helpers.UniqueStrings(a)
- sort.Strings(a)
+ dirnames = helpers.UniqueStringsSorted(dirnames)
- return a, nil
+ return dirnames, nil
}
func (c *commandeer) buildSites() (err error) {
@@ -812,26 +747,60 @@ func (c *commandeer) partialReRender(urls ...string) error {
return c.hugo.Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true})
}
-func (c *commandeer) fullRebuild() {
- c.commandeerHugoState = &commandeerHugoState{}
- err := c.loadConfig(true, true)
- 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
+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)
}
- if !c.paused {
- err := c.buildSites()
+ 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(), "Total")
+
+ c.commandeerHugoState = &commandeerHugoState{}
+ err := c.loadConfig(true, true)
if err != nil {
- c.logger.ERROR.Println(err)
- } else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
- livereload.ForceRefresh()
+ // 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.ERROR.Println(err)
+ return
+ }
+
+ err = c.buildSites()
+ if err != nil {
+ c.logger.ERROR.Println(err)
+ } else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
+ livereload.ForceRefresh()
+ }
+ }
+ }()
}
// newWatcher creates a new watcher to watch filesystem events.
@@ -886,26 +855,53 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
return watcher, nil
}
+func (c *commandeer) printChangeDetected(typ string) {
+ msg := "\nChange"
+ if typ != "" {
+ msg += " of " + typ
+ }
+ msg += " detected, rebuilding site."
+
+ c.logger.FEEDBACK.Println(msg)
+ const layout = "2006-01-02 15:04:05.000 -0700"
+ c.logger.FEEDBACK.Println(time.Now().Format(layout))
+}
+
+const (
+ configChangeConfig = "config file"
+ configChangeGoMod = "go.mod file"
+)
+
func (c *commandeer) handleEvents(watcher *watcher.Batcher,
staticSyncer *staticSyncer,
evs []fsnotify.Event,
configSet map[string]bool) {
+ 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 !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
@@ -917,13 +913,20 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
time.Sleep(100 * time.Millisecond)
}
}
+
+ // A write event will follow.
+ continue
}
+
// Config file(s) changed. Need full rebuild.
- c.fullRebuild()
- break
+ c.fullRebuild(configChangeType)
}
}
+ if isHandled {
+ return
+ }
+
if c.paused {
// Wait for the server to get into a consistent state before
// we continue with processing.
@@ -933,7 +936,9 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
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(c.fullRebuild)
+ c.debounce(func() {
+ c.fullRebuild("")
+ })
return
}
@@ -1015,7 +1020,7 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
continue
}
- walkAdder := func(path string, f os.FileInfo, err error) error {
+ walkAdder := func(path string, f hugofs.FileMetaInfo, err error) error {
if f.IsDir() {
c.logger.FEEDBACK.Println("adding created directory to watchlist", path)
if err := watcher.Add(path); err != nil {
@@ -1046,9 +1051,7 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
}
if len(staticEvents) > 0 {
- c.logger.FEEDBACK.Println("\nStatic file changes detected")
- const layout = "2006-01-02 15:04:05.000 -0700"
- c.logger.FEEDBACK.Println(time.Now().Format(layout))
+ c.printChangeDetected("Static files")
if c.Cfg.GetBool("forceSyncStatic") {
c.logger.FEEDBACK.Printf("Syncing all static files\n")
@@ -1087,10 +1090,7 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
- c.logger.FEEDBACK.Println("\nChange detected, rebuilding site")
- const layout = "2006-01-02 15:04:05.000 -0700"
- c.logger.FEEDBACK.Println(time.Now().Format(layout))
-
+ c.printChangeDetected("")
c.changeDetector.PrepareNew()
if err := c.rebuildSites(dynamicEvents); err != nil {
c.handleBuildErr(err, "Rebuild failed")
@@ -1167,41 +1167,3 @@ func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
return name
}
-
-// isThemeVsHugoVersionMismatch returns whether the current Hugo version is
-// less than any of the themes' min_version.
-func (c *commandeer) isThemeVsHugoVersionMismatch(fs afero.Fs) (dir string, mismatch bool, requiredMinVersion string) {
- if !c.hugo.PathSpec.ThemeSet() {
- return
- }
-
- for _, absThemeDir := range c.hugo.BaseFs.AbsThemeDirs {
-
- path := filepath.Join(absThemeDir, "theme.toml")
-
- exists, err := helpers.Exists(path, fs)
-
- if err != nil || !exists {
- continue
- }
-
- b, err := afero.ReadFile(fs, path)
- if err != nil {
- continue
- }
-
- tomlMeta, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.TOML)
- if err != nil {
- continue
- }
-
- if minVersion, ok := tomlMeta["min_version"]; ok {
- if hugo.CompareVersion(minVersion) > 0 {
- return absThemeDir, true, fmt.Sprint(minVersion)
- }
- }
-
- }
-
- return
-}
diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go
index 1d37cfd9d..e5c39dc34 100644
--- a/commands/import_jekyll.go
+++ b/commands/import_jekyll.go
@@ -17,7 +17,6 @@ import (
"bytes"
"errors"
"fmt"
- "io"
"io/ioutil"
"os"
"path/filepath"
@@ -27,6 +26,8 @@ import (
"time"
"unicode"
+ "github.com/gohugoio/hugo/common/hugio"
+
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/helpers"
@@ -113,7 +114,7 @@ func (i *importCmd) importFromJekyll(cmd *cobra.Command, args []string) error {
jww.FEEDBACK.Println("Importing...")
fileCount := 0
- callback := func(path string, fi os.FileInfo, err error) error {
+ callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
return err
}
@@ -302,66 +303,10 @@ func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind meta
return helpers.WriteToDisk(filepath.Join(inpath, "config."+string(kind)), &buf, fs)
}
-func copyFile(source string, dest string) error {
- sf, err := os.Open(source)
- if err != nil {
- return err
- }
- defer sf.Close()
- df, err := os.Create(dest)
- if err != nil {
- return err
- }
- defer df.Close()
- _, err = io.Copy(df, sf)
- if err == nil {
- si, err := os.Stat(source)
- if err != nil {
- err = os.Chmod(dest, si.Mode())
-
- if err != nil {
- return err
- }
- }
-
- }
- return nil
-}
-
-func copyDir(source string, dest string) error {
- fi, err := os.Stat(source)
- if err != nil {
- return err
- }
- if !fi.IsDir() {
- return errors.New(source + " is not a directory")
- }
- err = os.MkdirAll(dest, fi.Mode())
- if err != nil {
- return err
- }
- entries, _ := ioutil.ReadDir(source)
- for _, entry := range entries {
- sfp := filepath.Join(source, entry.Name())
- dfp := filepath.Join(dest, entry.Name())
- if entry.IsDir() {
- err = copyDir(sfp, dfp)
- if err != nil {
- jww.ERROR.Println(err)
- }
- } else {
- err = copyFile(sfp, dfp)
- if err != nil {
- jww.ERROR.Println(err)
- }
- }
-
- }
- return nil
-}
-
func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
- fi, err := os.Stat(jekyllRoot)
+ fs := hugofs.Os
+
+ fi, err := fs.Stat(jekyllRoot)
if err != nil {
return err
}
@@ -383,7 +328,7 @@ func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPos
if entry.IsDir() {
if entry.Name()[0] != '_' && entry.Name()[0] != '.' {
if _, ok := jekyllPostDirs[entry.Name()]; !ok {
- err = copyDir(sfp, dfp)
+ err = hugio.CopyDir(fs, sfp, dfp, nil)
if err != nil {
jww.ERROR.Println(err)
}
@@ -402,7 +347,7 @@ func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPos
}
if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
- err = copyFile(sfp, dfp)
+ err = hugio.CopyFile(fs, sfp, dfp)
if err != nil {
jww.ERROR.Println(err)
}
diff --git a/commands/list_test.go b/commands/list_test.go
index f2ce70010..55a067444 100644
--- a/commands/list_test.go
+++ b/commands/list_test.go
@@ -62,6 +62,7 @@ func TestListAll(t *testing.T) {
}, header)
record, err := r.Read()
+ assert.NoError(err)
assert.Equal([]string{
filepath.Join("content", "p1.md"), "", "P1",
"0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z",
diff --git a/commands/mod.go b/commands/mod.go
new file mode 100644
index 000000000..861704e35
--- /dev/null
+++ b/commands/mod.go
@@ -0,0 +1,189 @@
+// 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
+
+import (
+ "os"
+ "strings"
+
+ "github.com/gohugoio/hugo/modules"
+ "github.com/spf13/cobra"
+)
+
+var _ cmder = (*modCmd)(nil)
+
+type modCmd struct {
+ *baseBuilderCmd
+}
+
+func (b *commandsBuilder) newModCmd() *modCmd {
+ c := &modCmd{}
+
+ const commonUsage = `
+Note that Hugo will always start out by resolving the components defined in the site
+configuration, provided by a _vendor directory (if no --ignoreVendor flag provided),
+Go Modules, or a folder inside the themes directory, in that order.
+
+See https://gohugo.io/hugo-modules/ for more information.
+
+`
+
+ cmd := &cobra.Command{
+ Use: "mod",
+ Short: "Various Hugo Modules helpers.",
+ Long: `Various helpers to help manage the modules in your project's dependency graph.
+
+Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
+This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
+
+` + commonUsage,
+
+ RunE: nil,
+ }
+
+ cmd.AddCommand(
+ &cobra.Command{
+ Use: "get",
+ DisableFlagParsing: true,
+ Short: "Resolves dependencies in your current Hugo Project.",
+ Long: `
+Resolves dependencies in your current Hugo Project.
+
+Some examples:
+
+Install the latest version possible for a given module:
+
+ hugo mod get github.com/gohugoio/testshortcodes
+
+Install a specific version:
+
+ hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
+
+Install the latest versions of all module dependencies:
+
+ hugo mod get -u
+
+Run "go help get" for more information. All flags available for "go get" is also relevant here.
+` + commonUsage,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return c.withModsClient(false, func(c *modules.Client) error {
+ // We currently just pass on the flags we get to Go and
+ // need to do the flag handling manually.
+ if len(args) == 1 && strings.Contains(args[0], "-h") {
+ return cmd.Help()
+ }
+ return c.Get(args...)
+ })
+ },
+ },
+ &cobra.Command{
+ Use: "graph",
+ Short: "Print a module dependency graph.",
+ Long: `Print a module dependency graph with information about module status (disabled, vendored).
+Note that for vendored modules, that is the version listed and not the one from go.mod.
+`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return c.withModsClient(true, func(c *modules.Client) error {
+ return c.Graph(os.Stdout)
+ })
+ },
+ },
+ &cobra.Command{
+ Use: "init",
+ Short: "Initialize this project as a Hugo Module.",
+ Long: `Initialize this project as a Hugo Module.
+It will try to guess the module path, but you may help by passing it as an argument, e.g:
+
+ hugo mod init github.com/gohugoio/testshortcodes
+
+Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
+inside a subfolder on GitHub, as one example.
+`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var path string
+ if len(args) >= 1 {
+ path = args[0]
+ }
+ return c.withModsClient(false, func(c *modules.Client) error {
+ return c.Init(path)
+ })
+ },
+ },
+ &cobra.Command{
+ Use: "vendor",
+ Short: "Vendor all module dependencies into the _vendor directory.",
+ Long: `Vendor all module dependencies into the _vendor directory.
+
+If a module is vendored, that is where Hugo will look for it's dependencies.
+`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return c.withModsClient(true, func(c *modules.Client) error {
+ return c.Vendor()
+ })
+ },
+ },
+ &cobra.Command{
+ Use: "tidy",
+ Short: "Remove unused entries in go.mod and go.sum.",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return c.withModsClient(true, func(c *modules.Client) error {
+ return c.Tidy()
+ })
+ },
+ },
+ &cobra.Command{
+ Use: "clean",
+ Short: "Delete the entire Hugo Module cache.",
+ Long: `Delete the entire Hugo Module cache.
+
+Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
+
+Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
+
+`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ com, err := c.initConfig(true)
+ if err != nil {
+ return err
+ }
+
+ _, err = com.hugo.FileCaches.ModulesCache().Prune(true)
+ return err
+
+ },
+ },
+ )
+
+ c.baseBuilderCmd = b.newBuilderCmd(cmd)
+
+ return c
+
+}
+
+func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error {
+ com, err := c.initConfig(failOnMissingConfig)
+ if err != nil {
+ return err
+ }
+
+ return f(com.hugo.ModulesClient)
+}
+
+func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
+ com, err := initializeConfig(failOnNoConfig, false, &c.hugoBuilderCommon, c, nil)
+ if err != nil {
+ return nil, err
+ }
+ return com, nil
+}
diff --git a/commands/new.go b/commands/new.go
index f10369837..649be521e 100644
--- a/commands/new.go
+++ b/commands/new.go
@@ -53,8 +53,6 @@ Ensure you run this within the root directory of your site.`,
cc := &newCmd{baseBuilderCmd: b.newBuilderCmd(cmd)}
cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
- cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
- cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
cmd.AddCommand(newNewSiteCmd().getCommand())
@@ -120,8 +118,8 @@ func newContentPathSection(h *hugolib.HugoSites, path string) (string, string) {
createpath := filepath.FromSlash(path)
if h != nil {
- for _, s := range h.Sites {
- createpath = strings.TrimPrefix(createpath, s.PathSpec.ContentDir)
+ for _, dir := range h.BaseFs.Content.Dirs {
+ createpath = strings.TrimPrefix(createpath, dir.Meta().Filename())
}
}
diff --git a/commands/server.go b/commands/server.go
index 5d50ebe2c..ade8da5e8 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -256,15 +256,11 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
return err
}
- baseWatchDir := c.Cfg.GetString("workingDir")
- relWatchDirs := make([]string, len(watchDirs))
- for i, dir := range watchDirs {
- relWatchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir)
- }
-
- rootWatchDirs := strings.Join(helpers.UniqueStrings(helpers.ExtractRootPaths(relWatchDirs)), ",")
+ watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
- jww.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
+ for _, group := range watchGroups {
+ jww.FEEDBACK.Printf("Watching for changes in %s\n", group)
+ }
watcher, err := c.newWatcher(watchDirs...)
if err != nil {
@@ -279,6 +275,15 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
+func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
+ relWatchDirs := make([]string, len(watchDirs))
+ for i, dir := range watchDirs {
+ relWatchDirs[i], _ = helpers.GetRelativePath(dir, baseDir)
+ }
+
+ return strings.Join(helpers.UniqueStringsSorted(helpers.ExtractRootPaths(relWatchDirs)), ",")
+}
+
type fileServer struct {
baseURLs []string
roots []string
diff --git a/commands/static_syncer.go b/commands/static_syncer.go
index 237453868..6103fe71c 100644
--- a/commands/static_syncer.go
+++ b/commands/static_syncer.go
@@ -53,6 +53,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
syncer := fsync.NewSyncer()
syncer.NoTimes = c.Cfg.GetBool("noTimes")
syncer.NoChmod = c.Cfg.GetBool("noChmod")
+ syncer.ChmodFilter = chmodFilter
syncer.SrcFs = sourceFs.Fs
syncer.DestFs = c.Fs.Destination