"sync"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/helpers"
if c.maxAge < 0 {
return false
}
- return c.maxAge == 0 || time.Since(modTime) > c.maxAge
+ return c.maxAge == 0 || htime.Since(modTime) > c.maxAge
}
// For testing
import (
"errors"
+ "fmt"
"io/ioutil"
"net"
"os"
"golang.org/x/sync/semaphore"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
+ "github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugolib"
"github.com/spf13/afero"
+ "github.com/bep/clock"
"github.com/bep/debounce"
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/common/types"
return nil
}
+func (c *commandeer) initClock() error {
+ bt := c.Cfg.GetString("clock")
+ if bt == "" {
+ return nil
+ }
+
+ t, err := cast.StringToDateInDefaultLocation(bt, nil)
+ if err != nil {
+ return fmt.Errorf(`failed to parse "clock" flag: %s`, err)
+ }
+ htime.Clock = clock.Start(t)
+ return nil
+}
+
func newCommandeer(mustHaveConfigFile, failOnInitErr, running bool, h *hugoBuilderCommon, f flagsToConfigHandler, cfgInit func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
var rebuildDebouncer func(f func())
if running {
c.configFiles = configFiles
+ err = c.initClock()
+ if err != nil {
+ return err
+ }
+
if l, ok := c.Cfg.Get("languagesSorted").(langs.Languages); ok {
c.languagesConfigured = true
c.languages = l
"os"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
hpaths "github.com/gohugoio/hugo/common/paths"
Complete documentation is available at https://gohugo.io/.`,
RunE: func(cmd *cobra.Command, args []string) error {
- defer cc.timeTrack(time.Now(), "Total")
+ defer cc.timeTrack(htime.Now(), "Total")
cfgInit := func(c *commandeer) error {
if cc.buildWatch {
c.Set("disableLiveReload", true)
buildWatch bool
poll string
+ clock string
gc bool
if cc.quiet {
return
}
- elapsed := time.Since(start)
+ elapsed := htime.Since(start)
fmt.Printf("%s in %v ms\n", name, int(1000*elapsed.Seconds()))
}
cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
+ cmd.PersistentFlags().StringVar(&cc.clock, "clock", "", "set clock inside hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
}
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/tpl"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs"
"buildDrafts",
"buildFuture",
"buildExpired",
+ "clock",
"uglyURLs",
"canonifyURLs",
"enableRobotsTXT",
}
func (c *commandeer) timeTrack(start time.Time, name string) {
- elapsed := time.Since(start)
+ elapsed := htime.Since(start)
c.logger.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
}
time.Sleep(2 * time.Second)
}()
- defer c.timeTrack(time.Now(), "Rebuilt")
+ defer c.timeTrack(htime.Now(), "Rebuilt")
c.commandeerHugoState = newCommandeerHugoState()
err := c.loadConfig()
c.logger.Println(msg)
const layout = "2006-01-02 15:04:05.000 -0700"
- c.logger.Println(time.Now().Format(layout))
+ c.logger.Println(htime.Now().Format(layout))
}
const (
c.changeDetector.PrepareNew()
func() {
- defer c.timeTrack(time.Now(), "Total")
+ defer c.timeTrack(htime.Now(), "Total")
if err := c.rebuildSites(dynamicEvents); err != nil {
c.handleBuildErr(err, "Rebuild failed")
}
// Issue #5662
func TestHugoWithContentDirOverride(t *testing.T) {
- t.Parallel()
c := qt.New(t)
files := `
// Issue #9794
func TestHugoStaticFilesMultipleStaticAndManyFolders(t *testing.T) {
- t.Parallel()
c := qt.New(t)
files := `
}
+// Issue #8787
+func TestHugoListCommandsWithClockFlag(t *testing.T) {
+ c := qt.New(t)
+
+ files := `
+-- config.toml --
+baseURL = "https://example.org"
+title = "Hugo Commands"
+-- content/past.md --
+---
+title: "Past"
+date: 2000-11-06
+---
+-- content/future.md --
+---
+title: "Future"
+date: 2200-11-06
+---
+-- layouts/_default/single.html --
+Page: {{ .Title }}|
+
+`
+ s := newTestHugoCmdBuilder(c, files, []string{"list", "future"}).Build()
+ p := filepath.Join("content", "future.md")
+ s.AssertStdout(p + ",2200-11-06T00:00:00Z")
+
+ s = newTestHugoCmdBuilder(c, files, []string{"list", "future", "--clock", "2300-11-06"}).Build()
+ s.AssertStdout("")
+}
+
type testHugoCmdBuilder struct {
*qt.C
dir string
files string
args []string
+ out string
}
func newTestHugoCmdBuilder(c *qt.C, files string, args []string) *testHugoCmdBuilder {
args := append(s.args, "-s="+s.dir, "--quiet")
cmd.SetArgs(args)
- _, err := cmd.ExecuteC()
+ out, err := captureStdout(func() error {
+ _, err := cmd.ExecuteC()
+ return err
+ })
s.Assert(err, qt.IsNil)
+ s.out = out
return s
}
}
}
}
+
+func (s *testHugoCmdBuilder) AssertStdout(match string) {
+ s.Helper()
+ content := strings.TrimSpace(s.out)
+ s.Assert(content, qt.Contains, strings.TrimSpace(match))
+}
"github.com/gohugoio/hugo/parser/pageparser"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/parser/metadecoders"
re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`)
r := re.FindAllStringSubmatch(filename, -1)
if len(r) == 0 {
- return time.Now(), "", errors.New("filename not match")
+ return htime.Now(), "", errors.New("filename not match")
}
postDate, err := time.Parse("2006-1-2", r[0][1])
if err != nil {
- return time.Now(), "", err
+ return htime.Now(), "", err
}
postName := r[0][2]
return newSystemError("Error building sites", err)
}
+ if err != nil {
+ return newSystemError("Error building sites", err)
+ }
+
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
return newSystemError("Error building sites", err)
}
+ if err != nil {
+ return newSystemError("Error building sites", err)
+ }
+
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
hugoCmd := newCommandsBuilder().addAll().build()
cmd := hugoCmd.getCommand()
- defer func() {
+ t.Cleanup(func() {
os.RemoveAll(dir)
- }()
+ })
cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
"errors"
"path/filepath"
"strings"
- "time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cobra"
by := []byte(`The MIT License (MIT)
-Copyright (c) ` + time.Now().Format("2006") + ` YOUR_NAME_HERE
+Copyright (c) ` + htime.Now().Format("2006") + ` YOUR_NAME_HERE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
"syscall"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/paths"
"golang.org/x/sync/errgroup"
}
err = func() error {
- defer c.timeTrack(time.Now(), "Built")
+ defer c.timeTrack(htime.Now(), "Built")
err := c.serverBuild()
if err != nil {
cmd.PrintErrln("Error:", err.Error())
go func() {
var stats runtime.MemStats
- start := time.Now().UnixNano()
+ start := htime.Now().UnixNano()
for {
runtime.ReadMemStats(&stats)
if fileMemStats != nil {
fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
- (time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
+ (htime.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
time.Sleep(interval)
} else {
break
"strings"
"time"
+ "github.com/bep/clock"
"github.com/spf13/cast"
toml "github.com/pelletier/go-toml/v2"
"November",
"December",
}
+ Clock = clock.Start(time.Now())
)
func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
}
return cast.ToTimeInDefaultLocationE(i, location)
}
+
+// Now returns time.Now() or time value based on`clock` flag.
+// Use this function to fake time inside hugo.
+func Now() time.Time {
+ return Clock.Now()
+}
+
+func Since(t time.Time) time.Duration {
+ return Clock.Now().Sub(t)
+}
"runtime"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/terminal"
jww "github.com/spf13/jwalterweatherman"
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
// if considerable time is spent.
func (l *logger) PrintTimerIfDelayed(start time.Time, name string) {
- elapsed := time.Since(start)
+ elapsed := htime.Since(start)
milli := int(1000 * elapsed.Seconds())
if milli < 500 {
return
}
func (l *logger) PrintTimer(start time.Time, name string) {
- elapsed := time.Since(start)
+ elapsed := htime.Since(start)
milli := int(1000 * elapsed.Seconds())
l.Printf("%s in %v ms", name, milli)
}
github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 // indirect
github.com/aws/smithy-go v1.8.0 // indirect
+ github.com/bep/clock v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/bep/clock v0.1.0 h1:sdvJ08SsgwTY/ejA705YKBlktFfj2uVpmQSSQspZJ2c=
+github.com/bep/clock v0.1.0/go.mod h1:shVP9tZ3cXpbVj60SnlU1pMwKjFxECBRm9vZfpoA0Gs=
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840=
"errors"
"github.com/gohugoio/hugo/common/hreflect"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/spf13/afero"
)
m.IsOrdered = false
return NewFileMetaInfo(
- &dirNameOnlyFileInfo{name: base, modTime: time.Now()},
+ &dirNameOnlyFileInfo{name: base, modTime: htime.Now()},
m,
)
}
"strings"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/source"
d := &archetypeFileData{
Type: archetypeKind,
- Date: time.Now().Format(time.RFC3339),
+ Date: htime.Now().Format(time.RFC3339),
Page: p,
File: p.File(),
}
"testing"
"time"
+ "github.com/bep/clock"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/markup/asciidocext"
"github.com/gohugoio/hugo/markup/rst"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugofs"
}
func TestShouldBuild(t *testing.T) {
- t.Parallel()
past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
zero := time.Time{}
}
}
+func TestShouldBuildWithClock(t *testing.T) {
+ htime.Clock = clock.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
+ t.Cleanup(func() { htime.Clock = clock.Start(time.Now()) })
+ past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+ future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
+ zero := time.Time{}
+
+ publishSettings := []struct {
+ buildFuture bool
+ buildExpired bool
+ buildDrafts bool
+ draft bool
+ publishDate time.Time
+ expiryDate time.Time
+ out bool
+ }{
+ // publishDate and expiryDate
+ {false, false, false, false, zero, zero, true},
+ {false, false, false, false, zero, future, true},
+ {false, false, false, false, past, zero, true},
+ {false, false, false, false, past, future, true},
+ {false, false, false, false, past, past, false},
+ {false, false, false, false, future, future, false},
+ {false, false, false, false, future, past, false},
+
+ // buildFuture and buildExpired
+ {false, true, false, false, past, past, true},
+ {true, true, false, false, past, past, true},
+ {true, false, false, false, past, past, false},
+ {true, false, false, false, future, future, true},
+ {true, true, false, false, future, future, true},
+ {false, true, false, false, future, past, false},
+
+ // buildDrafts and draft
+ {true, true, false, true, past, future, false},
+ {true, true, true, true, past, future, true},
+ {true, true, true, true, past, future, true},
+ }
+
+ for _, ps := range publishSettings {
+ s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft,
+ ps.publishDate, ps.expiryDate)
+ if s != ps.out {
+ t.Errorf("AssertShouldBuildWithClock unexpected output with params: %+v", ps)
+ }
+ }
+}
+
// "dot" in path: #1885 and #2110
// disablePathToLower regression: #3374
func TestPathIssues(t *testing.T) {
"strings"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/modules"
if !(buildDrafts || !Draft) {
return false
}
- if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) {
+ hnow := htime.Now()
+ if !buildFuture && !publishDate.IsZero() && publishDate.After(hnow) {
return false
}
- if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) {
+ if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(hnow) {
return false
}
return true
"sync"
"time"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/compare"
"github.com/gohugoio/hugo/helpers"
// MeasureSince adds a measurement for key to the metric store.
func (s *Store) MeasureSince(key string, start time.Time) {
s.mu.Lock()
- s.metrics[key] = append(s.metrics[key], time.Since(start))
+ s.metrics[key] = append(s.metrics[key], htime.Since(start))
s.mu.Unlock()
}
"time"
"github.com/bep/debounce"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/loggers"
"github.com/spf13/cast"
}
func (c *collector) collect() {
- defer c.logger.PrintTimerIfDelayed(time.Now(), "hugo: collected modules")
+ defer c.logger.PrintTimerIfDelayed(htime.Now(), "hugo: collected modules")
d := debounce.New(2 * time.Second)
d(func() {
c.logger.Println("hugo: downloading modules …")
package resource
-import "time"
+import (
+ "time"
+
+ "github.com/gohugoio/hugo/common/htime"
+)
var _ Dated = Dates{}
if d.PublishDate().IsZero() {
return false
}
- return d.PublishDate().After(time.Now())
+
+ return d.PublishDate().After(htime.Now())
}
// IsExpired returns whether the argument is expired.
if d.ExpiryDate().IsZero() {
return false
}
- return d.ExpiryDate().Before(time.Now())
+ return d.ExpiryDate().Before(htime.Now())
}
// IsZeroDates returns true if all of the dates are zero.
)
func init() {
+ // htime.Now cannot be used here
rand.Seed(time.Now().UTC().UnixNano())
}
"reflect"
"strings"
"sync"
- "time"
+ "github.com/gohugoio/hugo/common/htime"
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
"github.com/gohugoio/hugo/helpers"
var errUnHashable = errors.New("unhashable")
func (ns *Namespace) getOrCreate(ctx context.Context, key partialCacheKey, context any) (result any, err error) {
- start := time.Now()
+ start := htime.Now()
defer func() {
if r := recover(); r != nil {
err = r.(error)
if d.Language == nil {
panic("Language must be set")
}
+
ctx := New(langs.GetTimeFormatter(d.Language), langs.GetLocation(d.Language))
ns := &internal.TemplateFuncsNamespace{
return ns.timeFormatter.Format(t, layout), nil
}
-// Now returns the current local time.
+// Now returns the current local time or `clock` time
func (ns *Namespace) Now() _time.Time {
- return _time.Now()
+ return htime.Now()
}
// ParseDuration parses the duration string s.
"sort"
"strings"
"sync"
- "time"
"unicode"
"unicode/utf8"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/helpers"
defer rlocker.RUnlock()
}
if t.Metrics != nil {
- defer t.Metrics.MeasureSince(templ.Name(), time.Now())
+ defer t.Metrics.MeasureSince(templ.Name(), htime.Now())
}
if t.templateUsageTracker != nil {