From 062aa7aa433f19667abbc94a4ebbd6a950d8dea3 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 5 Sep 2022 14:49:56 -0400 Subject: [PATCH] docs: add code for generating documentation from all old versions of dokku This generates the appropriate versions for our usage, in addition to ensuring the markdown is of a format that mkdocs understands. --- contrib/build-docs | 94 ++++++++++++++ contrib/write-mkdocs | 287 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100755 contrib/build-docs create mode 100644 contrib/write-mkdocs diff --git a/contrib/build-docs b/contrib/build-docs new file mode 100755 index 000000000..d26e8beb9 --- /dev/null +++ b/contrib/build-docs @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -eo pipefail + +readonly ROOT_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" + +main() { + pushd "$ROOT_DIR" >/dev/null || return 1 + rm -rf docs-main tmp/docs-source tmp/docs-build/docs # tmp/docs-build + mkdir -p tmp/docs-build + cp -r docs docs-main + + git clone https://github.com/dokku/dokku.git tmp/docs-source + + rm -rf docs/assets + mkdir -p docs/assets + cp -r docs-main/assets/favicons docs/assets/favicons + cp -r docs-main/assets/dokku-logo.svg docs/assets/dokku-logo.svg + + rm -rf tmp/docs-build/assets + cp -r docs-main/assets tmp/docs-build/assets + + make docs-build + mv site tmp/docs-build/docs + mv docs/home.html tmp/docs-build/index.html + sed -i.bak "s/{{NAME}}~{{REF}}/docs/g" tmp/docs-build/index.html && rm tmp/docs-build/index.html.bak + sed -i.bak "s/{{NAME}}/docs/g" tmp/docs-build/index.html && rm tmp/docs-build/index.html.bak + + jq -C -r '.["max-versions"][]' docs-main/assets/versions.json | while read -r current_version; do + major="$(echo "$current_version" | cut -d'.' -f1)" + minor="$(echo "$current_version" | cut -d'.' -f2)" + patch="$(echo "$current_version" | cut -d'.' -f3)" + while true; do + if [[ "$patch" -lt 0 ]]; then + break + fi + version="${major}.${minor}.${patch}" + echo "version: ${major}.${minor}.${patch}" + + if [[ "$major" -lt 1 ]]; then + if [[ "$current_version" != "$version" ]] && [[ "$minor" -lt 28 ]]; then + rm -rf "tmp/docs-build/docs~v${version}" + patch=$((patch - 1)) + continue + fi + fi + + if [[ -d "tmp/docs-build/docs~v${version}" ]]; then + patch=$((patch - 1)) + continue + fi + + git -C tmp/docs-source checkout -- . + git -C tmp/docs-source checkout "tags/v${version}" -b "v${version}-branch" + rm -rf docs + mv tmp/docs-source/docs docs + + rm -rf docs/assets + mkdir -p docs/assets + cp -r docs-main/assets/favicons docs/assets/favicons + cp -r docs-main/assets/dokku-logo.svg docs/assets/dokku-logo.svg + + cp -r docs-main/_build docs/_build + cp -r docs-main/_overrides docs/_overrides + git checkout -- mkdocs.yml + sed -i.bak "s/site_dir: site/site_dir: docs~v$version/g" mkdocs.yml && rm mkdocs.yml.bak + if [[ ! -f docs/template.html ]]; then + if [[ "$patch" -eq 0 ]]; then + break + fi + + continue + fi + + if ! make docs-build; then + continue + fi + + if [[ -d "docs~v${version}" ]]; then + mv "docs~v${version}" "tmp/docs-build/docs~v${version}" + fi + + if [[ "$patch" -eq 0 ]]; then + break + fi + patch=$((patch - 1)) + done + done + + rm -rf docs + mv docs-main docs + git checkout -- mkdocs.yml docs +} + +main "$@" diff --git a/contrib/write-mkdocs b/contrib/write-mkdocs new file mode 100644 index 000000000..dd2458371 --- /dev/null +++ b/contrib/write-mkdocs @@ -0,0 +1,287 @@ +#!/usr/bin/env python +from bs4 import BeautifulSoup +import os +import pathlib +import shutil +import sys +import yaml + + +def get_nav_from_selector(soup, selector, docs_dir): + navigation = [] + header_name = "" + for anchor in soup.select(selector): + classes = anchor.get("class", []) + if classes is None: + continue + + if "disabled" in classes: + header_name = anchor.text + navigation.append({header_name: []}) + + href = anchor.get("href", "") + if href is None: + continue + + href = str(href) + if href == "#": + continue + + url = href.replace("{{NAME}}", "") + filename = url.replace("http://progrium.viewdocs.io/dokku/", "") + filename = filename.replace("http://dokku.viewdocs.io/dokku/", "") + filename = filename.strip("/") + ".md" + filename = filename.removeprefix("dokku/") + if not os.path.exists(docs_dir + "/" + filename): + print("error fetching markdown file:", filename) + continue + + for nav in navigation: + if header_name in nav: + nav[header_name].append(filename) + return navigation + + +def generate_nav(src, dest): + navigation = [] + repo_dir = pathlib.Path(__file__).parent.parent.resolve() + docs_dir = str(repo_dir) + "/docs" + with open(docs_dir + "/template.html") as response: + soup = BeautifulSoup(response, "html.parser") + selectors = [ + ".container .row .list-group a", + ".container-fluid .row .list-group a", + ] + for selector in selectors: + navigation = get_nav_from_selector(soup, selector, docs_dir) + if len(navigation) > 0: + break + + if len(navigation) == 0: + print("No navigation found") + sys.exit(1) + print(navigation) + + with open(src) as f: + data = yaml.unsafe_load(f) + data["nav"] = [ + {"Docs": navigation}, + {"Pro": "https://pro.dokku.com/docs/getting-started/"}, + {"Blog": "https://pro.dokku.com/blog/"}, + {"Purchase Dokku Pro": "https://dokku.dpdcart.com/cart/add?product_id=217344&method_id=236878"}, + ] + + with open(dest, mode="wt", encoding="utf-8") as f: + yaml.dump(data, f) + + +def modify_content_noop(lines): + modified = False + updated_lines = [] + for line in lines: + updated_lines.append(line) + return updated_lines, modified + + +def modify_content_stripspace(lines): + modified = False + updated_lines = [] + for line in lines: + line = line.rstrip() + updated_lines.append(line) + return updated_lines, modified + + +def modify_content_inject_newlines(lines): + modified = False + updated_lines = [] + for line in lines: + updated_lines.append(line + "\n") + return updated_lines, modified + + +def is_info(line): + return line.startswith("> ") + + +def is_new(line): + return line.startswith("> ") and "new as of" in line.lower() + + +def is_note(line): + return line.startswith("> Note:") + + +def is_warning(line): + return line.startswith("> Warning:") + + +def modify_content_admonition(lines): + modified = False + updated_lines = [] + admonition_lines = [] + is_admonition = False + for line in lines: + if is_info(line) or is_new(line) or is_note(line) or is_warning(line): + if is_new(line): + line = line.replace("> ", "!!! tip \"New\"\n\n ") + line = line.replace("New as of", "Introduced in") + line = line.replace("new as of", "introduced in") + if is_note(line): + line = line.replace( + "> Note: ", "!!! note \"Note\"\n\n ") + elif is_warning(line): + line = line.replace( + "> Warning: ", "!!! warning \"Warning\"\n\n ") + elif is_info(line): + if not is_admonition: + line = line.replace("> ", "!!! info \"Info\"\n\n ") + elif line == ">": + line = "" + else: + line = " " + line.removeprefix("> ") + + is_admonition = True + admonition_lines.append(line) + elif is_admonition and line == ">": + line = "" + admonition_lines.append(line) + elif is_admonition and line.startswith("> "): + line = " " + line.removeprefix("> ") + admonition_lines.append(line) + elif line == "": + is_admonition = False + if len(admonition_lines) > 0: + updated_lines.extend(admonition_lines) + admonition_lines = [] + updated_lines.append("") + else: + updated_lines.append(line) + return updated_lines, modified + + +def is_shell_codeblock_start(line): + return line == "```shell" + + +def modify_content_terminal_example(lines): + modified = False + updated_lines = [] + command_block = [] + example_block = [] + in_command_block = False + in_example_block = False + previous_block = "" + next_line_must_be = None + for line in lines: + if is_shell_codeblock_start(line): + command_block.append(line) + modified = True + in_command_block = True + continue + elif in_command_block: + command_block.append(line) + if line == "```": + in_command_block = False + previous_block = "command_block" + next_line_must_be = "" + continue + elif line == "```": + if previous_block == "": + updated_lines.append(line) + continue + elif previous_block == "command_block": + example_block.append(line) + + if in_example_block: + previous_block = "" + in_example_block = False + updated_lines.append("=== \"Shell\"") + updated_lines.append("") + for command_line in command_block: + updated_lines.append(f" {command_line}") + command_block = [] + + updated_lines.append("") + updated_lines.append("=== \"Output\"") + updated_lines.append("") + for example_line in example_block: + updated_lines.append(f" {example_line}") + example_block = [] + else: + in_example_block = True + continue + elif previous_block == "command_block": + if next_line_must_be is None: + if in_example_block: + example_block.append(line) + else: + updated_lines.extend(command_block) + updated_lines.append("") + updated_lines.append(line) + command_block = [] + previous_block = "" + continue + elif next_line_must_be == "": + if line == "": + next_line_must_be = None + continue + + updated_lines.append(line) + + if len(command_block) > 0: + updated_lines.extend(command_block) + + return updated_lines, modified + + +def update_markdown(src): + markdown_files = [] + extensions = [".md"] + for subdir, _, files in os.walk(src): + for file in files: + ext = os.path.splitext(file)[-1].lower() + if ext in extensions: + markdown_files.append(os.path.join(subdir, file)) + + modifiers = [ + modify_content_noop, + modify_content_stripspace, + modify_content_admonition, + modify_content_terminal_example, + modify_content_inject_newlines, + ] + + for file in markdown_files: + modified = False + lines = [] + with open(file, 'r') as f: + lines = f.readlines() + for modifier in modifiers: + lines, m = modifier(lines) + if m: + modified = True + + if modified: + with open(file, 'w') as fp: + fp.writelines(lines) + + +def main(): + print("====> Copying mkdocs.yml") + print(" Generating navigation") + generate_nav("/usr/src/source/mkdocs.yml", "/usr/src/app/mkdocs.yml") + + print("====> Copying docs folder") + update_markdown("/usr/src/source/docs",) + + if os.path.exists("/usr/src/app/docs"): + print(" Removing old docs folder") + shutil.rmtree("/usr/src/app/docs") + + print(" Performing copy") + shutil.copytree("/usr/src/source/docs", "/usr/src/app/docs") + + +if __name__ == "__main__": + main()