summaryrefslogtreecommitdiffstats
path: root/debian/dconv/haproxy-dconv.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xdebian/dconv/haproxy-dconv.py534
1 files changed, 534 insertions, 0 deletions
diff --git a/debian/dconv/haproxy-dconv.py b/debian/dconv/haproxy-dconv.py
new file mode 100755
index 0000000..ec800cf
--- /dev/null
+++ b/debian/dconv/haproxy-dconv.py
@@ -0,0 +1,534 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2012 Cyril Bonté
+#
+# 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.
+
+'''
+TODO : ability to split chapters into several files
+TODO : manage keyword locality (server/proxy/global ; ex : maxconn)
+TODO : Remove global variables where possible
+'''
+import os
+import subprocess
+import sys
+import html
+import re
+import time
+import datetime
+
+from optparse import OptionParser
+
+from mako.template import Template
+from mako.lookup import TemplateLookup
+from mako.exceptions import TopLevelLookupException
+
+from parser import PContext
+from parser import remove_indent
+from parser import *
+
+from urllib.parse import quote
+
+VERSION = ""
+HAPROXY_GIT_VERSION = False
+
+def main():
+ global VERSION, HAPROXY_GIT_VERSION
+
+ usage="Usage: %prog --infile <infile> --outfile <outfile>"
+
+ optparser = OptionParser(description='Generate HTML Document from HAProxy configuation.txt',
+ version=VERSION,
+ usage=usage)
+ optparser.add_option('--infile', '-i', help='Input file mostly the configuration.txt')
+ optparser.add_option('--outfile','-o', help='Output file')
+ optparser.add_option('--base','-b', default = '', help='Base directory for relative links')
+ (option, args) = optparser.parse_args()
+
+ if not (option.infile and option.outfile) or len(args) > 0:
+ optparser.print_help()
+ exit(1)
+
+ option.infile = os.path.abspath(option.infile)
+ option.outfile = os.path.abspath(option.outfile)
+
+ os.chdir(os.path.dirname(__file__))
+
+ VERSION = get_git_version()
+ if not VERSION:
+ sys.exit(1)
+
+ HAPROXY_GIT_VERSION = get_haproxy_git_version(os.path.dirname(option.infile))
+
+ convert(option.infile, option.outfile, option.base)
+
+
+# Temporarily determine the version from git to follow which commit generated
+# the documentation
+def get_git_version():
+ if not os.path.isdir(".git"):
+ print("This does not appear to be a Git repository.", file=sys.stderr)
+ return
+ try:
+ p = subprocess.Popen(["git", "describe", "--tags", "--match", "v*"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except EnvironmentError:
+ print("Unable to run git", file=sys.stderr)
+ return
+ version = p.communicate()[0]
+ if p.returncode != 0:
+ print("Unable to run git", file=sys.stderr)
+ return
+
+ if len(version) < 2:
+ return
+
+ version = version[1:].strip()
+ version = re.sub(r'-g.*', '', version)
+ return version
+
+def get_haproxy_git_version(path):
+ try:
+ p = subprocess.Popen(["git", "describe", "--tags", "--match", "v*"], cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except EnvironmentError:
+ return False
+ version = p.communicate()[0]
+
+ if p.returncode != 0:
+ return False
+
+ if len(version) < 2:
+ return False
+
+ version = version[1:].strip()
+ version = re.sub(r'-g.*', '', version)
+ return version
+
+def getTitleDetails(string):
+ array = string.split(".")
+
+ title = array.pop().strip()
+ chapter = ".".join(array)
+ level = max(1, len(array))
+ if array:
+ toplevel = array[0]
+ else:
+ toplevel = False
+
+ return {
+ "title" : title,
+ "chapter" : chapter,
+ "level" : level,
+ "toplevel": toplevel
+ }
+
+# Parse the whole document to insert links on keywords
+def createLinks():
+ global document, keywords, keywordsCount, keyword_conflicts, chapters
+
+ print("Generating keywords links...", file=sys.stderr)
+
+ delimiters = [
+ dict(start='&quot;', end='&quot;', multi=True ),
+ dict(start='- ' , end='\n' , multi=False),
+ ]
+
+ for keyword in keywords:
+ keywordsCount[keyword] = 0
+ for delimiter in delimiters:
+ keywordsCount[keyword] += document.count(delimiter['start'] + keyword + delimiter['end'])
+ if (keyword in keyword_conflicts) and (not keywordsCount[keyword]):
+ # The keyword is never used, we can remove it from the conflicts list
+ del keyword_conflicts[keyword]
+
+ if keyword in keyword_conflicts:
+ chapter_list = ""
+ for chapter in keyword_conflicts[keyword]:
+ chapter_list += '<li><a href="#%s">%s</a></li>' % (quote("%s (%s)" % (keyword, chapters[chapter]['title'])), chapters[chapter]['title'])
+ for delimiter in delimiters:
+ if delimiter['multi']:
+ document = document.replace(delimiter['start'] + keyword + delimiter['end'],
+ delimiter['start'] + '<span class="dropdown">' +
+ '<a class="dropdown-toggle" data-toggle="dropdown" href="#">' +
+ keyword +
+ '<span class="caret"></span>' +
+ '</a>' +
+ '<ul class="dropdown-menu">' +
+ '<li class="dropdown-header">This keyword is available in sections :</li>' +
+ chapter_list +
+ '</ul>' +
+ '</span>' + delimiter['end'])
+ else:
+ document = document.replace(delimiter['start'] + keyword + delimiter['end'], delimiter['start'] + '<a href="#' + quote(keyword) + '">' + keyword + '</a>' + delimiter['end'])
+ else:
+ for delimiter in delimiters:
+ document = document.replace(delimiter['start'] + keyword + delimiter['end'], delimiter['start'] + '<a href="#' + quote(keyword) + '">' + keyword + '</a>' + delimiter['end'])
+ if keyword.startswith("option "):
+ shortKeyword = keyword[len("option "):]
+ keywordsCount[shortKeyword] = 0
+ for delimiter in delimiters:
+ keywordsCount[keyword] += document.count(delimiter['start'] + shortKeyword + delimiter['end'])
+ if (shortKeyword in keyword_conflicts) and (not keywordsCount[shortKeyword]):
+ # The keyword is never used, we can remove it from the conflicts list
+ del keyword_conflicts[shortKeyword]
+ for delimiter in delimiters:
+ document = document.replace(delimiter['start'] + shortKeyword + delimiter['start'], delimiter['start'] + '<a href="#' + quote(keyword) + '">' + shortKeyword + '</a>' + delimiter['end'])
+
+def documentAppend(text, retline = True):
+ global document
+ document += text
+ if retline:
+ document += "\n"
+
+def init_parsers(pctxt):
+ return [
+ underline.Parser(pctxt),
+ arguments.Parser(pctxt),
+ seealso.Parser(pctxt),
+ example.Parser(pctxt),
+ table.Parser(pctxt),
+ underline.Parser(pctxt),
+ keyword.Parser(pctxt),
+ ]
+
+# The parser itself
+def convert(infile, outfile, base=''):
+ global document, keywords, keywordsCount, chapters, keyword_conflicts
+
+ if len(base) > 0 and base[:-1] != '/':
+ base += '/'
+
+ hasSummary = False
+
+ data = []
+ fd = open(infile,"r")
+ for line in fd:
+ line.replace("\t", " " * 8)
+ line = line.rstrip()
+ data.append(line)
+ fd.close()
+
+ pctxt = PContext(
+ TemplateLookup(
+ directories=[
+ 'templates'
+ ]
+ )
+ )
+
+ parsers = init_parsers(pctxt)
+
+ pctxt.context = {
+ 'headers': {},
+ 'document': "",
+ 'base': base,
+ }
+
+ sections = []
+ currentSection = {
+ "details": getTitleDetails(""),
+ "content": "",
+ }
+
+ chapters = {}
+
+ keywords = {}
+ keywordsCount = {}
+
+ specialSections = {
+ "default": {
+ "hasKeywords": True,
+ },
+ "4.1": {
+ "hasKeywords": True,
+ },
+ }
+
+ pctxt.keywords = keywords
+ pctxt.keywordsCount = keywordsCount
+ pctxt.chapters = chapters
+
+ print("Importing %s..." % infile, file=sys.stderr)
+
+ nblines = len(data)
+ i = j = 0
+ while i < nblines:
+ line = data[i].rstrip()
+ if i < nblines - 1:
+ next = data[i + 1].rstrip()
+ else:
+ next = ""
+ if (line == "Summary" or re.match("^[0-9].*", line)) and (len(next) > 0) and (next[0] == '-') \
+ and ("-" * len(line)).startswith(next): # Fuzzy underline length detection
+ sections.append(currentSection)
+ currentSection = {
+ "details": getTitleDetails(line),
+ "content": "",
+ }
+ j = 0
+ i += 1 # Skip underline
+ while not data[i + 1].rstrip():
+ i += 1 # Skip empty lines
+
+ else:
+ if len(line) > 80:
+ print("Line `%i' exceeds 80 columns" % (i + 1), file=sys.stderr)
+
+ currentSection["content"] = currentSection["content"] + line + "\n"
+ j += 1
+ if currentSection["details"]["title"] == "Summary" and line != "":
+ hasSummary = True
+ # Learn chapters from the summary
+ details = getTitleDetails(line)
+ if details["chapter"]:
+ chapters[details["chapter"]] = details
+ i += 1
+ sections.append(currentSection)
+
+ chapterIndexes = sorted(chapters.keys())
+
+ document = ""
+
+ # Complete the summary
+ for section in sections:
+ details = section["details"]
+ title = details["title"]
+ if title:
+ fulltitle = title
+ if details["chapter"]:
+ #documentAppend("<a name=\"%s\"></a>" % details["chapter"])
+ fulltitle = details["chapter"] + ". " + title
+ if not details["chapter"] in chapters:
+ print("Adding '%s' to the summary" % details["title"], file=sys.stderr)
+ chapters[details["chapter"]] = details
+ chapterIndexes = sorted(chapters.keys())
+
+ for section in sections:
+ details = section["details"]
+ pctxt.details = details
+ level = details["level"]
+ title = details["title"]
+ content = section["content"].rstrip()
+
+ print("Parsing chapter %s..." % title, file=sys.stderr)
+
+ if (title == "Summary") or (title and not hasSummary):
+ summaryTemplate = pctxt.templates.get_template('summary.html')
+ documentAppend(summaryTemplate.render(
+ pctxt = pctxt,
+ chapters = chapters,
+ chapterIndexes = chapterIndexes,
+ ))
+ if title and not hasSummary:
+ hasSummary = True
+ else:
+ continue
+
+ if title:
+ documentAppend('<a class="anchor" id="%s" name="%s"></a>' % (details["chapter"], details["chapter"]))
+ if level == 1:
+ documentAppend("<div class=\"page-header\">", False)
+ documentAppend('<h%d id="chapter-%s" data-target="%s"><small><a class="small" href="#%s">%s.</a></small> %s</h%d>' % (level, details["chapter"], details["chapter"], details["chapter"], details["chapter"], html.escape(title, True), level))
+ if level == 1:
+ documentAppend("</div>", False)
+
+ if content:
+ if False and title:
+ # Display a navigation bar
+ documentAppend('<ul class="well pager">')
+ documentAppend('<li><a href="#top">Top</a></li>', False)
+ index = chapterIndexes.index(details["chapter"])
+ if index > 0:
+ documentAppend('<li class="previous"><a href="#%s">Previous</a></li>' % chapterIndexes[index - 1], False)
+ if index < len(chapterIndexes) - 1:
+ documentAppend('<li class="next"><a href="#%s">Next</a></li>' % chapterIndexes[index + 1], False)
+ documentAppend('</ul>', False)
+ content = html.escape(content, True)
+ content = re.sub(r'section ([0-9]+(.[0-9]+)*)', r'<a href="#\1">section \1</a>', content)
+
+ pctxt.set_content(content)
+
+ if not title:
+ lines = pctxt.get_lines()
+ pctxt.context['headers'] = {
+ 'title': '',
+ 'subtitle': '',
+ 'version': '',
+ 'author': '',
+ 'date': ''
+ }
+ if re.match("^-+$", pctxt.get_line().strip()):
+ # Try to analyze the header of the file, assuming it follows
+ # those rules :
+ # - it begins with a "separator line" (several '-' chars)
+ # - then the document title
+ # - an optional subtitle
+ # - a new separator line
+ # - the version
+ # - the author
+ # - the date
+ pctxt.next()
+ pctxt.context['headers']['title'] = pctxt.get_line().strip()
+ pctxt.next()
+ subtitle = ""
+ while not re.match("^-+$", pctxt.get_line().strip()):
+ subtitle += " " + pctxt.get_line().strip()
+ pctxt.next()
+ pctxt.context['headers']['subtitle'] += subtitle.strip()
+ if not pctxt.context['headers']['subtitle']:
+ # No subtitle, try to guess one from the title if it
+ # starts with the word "HAProxy"
+ if pctxt.context['headers']['title'].startswith('HAProxy '):
+ pctxt.context['headers']['subtitle'] = pctxt.context['headers']['title'][8:]
+ pctxt.context['headers']['title'] = 'HAProxy'
+ pctxt.next()
+ pctxt.context['headers']['version'] = pctxt.get_line().strip()
+ pctxt.next()
+ pctxt.context['headers']['author'] = pctxt.get_line().strip()
+ pctxt.next()
+ pctxt.context['headers']['date'] = pctxt.get_line().strip()
+ pctxt.next()
+ if HAPROXY_GIT_VERSION:
+ pctxt.context['headers']['version'] = 'version ' + HAPROXY_GIT_VERSION
+
+ # Skip header lines
+ pctxt.eat_lines()
+ pctxt.eat_empty_lines()
+
+ documentAppend('<div>', False)
+
+ delay = []
+ while pctxt.has_more_lines():
+ try:
+ specialSection = specialSections[details["chapter"]]
+ except:
+ specialSection = specialSections["default"]
+
+ line = pctxt.get_line()
+ if i < nblines - 1:
+ nextline = pctxt.get_line(1)
+ else:
+ nextline = ""
+
+ oldline = line
+ pctxt.stop = False
+ for parser in parsers:
+ line = parser.parse(line)
+ if pctxt.stop:
+ break
+ if oldline == line:
+ # nothing has changed,
+ # delays the rendering
+ if delay or line != "":
+ delay.append(line)
+ pctxt.next()
+ elif pctxt.stop:
+ while delay and delay[-1].strip() == "":
+ del delay[-1]
+ if delay:
+ remove_indent(delay)
+ documentAppend('<pre class="text">%s\n</pre>' % "\n".join(delay), False)
+ delay = []
+ documentAppend(line, False)
+ else:
+ while delay and delay[-1].strip() == "":
+ del delay[-1]
+ if delay:
+ remove_indent(delay)
+ documentAppend('<pre class="text">%s\n</pre>' % "\n".join(delay), False)
+ delay = []
+ documentAppend(line, True)
+ pctxt.next()
+
+ while delay and delay[-1].strip() == "":
+ del delay[-1]
+ if delay:
+ remove_indent(delay)
+ documentAppend('<pre class="text">%s\n</pre>' % "\n".join(delay), False)
+ delay = []
+ documentAppend('</div>')
+
+ if not hasSummary:
+ summaryTemplate = pctxt.templates.get_template('summary.html')
+ print(chapters)
+ document = summaryTemplate.render(
+ pctxt = pctxt,
+ chapters = chapters,
+ chapterIndexes = chapterIndexes,
+ ) + document
+
+
+ # Log warnings for keywords defined in several chapters
+ keyword_conflicts = {}
+ for keyword in keywords:
+ keyword_chapters = list(keywords[keyword])
+ keyword_chapters.sort()
+ if len(keyword_chapters) > 1:
+ print('Multi section keyword : "%s" in chapters %s' % (keyword, list(keyword_chapters)), file=sys.stderr)
+ keyword_conflicts[keyword] = keyword_chapters
+
+ keywords = list(keywords)
+ keywords.sort()
+
+ createLinks()
+
+ # Add the keywords conflicts to the keywords list to make them available in the search form
+ # And remove the original keyword which is now useless
+ for keyword in keyword_conflicts:
+ sections = keyword_conflicts[keyword]
+ offset = keywords.index(keyword)
+ for section in sections:
+ keywords.insert(offset, "%s (%s)" % (keyword, chapters[section]['title']))
+ offset += 1
+ keywords.remove(keyword)
+
+ print("Exporting to %s..." % outfile, file=sys.stderr)
+
+ template = pctxt.templates.get_template('template.html')
+ try:
+ footerTemplate = pctxt.templates.get_template('footer.html')
+ footer = footerTemplate.render(
+ pctxt = pctxt,
+ headers = pctxt.context['headers'],
+ document = document,
+ chapters = chapters,
+ chapterIndexes = chapterIndexes,
+ keywords = keywords,
+ keywordsCount = keywordsCount,
+ keyword_conflicts = keyword_conflicts,
+ version = VERSION,
+ date = datetime.datetime.now().strftime("%Y/%m/%d"),
+ )
+ except TopLevelLookupException:
+ footer = ""
+
+ fd = open(outfile,'w')
+
+ print(template.render(
+ pctxt = pctxt,
+ headers = pctxt.context['headers'],
+ base = base,
+ document = document,
+ chapters = chapters,
+ chapterIndexes = chapterIndexes,
+ keywords = keywords,
+ keywordsCount = keywordsCount,
+ keyword_conflicts = keyword_conflicts,
+ version = VERSION,
+ date = datetime.datetime.now().strftime("%Y/%m/%d"),
+ footer = footer
+ ), file=fd)
+ fd.close()
+
+if __name__ == '__main__':
+ main()