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

Support an "expiration" date #726 #2137

Closed
wants to merge 15 commits into from
Closed
6 changes: 6 additions & 0 deletions commands/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ var (
draft bool
forceSync bool
future bool
expired bool
ignoreCache bool
logging bool
noTimes bool
Expand Down Expand Up @@ -216,6 +217,7 @@ func initHugoBuildCommonFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&cleanDestination, "cleanDestinationDir", false, "Remove files from destination not found in static directories")
cmd.Flags().BoolVarP(&draft, "buildDrafts", "D", false, "include content marked as draft")
cmd.Flags().BoolVarP(&future, "buildFuture", "F", false, "include content with publishdate in the future")
cmd.Flags().BoolVarP(&expired, "buildExpired", "E", false, "include expired content")
cmd.Flags().BoolVar(&disable404, "disable404", false, "Do not render 404 page")
cmd.Flags().BoolVar(&disableRSS, "disableRSS", false, "Do not build RSS files")
cmd.Flags().BoolVar(&disableSitemap, "disableSitemap", false, "Do not build Sitemap file")
Expand Down Expand Up @@ -283,6 +285,7 @@ func loadDefaultSettings() {
viper.SetDefault("DefaultLayout", "post")
viper.SetDefault("BuildDrafts", false)
viper.SetDefault("BuildFuture", false)
viper.SetDefault("BuildExpired", false)
viper.SetDefault("UglyURLs", false)
viper.SetDefault("Verbose", false)
viper.SetDefault("IgnoreCache", false)
Expand Down Expand Up @@ -357,6 +360,9 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error {
if flagChanged(cmdV.Flags(), "buildFuture") {
viper.Set("BuildFuture", future)
}
if flagChanged(cmdV.Flags(), "buildExpired") {
viper.Set("BuildExpired", expired)
}
if flagChanged(cmdV.Flags(), "uglyURLs") {
viper.Set("UglyURLs", uglyURLs)
}
Expand Down
32 changes: 32 additions & 0 deletions commands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
func init() {
listCmd.AddCommand(listDraftsCmd)
listCmd.AddCommand(listFutureCmd)
listCmd.AddCommand(listExpiredCmd)
listCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
listCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
}
Expand Down Expand Up @@ -98,3 +99,34 @@ posted in the future.`,

},
}

var listExpiredCmd = &cobra.Command{
Use: "expired",
Short: "List all posts already expired",
Long: `List all of the posts in your content directory which has already
expired.`,
RunE: func(cmd *cobra.Command, args []string) error {

if err := InitializeConfig(); err != nil {
return err
}

viper.Set("BuildExpired", true)

site := &hugolib.Site{}

if err := site.Process(); err != nil {
return newSystemError("Error Processing Source Content", err)
}

for _, p := range site.Pages {
if p.IsExpired() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}

}

return nil

},
}
1 change: 1 addition & 0 deletions docs/content/content/front-matter.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Field names are always normalized to lowercase (e.g. `camelCase: true` is availa
See [Aliases]({{< relref "extras/aliases.md" >}}) for details.
* **draft** If true, the content will not be rendered unless `hugo` is called with `--buildDrafts`
* **publishdate** If in the future, content will not be rendered unless `hugo` is called with `--buildFuture`
* **expirydate** Content already expired will not be rendered unless `hugo` is called with `--buildExpired`
* **type** The type of the content (will be derived from the directory automatically if unset)
* **isCJKLanguage** If true, explicitly treat the content as CJKLanguage (.Summary and .WordCount can work properly in CJKLanguage)
* **weight** Used for sorting
Expand Down
2 changes: 2 additions & 0 deletions docs/content/overview/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ Following is a list of Hugo-defined variables that you can configure and their c
buildDrafts: false
# include content with publishdate in the future
buildFuture: false
# include content already expired
buildExpired: false
# enable this to make all relative URLs relative to content root. Note that this does not affect absolute URLs.
relativeURLs: false
canonifyURLs: false
Expand Down
9 changes: 9 additions & 0 deletions docs/content/templates/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ your list templates:
<div class="meta">{{ .PublishDate.Format "Mon, Jan 2, 2006" }}</div>
</li>
{{ end }}

### Order by ExpiryDate

{{ range .Data.Pages.ByExpiryDate }}
<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="meta">{{ .ExpiryDate.Format "Mon, Jan 2, 2006" }}</div>
</li>
{{ end }}

### Order by Lastmod

Expand Down
1 change: 1 addition & 0 deletions docs/content/templates/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ matter, content or derived from file location.
**.Date** The date the content is associated with.<br>
**.Lastmod** The date the content was last modified.<br>
**.PublishDate** The date the content is published on.<br>
**.ExpiryDate** The date where the content is scheduled to expire on.<br>
**.Type** The content [type](/content/types/) (e.g. post).<br>
**.Section** The [section](/content/sections/) this content belongs to.<br>
**.Permalink** The Permanent link for this page.<br>
Expand Down
37 changes: 30 additions & 7 deletions hugolib/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type Page struct {
Truncated bool
Draft bool
PublishDate time.Time
ExpiryDate time.Time
Markup string
extension string
contentType string
Expand Down Expand Up @@ -467,23 +468,40 @@ func (p *Page) LinkTitle() string {
}

func (p *Page) ShouldBuild() bool {
Copy link
Member

@bep bep May 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method has gotten complex enough to deserve a refactor.

I would pull out a function (as in not a method) with:

  • buildFuture
  • buildExpired
  • buildDrafts
  • publishDate
  • expiryDate

I.e. no dependency on Page nor Viper, then create a table driven test for that function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bep Is what I'm doing correct or does it need more work? 😁

if viper.GetBool("BuildFuture") || p.PublishDate.IsZero() || p.PublishDate.Before(time.Now()) {
if viper.GetBool("BuildDrafts") || !p.Draft {
return true
}
return AssertShouldBuild(viper.GetBool("BuildFuture"), viper.GetBool("BuildExpired"),
viper.GetBool("BuildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
}

func AssertShouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
publishDate time.Time, expiryDate time.Time) bool {
if !(buildDrafts || !Draft) {
return false
}
return false
if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) {
return false
}
if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) {
return false
}
return true
}

func (p *Page) IsDraft() bool {
return p.Draft
}

func (p *Page) IsFuture() bool {
if p.PublishDate.Before(time.Now()) {
if p.PublishDate.IsZero() {
return false
}
return true
return p.PublishDate.After(time.Now())
}

func (p *Page) IsExpired() bool {
if p.ExpiryDate.IsZero() {
return false
}
return p.ExpiryDate.Before(time.Now())
}

func (p *Page) Permalink() (string, error) {
Expand Down Expand Up @@ -564,6 +582,11 @@ func (p *Page) update(f interface{}) error {
if err != nil {
jww.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path())
}
case "expirydate", "unpublishdate":
p.ExpiryDate, err = cast.ToTimeE(v)
if err != nil {
jww.ERROR.Printf("Failed to parse expirydate '%v' in page %s", v, p.File.Path())
}
case "draft":
draft = new(bool)
*draft = cast.ToBool(v)
Expand Down
13 changes: 13 additions & 0 deletions hugolib/pageGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,19 @@ func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, e
return p.groupByDateField(sorter, formatter, order...)
}

// GroupByExpireDate groups by the given page's ExpireDate value in the given format and with the given order.
// Valid values for order is asc, desc, rev and reverse.
// For valid format strings, see https://golang.org/pkg/time/#Time.Format
func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages {
return p.ByExpiryDate()
}
formatter := func(p *Page) string {
return p.ExpiryDate.Format(format)
}
return p.groupByDateField(sorter, formatter, order...)
}

// GroupByParamDate groups by a date set as a param on the page in the given format and with the given order.
// Valid values for order is asc, desc, rev and reverse.
// For valid format strings, see https://golang.org/pkg/time/#Time.Format
Expand Down
18 changes: 18 additions & 0 deletions hugolib/pageGroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func preparePageGroupTestPages(t *testing.T) Pages {
p.Weight = s.weight
p.Date = cast.ToTime(s.date)
p.PublishDate = cast.ToTime(s.date)
p.ExpiryDate = cast.ToTime(s.date)
p.Params["custom_param"] = s.param
p.Params["custom_date"] = cast.ToTime(s.date)
pages = append(pages, p)
Expand Down Expand Up @@ -369,6 +370,23 @@ func TestGroupByPublishDateWithEmptyPages(t *testing.T) {
}
}

func TestGroupByExpiryDate(t *testing.T) {
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
{Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}},
{Key: "2012-03", Pages: Pages{pages[3]}},
{Key: "2012-01", Pages: Pages{pages[1]}},
}

groups, err := pages.GroupByExpiryDate("2006-01")
if err != nil {
t.Fatalf("Unable to make PagesGroup array: %s", err)
}
if !reflect.DeepEqual(groups, expect) {
t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups)
}
}

func TestGroupByParamDate(t *testing.T) {
pages := preparePageGroupTestPages(t)
expect := PagesGroup{
Expand Down
18 changes: 18 additions & 0 deletions hugolib/pageSort.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ func (p Pages) ByPublishDate() Pages {
return pages
}

// ByExpiryDate sorts the Pages by publish date and returns a copy.
//
// Adjacent invocactions on the same receiver will return a cached result.
//
// This may safely be executed in parallel.
func (p Pages) ByExpiryDate() Pages {

key := "pageSort.ByExpiryDate"

expDate := func(p1, p2 *Page) bool {
return p1.ExpiryDate.Unix() < p2.ExpiryDate.Unix()
}

pages, _ := spc.get(key, p, pageBy(expDate).Sort)

return pages
}

// ByLastmod sorts the Pages by the last modification date and returns a copy.
//
// Adjacent invocactions on the same receiver will return a cached result.
Expand Down
2 changes: 2 additions & 0 deletions hugolib/pageSort_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func TestSortByN(t *testing.T) {
{(Pages).ByLinkTitle, func(p Pages) bool { return p[0].LinkTitle() == "abl" }},
{(Pages).ByDate, func(p Pages) bool { return p[0].Date == d3 }},
{(Pages).ByPublishDate, func(p Pages) bool { return p[0].PublishDate == d3 }},
{(Pages).ByExpiryDate, func(p Pages) bool { return p[0].ExpiryDate == d3 }},
{(Pages).ByLastmod, func(p Pages) bool { return p[1].Lastmod == d2 }},
{(Pages).ByLength, func(p Pages) bool { return p[0].Content == "b_content" }},
} {
Expand Down Expand Up @@ -122,6 +123,7 @@ func setSortVals(dates [3]time.Time, titles [3]string, weights [3]int, pages Pag
// make sure we compare apples and ... apples ...
pages[len(dates)-1-i].linkTitle = pages[i].Title + "l"
pages[len(dates)-1-i].PublishDate = dates[i]
pages[len(dates)-1-i].ExpiryDate = dates[i]
pages[len(dates)-1-i].Content = template.HTML(titles[i] + "_content")
}
lastLastMod := pages[2].Lastmod
Expand Down
46 changes: 46 additions & 0 deletions hugolib/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1089,3 +1089,49 @@ func compareObjects(a interface{}, b interface{}) bool {

return strings.Join(aStr, "") == strings.Join(bStr, "")
}

func TestAssertShouldBuild(t *testing.T) {
var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
var zero = time.Time{}

var 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 := AssertShouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft,
ps.publishDate, ps.expiryDate)
if s != ps.out {
t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps)
}
}
}
Loading