From 23ed087c4e5cfad4b8ee9f4420b69182255043e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 24 Jun 2023 11:28:29 +0200 Subject: [PATCH 1/4] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e5e8ad0ee..745c70fd9 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Hugo is designed to work well for any kind of website including blogs, tumbles,

 

Linode           + CloudCannon

 

## Supported Architectures From 019299b0b0bb60355c41ad5364b631f18e7d21ae Mon Sep 17 00:00:00 2001 From: Joe Mooring Date: Sat, 24 Jun 2023 08:45:53 -0700 Subject: [PATCH 2/4] commands: Enable format flag with hugo new site Fixes #11155 --- commands/new.go | 15 ++++++++------- testscripts/commands/new.txt | 5 ++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/commands/new.go b/commands/new.go index 1b2a412fd..b5c4d2152 100644 --- a/commands/new.go +++ b/commands/new.go @@ -47,13 +47,13 @@ func newNewCommand() *newCommand { use: "content [path]", short: "Create new content for your site", long: `Create a new content file and automatically set the date and title. - It will guess which kind of file to create based on the path provided. - - You can also specify the kind with ` + "`-k KIND`" + `. - - If archetypes are provided in your theme or site, they will be used. - - Ensure you run this within the root directory of your site.`, +It will guess which kind of file to create based on the path provided. + +You can also specify the kind with ` + "`-k KIND`" + `. + +If archetypes are provided in your theme or site, they will be used. + +Ensure you run this within the root directory of your site.`, run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { if len(args) < 1 { return errors.New("path needs to be provided") @@ -150,6 +150,7 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`, }, withc: func(cmd *cobra.Command, r *rootCommand) { cmd.Flags().BoolVarP(&force, "force", "f", false, "init inside non-empty directory") + cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)") }, }, &simpleCommand{ diff --git a/testscripts/commands/new.txt b/testscripts/commands/new.txt index aad0d80f6..92cc3b7da 100644 --- a/testscripts/commands/new.txt +++ b/testscripts/commands/new.txt @@ -2,6 +2,8 @@ hugo new site -h stdout 'Create a new site in the provided directory' +hugo new site my-yaml-site --format yml +checkfile my-yaml-site/hugo.yml hugo new site mysite -f stdout 'Congratulations! Your new Hugo site is created in' cd mysite @@ -45,6 +47,3 @@ draft: true --- Dummy content. - - - From e3308a0bbc2de1cb4d7571521abeab07049ce31f Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Mon, 26 Jun 2023 14:26:36 +0300 Subject: [PATCH 3/4] tpl/tplimpl: Fix typo in global variable name --- tpl/tplimpl/template.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index e706bca4c..ba5e861af 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -754,15 +754,15 @@ func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *t //go:embed embedded/templates/* //go:embed embedded/templates/_default/* //go:embed embedded/templates/_server/* -var embededTemplatesFs embed.FS +var embeddedTemplatesFs embed.FS func (t *templateHandler) loadEmbedded() error { - return fs.WalkDir(embededTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error { + return fs.WalkDir(embeddedTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error { if d == nil || d.IsDir() { return nil } - templb, err := embededTemplatesFs.ReadFile(path) + templb, err := embeddedTemplatesFs.ReadFile(path) if err != nil { return err } From cc14c6a52c215fc43716f7b266c4c340b3d28230 Mon Sep 17 00:00:00 2001 From: Mai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com> Date: Mon, 26 Jun 2023 15:31:01 +0200 Subject: [PATCH 4/4] resources/page: Allow section and taxonomy pages to have a permalink configuration Allows using permalink configuration for sections (branch bundles) and also for taxonomy pages. Extends the current permalink configuration to be able to specified per page kind while also staying backward compatible: all permalink patterns not dedicated to a certain kind, get automatically added for both normal pages and term pages. Fixes #8523 --- config/allconfig/allconfig.go | 2 +- config/allconfig/alldecoders.go | 43 ++++++++++++++++++- hugofs/files/classifier.go | 1 + hugolib/page__paths.go | 35 +++++++++------ .../page_generate/generate_page_wrappers.go | 7 ++- resources/page/permalinks.go | 32 ++++++++++---- resources/page/permalinks_test.go | 33 +++++++++----- resources/page/zero_file.autogen.go | 6 +++ source/fileInfo.go | 8 ++++ 9 files changed, 132 insertions(+), 35 deletions(-) diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go index 9079e2ce3..2dc409be7 100644 --- a/config/allconfig/allconfig.go +++ b/config/allconfig/allconfig.go @@ -150,7 +150,7 @@ type Config struct { Minify minifiers.MinifyConfig `mapstructure:"-"` // Permalink configuration. - Permalinks map[string]string `mapstructure:"-"` + Permalinks map[string]map[string]string `mapstructure:"-"` // Taxonomy configuration. Taxonomies map[string]string `mapstructure:"-"` diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go index 1c8573f3d..4d9ef4f85 100644 --- a/config/allconfig/alldecoders.go +++ b/config/allconfig/alldecoders.go @@ -206,7 +206,48 @@ var allDecoderSetups = map[string]decodeWeight{ "permalinks": { key: "permalinks", decode: func(d decodeWeight, p decodeConfig) error { - p.c.Permalinks = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key)) + p.c.Permalinks = make(map[string]map[string]string) + + p.c.Permalinks["page"] = make(map[string]string) + p.c.Permalinks["section"] = make(map[string]string) + p.c.Permalinks["taxonomy"] = make(map[string]string) + p.c.Permalinks["term"] = make(map[string]string) + + config := maps.CleanConfigStringMap(p.p.GetStringMap(d.key)) + for k, v := range config { + switch v := v.(type) { + case string: + // [permalinks] + // key = '...' + + // To sucessfully be backward compatible, "default" patterns need to be set for both page and term + p.c.Permalinks["page"][k] = v; + p.c.Permalinks["term"][k] = v; + + case maps.Params: + // [permalinks.key] + // xyz = ??? + + if (k == "page") || (k == "section") || (k == "taxonomy") || (k == "term") { + // TODO: warn if we overwrite an already set value + for k2, v2 := range v { + switch v2 := v2.(type) { + case string: + p.c.Permalinks[k][k2] = v2 + + default: + return fmt.Errorf("permalinks configuration invalid: unknown value %q for key %q for kind %q", v2, k2, k) + } + } + } else { + return fmt.Errorf("permalinks configuration only allows per-kind configuration 'page', 'section', 'taxonomy' and 'term'; unknown kind: %q", k) + } + + default: + return fmt.Errorf("permalinks configuration invalid: unknown value %q for key %q", v, k) + } + } + return nil }, }, diff --git a/hugofs/files/classifier.go b/hugofs/files/classifier.go index 09b239c21..5690795ed 100644 --- a/hugofs/files/classifier.go +++ b/hugofs/files/classifier.go @@ -93,6 +93,7 @@ const ( ContentClassBranch ContentClass = "branch" ContentClassFile ContentClass = "zfile" // Sort below ContentClassContent ContentClass = "zcontent" + ContentClassZero ContentClass = "zero" // Special value for zeroFile ) func (c ContentClass) IsBundle() bool { diff --git a/hugolib/page__paths.go b/hugolib/page__paths.go index 72eac3182..f98531c10 100644 --- a/hugolib/page__paths.go +++ b/hugolib/page__paths.go @@ -18,6 +18,7 @@ import ( "strings" "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs/files" "github.com/gohugoio/hugo/resources/page" ) @@ -108,14 +109,16 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target ) d := s.Deps + classifier := files.ContentClassZero if !p.File().IsZero() { dir = p.File().Dir() baseName = p.File().TranslationBaseName() contentBaseName = p.File().ContentBaseName() + classifier = p.File().Classifier() } - if baseName != contentBaseName { + if classifier == files.ContentClassLeaf { // See https://github.com/gohugoio/hugo/issues/4870 // A leaf bundle dir = strings.TrimSuffix(dir, contentBaseName+helpers.FilePathSeparator) @@ -143,22 +146,26 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target desc.PrefixFilePath = s.getLanguageTargetPathLang(alwaysInSubDir) desc.PrefixLink = s.getLanguagePermalinkLang(alwaysInSubDir) - // Expand only page.KindPage and page.KindTaxonomy; don't expand other Kinds of Pages - // like page.KindSection or page.KindTaxonomyTerm because they are "shallower" and - // the permalink configuration values are likely to be redundant, e.g. - // naively expanding /category/:slug/ would give /category/categories/ for - // the "categories" page.KindTaxonomyTerm. - if p.Kind() == page.KindPage || p.Kind() == page.KindTerm { - opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p) - if err != nil { - return desc, err + opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p) + if err != nil { + return desc, err + } + + if opath != "" { + opath, _ = url.QueryUnescape(opath) + if strings.HasSuffix(opath, "//") { + // When rewriting the _index of the section the permalink config is applied to, + // we get douple slashes at the end sometimes; clear them up here + opath = strings.TrimSuffix(opath, "/") } - if opath != "" { - opath, _ = url.QueryUnescape(opath) - desc.ExpandedPermalink = opath - } + desc.ExpandedPermalink = opath + if !p.File().IsZero() { + s.Log.Debugf("Set expanded permalink path for %s %s to %#v", p.Kind(), p.File().Path(), opath) + } else { + s.Log.Debugf("Set expanded permalink path for %s in %v to %#v", p.Kind(), desc.Sections, opath) + } } return desc, nil diff --git a/resources/page/page_generate/generate_page_wrappers.go b/resources/page/page_generate/generate_page_wrappers.go index f4b40f717..c034bc002 100644 --- a/resources/page/page_generate/generate_page_wrappers.go +++ b/resources/page/page_generate/generate_page_wrappers.go @@ -221,7 +221,7 @@ func generateFileIsZeroWrappers(c *codegen.Inspector) error { methods := c.MethodsFromTypes([]reflect.Type{reflect.TypeOf((*source.File)(nil)).Elem()}, nil) for _, m := range methods { - if m.Name == "IsZero" { + if m.Name == "IsZero" || m.Name == "Classifier" { continue } fmt.Fprint(&buff, m.DeclarationNamed("zeroFile")) @@ -255,6 +255,11 @@ func (zeroFile) IsZero() bool { return true } +func (z zeroFile) Classifier() files.ContentClass { + z.log.Warnln(".File.Classifier on zero object. Wrap it in if or with: {{ with .File }}{{ .Classifier }}{{ end }}") + return files.ContentClassZero +} + %s `, header, importsString(pkgImports), buff.String()) diff --git a/resources/page/permalinks.go b/resources/page/permalinks.go index 3dfc36937..ab418a20b 100644 --- a/resources/page/permalinks.go +++ b/resources/page/permalinks.go @@ -26,6 +26,7 @@ import ( "errors" "github.com/gohugoio/hugo/helpers" + ) // PermalinkExpander holds permalin mappings per section. @@ -35,7 +36,7 @@ type PermalinkExpander struct { // to be used to replace that tag. knownPermalinkAttributes map[string]pageToPermaAttribute - expanders map[string]func(Page) (string, error) + expanders map[string]map[string]func(Page) (string, error) urlize func(uri string) string } @@ -68,7 +69,7 @@ func (p PermalinkExpander) callback(attr string) (pageToPermaAttribute, bool) { // NewPermalinkExpander creates a new PermalinkExpander configured by the given // urlize func. -func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]string) (PermalinkExpander, error) { +func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]map[string]string) (PermalinkExpander, error) { p := PermalinkExpander{urlize: urlize} p.knownPermalinkAttributes = map[string]pageToPermaAttribute{ @@ -87,12 +88,15 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]st "filename": p.pageToPermalinkFilename, } - e, err := p.parse(patterns) - if err != nil { - return p, err - } + p.expanders = make(map[string]map[string]func(Page) (string, error)) - p.expanders = e + for kind, patterns := range patterns { + e, err := p.parse(patterns) + if err != nil { + return p, err + } + p.expanders[kind] = e + } return p, nil } @@ -100,7 +104,13 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]st // Expand expands the path in p according to the rules defined for the given key. // If no rules are found for the given key, an empty string is returned. func (l PermalinkExpander) Expand(key string, p Page) (string, error) { - expand, found := l.expanders[key] + expanders, found := l.expanders[p.Kind()] + + if !found { + return "", nil + } + + expand, found := expanders[key] if !found { return "", nil @@ -242,6 +252,10 @@ func (l PermalinkExpander) pageToPermalinkDate(p Page, dateField string) (string // pageToPermalinkTitle returns the URL-safe form of the title func (l PermalinkExpander) pageToPermalinkTitle(p Page, _ string) (string, error) { + if p.File().TranslationBaseName() == "_index" { + return "", nil + } + return l.urlize(p.Title()), nil } @@ -252,6 +266,8 @@ func (l PermalinkExpander) pageToPermalinkFilename(p Page, _ string) (string, er // Page bundles; the directory name will hopefully have a better name. dir := strings.TrimSuffix(p.File().Dir(), helpers.FilePathSeparator) _, name = filepath.Split(dir) + } else if name == "_index" { + return "", nil } return l.urlize(name), nil diff --git a/resources/page/permalinks_test.go b/resources/page/permalinks_test.go index b9c0ca9cb..24eb27f5b 100644 --- a/resources/page/permalinks_test.go +++ b/resources/page/permalinks_test.go @@ -69,6 +69,7 @@ func TestPermalinkExpansion(t *testing.T) { page.date = d page.section = "blue" page.slug = "The Slug" + page.kind = "page" for _, item := range testdataPermalinks { if !item.valid { @@ -79,8 +80,10 @@ func TestPermalinkExpansion(t *testing.T) { name := specNameCleaner.ReplaceAllString(item.spec, "") c.Run(name, func(c *qt.C) { - patterns := map[string]string{ - "posts": item.spec, + patterns := map[string]map[string]string{ + "page": { + "posts": item.spec, + }, } expander, err := NewPermalinkExpander(urlize, patterns) c.Assert(err, qt.IsNil) @@ -103,14 +106,18 @@ func TestPermalinkExpansionMultiSection(t *testing.T) { page.date = d page.section = "blue" page.slug = "The Slug" + page.kind = "page" page_slug_fallback := newTestPageWithFile("/page-filename/index.md") page_slug_fallback.title = "Page Title" + page_slug_fallback.kind = "page" - permalinksConfig := map[string]string{ - "posts": "/:slug", - "blog": "/:section/:year", - "recipes": "/:slugorfilename", + permalinksConfig := map[string]map[string]string{ + "page": { + "posts": "/:slug", + "blog": "/:section/:year", + "recipes": "/:slugorfilename", + }, } expander, err := NewPermalinkExpander(urlize, permalinksConfig) c.Assert(err, qt.IsNil) @@ -137,8 +144,10 @@ func TestPermalinkExpansionConcurrent(t *testing.T) { c := qt.New(t) - permalinksConfig := map[string]string{ - "posts": "/:slug/", + permalinksConfig := map[string]map[string]string{ + "page": { + "posts": "/:slug/", + }, } expander, err := NewPermalinkExpander(urlize, permalinksConfig) @@ -151,6 +160,7 @@ func TestPermalinkExpansionConcurrent(t *testing.T) { go func(i int) { defer wg.Done() page := newTestPage() + page.kind = "page" for j := 1; j < 20; j++ { page.slug = fmt.Sprintf("slug%d", i+j) expanded, err := expander.Expand("posts", page) @@ -209,9 +219,12 @@ func BenchmarkPermalinkExpand(b *testing.B) { page.title = "Hugo Rocks" d, _ := time.Parse("2006-01-02", "2019-02-28") page.date = d + page.kind = "page" - permalinksConfig := map[string]string{ - "posts": "/:year-:month-:title", + permalinksConfig := map[string]map[string]string{ + "page": { + "posts": "/:year-:month-:title", + }, } expander, err := NewPermalinkExpander(urlize, permalinksConfig) if err != nil { diff --git a/resources/page/zero_file.autogen.go b/resources/page/zero_file.autogen.go index 72d98998e..979f92a0f 100644 --- a/resources/page/zero_file.autogen.go +++ b/resources/page/zero_file.autogen.go @@ -18,6 +18,7 @@ package page import ( "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugofs/files" "github.com/gohugoio/hugo/source" ) @@ -34,6 +35,11 @@ func (zeroFile) IsZero() bool { return true } +func (z zeroFile) Classifier() files.ContentClass { + z.log.Warnln(".File.Classifier on zero object. Wrap it in if or with: {{ with .File }}{{ .Classifier }}{{ end }}") + return files.ContentClassZero +} + func (z zeroFile) Path() (o0 string) { z.log.Warnln(".File.Path on zero object. Wrap it in if or with: {{ with .File }}{{ .Path }}{{ end }}") return diff --git a/source/fileInfo.go b/source/fileInfo.go index c58a0c3b9..03bb1b16c 100644 --- a/source/fileInfo.go +++ b/source/fileInfo.go @@ -92,6 +92,9 @@ type FileWithoutOverlap interface { // if file is a leaf bundle. ContentBaseName() string + // Classifier is the ContentClass of the file + Classifier() files.ContentClass + // UniqueID is the MD5 hash of the file's path and is for most practical applications, // Hugo content files being one of them, considered to be unique. UniqueID() string @@ -170,6 +173,11 @@ func (fi *FileInfo) ContentBaseName() string { return fi.contentBaseName } +// Classifier is the ContentClass of the file +func (fi *FileInfo) Classifier() files.ContentClass { + return fi.classifier; +} + // Section returns a file's section. func (fi *FileInfo) Section() string { fi.init()