Skip to content

Commit

Permalink
Use filename as fallback for date
Browse files Browse the repository at this point in the history
Why:

 * when migrating from systems like jekylla and awestruct to hugo
   you might have hundreds if not thousands of blog entries using the
   filename as a way to set the date and slug part of page.
   i.e. 2017-08-01-my-life-as-a-dog.md would have a date of 2017-08-01 and
   a url called my-life-as-a-dog.md.

This change addreses the need by:

 * introduce a setting called `useFilenameDateAsFallback` that defaults to true,
   which will derive date and slug from the filename.
  • Loading branch information
maxandersen committed Aug 17, 2017
1 parent 69329c4 commit b79112a
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 19 deletions.
8 changes: 2 additions & 6 deletions docs/content/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,6 @@ disablePathToLower = false
enableEmoji = false
# Show a placeholder instead of the default value or an empty string if a translation is missing
enableMissingTranslationPlaceholders = false
# Regex to use for dates in filenames.
filenameDateFallbackPattern = "(?P<year>\\d{4})\\-(?P<month>\\d{2})\\-(?P<day>\\d{2})"
# Time format for custom dates in filenames. Only used with `useFilenameDateAsFallback` and must match `filenameDateFallbackPattern`
filenameDateFallbackFormat = "2006-01-02"
footnoteAnchorPrefix = ""
footnoteReturnLinkContents = ""
# google analytics tracking id
Expand Down Expand Up @@ -302,8 +298,8 @@ theme = ""
title = ""
# if true, use /filename.html instead of /filename/
uglyURLs = false
#if true, use dates in filenames e.g. 2017-01-31-mypostname.md
useFilenameDateAsFallback = false
# use dates in filenames e.g. 2017-01-31-mypostname.md
useFilenameDateAsFallback = true
# verbose output
verbose = false
# verbose logging
Expand Down
6 changes: 5 additions & 1 deletion docs/content/variables/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ See [`.Scratch`](/functions/scratch/) for page-scoped, writable variables.

`.Date`
: the date associated with the page; `.Date` pulls from the `date` field in a content's front matter.
If you're migrating content to Hugo, you may have content with dates in the filename. For example `2017-01-31-myblog.md`. You can optionally enable the `useFilenameDateAsFallback` configuration option. This will attempt to parse the datestamp in the filename and use it as a fallback to providing a date variable in the front matter.
If no `date` field present Hugo will try and extract the date from the filename. For example `2017-01-31-myblog.md`. It will also set `.Slug` to contain the path without the
date prefix.'

You can disable the automatic date detection and slug modification by setting `useFilenameDateAsFallback` configuration option to false.

See also `.ExpiryDate`, `.PublishDate`, and `.Lastmod`.

`.Description`
Expand Down
3 changes: 1 addition & 2 deletions hugolib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ func loadDefaultSettingsFor(v *viper.Viper) {
v.SetDefault("enableEmoji", false)
v.SetDefault("pygmentsCodeFencesGuessSyntax", false)
v.SetDefault("useModTimeAsFallback", false)
v.SetDefault("filenameDateFallbackPattern", nil)
v.SetDefault("filenameDateFallbackFormat", "2006-01-02")
v.SetDefault("useFilenameDateAsFallback", true)
v.SetDefault("defaultContentLanguage", "en")
v.SetDefault("defaultContentLanguageInSubdir", false)
v.SetDefault("enableMissingTranslationPlaceholders", false)
Expand Down
36 changes: 28 additions & 8 deletions hugolib/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import (
var (
cjk = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)

// date expression used to find iso date in filename
dateExp = regexp.MustCompile("(?P<year>\\d{4})\\-(?P<month>\\d{2})\\-(?P<day>\\d{2})-(?P<slug>.*)\\..*")

// This is all the kinds we can expect to find in .Site.Pages.
allKindsInPages = []string{KindPage, KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm}

Expand Down Expand Up @@ -1079,14 +1082,31 @@ func (p *Page) update(f interface{}) error {
}
}

if p.Date.IsZero() && p.s.Cfg.GetString("filenameDateFallbackPattern") != "" {
dateExp := regexp.MustCompile(p.s.Cfg.GetString("filenameDateFallbackPattern"))
dateString := dateExp.FindString(p.File.Path())
filenameDate, err := time.Parse(p.s.Cfg.GetString("filenameDateFallbackFormat"), dateString)
if err == nil {
p.s.Log.DEBUG.Printf("using filename date as fallback for page %s", p.File.Path())
p.Date = filenameDate
p.Params["date"] = p.Date
if p.Date.IsZero() && p.s.Cfg.GetBool("useFilenameDateAsFallback") {
match := dateExp.FindStringSubmatch(p.File.Path())

if match != nil {
year := match[1]
month := match[2]
day := match[3]
slug := match[4]

filenameDate, err := time.Parse("2006-01-02", year+"-"+month+"-"+day)

if err == nil {
p.Date = filenameDate
p.Params["date"] = p.Date
if p.Slug == "" {
p.Slug = slug
p.s.Log.DEBUG.Printf("Using filename date (%s) and slug (%s) as fallback for page %s", p.Date, p.Slug, p.File.Path())

} else {
p.s.Log.DEBUG.Printf("Using filename date (%s) as fallback for page %s", p.Date, p.File.Path())

}
} else {
p.s.Log.WARN.Printf("File has what looks like a date, but the date is invalid for page %s.", p.File.Path())
}
}
}

Expand Down
63 changes: 61 additions & 2 deletions hugolib/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ func checkPageDate(t *testing.T, page *Page, time time.Time) {
if page.Date != time {
t.Fatalf("Page date is: %s. Expected: %s", page.Date, time)
}

}

func checkTruncation(t *testing.T, page *Page, shouldBe bool, msg string) {
Expand Down Expand Up @@ -855,22 +856,80 @@ func TestPageWithDate(t *testing.T) {
d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z")

checkPageDate(t, p, d)

}

func TestPageWithDateInFilename(t *testing.T) {
func TestDateAsFallbackWithDateInFilenameDisabled(t *testing.T) {
t.Parallel()
cfg, fs := newTestCfg()

cfg.Set("useFilenameDateAsFallback", false)

writeSource(t, fs, filepath.Join("content", "2017-01-31-simple.md"), simplePageNoDate)

s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})

require.Len(t, s.RegularPages, 1)

p := s.RegularPages[0]
d, _ := time.Parse(time.RFC3339, "2017-01-31")

assert.True(t, p.Date.IsZero(), "page date should be empty as no date in file nor filename.")

assert.Equal(t, "", p.Slug, "slug for page should not be set as filename as date is disabled.")
}

func TestDateAsFallbackWithDateInFilename(t *testing.T) {
t.Parallel()
cfg, fs := newTestCfg()

writeSource(t, fs, filepath.Join("content", "2017-01-31-simple.md"), simplePageNoDate)

s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})

require.Len(t, s.RegularPages, 1)

p := s.RegularPages[0]
d, err := time.Parse("2006-01-02", "2017-01-31")

assert.Equal(t, err, nil)

checkPageDate(t, p, d)

assert.Equal(t, "simple", p.Slug, "slug for page should be set as there is both date and title in filename")
}

func TestDateAsFallbackWithOutDateInFilename(t *testing.T) {
t.Parallel()
cfg, fs := newTestCfg()

writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageNoDate)

s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})

require.Len(t, s.RegularPages, 1)

p := s.RegularPages[0]

assert.True(t, p.Date.IsZero(), "page date should be empty as no date in filename")
assert.Equal(t, "", p.Slug, "page slug should not be set as no date in filename")

}

func TestDateAsFallbackWithInvalidDateInFilename(t *testing.T) {
t.Parallel()
cfg, fs := newTestCfg()

writeSource(t, fs, filepath.Join("content", "2017-31-31-simple.md"), simplePageNoDate)

s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})

require.Len(t, s.RegularPages, 1)

p := s.RegularPages[0]

assert.True(t, p.Date.IsZero(), "page date should be empty as no valid date in filename")
assert.Equal(t, "", p.Slug, "page slug should not be set as no valid date in filename")

}

func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
Expand Down

0 comments on commit b79112a

Please sign in to comment.