Repository: gohugoio/hugo (language: go)

Issue:
Add environment (e.g. production) as a new filter to _cascade.target
Hey there,

After looking this up and [confirming](https://discourse.gohugo.io/t/skipping-page-building-depending-on-environment-config/37502) this was not possible (thanks again @jmooring!) I would like to propose a feature, that works similar to the `draft` keyword, but allows specifying the environment(s) for which pages/posts shall be built for. Specifically, I suggest adding an `environment` keyword, which would accept a list of values:

```yaml
environment:
  - production
  - staging
  - development
```

This would allow for per-page/-post management of individual pages/posts, in which individual teams work on parts of a large Hugo deployment, in which none of them (but one admin-team) have access to the actual environment configuration files. Through a keyword inside the pages/posts, teams could individually decide to which environments pages/posts would get deployed to, without having the admin-team involved for adjusting the `cascade._target` configuration.

I would appreciate thoughts on this! Thank you.


Discussion / hints:
Yes, something like this would certainly be useful. I don't think, however, that we would want to add some other `draft` type of _thing_ (Hugo already has enough buttons ...).

I'm thinking out loud here, but what if we did:

```bash
[[cascade]]
[cascade._build]
list = false
render = false
[cascade._target]
 environment = '{production,staging}'
```
The way I understood this solution it would require people maintaining pages to also get access to the environment configuration(s). As mentioned before I ran into multiple situations where it's not easily possible to provide that and where it would make sense to have per post/page configuration.
Hi, I would like to try this issue. If I understand correctly, this involves adding environment as a new keyword to the PageMatcher?
@CathrinePaulsen yes, I think that would be the simplest approach. We may want to refactor this at some point, but for now I suggest  you just get the environment from `p.Site().Hugo().Environ()` (or something). There should be some tests that you can expand upon.

Fix patch:
diff --git a/docs/content/en/content-management/front-matter.md b/docs/content/en/content-management/front-matter.md
index 6986f067fa6..cac4ef9525e 100644
--- a/docs/content/en/content-management/front-matter.md
+++ b/docs/content/en/content-management/front-matter.md
@@ -189,6 +189,9 @@ kind
 lang
 : A Glob pattern matching the Page's language, e.g. "{en,sv}".
 
+environment
+: A Glob pattern matching the build environment, e.g. "{production,development}"
+
 Any of the above can be omitted. 
 
 ### Example
diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go
index 15b8ede8989..4626186c5cf 100644
--- a/resources/page/page_matcher.go
+++ b/resources/page/page_matcher.go
@@ -37,6 +37,9 @@ type PageMatcher struct {
 
 	// A Glob pattern matching the Page's language, e.g. "{en,sv}".
 	Lang string
+
+	// A Glob pattern matching the Page's Environment, e.g. "{production,development}".
+	Environment string
 }
 
 // Matches returns whether p matches this matcher.
@@ -67,6 +70,13 @@ func (m PageMatcher) Matches(p Page) bool {
 		}
 	}
 
+	if m.Environment != "" {
+		g, err := glob.GetGlob(m.Environment)
+		if err == nil && !g.Match(p.Site().Hugo().Environment) {
+			return false
+		}
+	}
+
 	return true
 }
 


Test patch:
diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go
index c218aa28263..0a7f66e6c1b 100644
--- a/hugolib/cascade_test.go
+++ b/hugolib/cascade_test.go
@@ -550,6 +550,32 @@ S1|p1:|p2:p2|
 `)
 	})
 
+	c.Run("slice with environment _target", func(c *qt.C) {
+		b := newBuilder(c)
+
+		b.WithContent("_index.md", `+++
+title = "Home"
+[[cascade]]
+p1 = "p1"
+[cascade._target]
+path="**p1**"
+environment="testing"
+[[cascade]]
+p2 = "p2"
+[cascade._target]
+kind="section"
+environment="production"
++++
+`)
+
+		b.Build(BuildCfg{})
+
+		b.AssertFileContent("public/index.html", `
+P1|p1:|p2:|
+S1|p1:|p2:p2|
+`)
+	})
+
 	c.Run("slice with yaml _target", func(c *qt.C) {
 		b := newBuilder(c)
 
diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go
index 72aec5f2a64..846ab2c0341 100644
--- a/resources/page/page_matcher_test.go
+++ b/resources/page/page_matcher_test.go
@@ -14,6 +14,7 @@
 package page
 
 import (
+	"github.com/gohugoio/hugo/common/hugo"
 	"path/filepath"
 	"testing"
 
@@ -22,8 +23,13 @@ import (
 
 func TestPageMatcher(t *testing.T) {
 	c := qt.New(t)
+	developmentTestSite := testSite{h: hugo.NewInfo("development", nil)}
+	productionTestSite := testSite{h: hugo.NewInfo("production", nil)}
 
-	p1, p2, p3 := &testPage{path: "/p1", kind: "section", lang: "en"}, &testPage{path: "p2", kind: "page", lang: "no"}, &testPage{path: "p3", kind: "page", lang: "en"}
+	p1, p2, p3 :=
+		&testPage{path: "/p1", kind: "section", lang: "en", site: developmentTestSite},
+		&testPage{path: "p2", kind: "page", lang: "no", site: productionTestSite},
+		&testPage{path: "p3", kind: "page", lang: "en"}
 
 	c.Run("Matches", func(c *qt.C) {
 		m := PageMatcher{Kind: "section"}
@@ -50,6 +56,16 @@ func TestPageMatcher(t *testing.T) {
 		c.Assert(m.Matches(p1), qt.Equals, true)
 		c.Assert(m.Matches(p2), qt.Equals, false)
 		c.Assert(m.Matches(p3), qt.Equals, true)
+
+		m = PageMatcher{Environment: "development"}
+		c.Assert(m.Matches(p1), qt.Equals, true)
+		c.Assert(m.Matches(p2), qt.Equals, false)
+		c.Assert(m.Matches(p3), qt.Equals, false)
+
+		m = PageMatcher{Environment: "production"}
+		c.Assert(m.Matches(p1), qt.Equals, false)
+		c.Assert(m.Matches(p2), qt.Equals, true)
+		c.Assert(m.Matches(p3), qt.Equals, false)
 	})
 
 	c.Run("Decode", func(c *qt.C) {
diff --git a/resources/page/testhelpers_test.go b/resources/page/testhelpers_test.go
index 6758667996e..6acb4cfe46e 100644
--- a/resources/page/testhelpers_test.go
+++ b/resources/page/testhelpers_test.go
@@ -94,6 +94,7 @@ type testPage struct {
 	linkTitle   string
 	lang        string
 	section     string
+	site        testSite
 
 	content string
 
@@ -532,7 +533,7 @@ func (p *testPage) SectionsPath() string {
 }
 
 func (p *testPage) Site() Site {
-	panic("not implemented")
+	return p.site
 }
 
 func (p *testPage) Sites() Sites {