Repository: gohugoio/hugo (language: go)

Issue:
Enable menu entry creation within a content adapter
```text
{{ range $firstLevel, $_ := site.Data }}
  {{ $page := dict
    "kind" "section"
    "path" $firstLevel
    "title" ($firstLevel | humanize)
    "menus" "main"
  }}
  {{ $.AddPage $page }}
  {{ range $secondLevel, $_ := . }}
    {{ $page := dict
      "kind" "page"
      "params" .
      "path" (path.Join $firstLevel $secondLevel)
      "title" ($secondLevel | humanize)
    }}
    {{ $.AddPage $page }}
  {{ end }}
{{ end }}
```

In the above I tried setting both "menu" and "menus" when adding the section page.

Example:

```text
git clone --single-branch -b hugo-forum-topic-49842 https://github.com/jmooring/hugo-testing hugo-forum-topic-49842
cd hugo-forum-topic-49842
hugo server
```

I'm still a bit amazed at how powerful this is, and how easy it is to use.


Discussion / hints:

Fix patch:
diff --git a/hugolib/page__content.go b/hugolib/page__content.go
index 1ef31f0f986..1119a8a95bb 100644
--- a/hugolib/page__content.go
+++ b/hugolib/page__content.go
@@ -57,14 +57,14 @@ type pageContentReplacement struct {
 
 func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo, error) {
 	var (
-		sourceKey  string
-		openSource hugio.OpenReadSeekCloser
-		hasContent = m.pageConfig.IsFromContentAdapter
+		sourceKey            string
+		openSource           hugio.OpenReadSeekCloser
+		isFromContentAdapter = m.pageConfig.IsFromContentAdapter
 	)
 
-	if m.f != nil && !hasContent {
+	if m.f != nil && !isFromContentAdapter {
 		sourceKey = filepath.ToSlash(m.f.Filename())
-		if !hasContent {
+		if !isFromContentAdapter {
 			meta := m.f.FileInfo().Meta()
 			openSource = func() (hugio.ReadSeekCloser, error) {
 				r, err := meta.Open()
@@ -74,7 +74,7 @@ func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo
 				return r, nil
 			}
 		}
-	} else if hasContent {
+	} else if isFromContentAdapter {
 		openSource = m.pageConfig.Content.ValueAsOpenReadSeekCloser()
 	}
 
@@ -96,7 +96,9 @@ func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo
 
 	items, err := pageparser.ParseBytes(
 		source,
-		pageparser.Config{},
+		pageparser.Config{
+			NoFrontMatter: isFromContentAdapter,
+		},
 	)
 	if err != nil {
 		return nil, err
@@ -104,7 +106,7 @@ func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo
 
 	pi.itemsStep1 = items
 
-	if hasContent {
+	if isFromContentAdapter {
 		// No front matter.
 		return pi, nil
 	}
diff --git a/hugolib/page__menus.go b/hugolib/page__menus.go
index 5bed2bc033d..1666036ce58 100644
--- a/hugolib/page__menus.go
+++ b/hugolib/page__menus.go
@@ -62,8 +62,22 @@ func (p *pageMenus) init() {
 			p.p,
 		)
 
+		params := p.p.Params()
+
+		var menus any
+		var ok bool
+
+		if p.p.m.pageConfig.Menus != nil {
+			menus = p.p.m.pageConfig.Menus
+		} else {
+			menus, ok = params["menus"]
+			if !ok {
+				menus = params["menu"]
+			}
+		}
+
 		var err error
-		p.pm, err = navigation.PageMenusFromPage(p.p)
+		p.pm, err = navigation.PageMenusFromPage(menus, p.p)
 		if err != nil {
 			p.p.s.Log.Errorln(p.p.wrapError(err))
 		}
diff --git a/magefile.go b/magefile.go
index 677c59d2c1c..ce2cd41ea84 100644
--- a/magefile.go
+++ b/magefile.go
@@ -167,19 +167,19 @@ func testGoFlags() string {
 // Note that we don't run with the extended tag. Currently not supported in 32 bit.
 func Test386() error {
 	env := map[string]string{"GOARCH": "386", "GOFLAGS": testGoFlags()}
-	return runCmd(env, goexe, "test", "./...")
+	return runCmd(env, goexe, "test", "-p", "2", "./...")
 }
 
 // Run tests
 func Test() error {
 	env := map[string]string{"GOFLAGS": testGoFlags()}
-	return runCmd(env, goexe, "test", "./...", "-tags", buildTags())
+	return runCmd(env, goexe, "test", "-p", "2", "./...", "-tags", buildTags())
 }
 
 // Run tests with race detector
 func TestRace() error {
 	env := map[string]string{"GOFLAGS": testGoFlags()}
-	return runCmd(env, goexe, "test", "-race", "./...", "-tags", buildTags())
+	return runCmd(env, goexe, "test", "-p", "2", "-race", "./...", "-tags", buildTags())
 }
 
 // Run gofmt linter
diff --git a/navigation/pagemenus.go b/navigation/pagemenus.go
index 3ff49cf8143..0919d93bb35 100644
--- a/navigation/pagemenus.go
+++ b/navigation/pagemenus.go
@@ -41,20 +41,11 @@ type MenuQueryProvider interface {
 	IsMenuCurrent(menuID string, inme *MenuEntry) bool
 }
 
-func PageMenusFromPage(p Page) (PageMenus, error) {
-	params := p.Params()
-
-	ms, ok := params["menus"]
-	if !ok {
-		ms, ok = params["menu"]
-	}
-
-	pm := PageMenus{}
-
-	if !ok {
+func PageMenusFromPage(ms any, p Page) (PageMenus, error) {
+	if ms == nil {
 		return nil, nil
 	}
-
+	pm := PageMenus{}
 	me := MenuEntry{}
 	SetPageValues(&me, p)
 
diff --git a/parser/pageparser/pagelexer.go b/parser/pageparser/pagelexer.go
index 5f90e3687b3..e3b0f1e5468 100644
--- a/parser/pageparser/pagelexer.go
+++ b/parser/pageparser/pagelexer.go
@@ -62,7 +62,9 @@ func (l *pageLexer) Input() []byte {
 	return l.input
 }
 
-type Config struct{}
+type Config struct {
+	NoFrontMatter bool
+}
 
 // note: the input position here is normally 0 (start), but
 // can be set if position of first shortcode is known
diff --git a/parser/pageparser/pageparser.go b/parser/pageparser/pageparser.go
index 9e8b6d8032b..988a80c83cf 100644
--- a/parser/pageparser/pageparser.go
+++ b/parser/pageparser/pageparser.go
@@ -36,7 +36,11 @@ var _ Result = (*pageLexer)(nil)
 
 // ParseBytes parses the page in b according to the given Config.
 func ParseBytes(b []byte, cfg Config) (Items, error) {
-	l, err := parseBytes(b, cfg, lexIntroSection)
+	startLexer := lexIntroSection
+	if cfg.NoFrontMatter {
+		startLexer = lexMainSection
+	}
+	l, err := parseBytes(b, cfg, startLexer)
 	if err != nil {
 		return nil, err
 	}
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index 10ba2a991af..c901ab57b5f 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -101,6 +101,7 @@ type PageConfig struct {
 	Cascade []map[string]any
 	Sitemap config.SitemapConfig
 	Build   BuildConfig
+	Menus   []string
 
 	// User defined params.
 	Params maps.Params


Test patch:
diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
index 7f0f19f1c93..3a37c233b96 100644
--- a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
+++ b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
@@ -585,3 +585,56 @@ value: data1
 
 	b.AssertLogNotContains("WARN")
 }
+
+func TestPagesFromGoTmplShortcodeNoPreceddingCharacterIssue12544(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+-- content/_content.gotmpl --
+{{ $content := dict "mediaType" "text/html" "value" "x{{< sc >}}" }}
+{{ .AddPage (dict "content" $content "path" "a") }}
+
+{{ $content := dict "mediaType" "text/html" "value" "{{< sc >}}" }}
+{{ .AddPage (dict "content" $content "path" "b") }}
+-- layouts/_default/single.html --
+|{{ .Content }}|
+-- layouts/shortcodes/sc.html --
+foo
+{{- /**/ -}}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/a/index.html", "|xfoo|")
+	b.AssertFileContent("public/b/index.html", "|foo|") // fails
+}
+
+func TestPagesFromGoTmplMenus(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['rss','section','sitemap','taxonomy','term']
+
+[menus]
+[[menus.main]]
+name = "Main"
+[[menus.footer]]
+name = "Footer"
+-- content/_content.gotmpl --
+{{ .AddPage (dict "path" "p1" "title" "p1" "menus" "main" ) }}
+{{ .AddPage (dict "path" "p2" "title" "p2" "menus" (slice "main" "footer")) }}
+-- layouts/index.html --
+Main: {{ range index site.Menus.main }}{{ .Name }}|{{ end }}|
+Footer: {{ range index site.Menus.footer }}{{ .Name }}|{{ end }}|
+
+`
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/index.html",
+		"Main: Main|p1|p2||",
+		"Footer: Footer|p2||",
+	)
+}