Merge branch 'main' into preview/tags
diff --git a/content/board/ASF-5-year-plan-02-21-2018.md b/content/board/ASF-5-year-plan-02-21-2018.md
index 72ba4bf..d2461e4 100644
--- a/content/board/ASF-5-year-plan-02-21-2018.md
+++ b/content/board/ASF-5-year-plan-02-21-2018.md
@@ -1,4 +1,5 @@
 Title: Five-year Strategic Plan for the Apache Software Foundation
+Tags: plan
 Slug: plan
 
 # {{title}}
diff --git a/content/board/services.md b/content/board/services.md
index a4ca68d..615dd2c 100644
--- a/content/board/services.md
+++ b/content/board/services.md
@@ -1,4 +1,5 @@
 Title: Foundation Services for Apache Projects
+Tags: services
 Slug: services
 
 # {{title}}
diff --git a/content/dev/pmc.md b/content/dev/pmc.md
index bfc2f82..425848c 100644
--- a/content/dev/pmc.md
+++ b/content/dev/pmc.md
@@ -1,5 +1,6 @@
 Title: Project Management Committee Guide
 license: https://www.apache.org/licenses/LICENSE-2.0
+Tags: pmc
 
 # Project Management Committee Guide
 
diff --git a/content/foundation/policies/anti-harassment.md b/content/foundation/policies/anti-harassment.md
index 37f357d..d119490 100644
--- a/content/foundation/policies/anti-harassment.md
+++ b/content/foundation/policies/anti-harassment.md
@@ -1,4 +1,5 @@
 Title:     Anti-Harassment Policy
+Tags: policy
 license: https://www.apache.org/licenses/LICENSE-2.0
 
 # {{title}}
diff --git a/content/foundation/policies/conduct.md b/content/foundation/policies/conduct.md
index 4aa8e2d..a4e2da1 100644
--- a/content/foundation/policies/conduct.md
+++ b/content/foundation/policies/conduct.md
@@ -1,4 +1,5 @@
 Title:     Code of Conduct
+Tags: policy
 license: https://www.apache.org/licenses/LICENSE-2.0
 
 # {{title}}
diff --git a/content/foundation/policies/index.md b/content/foundation/policies/index.md
index a6e54d9..c5be88d 100644
--- a/content/foundation/policies/index.md
+++ b/content/foundation/policies/index.md
@@ -1,4 +1,5 @@
 Title:     Foundation policies
+Tags: policy
 license: https://www.apache.org/licenses/LICENSE-2.0
 
 # {{title}}
diff --git a/content/history/mirror-history.md b/content/history/mirror-history.md
index 4ffd532..c5707f7 100644
--- a/content/history/mirror-history.md
+++ b/content/history/mirror-history.md
@@ -1,4 +1,5 @@
 Title: The history of the Apache mirror system
+Tags: history
 license: https://www.apache.org/licenses/LICENSE-2.0
 
 # {{title}}
diff --git a/content/history/timeline.md b/content/history/timeline.md
index e4aa417..a1e075b 100644
--- a/content/history/timeline.md
+++ b/content/history/timeline.md
@@ -1,4 +1,5 @@
 Title: ASF History Project - Timeline
+Tags: history
 license: https://www.apache.org/licenses/LICENSE-2.0
 
 # {{title}}
diff --git a/content/legal/3party.md b/content/legal/3party.md
index 506ef08..fa70830 100644
--- a/content/legal/3party.md
+++ b/content/legal/3party.md
@@ -1,6 +1,7 @@
 Title: Third-Party Licensing Policy - The Apache Software Foundation
 Atom: http://mail-archives.apache.org/mod_mbox/www-legal-discuss/?format=atom
 Notice: http://www.apache.org/licenses/LICENSE-2.0
+Tags: legal
 
 # Third-Party Licensing Policy #
 
diff --git a/content/legal/apply-license.md b/content/legal/apply-license.md
index 1d88e04..42938be 100644
--- a/content/legal/apply-license.md
+++ b/content/legal/apply-license.md
@@ -1,4 +1,5 @@
 Title: Applying the Apache license, version 2.0
+Tags: legal
 
 This document is to help Apache developers understand what they need to do to apply the <a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License, Version 2.0</a> or _ALv2_ to Apache software, including source code, documentation, and binary distributions. It is descriptive guidance, and does not supplant or otherwise modify any of the terms within the license itself. In case of uncertainty, consult <a href="https://www.apache.org/legal" target="_blank">general Apache policy</a>.
 
diff --git a/content/legal/dmca.md b/content/legal/dmca.md
index 14e8d46..84a46a6 100644
--- a/content/legal/dmca.md
+++ b/content/legal/dmca.md
@@ -1,5 +1,6 @@
 Title: ASF Legal Digital Millenium Copyright Act Designated Agent
 license: https://www.apache.org/licenses/LICENSE-2.0
+Tags: legal
 
 # The ASF DMCA Designated Agent
 
diff --git a/content/legal/index.md b/content/legal/index.md
index f86ebb4..d347591 100644
--- a/content/legal/index.md
+++ b/content/legal/index.md
@@ -1,4 +1,5 @@
 Title: ASF Legal & Trademark | Apache Software Foundation
+Tags: legal
 Atom: http://mail-archives.apache.org/mod_mbox/www-legal-discuss/?format=atom
 Notice: http://www.apache.org/licenses/LICENSE-2.0
 
diff --git a/content/legal/ramblings.md b/content/legal/ramblings.md
index aa418e0..125956d 100644
--- a/content/legal/ramblings.md
+++ b/content/legal/ramblings.md
@@ -1,4 +1,5 @@
 Title: Ramblings of an ASF VP of Legal Affairs
+Tags: legal
 Atom: http://mail-archives.apache.org/mod_mbox/www-legal-discuss/?format=atom
 Notice: http://www.apache.org/licenses/LICENSE-2.0
 
diff --git a/content/legal/release-policy.md b/content/legal/release-policy.md
index fe65ed6..0a2b002 100644
--- a/content/legal/release-policy.md
+++ b/content/legal/release-policy.md
@@ -1,4 +1,5 @@
 Title: Release Policy
+Tags: legal
 license: https://www.apache.org/licenses/LICENSE-2.0
 
 # {{title}}
diff --git a/content/legal/resolved.md b/content/legal/resolved.md
index c5c93dd..501c769 100644
--- a/content/legal/resolved.md
+++ b/content/legal/resolved.md
@@ -1,4 +1,5 @@
 Title: ASF 3rd Party License Policy
+Tags: legal,policy
 Atom: http://mail-archives.apache.org/mod_mbox/www-legal-discuss/?format=atom "ASF legal-discuss Mailing List"
 license: https://www.apache.org/licenses/LICENSE-2.0
 
diff --git a/content/legal/src-headers.md b/content/legal/src-headers.md
index 03a88e2..ec36fc4 100644
--- a/content/legal/src-headers.md
+++ b/content/legal/src-headers.md
@@ -1,4 +1,5 @@
 Title: ASF Source Header and Copyright Notice Policy
+Tags: legal,policy
 Atom: http://mail-archives.apache.org/mod_mbox/www-legal-discuss/?format=atom "ASF legal-discuss Mailing List"
 Comment: atom header to get a link like: "<link rel="alternate" title="ASF legal-discuss Mailing List" type="application/atom+xml" href="http://mail-archives.apache.org/mod_mbox/www-legal-discuss/?format=atom" />" in the generated html header (in the body it would be trivial)
 license: https://www.apache.org/licenses/LICENSE-2.0
diff --git a/pelicanconf.yaml b/pelicanconf.yaml
index c015fea..6c7af15 100644
--- a/pelicanconf.yaml
+++ b/pelicanconf.yaml
@@ -13,6 +13,9 @@
   use:
     - gfm
     - asfindex
+    - asfpagetags
+  paths:
+    - theme/plugins
 
 setup:
   data: asfdata.yaml
diff --git a/theme/apache/templates/pagetag.html b/theme/apache/templates/pagetag.html
new file mode 100644
index 0000000..362f758
--- /dev/null
+++ b/theme/apache/templates/pagetag.html
@@ -0,0 +1,10 @@
+<!-- Template runs against a generated page -->
+{% extends "base.html" %}
+{% block content %}
+<h2>Pages tagged with {{ page.title }}</h1>
+<ul>
+    {% for p in page.taggedpages %}
+    <li><a href="{{ p.save_as }}">{{ p.title }}</a></li>
+    {% endfor %}
+</ul>
+{% endblock %}
diff --git a/theme/apache/templates/pagetags.html b/theme/apache/templates/pagetags.html
new file mode 100644
index 0000000..1a118e0
--- /dev/null
+++ b/theme/apache/templates/pagetags.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+{% block content %}
+    <h2>List of page tags</h1>
+    <ul>
+    {% for tag in page.pagetags %}
+        <li><a href="/_{{ tag }}">{{ tag }}</a> ({{ page.pagetags[tag]|count }})</li>
+    {% endfor %}
+    </ul>
+{% endblock %}
+
diff --git a/theme/plugins/asfpagetags.py b/theme/plugins/asfpagetags.py
new file mode 100644
index 0000000..71940cc
--- /dev/null
+++ b/theme/plugins/asfpagetags.py
@@ -0,0 +1,110 @@
+#!/usr/bin/python -B
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# asfpagetags.py -- Pelican plugin to process page tags
+#
+
+import sys
+import functools
+import traceback
+from collections import defaultdict
+
+from pelican import signals
+from pelican.contents import Page
+
+# Wrap funtion to catch exception
+def catch_exception(func):
+
+    @functools.wraps(func)
+    def call_func(self, *args, **kw):
+        try:
+            func(self, *args, **kw)
+        except Exception:
+            print('-----', file=sys.stderr)
+            traceback.print_exc()
+            # exceptions here stop the build
+            raise
+
+    return call_func
+
+@catch_exception
+def page_generator_finalized(page_generator):
+    """ Generate tag pages
+
+    Scan all pages; if a page has tags, update a dict to associate
+    the tag name with the page.
+
+    Then generate a summary page (pagetags.html) listing all the tag names
+    Also generate a page for each tag name listing the pages which have the tag
+
+    """
+    print(">>page_generator_finalized")
+    pagetags = defaultdict(list)
+    baseReader = None
+    for page in page_generator.pages:
+        if hasattr(page, 'tags'):
+            tags = page.tags
+            if isinstance(tags, str): # gfm does not generate list of Tags (yet)
+                if baseReader is None:
+                    from pelican.readers import BaseReader
+                    baseReader = BaseReader(None)
+                tags = baseReader.process_metadata("tags", tags)
+            for tag in tags:
+                tagn=tag.name
+                pagetags[tagn].append(page)
+
+    newPage = Page('', metadata={
+        "title": "List of page tags",
+        "save_as": "pagetags.html",
+        "template": 'pagetags',
+        "pagetags": dict(sorted(pagetags.items(), key=lambda item: item[0])), # item[0] is the key from items()
+        },
+        source_path = "pagetags.html", # needed by asfgenid
+        )
+
+    page_generator.pages.insert(0, newPage)
+
+    for tagn, pages in pagetags.items():
+        addPage(page_generator, tagn, pages)
+
+    print(f"<<page_generator_finalized, found {len(pagetags)} tags in {len(page_generator.pages)} pages")
+
+def addPage(pageGenerator, tagn, pages):
+    # settings = pageGenerator.settings
+
+    # :param content: the string to parse, containing the original content.
+    # :param metadata: the metadata associated to this page (optional).
+    # :param settings: the settings dictionary (optional).
+    # :param source_path: The location of the source of this content (if any).
+    # :param context: The shared context between generators.
+
+    newPage = Page('', metadata={
+        "title": tagn,
+        "save_as": f"_{tagn}.html", # use prefix to reduce chance of clash with normal pages
+        "template": 'pagetag',
+        "taggedpages": sorted(pages, key=lambda item: item.title), # sort by page title
+        },
+        source_path = f"{tagn}.html", # needed by asfgenid
+    )
+
+    pageGenerator.pages.insert(0, newPage)
+
+
+def register():
+    signals.page_generator_finalized.connect(page_generator_finalized)