reorganization
diff --git a/docs/README.md b/docs/README.md
index 79089e1..1159b56 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,455 +1,11 @@
-# Pelican Builds
+# ASF Pelican Documentation
 
-This website is built using [Pelican][pelican]. Configure the build using the [pelicanconf.py][configure] settings.
+This folder contains documentation for the ASF Pelican build implemented for www.apache.org
 
-## Pelican Theme
+There are several sections.
 
-```python
-# Theme
-THEME = './theme/apache'
-```
-
-See [theme template][theme] for details about this site's theme.
-
-## Plugins
-
-The Pelican environment is enhanced with plugins. Our environment has its own copy of the `asf` plugins, while the `pelican-build.py` script provides `pelican-gfm`.
-
-```python
-# Pelican Plugins
-# pelican-gfm is installed in the buildbot as part of build_pelican.py. It is an ASF Infra custom plugin.
-# other plugins are discoverable and can be installed via pip by mentioning them in requirements.txt
-# You can find plugins here: https://github.com/pelican-plugins
-# Plugins that are custom for this site are found in PLUGIN_PATHS.
-PLUGIN_PATHS = ['./theme/plugins']
-PLUGINS = ['asfgenid', 'asfdata', 'pelican-gfm', 'asfreader']
-```
-
-1. [Data Model][asfdata]. The `asfdata.py` plugin builds a metadata model that is shared with every page.
-2. [GFM Content][pelican-gfm]. The `pelican-gfm` plugin reads **.md**, **.markdown**, **.mkd**, and **.mdown** files and converts the GFM Markdown into HTML.
-3. [EZMD Content][asfreader]. The `asfreader.py` plugin reads **.ezmd** files, injects data, translates ezt, and converts the GFM Markdown into HTML.
-4. [Generate ID][asfgenid]. The `asfgenid.py` plugin performs a number of enhancements to the HTML.
-
-See [process][process] for the steps signaled. See [plugins][plugins] for the Python code.
-
-## Tree Structure
-
-Pages and static content are stored in the same tree. Generated content is output with the same relative path, except with an html extension.
-These are the necessary settings.
-
-```python
-PATH = 'content'
-# Save pages using full directory preservation
-PAGE_PATHS = ['.']
-# Path with no extension
-PATH_METADATA = '(?P<path_no_ext>.*)\..*'
-# We are not slugifying any pages
-ARTICLE_URL = ARTICLE_SAVE_AS = PAGE_URL = PAGE_SAVE_AS = '{path_no_ext}.html'
-# We want to serve our static files mixed with content
-STATIC_PATHS = ['.']
-# we want any html to be served as-is
-READERS = {'html': None}
-# ignore README.md files in the content tree and the interviews and include folders
-IGNORE_FILES = ['README.md','interviews','include']
-```
-
-# Process
-
-Pelican uses [signals][signals] as it goes through the process of reading and generating content.
-Pages are processed in no particular order. Our plugins provide the following activity:
-
-| Pelican Signal | Step | [GFM Content][pelican-gfm] | [EZMD Content][asfreader] | Description |
-|----------------|---------|:-----:|:--:|------|
-| Initialization | [Data Model][asfdata]      |             |              | Read data sources |
-| Reader         | Class     |   [GFMReader][pelican-gfm]   | [ASFReader(GFMReader)][asfreader] | Pelican Reader class  |
-|                | [Read][read]               | read_source | super.read_source | read page source and metadata |
-|                | [Model Metadata][metadata] |             | add_data     | add asf data to the model and expand any `[{ reference }]` |
-|                | [Translate][ezttranslate]  |             | ezt          | ezt template translation |
-|                | [Render GFM][markdown]     | render      | super.render | render GFM/HTML into HTML  |
-| Content        | [Generate ID][asfgenid]    | generate_id | generate_id  | Perform ASF specific HTML enhancements |
-| Generator      | [Template][templates]      | translate   | translate    | Create output HTML by pushing the generated content and metadata through the theme's templates |
-
-## Read Source
-
-The `read_source` method is used to open a file and convert it into a metadata dictionary and text.
-
-Example:
-
-```md
-Title: ASF Export Classifications and Source Links
-license: https://www.apache.org/licenses/LICENSE-2.0
-asf_headings: False
-
-#### ASF Project
-...
-```
-
-The first three lines specify three `metadata` key-value pairs.
-There is a blank line and the rest is the `text`.
-
-Code from `pelican-gfm` with some parts elided.
-
-```python
-    def read_source(self, source_path):
-        "Read metadata and content from the source."
-	...
-	# Fetch the source content, with a few appropriate tweaks
-        with pelican.utils.pelican_open(source_path) as text:
-
-            # Extract the metadata from the header of the text
-            lines = text.splitlines()
-            for i in range(len(lines)):
-                line = lines[i]
-                match = GFMReader.RE_METADATA.match(line)
-                if match:
-                    name = match.group(1).strip().lower()
-		    ...
-                    metadata[name] = value
-                elif not line.strip():
-                    # blank line
-                    continue
-                else:
-                    # reached actual content
-                    break
-	    ...
-            # Reassemble content, minus the metadata
-            text = '\n'.join(lines[i:])
-
-            return text, metadata
-```
-
-## Model Metadata
-
-In `asfreader.py` we extend EZT syntax to do metadata substitution prior to EZT translation. This allows for a more natural and direct representation than with EZT sequences.
-
-### Examples
-
-```md
-|  |  |  |
-|-----------|-----------|-------------|
-| [{ board[0].name }] | [{ board[1].name }] | [{ board[2].name }] |
-| [{ board[3].name }] | [{ board[4].name }] | [{ board[5].name }] |
-| [{ board[6].name }] | [{ board[7].name }] | [{ board[8].name }] |
-```
-
-```md
-| Office    | Individual  |
-|-----------|-------------|
-| Board Chair |  [{ ci[boardchair][roster] }] |
-| Vice Chair |  [{ ci[vicechair][roster] }] |
-| President |  [{ ci[president][roster] }] |
-| Exec. V.P |  [{ ci[execvp][roster] }] |
-| [[]Treasurer](https://treasurer.apache.org/) |  [{ ci[treasurer][roster] }] |
-| Assistant Treasurer |  [{ ci[assistanttreasurer][roster] }] |
-| Secretary |  [{ ci[secretary][roster] }] |
-| Assistant Secretary |  [{ ci[assistantsecretary][roster] }] |
-| V.P., [[]Legal Affairs](/legal/) |  [{ ci[legal][chair] }] |
-| Assistant V.P., [[]Legal Affairs](/legal/) |  [{ ci[assistantvplegalaffairs][roster] }] |
-```
-
-```md
-- All volunteer community
-- [{ code_lines }]+ lines of code in&nbsp;stewardship
-- [{ code_changed }]+ lines of code&nbsp;changed
-- [{ code_commits }]+ code commits
-- [{ asf_members }] individual ASF&nbsp;Members
-- [{ asf_committers }]+ Apache Committers
-- [{ asf_contributors }]+ code contributors
-- [{ asf_people }]+ people involved in our&nbsp;communities
-```
-
-### EZMD Reader
-
-The `asfreader.py` plugin is responsible for [reading the source][read], adding metadata, [ezt translation][ezttranslate], and [rendering GFM][markdown]
-
-```python
-    def add_data(self, text, metadata):
-        "Mix in ASF data as metadata"
-
-        asf_metadata = self.settings.get('ASF_DATA', { }).get('metadata')
-        if asf_metadata:
-            metadata.update(asf_metadata)
-            # insert any direct references
-            m = 1
-            while m:
-                m = METADATA_RE.search(text)
-                if m:
-                    this_data = m.group(1).strip()
-                    format_string = '{{{0}}}'.format(this_data)
-                    try:
-                        new_string = format_string.format(**metadata)
-                        print(f'{{{{{m.group(1)}}}}} -> {new_string}')
-                    except Exception:
-                        # the data expression was not found
-                        new_string = format_string
-                        print(f'{{{{{m.group(1)}}}}} is not found')
-                    text = re.sub(METADATA_RE, new_string, text, count=1)
-        return text, metadata
-```
-
-## EZT Translation
-
-**ezmd* Pages files are [ezt][ezt] templates that create Markdown and HTML output. See [EZT Syntax][eztsyntax] for the directives.
-
-### EZT Examples
-
-Project list:
-
-```md
-| Office    | Individual  |
-|-----------|-------------|[for projects]
-| V.P., [if-any projects.site][[][end]Apache [projects.display_name][if-any projects.site]]([projects.site])[end] | [projects.chair] |[end]
-```
-
-Featured projects:
-
-```html
-[for featured_projs]<li [if-index featured_projs first]class="active"[end]>
-     <a href="#[featured_projs.key_id]" data-toggle="tab">[featured_projs.display_name]</a>
-</li>[end]
-```
-
-Insert a file as is into the output:
-
-```md
-Title: Apache Download Mirrors
-
-[insertfile "include/closer.ezt"]
-```
-
-### EZT Code
-
-Code from `asfreader.py`
-
-```python
-            # prepare text as an ezt template
-            # compress_whitespace=0 is required as blank lines and indentation have meaning in markdown
-            template = ezt.Template(compress_whitespace=0)
-            reader = ASFTemplateReader(source_path, text)
-            template.parse(reader, base_format=ezt.FORMAT_HTML)
-            assert template
-            # generate content from ezt template with metadata
-            fp = io.StringIO()
-            template.generate(fp, metadata)
-```
-
-## Render GFM
-
-Content is in [GitHub Flavored Markdown][mastering] (GFM).
-
-The site uses a version of [cmark-gfm][gfm] by [GitHub][gfmspec] through a Pelican Plugin *gfm.py* created by Apache Infra.
-
-- [Mastering Markdown][mastering]
-
-- [Detailed Specification][4]
-
-- Some differences from `markdown.pl` used in the Apache CMS.
-
-  - [HTML Blocks][5]
-    - Make sure the first line of your html block starts in column one.
-    - A blank line terminates an html block
-      - [Exception][6] to this rule for `style`, `pre`, and `script`.
-    - [Markdown content within an HTML block][7]
-
-  - [Autolinks][8]
-    - [www][9]
-    - [url][10]
-    - [email][11]
-
-  - [Disallowed html][12] the tagfilter extension disables certain html. The asfgenid plugin reenables `script`, `style`, and `iframe` html.
-    
-- [Examples][13]
-
-### Pelican GFM
-
-The main purpose for the `pelican-gfm` is to [read][read] the content file and render to HTML.
-
-From `asfreader.py`:
-
-```python
-            # Render the markdown into HTML
-            content = super().render(fp.getvalue().encode('utf-8')).decode('utf-8')
-            assert content
-```
-
-From `pelican-gfm`:
-
-```python
-    def render(self, text):
-      "Use cmark-gfm to render the Markdown into an HTML fragment."
-
-      parser = F_cmark_parser_new(OPTS)
-      assert parser
-      for name in EXTENSIONS:
-        ext = F_cmark_find_syntax_extension(name.encode('utf-8'))
-        assert ext
-        rv = F_cmark_parser_attach_syntax_extension(parser, ext)
-        assert rv
-      exts = F_cmark_parser_get_syntax_extensions(parser)
-      F_cmark_parser_feed(parser, text, len(text))
-      doc = F_cmark_parser_finish(parser)
-      assert doc
-
-      output = F_cmark_render_html(doc, OPTS, exts)
-
-      F_cmark_parser_free(parser)
-      F_cmark_node_free(doc)
-
-      return output
-```
-
-## Generate ID
-
-We use the `asfgenid` plugin to perform modifications on the generated content that mimics the markdown extensions in the Apache CMS.
-Many of these ASF-specific enhancements are controlled in [pelican settings][configure] in the `ASF_GENID` dictionary.
-
-| step | ASF_GENID key | default | process | page override |
-|-----:|-----|:--------:|---------|----------|
-| 1    |  -          | -           | fix up some HTML tags that the GFM autofilter extension marks as unsafe | |
-| 2    |  -          | -           | convert HTML into beautiful soup    | |
-| 3    | metadata    | True        | `{{ metadata }}` include data in the HTML | |
-| 4    |  -          | True        | inventory of all ID attributes; duplicates are invalid | |
-| 5    | elements    | True        | find all `{#id}` and `{.class}` texts and assign attributes | |
-| 6    | headings    | True        | assign IDs to all headings w/o IDs already present or assigned with `{#id}` text | asf_headings |
-|      | headings_re | `r'^h[1-6]'` | regex for finding headings that require IDs | |
-| 7    | tables      | True        | tables with a class attribute are assgned `class=table` | |
-| 8    | toc         | True        | generate a table of contents if [TOC] is found. If this is set to False then the `toc.py` plugin may used. | |
-|      | toc_headers | `r'h[1-6]'` | headings to include in the [TOC] | |
-| 9    |  -          | -           | convert beautiful soup back into HTML | |
-
-### Element examples
-
-Set the heading id and permalink to `#what`
-
-```md
-## What is the Apache Software Foundation?  {#what}
-
-The Apache Software Foundation (ASF) is a non-profit 501(c)(3) corporation,
-incorporated in Delaware, USA, in June of 1999. The ASF is a natural
-outgrowth of The Apache Group, which
-formed in 1995 to develop the Apache HTTP Server.
-```
-
-Set the class to display an image to `float-right`
-
-```md
-![Logo](images/logo.svg) {.float-right}
-```
-
-An HTML fragment is also feasible for a similar purpose
-
-```html
-<div class=".pull-right" style="float:right; border-style:dotted; width:200px; padding:5px; margin:5px">
-
-SEE INSTEAD: [Trademark Resources Site Map][resources].
-
-</div>
-```
-
-### Heading code
-
-Code from `asfgenid.py` uses [BeautifulSoup 4][bs4] to manipulate the rendered HTML. Here is an example
-
-```python
-# from Apache CMS markdown/extensions/headerid.py - slugify in the same way as the Apache CMS
-def slugify(value, separator):
-    """ Slugify a string, to make it URL friendly. """
-    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
-    value = re.sub('[^\\w\\s-]', '', value.decode('ascii')).strip().lower()
-    return re.sub('[%s\\s]+' % separator, separator, value)
-
-...
-
-# append a permalink
-def permalink(soup, mod_element):
-    new_tag = soup.new_tag('a', href='#' + mod_element['id'])
-    new_tag['class'] = 'headerlink'
-    new_tag['title'] = 'Permalink'
-    new_tag.string = LINK_CHAR
-    mod_element.append(new_tag)
-
-...
-
-# generate id for a heading
-def headingid_transform(ids, soup, tag, permalinks, perma_set):
-    new_string = tag.string
-    if not new_string:
-        # roll up strings if no immediate string
-        new_string = tag.find_all(
-            text=lambda t: not isinstance(t, Comment),
-            recursive=True)
-        new_string = ''.join(new_string)
-
-    # don't have an id create it from text
-    new_id = slugify(new_string, '-')
-    tag['id'] = unique(new_id, ids)
-    if permalinks:
-        permalink(soup, tag)
-        # inform if there is a duplicate permalink
-        unique(tag['id'], perma_set)
-
-...
-
-    # step 6 - find all headings w/o ids already present or assigned with {#id} text
-    if asf_headings == 'True':
-        if asf_genid['debug']:
-            print(f'headings: {content.relative_source_path}')
-        # Find heading tags
-        HEADING_RE = re.compile(asf_genid['headings_re'])
-        for tag in soup.findAll(HEADING_RE, id=False):
-            headingid_transform(ids, soup, tag, asf_genid['permalinks'], permalinks)
-```
-
-## Data
-
-- [Preview Branching](branches.md)
-   How to work on complex changes like updated templates, new data sources, and alternative content.
-
-- Local Builds
-   How to set up your local environment to work on a branch.
-
-- Error Analysis
-   How to find errors in your build.
-
-
-[pelican]:   	https://blog.getpelican.com
-[mastering]:	https://guides.github.com/features/mastering-markdown/
-[gfm]:		https://github.com/github/cmark-gfm
-[gfmspec]:	https://github.blog/2017-03-14-a-formal-spec-for-github-markdown/
-[4]: 		https://github.github.com/gfm/
-[5]: 		https://github.github.com/gfm/#html-block
-[6]: 		https://github.github.com/gfm/#example-139
-[7]: 		https://github.github.com/gfm/#example-122
-[8]: 		https://github.github.com/gfm/#autolink
-[9]: 		https://github.github.com/gfm/#extended-www-autolink
-[10]: 		https://github.github.com/gfm/#extended-url-autolink
-[11]: 		https://github.github.com/gfm/#extended-email-autolink
-[12]: 		https://github.github.com/gfm/#disallowed-raw-html-extension-
-[13]: 		https://sindresorhus.com/github-markdown-css/
-[ezt]:		https://github.com/gstein/ezt
-[eztsyntax]:	https://github.com/gstein/ezt/blob/wiki/Syntax.md
-[structure]:	https://docs.getpelican.com/en/latest/themes.html#structure
-[variables]:	https://docs.getpelican.com/en/latest/themes.html#templates-and-variables
-[pagemodel]:	https://docs.getpelican.com/en/latest/themes.html#page
-[settings]:	https://docs.getpelican.com/en/latest/settings.html#
-[signals]:	https://docs.getpelican.com/en/latest/plugins.html#list-of-signals
-[bs4]:		https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.html?highlight=javascript#
-[pelicanasf]:	https://github.com/apache/infrastructure-p6/tree/production/modules/pelican_asf/files
-[asfdata]:	#data-model
-[asfreader]:	#ezmd-reader
-[asfgenid]:	#generate-id
-[theme]:     	../theme/apache/templates/.
-[plugins]:     	../theme/plugins/.
-[configure]: 	../pelicanconf.py
-[datamodel]:	../asfdata.yaml
-[read]:         #read-source
-[metadata]:	#model-metadata
-[markdown]:  	#render-gfm
-[data]:		#data
-[ezttranslate]: #ezt-translation
-[process]:   	#process
-[branches]:  	#branches
-[local]:     	#local
-[asfref]:	#reference
-[templates]:	#templates
-[pelican-gfm]:	#pelican-gfm
+1. [Process](process) describes the full end to build from the developers perspecitve.
+2. [Markdown](markdown) focuses on what users need to know about GitHub Flavored Markdown (GFM) in order to main content.
+3. [Data modeling](data) focuses on what developers need to know in order to maintain and expand on the metadata made available to the site.
+4. [Feature branches](branches) focuses on handling major changes.
+5. [Local builds](builds) focuses on how to develop on a local linux or macOS system.
diff --git a/docs/process.md b/docs/process.md
new file mode 100644
index 0000000..79089e1
--- /dev/null
+++ b/docs/process.md
@@ -0,0 +1,455 @@
+# Pelican Builds
+
+This website is built using [Pelican][pelican]. Configure the build using the [pelicanconf.py][configure] settings.
+
+## Pelican Theme
+
+```python
+# Theme
+THEME = './theme/apache'
+```
+
+See [theme template][theme] for details about this site's theme.
+
+## Plugins
+
+The Pelican environment is enhanced with plugins. Our environment has its own copy of the `asf` plugins, while the `pelican-build.py` script provides `pelican-gfm`.
+
+```python
+# Pelican Plugins
+# pelican-gfm is installed in the buildbot as part of build_pelican.py. It is an ASF Infra custom plugin.
+# other plugins are discoverable and can be installed via pip by mentioning them in requirements.txt
+# You can find plugins here: https://github.com/pelican-plugins
+# Plugins that are custom for this site are found in PLUGIN_PATHS.
+PLUGIN_PATHS = ['./theme/plugins']
+PLUGINS = ['asfgenid', 'asfdata', 'pelican-gfm', 'asfreader']
+```
+
+1. [Data Model][asfdata]. The `asfdata.py` plugin builds a metadata model that is shared with every page.
+2. [GFM Content][pelican-gfm]. The `pelican-gfm` plugin reads **.md**, **.markdown**, **.mkd**, and **.mdown** files and converts the GFM Markdown into HTML.
+3. [EZMD Content][asfreader]. The `asfreader.py` plugin reads **.ezmd** files, injects data, translates ezt, and converts the GFM Markdown into HTML.
+4. [Generate ID][asfgenid]. The `asfgenid.py` plugin performs a number of enhancements to the HTML.
+
+See [process][process] for the steps signaled. See [plugins][plugins] for the Python code.
+
+## Tree Structure
+
+Pages and static content are stored in the same tree. Generated content is output with the same relative path, except with an html extension.
+These are the necessary settings.
+
+```python
+PATH = 'content'
+# Save pages using full directory preservation
+PAGE_PATHS = ['.']
+# Path with no extension
+PATH_METADATA = '(?P<path_no_ext>.*)\..*'
+# We are not slugifying any pages
+ARTICLE_URL = ARTICLE_SAVE_AS = PAGE_URL = PAGE_SAVE_AS = '{path_no_ext}.html'
+# We want to serve our static files mixed with content
+STATIC_PATHS = ['.']
+# we want any html to be served as-is
+READERS = {'html': None}
+# ignore README.md files in the content tree and the interviews and include folders
+IGNORE_FILES = ['README.md','interviews','include']
+```
+
+# Process
+
+Pelican uses [signals][signals] as it goes through the process of reading and generating content.
+Pages are processed in no particular order. Our plugins provide the following activity:
+
+| Pelican Signal | Step | [GFM Content][pelican-gfm] | [EZMD Content][asfreader] | Description |
+|----------------|---------|:-----:|:--:|------|
+| Initialization | [Data Model][asfdata]      |             |              | Read data sources |
+| Reader         | Class     |   [GFMReader][pelican-gfm]   | [ASFReader(GFMReader)][asfreader] | Pelican Reader class  |
+|                | [Read][read]               | read_source | super.read_source | read page source and metadata |
+|                | [Model Metadata][metadata] |             | add_data     | add asf data to the model and expand any `[{ reference }]` |
+|                | [Translate][ezttranslate]  |             | ezt          | ezt template translation |
+|                | [Render GFM][markdown]     | render      | super.render | render GFM/HTML into HTML  |
+| Content        | [Generate ID][asfgenid]    | generate_id | generate_id  | Perform ASF specific HTML enhancements |
+| Generator      | [Template][templates]      | translate   | translate    | Create output HTML by pushing the generated content and metadata through the theme's templates |
+
+## Read Source
+
+The `read_source` method is used to open a file and convert it into a metadata dictionary and text.
+
+Example:
+
+```md
+Title: ASF Export Classifications and Source Links
+license: https://www.apache.org/licenses/LICENSE-2.0
+asf_headings: False
+
+#### ASF Project
+...
+```
+
+The first three lines specify three `metadata` key-value pairs.
+There is a blank line and the rest is the `text`.
+
+Code from `pelican-gfm` with some parts elided.
+
+```python
+    def read_source(self, source_path):
+        "Read metadata and content from the source."
+	...
+	# Fetch the source content, with a few appropriate tweaks
+        with pelican.utils.pelican_open(source_path) as text:
+
+            # Extract the metadata from the header of the text
+            lines = text.splitlines()
+            for i in range(len(lines)):
+                line = lines[i]
+                match = GFMReader.RE_METADATA.match(line)
+                if match:
+                    name = match.group(1).strip().lower()
+		    ...
+                    metadata[name] = value
+                elif not line.strip():
+                    # blank line
+                    continue
+                else:
+                    # reached actual content
+                    break
+	    ...
+            # Reassemble content, minus the metadata
+            text = '\n'.join(lines[i:])
+
+            return text, metadata
+```
+
+## Model Metadata
+
+In `asfreader.py` we extend EZT syntax to do metadata substitution prior to EZT translation. This allows for a more natural and direct representation than with EZT sequences.
+
+### Examples
+
+```md
+|  |  |  |
+|-----------|-----------|-------------|
+| [{ board[0].name }] | [{ board[1].name }] | [{ board[2].name }] |
+| [{ board[3].name }] | [{ board[4].name }] | [{ board[5].name }] |
+| [{ board[6].name }] | [{ board[7].name }] | [{ board[8].name }] |
+```
+
+```md
+| Office    | Individual  |
+|-----------|-------------|
+| Board Chair |  [{ ci[boardchair][roster] }] |
+| Vice Chair |  [{ ci[vicechair][roster] }] |
+| President |  [{ ci[president][roster] }] |
+| Exec. V.P |  [{ ci[execvp][roster] }] |
+| [[]Treasurer](https://treasurer.apache.org/) |  [{ ci[treasurer][roster] }] |
+| Assistant Treasurer |  [{ ci[assistanttreasurer][roster] }] |
+| Secretary |  [{ ci[secretary][roster] }] |
+| Assistant Secretary |  [{ ci[assistantsecretary][roster] }] |
+| V.P., [[]Legal Affairs](/legal/) |  [{ ci[legal][chair] }] |
+| Assistant V.P., [[]Legal Affairs](/legal/) |  [{ ci[assistantvplegalaffairs][roster] }] |
+```
+
+```md
+- All volunteer community
+- [{ code_lines }]+ lines of code in&nbsp;stewardship
+- [{ code_changed }]+ lines of code&nbsp;changed
+- [{ code_commits }]+ code commits
+- [{ asf_members }] individual ASF&nbsp;Members
+- [{ asf_committers }]+ Apache Committers
+- [{ asf_contributors }]+ code contributors
+- [{ asf_people }]+ people involved in our&nbsp;communities
+```
+
+### EZMD Reader
+
+The `asfreader.py` plugin is responsible for [reading the source][read], adding metadata, [ezt translation][ezttranslate], and [rendering GFM][markdown]
+
+```python
+    def add_data(self, text, metadata):
+        "Mix in ASF data as metadata"
+
+        asf_metadata = self.settings.get('ASF_DATA', { }).get('metadata')
+        if asf_metadata:
+            metadata.update(asf_metadata)
+            # insert any direct references
+            m = 1
+            while m:
+                m = METADATA_RE.search(text)
+                if m:
+                    this_data = m.group(1).strip()
+                    format_string = '{{{0}}}'.format(this_data)
+                    try:
+                        new_string = format_string.format(**metadata)
+                        print(f'{{{{{m.group(1)}}}}} -> {new_string}')
+                    except Exception:
+                        # the data expression was not found
+                        new_string = format_string
+                        print(f'{{{{{m.group(1)}}}}} is not found')
+                    text = re.sub(METADATA_RE, new_string, text, count=1)
+        return text, metadata
+```
+
+## EZT Translation
+
+**ezmd* Pages files are [ezt][ezt] templates that create Markdown and HTML output. See [EZT Syntax][eztsyntax] for the directives.
+
+### EZT Examples
+
+Project list:
+
+```md
+| Office    | Individual  |
+|-----------|-------------|[for projects]
+| V.P., [if-any projects.site][[][end]Apache [projects.display_name][if-any projects.site]]([projects.site])[end] | [projects.chair] |[end]
+```
+
+Featured projects:
+
+```html
+[for featured_projs]<li [if-index featured_projs first]class="active"[end]>
+     <a href="#[featured_projs.key_id]" data-toggle="tab">[featured_projs.display_name]</a>
+</li>[end]
+```
+
+Insert a file as is into the output:
+
+```md
+Title: Apache Download Mirrors
+
+[insertfile "include/closer.ezt"]
+```
+
+### EZT Code
+
+Code from `asfreader.py`
+
+```python
+            # prepare text as an ezt template
+            # compress_whitespace=0 is required as blank lines and indentation have meaning in markdown
+            template = ezt.Template(compress_whitespace=0)
+            reader = ASFTemplateReader(source_path, text)
+            template.parse(reader, base_format=ezt.FORMAT_HTML)
+            assert template
+            # generate content from ezt template with metadata
+            fp = io.StringIO()
+            template.generate(fp, metadata)
+```
+
+## Render GFM
+
+Content is in [GitHub Flavored Markdown][mastering] (GFM).
+
+The site uses a version of [cmark-gfm][gfm] by [GitHub][gfmspec] through a Pelican Plugin *gfm.py* created by Apache Infra.
+
+- [Mastering Markdown][mastering]
+
+- [Detailed Specification][4]
+
+- Some differences from `markdown.pl` used in the Apache CMS.
+
+  - [HTML Blocks][5]
+    - Make sure the first line of your html block starts in column one.
+    - A blank line terminates an html block
+      - [Exception][6] to this rule for `style`, `pre`, and `script`.
+    - [Markdown content within an HTML block][7]
+
+  - [Autolinks][8]
+    - [www][9]
+    - [url][10]
+    - [email][11]
+
+  - [Disallowed html][12] the tagfilter extension disables certain html. The asfgenid plugin reenables `script`, `style`, and `iframe` html.
+    
+- [Examples][13]
+
+### Pelican GFM
+
+The main purpose for the `pelican-gfm` is to [read][read] the content file and render to HTML.
+
+From `asfreader.py`:
+
+```python
+            # Render the markdown into HTML
+            content = super().render(fp.getvalue().encode('utf-8')).decode('utf-8')
+            assert content
+```
+
+From `pelican-gfm`:
+
+```python
+    def render(self, text):
+      "Use cmark-gfm to render the Markdown into an HTML fragment."
+
+      parser = F_cmark_parser_new(OPTS)
+      assert parser
+      for name in EXTENSIONS:
+        ext = F_cmark_find_syntax_extension(name.encode('utf-8'))
+        assert ext
+        rv = F_cmark_parser_attach_syntax_extension(parser, ext)
+        assert rv
+      exts = F_cmark_parser_get_syntax_extensions(parser)
+      F_cmark_parser_feed(parser, text, len(text))
+      doc = F_cmark_parser_finish(parser)
+      assert doc
+
+      output = F_cmark_render_html(doc, OPTS, exts)
+
+      F_cmark_parser_free(parser)
+      F_cmark_node_free(doc)
+
+      return output
+```
+
+## Generate ID
+
+We use the `asfgenid` plugin to perform modifications on the generated content that mimics the markdown extensions in the Apache CMS.
+Many of these ASF-specific enhancements are controlled in [pelican settings][configure] in the `ASF_GENID` dictionary.
+
+| step | ASF_GENID key | default | process | page override |
+|-----:|-----|:--------:|---------|----------|
+| 1    |  -          | -           | fix up some HTML tags that the GFM autofilter extension marks as unsafe | |
+| 2    |  -          | -           | convert HTML into beautiful soup    | |
+| 3    | metadata    | True        | `{{ metadata }}` include data in the HTML | |
+| 4    |  -          | True        | inventory of all ID attributes; duplicates are invalid | |
+| 5    | elements    | True        | find all `{#id}` and `{.class}` texts and assign attributes | |
+| 6    | headings    | True        | assign IDs to all headings w/o IDs already present or assigned with `{#id}` text | asf_headings |
+|      | headings_re | `r'^h[1-6]'` | regex for finding headings that require IDs | |
+| 7    | tables      | True        | tables with a class attribute are assgned `class=table` | |
+| 8    | toc         | True        | generate a table of contents if [TOC] is found. If this is set to False then the `toc.py` plugin may used. | |
+|      | toc_headers | `r'h[1-6]'` | headings to include in the [TOC] | |
+| 9    |  -          | -           | convert beautiful soup back into HTML | |
+
+### Element examples
+
+Set the heading id and permalink to `#what`
+
+```md
+## What is the Apache Software Foundation?  {#what}
+
+The Apache Software Foundation (ASF) is a non-profit 501(c)(3) corporation,
+incorporated in Delaware, USA, in June of 1999. The ASF is a natural
+outgrowth of The Apache Group, which
+formed in 1995 to develop the Apache HTTP Server.
+```
+
+Set the class to display an image to `float-right`
+
+```md
+![Logo](images/logo.svg) {.float-right}
+```
+
+An HTML fragment is also feasible for a similar purpose
+
+```html
+<div class=".pull-right" style="float:right; border-style:dotted; width:200px; padding:5px; margin:5px">
+
+SEE INSTEAD: [Trademark Resources Site Map][resources].
+
+</div>
+```
+
+### Heading code
+
+Code from `asfgenid.py` uses [BeautifulSoup 4][bs4] to manipulate the rendered HTML. Here is an example
+
+```python
+# from Apache CMS markdown/extensions/headerid.py - slugify in the same way as the Apache CMS
+def slugify(value, separator):
+    """ Slugify a string, to make it URL friendly. """
+    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
+    value = re.sub('[^\\w\\s-]', '', value.decode('ascii')).strip().lower()
+    return re.sub('[%s\\s]+' % separator, separator, value)
+
+...
+
+# append a permalink
+def permalink(soup, mod_element):
+    new_tag = soup.new_tag('a', href='#' + mod_element['id'])
+    new_tag['class'] = 'headerlink'
+    new_tag['title'] = 'Permalink'
+    new_tag.string = LINK_CHAR
+    mod_element.append(new_tag)
+
+...
+
+# generate id for a heading
+def headingid_transform(ids, soup, tag, permalinks, perma_set):
+    new_string = tag.string
+    if not new_string:
+        # roll up strings if no immediate string
+        new_string = tag.find_all(
+            text=lambda t: not isinstance(t, Comment),
+            recursive=True)
+        new_string = ''.join(new_string)
+
+    # don't have an id create it from text
+    new_id = slugify(new_string, '-')
+    tag['id'] = unique(new_id, ids)
+    if permalinks:
+        permalink(soup, tag)
+        # inform if there is a duplicate permalink
+        unique(tag['id'], perma_set)
+
+...
+
+    # step 6 - find all headings w/o ids already present or assigned with {#id} text
+    if asf_headings == 'True':
+        if asf_genid['debug']:
+            print(f'headings: {content.relative_source_path}')
+        # Find heading tags
+        HEADING_RE = re.compile(asf_genid['headings_re'])
+        for tag in soup.findAll(HEADING_RE, id=False):
+            headingid_transform(ids, soup, tag, asf_genid['permalinks'], permalinks)
+```
+
+## Data
+
+- [Preview Branching](branches.md)
+   How to work on complex changes like updated templates, new data sources, and alternative content.
+
+- Local Builds
+   How to set up your local environment to work on a branch.
+
+- Error Analysis
+   How to find errors in your build.
+
+
+[pelican]:   	https://blog.getpelican.com
+[mastering]:	https://guides.github.com/features/mastering-markdown/
+[gfm]:		https://github.com/github/cmark-gfm
+[gfmspec]:	https://github.blog/2017-03-14-a-formal-spec-for-github-markdown/
+[4]: 		https://github.github.com/gfm/
+[5]: 		https://github.github.com/gfm/#html-block
+[6]: 		https://github.github.com/gfm/#example-139
+[7]: 		https://github.github.com/gfm/#example-122
+[8]: 		https://github.github.com/gfm/#autolink
+[9]: 		https://github.github.com/gfm/#extended-www-autolink
+[10]: 		https://github.github.com/gfm/#extended-url-autolink
+[11]: 		https://github.github.com/gfm/#extended-email-autolink
+[12]: 		https://github.github.com/gfm/#disallowed-raw-html-extension-
+[13]: 		https://sindresorhus.com/github-markdown-css/
+[ezt]:		https://github.com/gstein/ezt
+[eztsyntax]:	https://github.com/gstein/ezt/blob/wiki/Syntax.md
+[structure]:	https://docs.getpelican.com/en/latest/themes.html#structure
+[variables]:	https://docs.getpelican.com/en/latest/themes.html#templates-and-variables
+[pagemodel]:	https://docs.getpelican.com/en/latest/themes.html#page
+[settings]:	https://docs.getpelican.com/en/latest/settings.html#
+[signals]:	https://docs.getpelican.com/en/latest/plugins.html#list-of-signals
+[bs4]:		https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.html?highlight=javascript#
+[pelicanasf]:	https://github.com/apache/infrastructure-p6/tree/production/modules/pelican_asf/files
+[asfdata]:	#data-model
+[asfreader]:	#ezmd-reader
+[asfgenid]:	#generate-id
+[theme]:     	../theme/apache/templates/.
+[plugins]:     	../theme/plugins/.
+[configure]: 	../pelicanconf.py
+[datamodel]:	../asfdata.yaml
+[read]:         #read-source
+[metadata]:	#model-metadata
+[markdown]:  	#render-gfm
+[data]:		#data
+[ezttranslate]: #ezt-translation
+[process]:   	#process
+[branches]:  	#branches
+[local]:     	#local
+[asfref]:	#reference
+[templates]:	#templates
+[pelican-gfm]:	#pelican-gfm