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