| #!/usr/bin/env python3 | |
| # coding=utf-8 | |
| # 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. | |
| import argparse | |
| 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> | |
| %s | |
| </div> | |
| ++++ | |
| """ | |
| BUILTIN_SEARCH = """ | |
| <script type="text/javascript"> | |
| var f = function() { | |
| window.location = '../#/Documentation/q/' + | |
| 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> | |
| """ | |
| GOOGLE_SITE_SEARCH = """ | |
| <script type="text/javascript"> | |
| var f = function() { | |
| window.location = 'https://www.google.com/search?q=' + | |
| encodeURIComponent(document.getElementById("docSearch").value + | |
| ' site:@SITE@'); | |
| } | |
| document.getElementById("searchBox").onclick = f; | |
| document.getElementById("docSearch").onkeypress = function(e) { | |
| if (13 == (e.keyCode ? e.keyCode : e.which)) { | |
| f(); | |
| } | |
| } | |
| </script> | |
| """ | |
| 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;'); | |
| var span = document.createElement('span'); | |
| span.setAttribute('style', 'height: ' + element.offsetHeight + 'px;' | |
| + ' display: inline-block; vertical-align: baseline;' | |
| + ' font-size: 16px; text-decoration: none; color: grey;'); | |
| a.appendChild(span); | |
| var link = document.createTextNode('🔗'); | |
| span.appendChild(link); | |
| 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) != span | |
| && document.elementFromPoint(evt.clientX, evt.clientY) != link | |
| && element.contains(a)) { | |
| element.removeChild(a); | |
| } | |
| } | |
| element.onmouseout = hide; | |
| a.onmouseout = hide; | |
| span.onmouseout = hide; | |
| link.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> | |
| ++++ | |
| """ | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('-o', '--out', help='output file') | |
| parser.add_argument('-s', '--src', help='source file') | |
| parser.add_argument('-x', '--suffix', help='suffix for included filenames') | |
| parser.add_argument('-b', '--searchbox', action="store_true", default=True, | |
| help="generate the search boxes") | |
| parser.add_argument('--no-searchbox', action="store_false", dest='searchbox', | |
| help="don't generate the search boxes") | |
| parser.add_argument('--site-search', action="store", metavar="SITE", | |
| help=("generate the search box using google. SITE should " + | |
| "point to the domain/path of the site, eg. " + | |
| "gerrit-review.googlesource.com/Documentation")) | |
| args = parser.parse_args() | |
| if args.site_search: | |
| SEARCH_BOX = (SEARCH_BOX % | |
| GOOGLE_SITE_SEARCH.replace("@SITE@", args.site_search)) | |
| else: | |
| SEARCH_BOX = SEARCH_BOX % BUILTIN_SEARCH | |
| try: | |
| try: | |
| out_file = open(args.out, 'w', errors='ignore') | |
| src_file = open(args.src, 'r', errors='ignore') | |
| except TypeError: | |
| out_file = open(args.out, 'w') | |
| src_file = open(args.src, 'r') | |
| last_line = '' | |
| ignore_next_line = False | |
| last_title = '' | |
| for line in src_file: | |
| 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 args.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) + args.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" % (args.src, args.out, err)) | |
| exit(1) |