diff --git a/docs/content/getting-started/configuration.md b/docs/content/getting-started/configuration.md index e3feb5c75bb..d2f855e01fa 100644 --- a/docs/content/getting-started/configuration.md +++ b/docs/content/getting-started/configuration.md @@ -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\\d{4})\\-(?P\\d{2})\\-(?P\\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 @@ -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 diff --git a/docs/content/variables/page.md b/docs/content/variables/page.md index 30fa39f1d4f..32080453930 100644 --- a/docs/content/variables/page.md +++ b/docs/content/variables/page.md @@ -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` diff --git a/hugolib/config.go b/hugolib/config.go index 5199e5855c8..0ea0dd3315a 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -139,8 +139,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) diff --git a/hugolib/page.go b/hugolib/page.go index c39b8aa2c2e..66769747456 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -46,6 +46,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\\d{4})\\-(?P\\d{2})\\-(?P\\d{2})-(?P.*)\\..*") + // This is all the kinds we can expect to find in .Site.Pages. allKindsInPages = []string{KindPage, KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm} @@ -921,7 +924,9 @@ func (p *Page) initURLs() error { var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter") -func (p *Page) update(f interface{}) error { +// should just read metadata - should not handle any default fallback that +// is done in updateMetadata +func (p *Page) readMetadata(f interface{}) error { if f == nil { return errors.New("no metadata found") } @@ -1082,6 +1087,28 @@ func (p *Page) update(f interface{}) error { } p.Params["draft"] = p.Draft + if isCJKLanguage != nil { + p.isCJKLanguage = *isCJKLanguage + } else if p.s.Cfg.GetBool("hasCJKLanguage") { + if cjk.Match(p.rawContent) { + p.isCJKLanguage = true + } else { + p.isCJKLanguage = false + } + } + p.Params["iscjklanguage"] = p.isCJKLanguage + + return nil + +} + +func (p *Page) updateMetadata() error { + + if p.Title == "" { + p.s.Log.DEBUG.Printf("Defaulting title for %s", p.File.Path()) + // p.Title = "Untitled" + } + if p.Date.IsZero() && p.s.Cfg.GetBool("useModTimeAsFallback") { fi, err := p.s.Fs.Source.Stat(filepath.Join(p.s.PathSpec.AbsPathify(p.s.Cfg.GetString("contentDir")), p.File.Path())) @@ -1092,14 +1119,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()) + } } } @@ -1107,18 +1151,6 @@ func (p *Page) update(f interface{}) error { p.Lastmod = p.Date } p.Params["lastmod"] = p.Lastmod - - if isCJKLanguage != nil { - p.isCJKLanguage = *isCJKLanguage - } else if p.s.Cfg.GetBool("hasCJKLanguage") { - if cjk.Match(p.rawContent) { - p.isCJKLanguage = true - } else { - p.isCJKLanguage = false - } - } - p.Params["iscjklanguage"] = p.isCJKLanguage - return nil } @@ -1361,10 +1393,11 @@ func (p *Page) parse(reader io.Reader) error { } if meta != nil { - if err = p.update(meta); err != nil { + if err = p.readMetadata(meta); err != nil { return err } } + p.updateMetadata() return nil } diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 3bb64c7335b..1fb5972a14f 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -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) { @@ -855,12 +856,15 @@ 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}) @@ -868,9 +872,64 @@ func TestPageWithDateInFilename(t *testing.T) { 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) {