gerrit/Documentation/replace_macros.py
Saša Živkov 0b6f8feec2 Use default asciidoctor CSS style instead of doc.css
We tried building Gerrit's documentation using the asciidoctor's
standard CSS style and for many people the result looks better
than Gerrit's default doc.css.

The search box is rendered with fixed position now.  With the
asciidoctor default style sheet the "position:absolute" attribute no
longer worked as intended, because there are other elements in the
page with a "position" attribute. So the search box was placed randomly
somewhere next to a section header and screwed up the text layout
completely.

Test Plan:
$ buck build Documentation:html
$ open buck-out/gen/Documentation/html__tmp/Documentation/index.html

Change-Id: Ia276aeb5c79b9fbd9589db444a83c084f07aee40
Also-by: Michael Ochmann <michael.ochmann@sap.com>
2016-04-07 08:39:02 +00:00

273 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed 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.
from optparse import OptionParser
import re
import sys
PAT_GERRIT = re.compile(r'^GERRIT')
PAT_INCLUDE = re.compile(r'^(include::.*)(\[\])$')
PAT_GET = re.compile(r'^get::([^ \t\n]*)')
PAT_TITLE = re.compile(r'^\.(.*)')
PAT_STARS = re.compile(r'^\*\*\*\*')
PAT_SEARCHBOX = re.compile(r'^SEARCHBOX')
GERRIT_UPLINK = """
++++
<hr style=\"
height: 2px;
color: silver;
margin-top: 1.2em;
margin-bottom: 0.5em;
\">
++++
"""
GET_TITLE = '<div class="title">%s</div>'
GET_MACRO = """
++++
<div class="listingblock">
%s
<div class="content">
<a id=\"{0}\" onmousedown="javascript:
var i = document.URL.lastIndexOf(\'/Documentation/\');
var url = document.URL.substring(0, i) + \'{0}\';
document.getElementById(\'{0}\').href = url;">
GET {0} HTTP/1.0
</a>
</div>
</div>
++++
"""
SEARCH_BOX = """
++++
<div style="
position:fixed;
top:0px;
right:0px;
text-align:
right;
padding-top:2px;
padding-right:0.5em;
padding-bottom:2px;">
<input size="40"
style="line-height: 0.75em;font-size: 0.75em;"
id="docSearch"
type="text">
<button style="
background:none!important;
border:none;
padding:0!important;
vertical-align:bottom;
font-family:'Open Sans','DejaVu Sans',sans-serif;
font-size:0.8em;
color:#1d4b8f;
text-decoration:none;"
type="button"
id="searchBox">
Search
</button>
<script type="text/javascript">
var f = function() {
window.location = '../#/Documentation/' +
encodeURIComponent(document.getElementById("docSearch").value);
}
document.getElementById("searchBox").onclick = f;
document.getElementById("docSearch").onkeypress = function(e) {
if (13 == (e.keyCode ? e.keyCode : e.which)) {
f();
}
}
</script>
</div>
++++
"""
LINK_SCRIPT = """
++++
<script type="text/javascript">
decorate(document.getElementsByTagName('h1'));
decorate(document.getElementsByTagName('h2'));
decorate(document.getElementsByTagName('h3'));
decorate(document.getElementsByTagName('h4'));
var divs = document.getElementsByTagName('div');
var arr = new Array();
var excluded = getExcludedIds();
for(var i = 0; i < divs.length; i++) {
var d = divs[i];
var id = d.getAttribute('id');
if (id != null && !(id in excluded)) {
arr[arr.length] = d;
}
}
decorate(arr);
var anchors = document.getElementsByTagName('a');
arr = new Array();
for(var i = 0; i < anchors.length; i++) {
var a = anchors[i];
// if the anchor has no id there is no target to
// which we can link
if (a.getAttribute('id') != null) {
// if the anchor is empty there is no content which
// can receive the mouseover event, an empty anchor
// applies to the element that follows, move the
// element that follows into the anchor so that there
// is content which can receive the mouseover event
if (a.firstChild == null) {
var next = a.nextSibling;
if (next != null) {
next.parentNode.removeChild(next);
a.appendChild(next);
}
}
arr[arr.length] = a;
}
}
decorate(arr);
function decorate(e) {
for(var i = 0; i < e.length; i++) {
e[i].onmouseover = function (evt) {
var element = this;
// do nothing if the link icon is currently showing
var a = element.firstChild;
if (a != null && a instanceof Element
&& a.getAttribute('id') == 'LINK') {
return;
}
// if there is no id there is no target to link to
var id = element.getAttribute('id');
if (id == null) {
return;
}
// create and show a link icon that links to this element
a = document.createElement('a');
a.setAttribute('id', 'LINK');
a.setAttribute('href', '#' + id);
a.setAttribute('style', 'position: absolute;'
+ ' left: ' + (element.offsetLeft - 16 - 2 * 4) + 'px;'
+ ' padding-left: 4px; padding-right: 4px; padding-top:4px;');
var img = document.createElement('img');
img.setAttribute('src', 'images/link.png');
img.setAttribute('style', 'background-color: #FFFFFF;');
a.appendChild(img);
element.insertBefore(a, element.firstChild);
// remove the link icon when the mouse is moved away,
// but keep it shown if the mouse is over the element, the link or the icon
hide = function(evt) {
if (document.elementFromPoint(evt.clientX, evt.clientY) != element
&& document.elementFromPoint(evt.clientX, evt.clientY) != a
&& document.elementFromPoint(evt.clientX, evt.clientY) != img
&& element.contains(a)) {
element.removeChild(a);
}
}
element.onmouseout = hide;
a.onmouseout = hide;
img.onmouseout = hide;
}
}
}
function getExcludedIds() {
var excluded = {};
excluded['header'] = true;
excluded['toc'] = true;
excluded['toctitle'] = true;
excluded['content'] = true;
excluded['preamble'] = true;
excluded['footer'] = true;
excluded['footer-text'] = true;
return excluded;
}
</script>
++++
"""
opts = OptionParser()
opts.add_option('-o', '--out', help='output file')
opts.add_option('-s', '--src', help='source file')
opts.add_option('-x', '--suffix', help='suffix for included filenames')
opts.add_option('-b', '--searchbox', action="store_true", default=True,
help="generate the search boxes")
opts.add_option('--no-searchbox', action="store_false", dest='searchbox',
help="don't generate the search boxes")
options, _ = opts.parse_args()
try:
out_file = open(options.out, 'w')
src_file = open(options.src, 'r')
last_line = ''
ignore_next_line = False
last_title = ''
for line in src_file.xreadlines():
if PAT_GERRIT.match(last_line):
# Case of "GERRIT\n------" at the footer
out_file.write(GERRIT_UPLINK)
last_line = ''
elif PAT_SEARCHBOX.match(last_line):
# Case of 'SEARCHBOX\n---------'
if options.searchbox:
out_file.write(SEARCH_BOX)
last_line = ''
elif PAT_INCLUDE.match(line):
# Case of 'include::<filename>'
match = PAT_INCLUDE.match(line)
out_file.write(last_line)
last_line = match.group(1) + options.suffix + match.group(2) + '\n'
elif PAT_STARS.match(line):
if PAT_TITLE.match(last_line):
# Case of the title in '.<title>\n****\nget::<url>\n****'
match = PAT_TITLE.match(last_line)
last_title = GET_TITLE % match.group(1)
else:
out_file.write(last_line)
last_title = ''
elif PAT_GET.match(line):
# Case of '****\nget::<url>\n****' in rest api
url = PAT_GET.match(line).group(1)
out_file.write(GET_MACRO.format(url) % last_title)
ignore_next_line = True
elif ignore_next_line:
# Handle the trailing '****' of the 'get::' case
last_line = ''
ignore_next_line = False
else:
out_file.write(last_line)
last_line = line
out_file.write(last_line)
out_file.write(LINK_SCRIPT)
out_file.close()
except IOError as err:
sys.stderr.write(
"error while expanding %s to %s: %s" % (options.src, options.out, err))
exit(1)