mirror of
https://github.com/Kozea/Radicale.git
synced 2025-07-02 16:58:30 +00:00
Initial version of documentation generator
This commit is contained in:
parent
3f032e00b0
commit
b2c3f38766
12 changed files with 890 additions and 0 deletions
17
.github/workflows/generate-documentation.yml
vendored
Normal file
17
.github/workflows/generate-documentation.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: Generate documentation
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'documentation-tools/**'
|
||||
- DOCUMENTATION.md
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: gh-pages
|
||||
- name: Run generator
|
||||
run: documentation-tools/generate.py
|
||||
|
107
beta/assets/default.css
Normal file
107
beta/assets/default.css
Normal file
|
@ -0,0 +1,107 @@
|
|||
/* https://www.w3.org/TR/CSS22/ */
|
||||
html, address,
|
||||
blockquote,
|
||||
body, dd, div,
|
||||
dl, dt, fieldset, form,
|
||||
frame, frameset,
|
||||
h1, h2, h3, h4,
|
||||
h5, h6, noframes,
|
||||
ol, p, ul, center,
|
||||
dir, hr, menu, pre { display: block; unicode-bidi: embed }
|
||||
li { display: list-item }
|
||||
head { display: none }
|
||||
table { display: table }
|
||||
tr { display: table-row }
|
||||
thead { display: table-header-group }
|
||||
tbody { display: table-row-group }
|
||||
tfoot { display: table-footer-group }
|
||||
col { display: table-column }
|
||||
colgroup { display: table-column-group }
|
||||
td, th { display: table-cell }
|
||||
caption { display: table-caption }
|
||||
th { font-weight: bolder; text-align: center }
|
||||
caption { text-align: center }
|
||||
body { margin: 8px }
|
||||
h1 { font-size: 2em; margin: .67em 0 }
|
||||
h2 { font-size: 1.5em; margin: .75em 0 }
|
||||
h3 { font-size: 1.17em; margin: .83em 0 }
|
||||
h4, p,
|
||||
blockquote, ul,
|
||||
fieldset, form,
|
||||
ol, dl, dir,
|
||||
menu { margin: 1.12em 0 }
|
||||
h5 { font-size: .83em; margin: 1.5em 0 }
|
||||
h6 { font-size: .75em; margin: 1.67em 0 }
|
||||
h1, h2, h3, h4,
|
||||
h5, h6, b,
|
||||
strong { font-weight: bolder }
|
||||
blockquote { margin-left: 40px; margin-right: 40px }
|
||||
i, cite, em,
|
||||
var, address { font-style: italic }
|
||||
pre, tt, code,
|
||||
kbd, samp { font-family: monospace }
|
||||
pre { white-space: pre }
|
||||
button, textarea,
|
||||
input, select { display: inline-block }
|
||||
big { font-size: 1.17em }
|
||||
small, sub, sup { font-size: .83em }
|
||||
sub { vertical-align: sub }
|
||||
sup { vertical-align: super }
|
||||
table { border-spacing: 2px; }
|
||||
thead, tbody,
|
||||
tfoot { vertical-align: middle }
|
||||
td, th, tr { vertical-align: inherit }
|
||||
s, strike, del { text-decoration: line-through }
|
||||
hr { border: 1px inset }
|
||||
ol, ul, dir,
|
||||
menu, dd { margin-left: 40px }
|
||||
ol { list-style-type: decimal }
|
||||
ol ul, ul ol,
|
||||
ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
|
||||
u, ins { text-decoration: underline }
|
||||
br:before { content: "\A"; white-space: pre-line }
|
||||
center { text-align: center }
|
||||
:link, :visited { text-decoration: underline }
|
||||
:focus { outline: thin dotted invert }
|
||||
|
||||
/* Begin bidirectionality settings (do not change) */
|
||||
BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override }
|
||||
BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override }
|
||||
|
||||
*[DIR="ltr"] { direction: ltr; unicode-bidi: embed }
|
||||
*[DIR="rtl"] { direction: rtl; unicode-bidi: embed }
|
||||
|
||||
@media print {
|
||||
h1 { page-break-before: always }
|
||||
h1, h2, h3,
|
||||
h4, h5, h6 { page-break-after: avoid }
|
||||
ul, ol, dl { page-break-before: avoid }
|
||||
}
|
||||
/* END: https://www.w3.org/TR/CSS22/ */
|
||||
|
||||
@media not screen {
|
||||
header select.documentBranch, nav, .headerlink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p.heading {
|
||||
font-size: .67em;
|
||||
margin: 2em 0;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
nav {
|
||||
/* Override changes made by JS to HTMLElement */
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
h1 {
|
||||
page-break-before: auto;
|
||||
}
|
||||
|
||||
p.heading {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
}
|
20
beta/assets/document-branches.js
Normal file
20
beta/assets/document-branches.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
window.addEventListener("load", function() {
|
||||
let select = document.querySelector("header select.documentBranch");
|
||||
while (select.firstChild) {
|
||||
select.removeChild(select.firstChild);
|
||||
}
|
||||
for (let branch of documentBranches) {
|
||||
let option = document.createElement("option");
|
||||
option.textContent = branch;
|
||||
if (branch === documentBranch) {
|
||||
option.setAttribute("selected", "");
|
||||
}
|
||||
select.appendChild(option);
|
||||
}
|
||||
select.addEventListener("change", function() {
|
||||
if (select.value !== documentBranch) {
|
||||
location.assign(select.value + ".html");
|
||||
select.value = documentBranch;
|
||||
}
|
||||
});
|
||||
});
|
BIN
beta/assets/icon.png
Normal file
BIN
beta/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
10
beta/assets/logo.svg
Normal file
10
beta/assets/logo.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="200" height="300" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#a40000" d="M 186,188 C 184,98 34,105 47,192 C 59,279 130,296 130,296 C 130,296 189,277 186,188 z" />
|
||||
<path fill="#ffffff" d="M 73,238 C 119,242 140,241 177,222 C 172,270 131,288 131,288 C 131,288 88,276 74,238 z" />
|
||||
<g fill="none" stroke="#4e9a06" stroke-width="15">
|
||||
<path d="M 103,137 C 77,69 13,62 13,62" />
|
||||
<path d="M 105,136 C 105,86 37,20 37,20" />
|
||||
<path d="M 105,135 C 112,73 83,17 83,17" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 564 B |
67
beta/assets/navigation.js
Normal file
67
beta/assets/navigation.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
window.addEventListener("load", function() {
|
||||
function findActiveSection(sections) {
|
||||
let result = sections[0];
|
||||
for (let [section, link] of sections) {
|
||||
if (section.getBoundingClientRect().y > 10) {
|
||||
break;
|
||||
}
|
||||
result = [section, link];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
let nav = document.querySelector("nav");
|
||||
let sections = new Array();
|
||||
sections.push([document.querySelector("main"), null]);
|
||||
for (let section of document.querySelectorAll("section")) {
|
||||
let id = section.getAttribute("id");
|
||||
let link = nav.querySelector("a[href=\\#" + id.replace(/\//g, "\\/") + "]");
|
||||
if (link !== null) {
|
||||
link = link.parentElement;
|
||||
link.classList.remove("active")
|
||||
sections.push([section, link]);
|
||||
}
|
||||
}
|
||||
let oldLink = null;
|
||||
function updateLink() {
|
||||
let [section, link] = findActiveSection(sections);
|
||||
while (oldLink) {
|
||||
if (oldLink.tagName === "LI") {
|
||||
oldLink.classList.remove("active");
|
||||
if (!oldLink.classList.contains("level4")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
oldLink = oldLink.parentElement;
|
||||
}
|
||||
oldLink = link;
|
||||
while (link) {
|
||||
if (link.tagName === "LI") {
|
||||
link.classList.add("active");
|
||||
if (!link.classList.contains("level4")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
link = link.parentElement;
|
||||
}
|
||||
if (link === null) {
|
||||
nav.scrollTop = 0;
|
||||
} else {
|
||||
let topLink = link.getBoundingClientRect().y;
|
||||
let topNav = nav.getBoundingClientRect().y;
|
||||
let y = nav.scrollTop + topLink - topNav - 10;
|
||||
nav.scrollTo(0, y);
|
||||
}
|
||||
}
|
||||
function resizeNav() {
|
||||
let height = window.innerHeight - nav.getBoundingClientRect().y;
|
||||
nav.style.maxHeight = height > 0 ? height + "px" : "none";
|
||||
}
|
||||
function updateNav() {
|
||||
resizeNav();
|
||||
updateLink();
|
||||
}
|
||||
document.addEventListener("scroll", updateNav);
|
||||
window.addEventListener("resize", updateNav);
|
||||
updateNav();
|
||||
});
|
27
beta/assets/screen-noscript.css
Normal file
27
beta/assets/screen-noscript.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
header select.documentBranch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header span.documentBranch {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: relative;
|
||||
height: auto;
|
||||
/* Override changes made by JS to HTMLElement */
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
nav .level2.active > a, nav .level3.active > a {
|
||||
background: none;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
nav .level4 {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
nav .level4 > a::after {
|
||||
display: none !important;
|
||||
}
|
349
beta/assets/screen.css
Normal file
349
beta/assets/screen.css
Normal file
|
@ -0,0 +1,349 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.4;
|
||||
background-color: #E4E9F6;
|
||||
color: #424247;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
body > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #a40000;
|
||||
}
|
||||
|
||||
a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #050a02;
|
||||
color: #efdddd;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header .logoContainer {
|
||||
max-width: 1400px;
|
||||
padding: 2em 10px;
|
||||
padding-left: calc(210px + 2em);
|
||||
box-sizing: border-box;
|
||||
background: url(logo.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 10px 50%;
|
||||
min-height: calc(300px + 4em);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
header p {
|
||||
margin: 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-weight: lighter;
|
||||
font-size: 3em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header span.documentBranch {
|
||||
border: 1px dashed #efdddd;
|
||||
padding: 0 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
header select.documentBranch {
|
||||
background: none;
|
||||
color: #efdddd;
|
||||
border: none;
|
||||
font-size: 1em;
|
||||
font-weight: lighter;
|
||||
font-family: sans-serif;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border: 1px dashed #efdddd;
|
||||
cursor: pointer;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
header select.documentBranch:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
header select.documentBranch option {
|
||||
color: initial;
|
||||
font-size: initial;
|
||||
font-weight: initial;
|
||||
font-family: initial;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
header .linkContainer {
|
||||
order: -1;
|
||||
background-color: #a40000;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
header a {
|
||||
color: white;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
transition: color .2s ease;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
header a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
color: #050a02;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
max-width: 1400px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Shift headers by 1 */
|
||||
h2 {
|
||||
font-size: 2em;
|
||||
margin: .67em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
margin: .75em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.17em;
|
||||
margin: .83em 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
margin: 1.12em 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: .83em;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
p.heading {
|
||||
font-size: .75em;
|
||||
margin: 1.67em 0;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
img, .tableContainer {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tableContainer {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-radius: 3px;
|
||||
background-color: #ECE7D5;
|
||||
padding: 10px;
|
||||
border-left: 3px solid #F6E39A;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
blockquote > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sourceCode {
|
||||
border-radius: 3px;
|
||||
background-color: #D2D7E3;
|
||||
padding: 10px;
|
||||
border-left: 3px solid #a40000;
|
||||
}
|
||||
|
||||
:not(pre) > code {
|
||||
border-radius: 2px;
|
||||
background-color: #D2D7E3;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
section.last {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
nav h2 {
|
||||
font-weight: normal;
|
||||
padding: 0 10px;
|
||||
font-size: 1.5em;
|
||||
margin: .75em 0;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 10px 0;
|
||||
margin-right: 2rem;
|
||||
flex: 1;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
nav::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav .level2 {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
padding-left: 7px;
|
||||
border-left: 3px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
nav .level2 > a {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
nav .level4.active > a::after {
|
||||
content: "▷";
|
||||
color: #424247;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 10px;
|
||||
height: 100%;
|
||||
width: 20px;
|
||||
font-size: 0.8em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav .level4 > a {
|
||||
color: #424247;
|
||||
padding-left: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav .level2.active > a, nav .level3.active > a {
|
||||
background: #D2D7E3;
|
||||
border-left-color: #a40000;
|
||||
}
|
||||
|
||||
nav .level4 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav .active > ul > .level4 {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
main p, main ul, main ol {
|
||||
max-width: 42em;
|
||||
box-sizing: border-box;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.documentContainer {
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
table, tr, td, th, thead {
|
||||
border: 1px solid #424247;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: #D2D7E3;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.headerlink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section > *:first-child:hover .headerlink {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
@media all and (max-width: 50em) {
|
||||
nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.documentContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header .logoContainer {
|
||||
padding: 2em 10px;
|
||||
background-position: 50%;
|
||||
min-height: 320px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header .logoContainer, header select.documentBranch {
|
||||
text-shadow: 0 0 3px #050a02, 0 0 3px #050a02;
|
||||
}
|
||||
}
|
55
documentation-tools/filter.py
Executable file
55
documentation-tools/filter.py
Executable file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Filter program for transforming the Pandoc AST
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
from build import SHIFT_HEADING
|
||||
|
||||
|
||||
def text_content(content):
|
||||
text = ""
|
||||
for block in content:
|
||||
if block["t"] == "Space":
|
||||
text += " "
|
||||
elif block["t"] == "Str":
|
||||
text += block["c"]
|
||||
return text
|
||||
|
||||
|
||||
def convert_framgent(*titles):
|
||||
titles = list(titles)
|
||||
for i, title in enumerate(titles):
|
||||
title = re.sub(r"\s", "-", title)
|
||||
title = re.sub(r"[^\w-]", "", title)
|
||||
titles[i] = title.lower()
|
||||
return "/".join(titles)
|
||||
|
||||
|
||||
def main():
|
||||
data = json.load(sys.stdin)
|
||||
|
||||
# Use hierachical link fragments (e.g. #heading/subheading)
|
||||
headings = []
|
||||
for block in data["blocks"]:
|
||||
if block["t"] != "Header":
|
||||
continue
|
||||
level, (attr_id, attr_class, attr_name), content = block["c"]
|
||||
shifted_level = level - SHIFT_HEADING
|
||||
title = text_content(content)
|
||||
headings = headings[:shifted_level - 1]
|
||||
while len(headings) < shifted_level - 1:
|
||||
headings.append("")
|
||||
headings.append(title)
|
||||
full_attr_id = convert_framgent(*headings)
|
||||
block["c"] = [level, [full_attr_id, attr_class, attr_name], content]
|
||||
|
||||
json.dump(data, sys.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
136
documentation-tools/generate.py
Executable file
136
documentation-tools/generate.py
Executable file
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Documentation generator
|
||||
|
||||
Generates the documentation for every Git branch and commits it.
|
||||
Gracefully handles conflicting commits.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
REMOTE = "origin"
|
||||
GIT_CONFIG = {"protocol.version": "2",
|
||||
"user.email": "<>",
|
||||
"user.name": "Github Actions"}
|
||||
COMMIT_MESSAGE = "Generate documentation"
|
||||
DOCUMENTATION_SRC = "DOCUMENTATION.md"
|
||||
TARGET_DIR = "beta"
|
||||
SHIFT_HEADING = 1
|
||||
TOOLS_PATH = os.path.dirname(__file__)
|
||||
TEMPLATE_PATH = os.path.join(TOOLS_PATH, "template.html")
|
||||
FILTER_EXE = os.path.join(TOOLS_PATH, "filter.py")
|
||||
POSTPROCESSOR_EXE = os.path.join(TOOLS_PATH, "postprocessor.py")
|
||||
PANDOC_EXE = "pandoc"
|
||||
PANDOC_DOWNLOAD = "https://github.com/jgm/pandoc/releases/download/2.9.2/pandoc-2.9.2-1-amd64.deb"
|
||||
|
||||
|
||||
def convert_doc(src_path, to_path, branch, branches):
|
||||
subprocess.run([
|
||||
PANDOC_EXE,
|
||||
"--from=gfm",
|
||||
"--to=html5",
|
||||
os.path.abspath(src_path),
|
||||
"--toc",
|
||||
"--template=%s" % os.path.basename(TEMPLATE_PATH),
|
||||
"--output=%s" % os.path.abspath(to_path),
|
||||
"--section-divs",
|
||||
"--shift-heading-level-by=%d" % SHIFT_HEADING,
|
||||
"--toc-depth=4",
|
||||
"--filter=%s" % os.path.abspath(FILTER_EXE),
|
||||
"--variable=branch=%s" % branch,
|
||||
*["--variable=branches=%s" % b for b in branches]],
|
||||
check=True, cwd=os.path.dirname(TEMPLATE_PATH))
|
||||
with open(to_path, "rb+") as f:
|
||||
data = subprocess.run([POSTPROCESSOR_EXE], input=f.read(),
|
||||
stdout=subprocess.PIPE, check=True).stdout
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(data)
|
||||
|
||||
|
||||
def install_dependencies():
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "beautifulsoup4"],
|
||||
check=True)
|
||||
with TemporaryDirectory() as temp:
|
||||
subprocess.run(["curl", "--location", "--output", "pandoc.deb",
|
||||
PANDOC_DOWNLOAD], check=True, cwd=temp)
|
||||
subprocess.run(["apt", "install", "--assume-yes", "./pandoc.deb"],
|
||||
check=True, cwd=temp)
|
||||
|
||||
|
||||
def natural_sort_key(s):
|
||||
# https://stackoverflow.com/a/16090640
|
||||
return [int(part) if part.isdigit() else part.lower()
|
||||
for part in re.split(r"(\d+)", s)]
|
||||
|
||||
|
||||
def run_git(*args):
|
||||
config_args = []
|
||||
for key, value in GIT_CONFIG.items():
|
||||
config_args.extend(["-c", "%s=%s" % (key, value)])
|
||||
output = subprocess.run(["git", *config_args, *args],
|
||||
stdout=subprocess.PIPE, check=True,
|
||||
universal_newlines=True).stdout
|
||||
return tuple(filter(None, output.split("\n")))
|
||||
|
||||
|
||||
def checkout(branch):
|
||||
run_git("checkout", "--progress", "--force", "-B", branch,
|
||||
"refs/remotes/%s/%s" % (REMOTE, branch))
|
||||
|
||||
|
||||
def run_git_fetch_and_restart_if_changed(remote_commits, target_branch):
|
||||
run_git("fetch", "--no-tags", "--prune", "--progress",
|
||||
"--no-recurse-submodules", "--depth=1", REMOTE,
|
||||
"+refs/heads/*:refs/remotes/%s/*" % REMOTE)
|
||||
if remote_commits != run_git("rev-parse", "--remotes=%s" % REMOTE):
|
||||
checkout(target_branch)
|
||||
print("Remote changed, restarting", file=sys.stderr)
|
||||
os.execv(__file__, sys.argv)
|
||||
|
||||
|
||||
def main():
|
||||
install_dependencies()
|
||||
target_branch, = run_git("rev-parse", "--abbrev-ref", "HEAD")
|
||||
remote_commits = run_git("rev-parse", "--remotes=%s" % REMOTE)
|
||||
run_git_fetch_and_restart_if_changed(remote_commits, target_branch)
|
||||
branches = [ref[len("refs/remotes/%s/" % REMOTE):] for ref in run_git(
|
||||
"rev-parse", "--symbolic-full-name", "--remotes=%s" % REMOTE)]
|
||||
branches.sort(key=natural_sort_key, reverse=True)
|
||||
os.makedirs(TARGET_DIR, exist_ok=True)
|
||||
for path in glob.iglob(os.path.join(TARGET_DIR, "*.html")):
|
||||
run_git("rm", "--", path)
|
||||
with TemporaryDirectory() as temp:
|
||||
branch_docs = {}
|
||||
for branch in branches:
|
||||
checkout(branch)
|
||||
if os.path.exists(DOCUMENTATION_SRC):
|
||||
branch_docs[branch] = os.path.join(temp, "%s.md" % branch)
|
||||
shutil.copy(DOCUMENTATION_SRC, branch_docs[branch])
|
||||
checkout(target_branch)
|
||||
for branch, src_path in branch_docs.items():
|
||||
to_path = os.path.join(TARGET_DIR, "%s.html" % branch)
|
||||
convert_doc(src_path, to_path, branch, branches)
|
||||
run_git("add", "--", to_path)
|
||||
with contextlib.suppress(subprocess.CalledProcessError):
|
||||
run_git("diff", "--cached", "--quiet")
|
||||
print("No changes", file=sys.stderr)
|
||||
return
|
||||
run_git("commit", "-m", COMMIT_MESSAGE)
|
||||
try:
|
||||
run_git("push", REMOTE, "HEAD:%s" % target_branch)
|
||||
except subprocess.CalledProcessError:
|
||||
run_git_fetch_and_restart_if_changed(remote_commits, target_branch)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
49
documentation-tools/postprocessor.py
Executable file
49
documentation-tools/postprocessor.py
Executable file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Postprocessor program for the HTML output of Pandoc
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def add_class(element, class_):
|
||||
element["class"] = element.get("class", []) + [class_]
|
||||
|
||||
|
||||
def main():
|
||||
soup = BeautifulSoup(sys.stdin.buffer, "html.parser")
|
||||
|
||||
# Mark the hierachical levels in the navigation
|
||||
for section in soup.select("section"):
|
||||
link = soup.find("a", href="#" + section["id"])
|
||||
if link is None:
|
||||
continue
|
||||
add_class(link.parent, section["class"][0])
|
||||
|
||||
# Mark last section
|
||||
add_class(soup.select("section")[-1], "last")
|
||||
|
||||
# Wrap tables in a div container (for scrolling)
|
||||
for table in soup.select("main table"):
|
||||
container = soup.new_tag("div")
|
||||
add_class(container, "tableContainer")
|
||||
table.wrap(container)
|
||||
|
||||
# Add a link with the fragment to every header
|
||||
for header in soup.select("section > *:first-child"):
|
||||
section = header.parent
|
||||
link = soup.new_tag("a")
|
||||
add_class(link, "headerlink")
|
||||
link["href"] = "#" + section["id"]
|
||||
link.string = "¶"
|
||||
header.append(" ")
|
||||
header.append(link)
|
||||
|
||||
sys.stdout.buffer.write(soup.encode(formatter="html5") + b"\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
53
documentation-tools/template.html
Normal file
53
documentation-tools/template.html
Normal file
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="generator" content="pandoc">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<style>
|
||||
$styles.html()$
|
||||
</style>
|
||||
$if(math)$
|
||||
$math$
|
||||
$endif$
|
||||
<link href="assets/default.css" media="all" rel="stylesheet">
|
||||
<link href="assets/screen.css" media="screen" rel="stylesheet">
|
||||
<noscript><link href="assets/screen-noscript.css" media="screen" rel="stylesheet"></noscript>
|
||||
<link href="https://github.com/Kozea/Radicale/releases.atom" type="application/atom+xml" rel="alternate" title="Radicale Releases">
|
||||
<link href="assets/icon.png" type="image/png" rel="shortcut icon">
|
||||
<title>Radicale - Free and Open-Source CalDAV and CardDAV Server</title>
|
||||
<meta name="description" content="Free and Open-Source CalDAV and CardDAV Server">
|
||||
<script src="assets/navigation.js"></script>
|
||||
<script src="assets/document-branches.js"></script>
|
||||
<script>
|
||||
const documentBranch = "$branch$";
|
||||
const documentBranches = ["$for(branches)$$branches$$sep$", "$endfor$"];
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="logoContainer">
|
||||
<h1>
|
||||
Radicale
|
||||
<span class="documentBranch">$branch$</span>
|
||||
<select class="documentBranch"></select>
|
||||
</h1>
|
||||
<p>Free and Open-Source CalDAV and CardDAV Server</p>
|
||||
</div>
|
||||
<div class="linkContainer">
|
||||
<ul>
|
||||
<li><a href="https://community.kozea.fr">Made with ❤ by Kozea Community</a></li>
|
||||
<li><a href="https://github.com/Kozea/Radicale">Fork me on GitHub</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<nav>
|
||||
<h2>Contents</h2>
|
||||
$table-of-contents$
|
||||
</nav>
|
||||
<div class="documentContainer">
|
||||
$body$
|
||||
</div>
|
||||
</main>
|
Loading…
Add table
Add a link
Reference in a new issue