blob: d1a83e3b5504a5f888c5d4b11074d25490056beb [file]
# 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.
"""
MkDocs hook that downloads the Apache Ossie spec.yaml from GitHub and generates
a virtual definitions page as a markdown definition list.
Uses on_files to inject a virtual File into the build and
on_page_read_source to supply its content — no file is written to disk.
"""
import re
import urllib.request
from datetime import datetime, timezone
from mkdocs.structure.files import File
SPEC_YAML_URL = (
"https://raw.githubusercontent.com/"
"apache/ossie/main/core-spec/spec.yaml"
)
DEFINITIONS_SRC_PATH = "spec/nightly/definitions.md"
SCHEMA_SECTIONS = {
"semantic_model": "Semantic Model",
"datasets": "Datasets",
"relationships": "Relationships",
"fields": "Fields",
"metrics": "Metrics",
}
ENUM_KEYS = {"dialects", "vendors"}
_definitions_markdown = None
def _make_front_matter():
now = datetime.now(timezone.utc)
date_str = now.strftime("%-d %B %Y")
time_str = now.strftime("%H:%M UTC")
return (
"---\n"
"template: content.html\n"
"title: Definitions (Nightly)\n"
"---\n\n"
"# Apache Ossie Schema Definitions — Nightly\n\n"
"Field-level reference generated from\n"
"[`spec.yaml`](https://github.com/apache/ossie/"
f"blob/main/core-spec/spec.yaml) on {date_str} at {time_str}.\n\n"
)
def _download(url):
with urllib.request.urlopen(url, timeout=30) as resp:
return resp.read().decode("utf-8")
def _parse_sections(raw_text):
"""Split multi-document YAML into per-section blocks.
Returns a list of (key, description, field_lines) tuples where
description comes from the comment block immediately above the key.
"""
documents = re.split(r"^---\s*$", raw_text, flags=re.MULTILINE)
sections = []
for doc in documents:
lines = doc.strip().splitlines()
if not lines:
continue
top_key = None
key_index = 0
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith("#") or not stripped:
continue
match = re.match(r"^(\w[\w_]*):", stripped)
if match and match.group(1) in SCHEMA_SECTIONS:
top_key = match.group(1)
key_index = i
break
if not top_key:
continue
desc_parts = []
for line in reversed(lines[:key_index]):
stripped = line.strip()
if stripped.startswith("#"):
text = stripped.lstrip("# ").rstrip()
if text and not text.endswith("Schema"):
desc_parts.insert(0, text)
elif not stripped:
continue
else:
break
description = " ".join(desc_parts) if desc_parts else ""
sections.append((top_key, description, lines[key_index + 1:]))
return sections
def _is_example_line(text):
"""Return True if this comment line looks like a YAML example."""
return bool(re.match(r"^[\[\-]|^\w+:\s*$", text))
def _parse_fields(lines):
"""Extract (field_name, required, description) tuples from comment+key pairs."""
fields = []
comment_block = []
for line in lines:
stripped = line.strip()
if stripped.startswith("#"):
comment_text = stripped.lstrip("# ").rstrip()
if comment_text:
comment_block.append(comment_text)
continue
if not stripped:
continue
match = re.match(r"^-?\s*(\w[\w_]*):", stripped)
if not match:
comment_block = []
continue
field_name = match.group(1)
if field_name in SCHEMA_SECTIONS or field_name in ENUM_KEYS or field_name == "version":
comment_block = []
continue
required = False
desc_parts = []
for c in comment_block:
if c.startswith("Required:"):
required = True
rest = c[len("Required:"):].strip()
if rest:
desc_parts.append(rest)
elif c.startswith("Optional:"):
rest = c[len("Optional:"):].strip()
if rest:
desc_parts.append(rest)
elif c.startswith("Examples:") or c.startswith("See "):
break
elif _is_example_line(c):
continue
else:
desc_parts.append(c)
description = " ".join(p for p in desc_parts if p)
if not description:
inline = re.search(r"#\s*(.+)$", stripped)
if inline:
description = inline.group(1).strip()
fields.append((field_name, required, description))
comment_block = []
return fields
def _render_markdown(sections):
"""Produce the full definitions page as a markdown string."""
parts = [_make_front_matter()]
for key, description, field_lines in sections:
title = SCHEMA_SECTIONS.get(key, key)
parts.append(f"## {title}\n\n")
if description:
parts.append(f"{description}\n\n")
fields = _parse_fields(field_lines)
for field_name, required, desc in fields:
marker = "**Required**" if required else "*Optional*"
suffix = f" — {desc}" if desc else ""
parts.append(f"`{field_name}`\n")
parts.append(f": {marker}{suffix}\n\n")
return "".join(parts)
def on_files(files, config):
"""Inject a virtual File for the definitions page."""
global _definitions_markdown
raw_yaml = _download(SPEC_YAML_URL)
sections = _parse_sections(raw_yaml)
_definitions_markdown = _render_markdown(sections)
definitions_file = File(
path=DEFINITIONS_SRC_PATH,
src_dir=config["docs_dir"],
dest_dir=config["site_dir"],
use_directory_urls=config["use_directory_urls"],
)
files.append(definitions_file)
return files
def on_page_read_source(page, config):
"""Supply generated markdown for the virtual definitions page."""
if page.file.src_path == DEFINITIONS_SRC_PATH:
return _definitions_markdown
return None