Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix raw TOML dates in where/eq #9981

Merged
merged 1 commit into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions common/hreflect/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"context"
"reflect"
"sync"
"time"

"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/types"
)

Expand Down Expand Up @@ -168,6 +170,44 @@ func GetMethodIndexByName(tp reflect.Type, name string) int {
return m.Index
}

var (
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
asTimeProviderType = reflect.TypeOf((*htime.AsTimeProvider)(nil)).Elem()
)

// IsTime returns whether tp is a time.Time type or if it can be converted into one
// in ToTime.
func IsTime(tp reflect.Type) bool {
if tp == timeType {
return true
}

if tp.Implements(asTimeProviderType) {
return true
}
return false
}

// AsTime returns v as a time.Time if possible.
// The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
// A zero Time and false is returned if this isn't possible.
// Note that this function does not accept string dates.
func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
if v.Kind() == reflect.Interface {
return AsTime(v.Elem(), loc)
}

if v.Type() == timeType {
return v.Interface().(time.Time), true
}

if v.Type().Implements(asTimeProviderType) {
return v.Interface().(htime.AsTimeProvider).AsTime(loc), true
}

return time.Time{}, false
}

// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
Expand Down
12 changes: 7 additions & 5 deletions common/htime/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"github.com/bep/clock"
"github.com/spf13/cast"

toml "github.com/pelletier/go-toml/v2"

"github.com/gohugoio/locales"
)

Expand Down Expand Up @@ -139,13 +137,12 @@ func (f TimeFormatter) Format(t time.Time, layout string) string {

func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) {
switch vv := i.(type) {
case toml.LocalDate:
return vv.AsTime(location), nil
case toml.LocalDateTime:
case AsTimeProvider:
return vv.AsTime(location), nil
// issue #8895
// datetimes parsed by `go-toml` have empty zone name
// convert back them into string and use `cast`
// TODO(bep) add tests, make sure we really need this.
case time.Time:
i = vv.Format(time.RFC3339)
}
Expand All @@ -161,3 +158,8 @@ func Now() time.Time {
func Since(t time.Time) time.Duration {
return Clock.Since(t)
}

// AsTimeProvider is implemented by go-toml's LocalDate and LocalDateTime.
type AsTimeProvider interface {
AsTime(zone *time.Location) time.Time
}
59 changes: 59 additions & 0 deletions hugolib/dates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,62 @@ func TestTimeOnError(t *testing.T) {
b.Assert(b.BuildE(BuildCfg{}), qt.Not(qt.IsNil))

}

func TestTOMLDates(t *testing.T) {
t.Parallel()

files := `
-- config.toml --
timeZone = "America/Los_Angeles"
-- content/_index.md --
---
date: "2020-10-20"
---
-- content/p1.md --
+++
title = "TOML Date with UTC offset"
date = 2021-08-16T06:00:00+00:00
+++


## Foo
-- data/mydata.toml --
date = 2020-10-20
talks = [
{ date = 2017-01-23, name = "Past talk 1" },
{ date = 2017-01-24, name = "Past talk 2" },
{ date = 2017-01-26, name = "Past talk 3" },
{ date = 2050-02-12, name = "Future talk 1" },
{ date = 2050-02-13, name = "Future talk 2" },
]
-- layouts/index.html --
{{ $futureTalks := where site.Data.mydata.talks "date" ">" now }}
{{ $pastTalks := where site.Data.mydata.talks "date" "<" now }}

{{ $homeDate := site.Home.Date }}
{{ $p1Date := (site.GetPage "p1").Date }}
Future talks: {{ len $futureTalks }}
Past talks: {{ len $pastTalks }}

Home's Date should be greater than past: {{ gt $homeDate (index $pastTalks 0).date }}
Home's Date should be less than future: {{ lt $homeDate (index $futureTalks 0).date }}
Home's Date should be equal mydata date: {{ eq $homeDate site.Data.mydata.date }}
Full time: {{ $p1Date | time.Format ":time_full" }}
`

b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()

b.AssertFileContent("public/index.html", `
Future talks: 2
Past talks: 3
Home's Date should be greater than past: true
Home's Date should be less than future: true
Home's Date should be equal mydata date: true
Full time: 6:00:00 am UTC
`)
}
4 changes: 3 additions & 1 deletion tpl/collections/append_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ import (
"testing"

qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
)

// Also see tests in common/collection.
func TestAppend(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
start any
Expand Down
4 changes: 3 additions & 1 deletion tpl/collections/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"testing"

qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/tpl"
)
Expand Down Expand Up @@ -67,7 +69,7 @@ func (templateFinder) GetFunc(name string) (reflect.Value, bool) {
func TestApply(t *testing.T) {
t.Parallel()
c := qt.New(t)
d := &deps.Deps{}
d := &deps.Deps{Language: langs.NewDefaultLanguage(config.New())}
d.SetTmpl(new(templateFinder))
ns := New(d)

Expand Down
16 changes: 14 additions & 2 deletions tpl/collections/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/compare"
"github.com/spf13/cast"
)

Expand All @@ -41,14 +43,24 @@ func init() {

// New returns a new instance of the collections-namespaced template functions.
func New(deps *deps.Deps) *Namespace {
if deps.Language == nil {
panic("language must be set")
}

loc := langs.GetLocation(deps.Language)

return &Namespace{
deps: deps,
loc: loc,
sortComp: compare.New(loc, true),
deps: deps,
}
}

// Namespace provides template functions for the "collections" namespace.
type Namespace struct {
deps *deps.Deps
loc *time.Location
sortComp *compare.Namespace
deps *deps.Deps
}

// After returns all the items after the first N in a rangeable list.
Expand Down
37 changes: 19 additions & 18 deletions tpl/collections/collections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestAfter(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
index any
Expand Down Expand Up @@ -97,7 +97,7 @@ func (g *tstGrouper2) Group(key any, items any) (any, error) {
func TestGroup(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
key any
Expand Down Expand Up @@ -187,7 +187,7 @@ func TestDelimit(t *testing.T) {
func TestDictionary(t *testing.T) {
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
values []any
Expand Down Expand Up @@ -226,7 +226,7 @@ func TestDictionary(t *testing.T) {
func TestReverse(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

s := []string{"a", "b", "c"}
reversed, err := ns.Reverse(s)
Expand All @@ -245,7 +245,7 @@ func TestEchoParam(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
a any
Expand Down Expand Up @@ -277,7 +277,7 @@ func TestFirst(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
limit any
Expand Down Expand Up @@ -315,7 +315,7 @@ func TestIn(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
l1 any
Expand Down Expand Up @@ -391,7 +391,7 @@ func TestIntersect(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
l1, l2 any
Expand Down Expand Up @@ -518,7 +518,7 @@ func TestLast(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
limit any
Expand Down Expand Up @@ -557,7 +557,7 @@ func TestLast(t *testing.T) {
func TestQuerify(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
params []any
Expand Down Expand Up @@ -591,7 +591,7 @@ func TestQuerify(t *testing.T) {
}

func BenchmarkQuerify(b *testing.B) {
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
params := []any{"a", "b", "c", "d", "f", " &"}

b.ResetTimer()
Expand All @@ -604,7 +604,7 @@ func BenchmarkQuerify(b *testing.B) {
}

func BenchmarkQuerifySlice(b *testing.B) {
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
params := []string{"a", "b", "c", "d", "f", " &"}

b.ResetTimer()
Expand All @@ -619,7 +619,7 @@ func BenchmarkQuerifySlice(b *testing.B) {
func TestSeq(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
args []any
Expand Down Expand Up @@ -663,7 +663,7 @@ func TestSeq(t *testing.T) {
func TestShuffle(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
seq any
Expand Down Expand Up @@ -703,7 +703,7 @@ func TestShuffle(t *testing.T) {
func TestShuffleRandomising(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

// Note that this test can fail with false negative result if the shuffle
// of the sequence happens to be the same as the original sequence. However
Expand Down Expand Up @@ -734,7 +734,7 @@ func TestShuffleRandomising(t *testing.T) {
func TestSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
args []any
Expand All @@ -758,7 +758,7 @@ func TestUnion(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})

for i, test := range []struct {
l1 any
Expand Down Expand Up @@ -847,7 +847,7 @@ func TestUnion(t *testing.T) {
func TestUniq(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
for i, test := range []struct {
l any
expect any
Expand Down Expand Up @@ -979,6 +979,7 @@ func newDeps(cfg config.Provider) *deps.Deps {
panic(err)
}
return &deps.Deps{
Language: l,
Cfg: cfg,
Fs: hugofs.NewMem(l),
ContentSpec: cs,
Expand Down
Loading