summaryrefslogtreecommitdiffstats
path: root/debian/dconv
diff options
context:
space:
mode:
Diffstat (limited to 'debian/dconv')
-rw-r--r--debian/dconv/LICENSE202
-rw-r--r--debian/dconv/NOTICE13
-rw-r--r--debian/dconv/README.md21
-rw-r--r--debian/dconv/css/check.pngbin0 -> 531 bytes
-rw-r--r--debian/dconv/css/cross.pngbin0 -> 640 bytes
-rw-r--r--debian/dconv/css/page.css223
-rwxr-xr-xdebian/dconv/haproxy-dconv.py534
-rw-r--r--debian/dconv/img/logo-med.pngbin0 -> 3522 bytes
-rw-r--r--debian/dconv/js/typeahead.bundle.js2451
-rw-r--r--debian/dconv/parser/__init__.py81
-rw-r--r--debian/dconv/parser/arguments.py132
-rw-r--r--debian/dconv/parser/example.py77
-rw-r--r--debian/dconv/parser/keyword.py142
-rw-r--r--debian/dconv/parser/seealso.py32
-rw-r--r--debian/dconv/parser/table.py244
-rw-r--r--debian/dconv/parser/underline.py16
-rw-r--r--debian/dconv/templates/parser/arguments.tpl9
-rw-r--r--debian/dconv/templates/parser/example.tpl12
-rw-r--r--debian/dconv/templates/parser/example/comment.tpl1
-rw-r--r--debian/dconv/templates/parser/seealso.tpl1
-rw-r--r--debian/dconv/templates/parser/table.tpl11
-rw-r--r--debian/dconv/templates/parser/table/header.tpl6
-rw-r--r--debian/dconv/templates/parser/table/row.tpl36
-rw-r--r--debian/dconv/templates/parser/underline.tpl1
-rw-r--r--debian/dconv/templates/summary.html43
-rw-r--r--debian/dconv/templates/template.html238
-rwxr-xr-xdebian/dconv/tools/generate-docs.sh177
27 files changed, 4703 insertions, 0 deletions
diff --git a/debian/dconv/LICENSE b/debian/dconv/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/debian/dconv/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/debian/dconv/NOTICE b/debian/dconv/NOTICE
new file mode 100644
index 0000000..c9575a7
--- /dev/null
+++ b/debian/dconv/NOTICE
@@ -0,0 +1,13 @@
+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.
diff --git a/debian/dconv/README.md b/debian/dconv/README.md
new file mode 100644
index 0000000..4ca89b2
--- /dev/null
+++ b/debian/dconv/README.md
@@ -0,0 +1,21 @@
+# HAProxy Documentation Converter
+
+Made to convert the HAProxy documentation into HTML.
+
+More than HTML, the main goal is to provide easy navigation.
+
+## Documentations
+
+A bot periodically fetches last commits for HAProxy 1.4 and 1.5 to produce up-to-date documentations.
+
+Converted documentations are then stored online :
+- HAProxy 1.4 Configuration Manual : [stable](http://cbonte.github.com/haproxy-dconv/configuration-1.4.html) / [snapshot](http://cbonte.github.com/haproxy-dconv/snapshot/configuration-1.4.html)
+- HAProxy 1.5 Configuration Manual : [stable](http://cbonte.github.com/haproxy-dconv/configuration-1.5.html) / [snapshot](http://cbonte.github.com/haproxy-dconv/snapshot/configuration-1.5.html)
+- HAProxy 1.6 Configuration Manual : [stable](http://cbonte.github.com/haproxy-dconv/configuration-1.6.html) / [snapshot](http://cbonte.github.com/haproxy-dconv/snapshot/configuration-1.6.html)
+
+
+## Contribute
+
+The project now lives by itself, as it is sufficiently useable. But I'm sure we can do even better.
+Feel free to report feature requests or to provide patches !
+
diff --git a/debian/dconv/css/check.png b/debian/dconv/css/check.png
new file mode 100644
index 0000000..a7fab32
--- /dev/null
+++ b/debian/dconv/css/check.png
Binary files differ
diff --git a/debian/dconv/css/cross.png b/debian/dconv/css/cross.png
new file mode 100644
index 0000000..24f5064
--- /dev/null
+++ b/debian/dconv/css/cross.png
Binary files differ
diff --git a/debian/dconv/css/page.css b/debian/dconv/css/page.css
new file mode 100644
index 0000000..b48fdd2
--- /dev/null
+++ b/debian/dconv/css/page.css
@@ -0,0 +1,223 @@
+/* Global Styles */
+
+body {
+ margin-top: 50px;
+ background: #eee;
+}
+
+a.anchor {
+ display: block; position: relative; top: -50px; visibility: hidden;
+}
+
+/* ------------------------------- */
+
+/* Wrappers */
+
+/* ------------------------------- */
+
+#wrapper {
+ width: 100%;
+}
+
+#page-wrapper {
+ padding: 0 15px 50px;
+ width: 740px;
+ background-color: #fff;
+ margin-left: 250px;
+}
+
+#sidebar {
+ position: fixed;
+ width: 250px;
+ top: 50px;
+ bottom: 0;
+ padding: 15px;
+ background: #f5f5f5;
+ border-right: 1px solid #ccc;
+}
+
+
+/* ------------------------------- */
+
+/* Twitter typeahead.js */
+
+/* ------------------------------- */
+
+.twitter-typeahead {
+ width: 100%;
+}
+.typeahead,
+.tt-query,
+.tt-hint {
+ width: 100%;
+ padding: 8px 12px;
+ border: 2px solid #ccc;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
+ outline: none;
+}
+
+.typeahead {
+ background-color: #fff;
+}
+
+.typeahead:focus {
+ border: 2px solid #0097cf;
+}
+
+.tt-query {
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.tt-hint {
+ color: #999
+}
+
+.tt-menu {
+ width: 100%;
+ margin-top: 4px;
+ padding: 8px 0;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
+ -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ box-shadow: 0 5px 10px rgba(0,0,0,.2);
+}
+
+.tt-suggestion {
+ padding: 3px 8px;
+ line-height: 24px;
+}
+
+.tt-suggestion:hover {
+ cursor: pointer;
+ color: #fff;
+ background-color: #0097cf;
+}
+
+.tt-suggestion.tt-cursor {
+ color: #fff;
+ background-color: #0097cf;
+
+}
+
+.tt-suggestion p {
+ margin: 0;
+}
+
+#searchKeyword {
+ width: 100%;
+ margin: 0;
+}
+
+#searchKeyword .tt-menu {
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+/* ------------------------------- */
+
+/* Misc */
+
+/* ------------------------------- */
+
+.well-small ul {
+ padding: 0px;
+}
+.table th,
+.table td.pagination-centered {
+ text-align: center;
+}
+
+pre {
+ overflow: visible; /* Workaround for dropdown menus */
+}
+
+pre.text {
+ padding: 0;
+ font-size: 13px;
+ color: #000;
+ background: transparent;
+ border: none;
+ margin-bottom: 18px;
+}
+pre.arguments {
+ font-size: 13px;
+ color: #000;
+ background: transparent;
+}
+
+.comment {
+ color: #888;
+}
+small, .small {
+ color: #888;
+}
+.level1 {
+ font-size: 125%;
+}
+.sublevels {
+ border-left: 1px solid #ccc;
+ padding-left: 10px;
+}
+.tab {
+ padding-left: 20px;
+}
+.keyword {
+ font-family: Menlo, Monaco, "Courier New", monospace;
+ white-space: pre;
+ background: #eee;
+ border-top: 1px solid #fff;
+ border-bottom: 1px solid #ccc;
+}
+
+.label-see-also {
+ background-color: #999;
+}
+.label-disabled {
+ background-color: #ccc;
+}
+h5 {
+ text-decoration: underline;
+}
+
+.example-desc {
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 18px;
+}
+.noheight {
+ min-height: 0 !important;
+}
+.separator {
+ margin-bottom: 18px;
+}
+
+div {
+ word-wrap: break-word;
+}
+
+html, body {
+ width: 100%;
+ min-height: 100%:
+}
+
+.dropdown-menu > li {
+ white-space: nowrap;
+}
+/* TEMPORARILY HACKS WHILE PRE TAGS ARE USED
+-------------------------------------------------- */
+
+h5,
+.unpre,
+.example-desc,
+.dropdown-menu {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ white-space: normal;
+}
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()
diff --git a/debian/dconv/img/logo-med.png b/debian/dconv/img/logo-med.png
new file mode 100644
index 0000000..1be03b2
--- /dev/null
+++ b/debian/dconv/img/logo-med.png
Binary files differ
diff --git a/debian/dconv/js/typeahead.bundle.js b/debian/dconv/js/typeahead.bundle.js
new file mode 100644
index 0000000..bb0c8ae
--- /dev/null
+++ b/debian/dconv/js/typeahead.bundle.js
@@ -0,0 +1,2451 @@
+/*!
+ * typeahead.js 0.11.1
+ * https://github.com/twitter/typeahead.js
+ * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+(function(root, factory) {
+ if (typeof define === "function" && define.amd) {
+ define("bloodhound", [ "jquery" ], function(a0) {
+ return root["Bloodhound"] = factory(a0);
+ });
+ } else if (typeof exports === "object") {
+ module.exports = factory(require("jquery"));
+ } else {
+ root["Bloodhound"] = factory(jQuery);
+ }
+})(this, function($) {
+ var _ = function() {
+ "use strict";
+ return {
+ isMsie: function() {
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
+ },
+ isBlankString: function(str) {
+ return !str || /^\s*$/.test(str);
+ },
+ escapeRegExChars: function(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+ },
+ isString: function(obj) {
+ return typeof obj === "string";
+ },
+ isNumber: function(obj) {
+ return typeof obj === "number";
+ },
+ isArray: $.isArray,
+ isFunction: $.isFunction,
+ isObject: $.isPlainObject,
+ isUndefined: function(obj) {
+ return typeof obj === "undefined";
+ },
+ isElement: function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ },
+ isJQuery: function(obj) {
+ return obj instanceof $;
+ },
+ toStr: function toStr(s) {
+ return _.isUndefined(s) || s === null ? "" : s + "";
+ },
+ bind: $.proxy,
+ each: function(collection, cb) {
+ $.each(collection, reverseArgs);
+ function reverseArgs(index, value) {
+ return cb(value, index);
+ }
+ },
+ map: $.map,
+ filter: $.grep,
+ every: function(obj, test) {
+ var result = true;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (!(result = test.call(null, val, key, obj))) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ some: function(obj, test) {
+ var result = false;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (result = test.call(null, val, key, obj)) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ mixin: $.extend,
+ identity: function(x) {
+ return x;
+ },
+ clone: function(obj) {
+ return $.extend(true, {}, obj);
+ },
+ getIdGenerator: function() {
+ var counter = 0;
+ return function() {
+ return counter++;
+ };
+ },
+ templatify: function templatify(obj) {
+ return $.isFunction(obj) ? obj : template;
+ function template() {
+ return String(obj);
+ }
+ },
+ defer: function(fn) {
+ setTimeout(fn, 0);
+ },
+ debounce: function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments, later, callNow;
+ later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+ return result;
+ };
+ },
+ throttle: function(func, wait) {
+ var context, args, timeout, result, previous, later;
+ previous = 0;
+ later = function() {
+ previous = new Date();
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date(), remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ },
+ stringify: function(val) {
+ return _.isString(val) ? val : JSON.stringify(val);
+ },
+ noop: function() {}
+ };
+ }();
+ var VERSION = "0.11.1";
+ var tokenizers = function() {
+ "use strict";
+ return {
+ nonword: nonword,
+ whitespace: whitespace,
+ obj: {
+ nonword: getObjTokenizer(nonword),
+ whitespace: getObjTokenizer(whitespace)
+ }
+ };
+ function whitespace(str) {
+ str = _.toStr(str);
+ return str ? str.split(/\s+/) : [];
+ }
+ function nonword(str) {
+ str = _.toStr(str);
+ return str ? str.split(/\W+/) : [];
+ }
+ function getObjTokenizer(tokenizer) {
+ return function setKey(keys) {
+ keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
+ return function tokenize(o) {
+ var tokens = [];
+ _.each(keys, function(k) {
+ tokens = tokens.concat(tokenizer(_.toStr(o[k])));
+ });
+ return tokens;
+ };
+ };
+ }
+ }();
+ var LruCache = function() {
+ "use strict";
+ function LruCache(maxSize) {
+ this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
+ this.reset();
+ if (this.maxSize <= 0) {
+ this.set = this.get = $.noop;
+ }
+ }
+ _.mixin(LruCache.prototype, {
+ set: function set(key, val) {
+ var tailItem = this.list.tail, node;
+ if (this.size >= this.maxSize) {
+ this.list.remove(tailItem);
+ delete this.hash[tailItem.key];
+ this.size--;
+ }
+ if (node = this.hash[key]) {
+ node.val = val;
+ this.list.moveToFront(node);
+ } else {
+ node = new Node(key, val);
+ this.list.add(node);
+ this.hash[key] = node;
+ this.size++;
+ }
+ },
+ get: function get(key) {
+ var node = this.hash[key];
+ if (node) {
+ this.list.moveToFront(node);
+ return node.val;
+ }
+ },
+ reset: function reset() {
+ this.size = 0;
+ this.hash = {};
+ this.list = new List();
+ }
+ });
+ function List() {
+ this.head = this.tail = null;
+ }
+ _.mixin(List.prototype, {
+ add: function add(node) {
+ if (this.head) {
+ node.next = this.head;
+ this.head.prev = node;
+ }
+ this.head = node;
+ this.tail = this.tail || node;
+ },
+ remove: function remove(node) {
+ node.prev ? node.prev.next = node.next : this.head = node.next;
+ node.next ? node.next.prev = node.prev : this.tail = node.prev;
+ },
+ moveToFront: function(node) {
+ this.remove(node);
+ this.add(node);
+ }
+ });
+ function Node(key, val) {
+ this.key = key;
+ this.val = val;
+ this.prev = this.next = null;
+ }
+ return LruCache;
+ }();
+ var PersistentStorage = function() {
+ "use strict";
+ var LOCAL_STORAGE;
+ try {
+ LOCAL_STORAGE = window.localStorage;
+ LOCAL_STORAGE.setItem("~~~", "!");
+ LOCAL_STORAGE.removeItem("~~~");
+ } catch (err) {
+ LOCAL_STORAGE = null;
+ }
+ function PersistentStorage(namespace, override) {
+ this.prefix = [ "__", namespace, "__" ].join("");
+ this.ttlKey = "__ttl__";
+ this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
+ this.ls = override || LOCAL_STORAGE;
+ !this.ls && this._noop();
+ }
+ _.mixin(PersistentStorage.prototype, {
+ _prefix: function(key) {
+ return this.prefix + key;
+ },
+ _ttlKey: function(key) {
+ return this._prefix(key) + this.ttlKey;
+ },
+ _noop: function() {
+ this.get = this.set = this.remove = this.clear = this.isExpired = _.noop;
+ },
+ _safeSet: function(key, val) {
+ try {
+ this.ls.setItem(key, val);
+ } catch (err) {
+ if (err.name === "QuotaExceededError") {
+ this.clear();
+ this._noop();
+ }
+ }
+ },
+ get: function(key) {
+ if (this.isExpired(key)) {
+ this.remove(key);
+ }
+ return decode(this.ls.getItem(this._prefix(key)));
+ },
+ set: function(key, val, ttl) {
+ if (_.isNumber(ttl)) {
+ this._safeSet(this._ttlKey(key), encode(now() + ttl));
+ } else {
+ this.ls.removeItem(this._ttlKey(key));
+ }
+ return this._safeSet(this._prefix(key), encode(val));
+ },
+ remove: function(key) {
+ this.ls.removeItem(this._ttlKey(key));
+ this.ls.removeItem(this._prefix(key));
+ return this;
+ },
+ clear: function() {
+ var i, keys = gatherMatchingKeys(this.keyMatcher);
+ for (i = keys.length; i--; ) {
+ this.remove(keys[i]);
+ }
+ return this;
+ },
+ isExpired: function(key) {
+ var ttl = decode(this.ls.getItem(this._ttlKey(key)));
+ return _.isNumber(ttl) && now() > ttl ? true : false;
+ }
+ });
+ return PersistentStorage;
+ function now() {
+ return new Date().getTime();
+ }
+ function encode(val) {
+ return JSON.stringify(_.isUndefined(val) ? null : val);
+ }
+ function decode(val) {
+ return $.parseJSON(val);
+ }
+ function gatherMatchingKeys(keyMatcher) {
+ var i, key, keys = [], len = LOCAL_STORAGE.length;
+ for (i = 0; i < len; i++) {
+ if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) {
+ keys.push(key.replace(keyMatcher, ""));
+ }
+ }
+ return keys;
+ }
+ }();
+ var Transport = function() {
+ "use strict";
+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10);
+ function Transport(o) {
+ o = o || {};
+ this.cancelled = false;
+ this.lastReq = null;
+ this._send = o.transport;
+ this._get = o.limiter ? o.limiter(this._get) : this._get;
+ this._cache = o.cache === false ? new LruCache(0) : sharedCache;
+ }
+ Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
+ maxPendingRequests = num;
+ };
+ Transport.resetCache = function resetCache() {
+ sharedCache.reset();
+ };
+ _.mixin(Transport.prototype, {
+ _fingerprint: function fingerprint(o) {
+ o = o || {};
+ return o.url + o.type + $.param(o.data || {});
+ },
+ _get: function(o, cb) {
+ var that = this, fingerprint, jqXhr;
+ fingerprint = this._fingerprint(o);
+ if (this.cancelled || fingerprint !== this.lastReq) {
+ return;
+ }
+ if (jqXhr = pendingRequests[fingerprint]) {
+ jqXhr.done(done).fail(fail);
+ } else if (pendingRequestsCount < maxPendingRequests) {
+ pendingRequestsCount++;
+ pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
+ } else {
+ this.onDeckRequestArgs = [].slice.call(arguments, 0);
+ }
+ function done(resp) {
+ cb(null, resp);
+ that._cache.set(fingerprint, resp);
+ }
+ function fail() {
+ cb(true);
+ }
+ function always() {
+ pendingRequestsCount--;
+ delete pendingRequests[fingerprint];
+ if (that.onDeckRequestArgs) {
+ that._get.apply(that, that.onDeckRequestArgs);
+ that.onDeckRequestArgs = null;
+ }
+ }
+ },
+ get: function(o, cb) {
+ var resp, fingerprint;
+ cb = cb || $.noop;
+ o = _.isString(o) ? {
+ url: o
+ } : o || {};
+ fingerprint = this._fingerprint(o);
+ this.cancelled = false;
+ this.lastReq = fingerprint;
+ if (resp = this._cache.get(fingerprint)) {
+ cb(null, resp);
+ } else {
+ this._get(o, cb);
+ }
+ },
+ cancel: function() {
+ this.cancelled = true;
+ }
+ });
+ return Transport;
+ }();
+ var SearchIndex = window.SearchIndex = function() {
+ "use strict";
+ var CHILDREN = "c", IDS = "i";
+ function SearchIndex(o) {
+ o = o || {};
+ if (!o.datumTokenizer || !o.queryTokenizer) {
+ $.error("datumTokenizer and queryTokenizer are both required");
+ }
+ this.identify = o.identify || _.stringify;
+ this.datumTokenizer = o.datumTokenizer;
+ this.queryTokenizer = o.queryTokenizer;
+ this.reset();
+ }
+ _.mixin(SearchIndex.prototype, {
+ bootstrap: function bootstrap(o) {
+ this.datums = o.datums;
+ this.trie = o.trie;
+ },
+ add: function(data) {
+ var that = this;
+ data = _.isArray(data) ? data : [ data ];
+ _.each(data, function(datum) {
+ var id, tokens;
+ that.datums[id = that.identify(datum)] = datum;
+ tokens = normalizeTokens(that.datumTokenizer(datum));
+ _.each(tokens, function(token) {
+ var node, chars, ch;
+ node = that.trie;
+ chars = token.split("");
+ while (ch = chars.shift()) {
+ node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode());
+ node[IDS].push(id);
+ }
+ });
+ });
+ },
+ get: function get(ids) {
+ var that = this;
+ return _.map(ids, function(id) {
+ return that.datums[id];
+ });
+ },
+ search: function search(query) {
+ var that = this, tokens, matches;
+ tokens = normalizeTokens(this.queryTokenizer(query));
+ _.each(tokens, function(token) {
+ var node, chars, ch, ids;
+ if (matches && matches.length === 0) {
+ return false;
+ }
+ node = that.trie;
+ chars = token.split("");
+ while (node && (ch = chars.shift())) {
+ node = node[CHILDREN][ch];
+ }
+ if (node && chars.length === 0) {
+ ids = node[IDS].slice(0);
+ matches = matches ? getIntersection(matches, ids) : ids;
+ } else {
+ matches = [];
+ return false;
+ }
+ });
+ return matches ? _.map(unique(matches), function(id) {
+ return that.datums[id];
+ }) : [];
+ },
+ all: function all() {
+ var values = [];
+ for (var key in this.datums) {
+ values.push(this.datums[key]);
+ }
+ return values;
+ },
+ reset: function reset() {
+ this.datums = {};
+ this.trie = newNode();
+ },
+ serialize: function serialize() {
+ return {
+ datums: this.datums,
+ trie: this.trie
+ };
+ }
+ });
+ return SearchIndex;
+ function normalizeTokens(tokens) {
+ tokens = _.filter(tokens, function(token) {
+ return !!token;
+ });
+ tokens = _.map(tokens, function(token) {
+ return token.toLowerCase();
+ });
+ return tokens;
+ }
+ function newNode() {
+ var node = {};
+ node[IDS] = [];
+ node[CHILDREN] = {};
+ return node;
+ }
+ function unique(array) {
+ var seen = {}, uniques = [];
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (!seen[array[i]]) {
+ seen[array[i]] = true;
+ uniques.push(array[i]);
+ }
+ }
+ return uniques;
+ }
+ function getIntersection(arrayA, arrayB) {
+ var ai = 0, bi = 0, intersection = [];
+ arrayA = arrayA.sort();
+ arrayB = arrayB.sort();
+ var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
+ while (ai < lenArrayA && bi < lenArrayB) {
+ if (arrayA[ai] < arrayB[bi]) {
+ ai++;
+ } else if (arrayA[ai] > arrayB[bi]) {
+ bi++;
+ } else {
+ intersection.push(arrayA[ai]);
+ ai++;
+ bi++;
+ }
+ }
+ return intersection;
+ }
+ }();
+ var Prefetch = function() {
+ "use strict";
+ var keys;
+ keys = {
+ data: "data",
+ protocol: "protocol",
+ thumbprint: "thumbprint"
+ };
+ function Prefetch(o) {
+ this.url = o.url;
+ this.ttl = o.ttl;
+ this.cache = o.cache;
+ this.prepare = o.prepare;
+ this.transform = o.transform;
+ this.transport = o.transport;
+ this.thumbprint = o.thumbprint;
+ this.storage = new PersistentStorage(o.cacheKey);
+ }
+ _.mixin(Prefetch.prototype, {
+ _settings: function settings() {
+ return {
+ url: this.url,
+ type: "GET",
+ dataType: "json"
+ };
+ },
+ store: function store(data) {
+ if (!this.cache) {
+ return;
+ }
+ this.storage.set(keys.data, data, this.ttl);
+ this.storage.set(keys.protocol, location.protocol, this.ttl);
+ this.storage.set(keys.thumbprint, this.thumbprint, this.ttl);
+ },
+ fromCache: function fromCache() {
+ var stored = {}, isExpired;
+ if (!this.cache) {
+ return null;
+ }
+ stored.data = this.storage.get(keys.data);
+ stored.protocol = this.storage.get(keys.protocol);
+ stored.thumbprint = this.storage.get(keys.thumbprint);
+ isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol;
+ return stored.data && !isExpired ? stored.data : null;
+ },
+ fromNetwork: function(cb) {
+ var that = this, settings;
+ if (!cb) {
+ return;
+ }
+ settings = this.prepare(this._settings());
+ this.transport(settings).fail(onError).done(onResponse);
+ function onError() {
+ cb(true);
+ }
+ function onResponse(resp) {
+ cb(null, that.transform(resp));
+ }
+ },
+ clear: function clear() {
+ this.storage.clear();
+ return this;
+ }
+ });
+ return Prefetch;
+ }();
+ var Remote = function() {
+ "use strict";
+ function Remote(o) {
+ this.url = o.url;
+ this.prepare = o.prepare;
+ this.transform = o.transform;
+ this.transport = new Transport({
+ cache: o.cache,
+ limiter: o.limiter,
+ transport: o.transport
+ });
+ }
+ _.mixin(Remote.prototype, {
+ _settings: function settings() {
+ return {
+ url: this.url,
+ type: "GET",
+ dataType: "json"
+ };
+ },
+ get: function get(query, cb) {
+ var that = this, settings;
+ if (!cb) {
+ return;
+ }
+ query = query || "";
+ settings = this.prepare(query, this._settings());
+ return this.transport.get(settings, onResponse);
+ function onResponse(err, resp) {
+ err ? cb([]) : cb(that.transform(resp));
+ }
+ },
+ cancelLastRequest: function cancelLastRequest() {
+ this.transport.cancel();
+ }
+ });
+ return Remote;
+ }();
+ var oParser = function() {
+ "use strict";
+ return function parse(o) {
+ var defaults, sorter;
+ defaults = {
+ initialize: true,
+ identify: _.stringify,
+ datumTokenizer: null,
+ queryTokenizer: null,
+ sufficient: 5,
+ sorter: null,
+ local: [],
+ prefetch: null,
+ remote: null
+ };
+ o = _.mixin(defaults, o || {});
+ !o.datumTokenizer && $.error("datumTokenizer is required");
+ !o.queryTokenizer && $.error("queryTokenizer is required");
+ sorter = o.sorter;
+ o.sorter = sorter ? function(x) {
+ return x.sort(sorter);
+ } : _.identity;
+ o.local = _.isFunction(o.local) ? o.local() : o.local;
+ o.prefetch = parsePrefetch(o.prefetch);
+ o.remote = parseRemote(o.remote);
+ return o;
+ };
+ function parsePrefetch(o) {
+ var defaults;
+ if (!o) {
+ return null;
+ }
+ defaults = {
+ url: null,
+ ttl: 24 * 60 * 60 * 1e3,
+ cache: true,
+ cacheKey: null,
+ thumbprint: "",
+ prepare: _.identity,
+ transform: _.identity,
+ transport: null
+ };
+ o = _.isString(o) ? {
+ url: o
+ } : o;
+ o = _.mixin(defaults, o);
+ !o.url && $.error("prefetch requires url to be set");
+ o.transform = o.filter || o.transform;
+ o.cacheKey = o.cacheKey || o.url;
+ o.thumbprint = VERSION + o.thumbprint;
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
+ return o;
+ }
+ function parseRemote(o) {
+ var defaults;
+ if (!o) {
+ return;
+ }
+ defaults = {
+ url: null,
+ cache: true,
+ prepare: null,
+ replace: null,
+ wildcard: null,
+ limiter: null,
+ rateLimitBy: "debounce",
+ rateLimitWait: 300,
+ transform: _.identity,
+ transport: null
+ };
+ o = _.isString(o) ? {
+ url: o
+ } : o;
+ o = _.mixin(defaults, o);
+ !o.url && $.error("remote requires url to be set");
+ o.transform = o.filter || o.transform;
+ o.prepare = toRemotePrepare(o);
+ o.limiter = toLimiter(o);
+ o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
+ delete o.replace;
+ delete o.wildcard;
+ delete o.rateLimitBy;
+ delete o.rateLimitWait;
+ return o;
+ }
+ function toRemotePrepare(o) {
+ var prepare, replace, wildcard;
+ prepare = o.prepare;
+ replace = o.replace;
+ wildcard = o.wildcard;
+ if (prepare) {
+ return prepare;
+ }
+ if (replace) {
+ prepare = prepareByReplace;
+ } else if (o.wildcard) {
+ prepare = prepareByWildcard;
+ } else {
+ prepare = idenityPrepare;
+ }
+ return prepare;
+ function prepareByReplace(query, settings) {
+ settings.url = replace(settings.url, query);
+ return settings;
+ }
+ function prepareByWildcard(query, settings) {
+ settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
+ return settings;
+ }
+ function idenityPrepare(query, settings) {
+ return settings;
+ }
+ }
+ function toLimiter(o) {
+ var limiter, method, wait;
+ limiter = o.limiter;
+ method = o.rateLimitBy;
+ wait = o.rateLimitWait;
+ if (!limiter) {
+ limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait);
+ }
+ return limiter;
+ function debounce(wait) {
+ return function debounce(fn) {
+ return _.debounce(fn, wait);
+ };
+ }
+ function throttle(wait) {
+ return function throttle(fn) {
+ return _.throttle(fn, wait);
+ };
+ }
+ }
+ function callbackToDeferred(fn) {
+ return function wrapper(o) {
+ var deferred = $.Deferred();
+ fn(o, onSuccess, onError);
+ return deferred;
+ function onSuccess(resp) {
+ _.defer(function() {
+ deferred.resolve(resp);
+ });
+ }
+ function onError(err) {
+ _.defer(function() {
+ deferred.reject(err);
+ });
+ }
+ };
+ }
+ }();
+ var Bloodhound = function() {
+ "use strict";
+ var old;
+ old = window && window.Bloodhound;
+ function Bloodhound(o) {
+ o = oParser(o);
+ this.sorter = o.sorter;
+ this.identify = o.identify;
+ this.sufficient = o.sufficient;
+ this.local = o.local;
+ this.remote = o.remote ? new Remote(o.remote) : null;
+ this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
+ this.index = new SearchIndex({
+ identify: this.identify,
+ datumTokenizer: o.datumTokenizer,
+ queryTokenizer: o.queryTokenizer
+ });
+ o.initialize !== false && this.initialize();
+ }
+ Bloodhound.noConflict = function noConflict() {
+ window && (window.Bloodhound = old);
+ return Bloodhound;
+ };
+ Bloodhound.tokenizers = tokenizers;
+ _.mixin(Bloodhound.prototype, {
+ __ttAdapter: function ttAdapter() {
+ var that = this;
+ return this.remote ? withAsync : withoutAsync;
+ function withAsync(query, sync, async) {
+ return that.search(query, sync, async);
+ }
+ function withoutAsync(query, sync) {
+ return that.search(query, sync);
+ }
+ },
+ _loadPrefetch: function loadPrefetch() {
+ var that = this, deferred, serialized;
+ deferred = $.Deferred();
+ if (!this.prefetch) {
+ deferred.resolve();
+ } else if (serialized = this.prefetch.fromCache()) {
+ this.index.bootstrap(serialized);
+ deferred.resolve();
+ } else {
+ this.prefetch.fromNetwork(done);
+ }
+ return deferred.promise();
+ function done(err, data) {
+ if (err) {
+ return deferred.reject();
+ }
+ that.add(data);
+ that.prefetch.store(that.index.serialize());
+ deferred.resolve();
+ }
+ },
+ _initialize: function initialize() {
+ var that = this, deferred;
+ this.clear();
+ (this.initPromise = this._loadPrefetch()).done(addLocalToIndex);
+ return this.initPromise;
+ function addLocalToIndex() {
+ that.add(that.local);
+ }
+ },
+ initialize: function initialize(force) {
+ return !this.initPromise || force ? this._initialize() : this.initPromise;
+ },
+ add: function add(data) {
+ this.index.add(data);
+ return this;
+ },
+ get: function get(ids) {
+ ids = _.isArray(ids) ? ids : [].slice.call(arguments);
+ return this.index.get(ids);
+ },
+ search: function search(query, sync, async) {
+ var that = this, local;
+ local = this.sorter(this.index.search(query));
+ sync(this.remote ? local.slice() : local);
+ if (this.remote && local.length < this.sufficient) {
+ this.remote.get(query, processRemote);
+ } else if (this.remote) {
+ this.remote.cancelLastRequest();
+ }
+ return this;
+ function processRemote(remote) {
+ var nonDuplicates = [];
+ _.each(remote, function(r) {
+ !_.some(local, function(l) {
+ return that.identify(r) === that.identify(l);
+ }) && nonDuplicates.push(r);
+ });
+ async && async(nonDuplicates);
+ }
+ },
+ all: function all() {
+ return this.index.all();
+ },
+ clear: function clear() {
+ this.index.reset();
+ return this;
+ },
+ clearPrefetchCache: function clearPrefetchCache() {
+ this.prefetch && this.prefetch.clear();
+ return this;
+ },
+ clearRemoteCache: function clearRemoteCache() {
+ Transport.resetCache();
+ return this;
+ },
+ ttAdapter: function ttAdapter() {
+ return this.__ttAdapter();
+ }
+ });
+ return Bloodhound;
+ }();
+ return Bloodhound;
+});
+
+(function(root, factory) {
+ if (typeof define === "function" && define.amd) {
+ define("typeahead.js", [ "jquery" ], function(a0) {
+ return factory(a0);
+ });
+ } else if (typeof exports === "object") {
+ module.exports = factory(require("jquery"));
+ } else {
+ factory(jQuery);
+ }
+})(this, function($) {
+ var _ = function() {
+ "use strict";
+ return {
+ isMsie: function() {
+ return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
+ },
+ isBlankString: function(str) {
+ return !str || /^\s*$/.test(str);
+ },
+ escapeRegExChars: function(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+ },
+ isString: function(obj) {
+ return typeof obj === "string";
+ },
+ isNumber: function(obj) {
+ return typeof obj === "number";
+ },
+ isArray: $.isArray,
+ isFunction: $.isFunction,
+ isObject: $.isPlainObject,
+ isUndefined: function(obj) {
+ return typeof obj === "undefined";
+ },
+ isElement: function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ },
+ isJQuery: function(obj) {
+ return obj instanceof $;
+ },
+ toStr: function toStr(s) {
+ return _.isUndefined(s) || s === null ? "" : s + "";
+ },
+ bind: $.proxy,
+ each: function(collection, cb) {
+ $.each(collection, reverseArgs);
+ function reverseArgs(index, value) {
+ return cb(value, index);
+ }
+ },
+ map: $.map,
+ filter: $.grep,
+ every: function(obj, test) {
+ var result = true;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (!(result = test.call(null, val, key, obj))) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ some: function(obj, test) {
+ var result = false;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (result = test.call(null, val, key, obj)) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ mixin: $.extend,
+ identity: function(x) {
+ return x;
+ },
+ clone: function(obj) {
+ return $.extend(true, {}, obj);
+ },
+ getIdGenerator: function() {
+ var counter = 0;
+ return function() {
+ return counter++;
+ };
+ },
+ templatify: function templatify(obj) {
+ return $.isFunction(obj) ? obj : template;
+ function template() {
+ return String(obj);
+ }
+ },
+ defer: function(fn) {
+ setTimeout(fn, 0);
+ },
+ debounce: function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments, later, callNow;
+ later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+ return result;
+ };
+ },
+ throttle: function(func, wait) {
+ var context, args, timeout, result, previous, later;
+ previous = 0;
+ later = function() {
+ previous = new Date();
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date(), remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ },
+ stringify: function(val) {
+ return _.isString(val) ? val : JSON.stringify(val);
+ },
+ noop: function() {}
+ };
+ }();
+ var WWW = function() {
+ "use strict";
+ var defaultClassNames = {
+ wrapper: "twitter-typeahead",
+ input: "tt-input",
+ hint: "tt-hint",
+ menu: "tt-menu",
+ dataset: "tt-dataset",
+ suggestion: "tt-suggestion",
+ selectable: "tt-selectable",
+ empty: "tt-empty",
+ open: "tt-open",
+ cursor: "tt-cursor",
+ highlight: "tt-highlight"
+ };
+ return build;
+ function build(o) {
+ var www, classes;
+ classes = _.mixin({}, defaultClassNames, o);
+ www = {
+ css: buildCss(),
+ classes: classes,
+ html: buildHtml(classes),
+ selectors: buildSelectors(classes)
+ };
+ return {
+ css: www.css,
+ html: www.html,
+ classes: www.classes,
+ selectors: www.selectors,
+ mixin: function(o) {
+ _.mixin(o, www);
+ }
+ };
+ }
+ function buildHtml(c) {
+ return {
+ wrapper: '<span class="' + c.wrapper + '"></span>',
+ menu: '<div class="' + c.menu + '"></div>'
+ };
+ }
+ function buildSelectors(classes) {
+ var selectors = {};
+ _.each(classes, function(v, k) {
+ selectors[k] = "." + v;
+ });
+ return selectors;
+ }
+ function buildCss() {
+ var css = {
+ wrapper: {
+ position: "relative",
+ display: "inline-block"
+ },
+ hint: {
+ position: "absolute",
+ top: "0",
+ left: "0",
+ borderColor: "transparent",
+ boxShadow: "none",
+ opacity: "1"
+ },
+ input: {
+ position: "relative",
+ verticalAlign: "top",
+ backgroundColor: "transparent"
+ },
+ inputWithNoHint: {
+ position: "relative",
+ verticalAlign: "top"
+ },
+ menu: {
+ position: "absolute",
+ top: "100%",
+ left: "0",
+ zIndex: "100",
+ display: "none"
+ },
+ ltr: {
+ left: "0",
+ right: "auto"
+ },
+ rtl: {
+ left: "auto",
+ right: " 0"
+ }
+ };
+ if (_.isMsie()) {
+ _.mixin(css.input, {
+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
+ });
+ }
+ return css;
+ }
+ }();
+ var EventBus = function() {
+ "use strict";
+ var namespace, deprecationMap;
+ namespace = "typeahead:";
+ deprecationMap = {
+ render: "rendered",
+ cursorchange: "cursorchanged",
+ select: "selected",
+ autocomplete: "autocompleted"
+ };
+ function EventBus(o) {
+ if (!o || !o.el) {
+ $.error("EventBus initialized without el");
+ }
+ this.$el = $(o.el);
+ }
+ _.mixin(EventBus.prototype, {
+ _trigger: function(type, args) {
+ var $e;
+ $e = $.Event(namespace + type);
+ (args = args || []).unshift($e);
+ this.$el.trigger.apply(this.$el, args);
+ return $e;
+ },
+ before: function(type) {
+ var args, $e;
+ args = [].slice.call(arguments, 1);
+ $e = this._trigger("before" + type, args);
+ return $e.isDefaultPrevented();
+ },
+ trigger: function(type) {
+ var deprecatedType;
+ this._trigger(type, [].slice.call(arguments, 1));
+ if (deprecatedType = deprecationMap[type]) {
+ this._trigger(deprecatedType, [].slice.call(arguments, 1));
+ }
+ }
+ });
+ return EventBus;
+ }();
+ var EventEmitter = function() {
+ "use strict";
+ var splitter = /\s+/, nextTick = getNextTick();
+ return {
+ onSync: onSync,
+ onAsync: onAsync,
+ off: off,
+ trigger: trigger
+ };
+ function on(method, types, cb, context) {
+ var type;
+ if (!cb) {
+ return this;
+ }
+ types = types.split(splitter);
+ cb = context ? bindContext(cb, context) : cb;
+ this._callbacks = this._callbacks || {};
+ while (type = types.shift()) {
+ this._callbacks[type] = this._callbacks[type] || {
+ sync: [],
+ async: []
+ };
+ this._callbacks[type][method].push(cb);
+ }
+ return this;
+ }
+ function onAsync(types, cb, context) {
+ return on.call(this, "async", types, cb, context);
+ }
+ function onSync(types, cb, context) {
+ return on.call(this, "sync", types, cb, context);
+ }
+ function off(types) {
+ var type;
+ if (!this._callbacks) {
+ return this;
+ }
+ types = types.split(splitter);
+ while (type = types.shift()) {
+ delete this._callbacks[type];
+ }
+ return this;
+ }
+ function trigger(types) {
+ var type, callbacks, args, syncFlush, asyncFlush;
+ if (!this._callbacks) {
+ return this;
+ }
+ types = types.split(splitter);
+ args = [].slice.call(arguments, 1);
+ while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
+ syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
+ asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
+ syncFlush() && nextTick(asyncFlush);
+ }
+ return this;
+ }
+ function getFlush(callbacks, context, args) {
+ return flush;
+ function flush() {
+ var cancelled;
+ for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) {
+ cancelled = callbacks[i].apply(context, args) === false;
+ }
+ return !cancelled;
+ }
+ }
+ function getNextTick() {
+ var nextTickFn;
+ if (window.setImmediate) {
+ nextTickFn = function nextTickSetImmediate(fn) {
+ setImmediate(function() {
+ fn();
+ });
+ };
+ } else {
+ nextTickFn = function nextTickSetTimeout(fn) {
+ setTimeout(function() {
+ fn();
+ }, 0);
+ };
+ }
+ return nextTickFn;
+ }
+ function bindContext(fn, context) {
+ return fn.bind ? fn.bind(context) : function() {
+ fn.apply(context, [].slice.call(arguments, 0));
+ };
+ }
+ }();
+ var highlight = function(doc) {
+ "use strict";
+ var defaults = {
+ node: null,
+ pattern: null,
+ tagName: "strong",
+ className: null,
+ wordsOnly: false,
+ caseSensitive: false
+ };
+ return function hightlight(o) {
+ var regex;
+ o = _.mixin({}, defaults, o);
+ if (!o.node || !o.pattern) {
+ return;
+ }
+ o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
+ regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
+ traverse(o.node, hightlightTextNode);
+ function hightlightTextNode(textNode) {
+ var match, patternNode, wrapperNode;
+ if (match = regex.exec(textNode.data)) {
+ wrapperNode = doc.createElement(o.tagName);
+ o.className && (wrapperNode.className = o.className);
+ patternNode = textNode.splitText(match.index);
+ patternNode.splitText(match[0].length);
+ wrapperNode.appendChild(patternNode.cloneNode(true));
+ textNode.parentNode.replaceChild(wrapperNode, patternNode);
+ }
+ return !!match;
+ }
+ function traverse(el, hightlightTextNode) {
+ var childNode, TEXT_NODE_TYPE = 3;
+ for (var i = 0; i < el.childNodes.length; i++) {
+ childNode = el.childNodes[i];
+ if (childNode.nodeType === TEXT_NODE_TYPE) {
+ i += hightlightTextNode(childNode) ? 1 : 0;
+ } else {
+ traverse(childNode, hightlightTextNode);
+ }
+ }
+ }
+ };
+ function getRegex(patterns, caseSensitive, wordsOnly) {
+ var escapedPatterns = [], regexStr;
+ for (var i = 0, len = patterns.length; i < len; i++) {
+ escapedPatterns.push(_.escapeRegExChars(patterns[i]));
+ }
+ regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
+ return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
+ }
+ }(window.document);
+ var Input = function() {
+ "use strict";
+ var specialKeyCodeMap;
+ specialKeyCodeMap = {
+ 9: "tab",
+ 27: "esc",
+ 37: "left",
+ 39: "right",
+ 13: "enter",
+ 38: "up",
+ 40: "down"
+ };
+ function Input(o, www) {
+ o = o || {};
+ if (!o.input) {
+ $.error("input is missing");
+ }
+ www.mixin(this);
+ this.$hint = $(o.hint);
+ this.$input = $(o.input);
+ this.query = this.$input.val();
+ this.queryWhenFocused = this.hasFocus() ? this.query : null;
+ this.$overflowHelper = buildOverflowHelper(this.$input);
+ this._checkLanguageDirection();
+ if (this.$hint.length === 0) {
+ this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
+ }
+ }
+ Input.normalizeQuery = function(str) {
+ return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
+ };
+ _.mixin(Input.prototype, EventEmitter, {
+ _onBlur: function onBlur() {
+ this.resetInputValue();
+ this.trigger("blurred");
+ },
+ _onFocus: function onFocus() {
+ this.queryWhenFocused = this.query;
+ this.trigger("focused");
+ },
+ _onKeydown: function onKeydown($e) {
+ var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
+ this._managePreventDefault(keyName, $e);
+ if (keyName && this._shouldTrigger(keyName, $e)) {
+ this.trigger(keyName + "Keyed", $e);
+ }
+ },
+ _onInput: function onInput() {
+ this._setQuery(this.getInputValue());
+ this.clearHintIfInvalid();
+ this._checkLanguageDirection();
+ },
+ _managePreventDefault: function managePreventDefault(keyName, $e) {
+ var preventDefault;
+ switch (keyName) {
+ case "up":
+ case "down":
+ preventDefault = !withModifier($e);
+ break;
+
+ default:
+ preventDefault = false;
+ }
+ preventDefault && $e.preventDefault();
+ },
+ _shouldTrigger: function shouldTrigger(keyName, $e) {
+ var trigger;
+ switch (keyName) {
+ case "tab":
+ trigger = !withModifier($e);
+ break;
+
+ default:
+ trigger = true;
+ }
+ return trigger;
+ },
+ _checkLanguageDirection: function checkLanguageDirection() {
+ var dir = (this.$input.css("direction") || "ltr").toLowerCase();
+ if (this.dir !== dir) {
+ this.dir = dir;
+ this.$hint.attr("dir", dir);
+ this.trigger("langDirChanged", dir);
+ }
+ },
+ _setQuery: function setQuery(val, silent) {
+ var areEquivalent, hasDifferentWhitespace;
+ areEquivalent = areQueriesEquivalent(val, this.query);
+ hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false;
+ this.query = val;
+ if (!silent && !areEquivalent) {
+ this.trigger("queryChanged", this.query);
+ } else if (!silent && hasDifferentWhitespace) {
+ this.trigger("whitespaceChanged", this.query);
+ }
+ },
+ bind: function() {
+ var that = this, onBlur, onFocus, onKeydown, onInput;
+ onBlur = _.bind(this._onBlur, this);
+ onFocus = _.bind(this._onFocus, this);
+ onKeydown = _.bind(this._onKeydown, this);
+ onInput = _.bind(this._onInput, this);
+ this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
+ if (!_.isMsie() || _.isMsie() > 9) {
+ this.$input.on("input.tt", onInput);
+ } else {
+ this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
+ if (specialKeyCodeMap[$e.which || $e.keyCode]) {
+ return;
+ }
+ _.defer(_.bind(that._onInput, that, $e));
+ });
+ }
+ return this;
+ },
+ focus: function focus() {
+ this.$input.focus();
+ },
+ blur: function blur() {
+ this.$input.blur();
+ },
+ getLangDir: function getLangDir() {
+ return this.dir;
+ },
+ getQuery: function getQuery() {
+ return this.query || "";
+ },
+ setQuery: function setQuery(val, silent) {
+ this.setInputValue(val);
+ this._setQuery(val, silent);
+ },
+ hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() {
+ return this.query !== this.queryWhenFocused;
+ },
+ getInputValue: function getInputValue() {
+ return this.$input.val();
+ },
+ setInputValue: function setInputValue(value) {
+ this.$input.val(value);
+ this.clearHintIfInvalid();
+ this._checkLanguageDirection();
+ },
+ resetInputValue: function resetInputValue() {
+ this.setInputValue(this.query);
+ },
+ getHint: function getHint() {
+ return this.$hint.val();
+ },
+ setHint: function setHint(value) {
+ this.$hint.val(value);
+ },
+ clearHint: function clearHint() {
+ this.setHint("");
+ },
+ clearHintIfInvalid: function clearHintIfInvalid() {
+ var val, hint, valIsPrefixOfHint, isValid;
+ val = this.getInputValue();
+ hint = this.getHint();
+ valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
+ isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow();
+ !isValid && this.clearHint();
+ },
+ hasFocus: function hasFocus() {
+ return this.$input.is(":focus");
+ },
+ hasOverflow: function hasOverflow() {
+ var constraint = this.$input.width() - 2;
+ this.$overflowHelper.text(this.getInputValue());
+ return this.$overflowHelper.width() >= constraint;
+ },
+ isCursorAtEnd: function() {
+ var valueLength, selectionStart, range;
+ valueLength = this.$input.val().length;
+ selectionStart = this.$input[0].selectionStart;
+ if (_.isNumber(selectionStart)) {
+ return selectionStart === valueLength;
+ } else if (document.selection) {
+ range = document.selection.createRange();
+ range.moveStart("character", -valueLength);
+ return valueLength === range.text.length;
+ }
+ return true;
+ },
+ destroy: function destroy() {
+ this.$hint.off(".tt");
+ this.$input.off(".tt");
+ this.$overflowHelper.remove();
+ this.$hint = this.$input = this.$overflowHelper = $("<div>");
+ }
+ });
+ return Input;
+ function buildOverflowHelper($input) {
+ return $('<pre aria-hidden="true"></pre>').css({
+ position: "absolute",
+ visibility: "hidden",
+ whiteSpace: "pre",
+ fontFamily: $input.css("font-family"),
+ fontSize: $input.css("font-size"),
+ fontStyle: $input.css("font-style"),
+ fontVariant: $input.css("font-variant"),
+ fontWeight: $input.css("font-weight"),
+ wordSpacing: $input.css("word-spacing"),
+ letterSpacing: $input.css("letter-spacing"),
+ textIndent: $input.css("text-indent"),
+ textRendering: $input.css("text-rendering"),
+ textTransform: $input.css("text-transform")
+ }).insertAfter($input);
+ }
+ function areQueriesEquivalent(a, b) {
+ return Input.normalizeQuery(a) === Input.normalizeQuery(b);
+ }
+ function withModifier($e) {
+ return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
+ }
+ }();
+ var Dataset = function() {
+ "use strict";
+ var keys, nameGenerator;
+ keys = {
+ val: "tt-selectable-display",
+ obj: "tt-selectable-object"
+ };
+ nameGenerator = _.getIdGenerator();
+ function Dataset(o, www) {
+ o = o || {};
+ o.templates = o.templates || {};
+ o.templates.notFound = o.templates.notFound || o.templates.empty;
+ if (!o.source) {
+ $.error("missing source");
+ }
+ if (!o.node) {
+ $.error("missing node");
+ }
+ if (o.name && !isValidName(o.name)) {
+ $.error("invalid dataset name: " + o.name);
+ }
+ www.mixin(this);
+ this.highlight = !!o.highlight;
+ this.name = o.name || nameGenerator();
+ this.limit = o.limit || 5;
+ this.displayFn = getDisplayFn(o.display || o.displayKey);
+ this.templates = getTemplates(o.templates, this.displayFn);
+ this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source;
+ this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async;
+ this._resetLastSuggestion();
+ this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name);
+ }
+ Dataset.extractData = function extractData(el) {
+ var $el = $(el);
+ if ($el.data(keys.obj)) {
+ return {
+ val: $el.data(keys.val) || "",
+ obj: $el.data(keys.obj) || null
+ };
+ }
+ return null;
+ };
+ _.mixin(Dataset.prototype, EventEmitter, {
+ _overwrite: function overwrite(query, suggestions) {
+ suggestions = suggestions || [];
+ if (suggestions.length) {
+ this._renderSuggestions(query, suggestions);
+ } else if (this.async && this.templates.pending) {
+ this._renderPending(query);
+ } else if (!this.async && this.templates.notFound) {
+ this._renderNotFound(query);
+ } else {
+ this._empty();
+ }
+ this.trigger("rendered", this.name, suggestions, false);
+ },
+ _append: function append(query, suggestions) {
+ suggestions = suggestions || [];
+ if (suggestions.length && this.$lastSuggestion.length) {
+ this._appendSuggestions(query, suggestions);
+ } else if (suggestions.length) {
+ this._renderSuggestions(query, suggestions);
+ } else if (!this.$lastSuggestion.length && this.templates.notFound) {
+ this._renderNotFound(query);
+ }
+ this.trigger("rendered", this.name, suggestions, true);
+ },
+ _renderSuggestions: function renderSuggestions(query, suggestions) {
+ var $fragment;
+ $fragment = this._getSuggestionsFragment(query, suggestions);
+ this.$lastSuggestion = $fragment.children().last();
+ this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions));
+ },
+ _appendSuggestions: function appendSuggestions(query, suggestions) {
+ var $fragment, $lastSuggestion;
+ $fragment = this._getSuggestionsFragment(query, suggestions);
+ $lastSuggestion = $fragment.children().last();
+ this.$lastSuggestion.after($fragment);
+ this.$lastSuggestion = $lastSuggestion;
+ },
+ _renderPending: function renderPending(query) {
+ var template = this.templates.pending;
+ this._resetLastSuggestion();
+ template && this.$el.html(template({
+ query: query,
+ dataset: this.name
+ }));
+ },
+ _renderNotFound: function renderNotFound(query) {
+ var template = this.templates.notFound;
+ this._resetLastSuggestion();
+ template && this.$el.html(template({
+ query: query,
+ dataset: this.name
+ }));
+ },
+ _empty: function empty() {
+ this.$el.empty();
+ this._resetLastSuggestion();
+ },
+ _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) {
+ var that = this, fragment;
+ fragment = document.createDocumentFragment();
+ _.each(suggestions, function getSuggestionNode(suggestion) {
+ var $el, context;
+ context = that._injectQuery(query, suggestion);
+ $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable);
+ fragment.appendChild($el[0]);
+ });
+ this.highlight && highlight({
+ className: this.classes.highlight,
+ node: fragment,
+ pattern: query
+ });
+ return $(fragment);
+ },
+ _getFooter: function getFooter(query, suggestions) {
+ return this.templates.footer ? this.templates.footer({
+ query: query,
+ suggestions: suggestions,
+ dataset: this.name
+ }) : null;
+ },
+ _getHeader: function getHeader(query, suggestions) {
+ return this.templates.header ? this.templates.header({
+ query: query,
+ suggestions: suggestions,
+ dataset: this.name
+ }) : null;
+ },
+ _resetLastSuggestion: function resetLastSuggestion() {
+ this.$lastSuggestion = $();
+ },
+ _injectQuery: function injectQuery(query, obj) {
+ return _.isObject(obj) ? _.mixin({
+ _query: query
+ }, obj) : obj;
+ },
+ update: function update(query) {
+ var that = this, canceled = false, syncCalled = false, rendered = 0;
+ this.cancel();
+ this.cancel = function cancel() {
+ canceled = true;
+ that.cancel = $.noop;
+ that.async && that.trigger("asyncCanceled", query);
+ };
+ this.source(query, sync, async);
+ !syncCalled && sync([]);
+ function sync(suggestions) {
+ if (syncCalled) {
+ return;
+ }
+ syncCalled = true;
+ suggestions = (suggestions || []).slice(0, that.limit);
+ rendered = suggestions.length;
+ that._overwrite(query, suggestions);
+ if (rendered < that.limit && that.async) {
+ that.trigger("asyncRequested", query);
+ }
+ }
+ function async(suggestions) {
+ suggestions = suggestions || [];
+ if (!canceled && rendered < that.limit) {
+ that.cancel = $.noop;
+ rendered += suggestions.length;
+ that._append(query, suggestions.slice(0, that.limit - rendered));
+ that.async && that.trigger("asyncReceived", query);
+ }
+ }
+ },
+ cancel: $.noop,
+ clear: function clear() {
+ this._empty();
+ this.cancel();
+ this.trigger("cleared");
+ },
+ isEmpty: function isEmpty() {
+ return this.$el.is(":empty");
+ },
+ destroy: function destroy() {
+ this.$el = $("<div>");
+ }
+ });
+ return Dataset;
+ function getDisplayFn(display) {
+ display = display || _.stringify;
+ return _.isFunction(display) ? display : displayFn;
+ function displayFn(obj) {
+ return obj[display];
+ }
+ }
+ function getTemplates(templates, displayFn) {
+ return {
+ notFound: templates.notFound && _.templatify(templates.notFound),
+ pending: templates.pending && _.templatify(templates.pending),
+ header: templates.header && _.templatify(templates.header),
+ footer: templates.footer && _.templatify(templates.footer),
+ suggestion: templates.suggestion || suggestionTemplate
+ };
+ function suggestionTemplate(context) {
+ return $("<div>").text(displayFn(context));
+ }
+ }
+ function isValidName(str) {
+ return /^[_a-zA-Z0-9-]+$/.test(str);
+ }
+ }();
+ var Menu = function() {
+ "use strict";
+ function Menu(o, www) {
+ var that = this;
+ o = o || {};
+ if (!o.node) {
+ $.error("node is required");
+ }
+ www.mixin(this);
+ this.$node = $(o.node);
+ this.query = null;
+ this.datasets = _.map(o.datasets, initializeDataset);
+ function initializeDataset(oDataset) {
+ var node = that.$node.find(oDataset.node).first();
+ oDataset.node = node.length ? node : $("<div>").appendTo(that.$node);
+ return new Dataset(oDataset, www);
+ }
+ }
+ _.mixin(Menu.prototype, EventEmitter, {
+ _onSelectableClick: function onSelectableClick($e) {
+ this.trigger("selectableClicked", $($e.currentTarget));
+ },
+ _onRendered: function onRendered(type, dataset, suggestions, async) {
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
+ this.trigger("datasetRendered", dataset, suggestions, async);
+ },
+ _onCleared: function onCleared() {
+ this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty());
+ this.trigger("datasetCleared");
+ },
+ _propagate: function propagate() {
+ this.trigger.apply(this, arguments);
+ },
+ _allDatasetsEmpty: function allDatasetsEmpty() {
+ return _.every(this.datasets, isDatasetEmpty);
+ function isDatasetEmpty(dataset) {
+ return dataset.isEmpty();
+ }
+ },
+ _getSelectables: function getSelectables() {
+ return this.$node.find(this.selectors.selectable);
+ },
+ _removeCursor: function _removeCursor() {
+ var $selectable = this.getActiveSelectable();
+ $selectable && $selectable.removeClass(this.classes.cursor);
+ },
+ _ensureVisible: function ensureVisible($el) {
+ var elTop, elBottom, nodeScrollTop, nodeHeight;
+ elTop = $el.position().top;
+ elBottom = elTop + $el.outerHeight(true);
+ nodeScrollTop = this.$node.scrollTop();
+ nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10);
+ if (elTop < 0) {
+ this.$node.scrollTop(nodeScrollTop + elTop);
+ } else if (nodeHeight < elBottom) {
+ this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight));
+ }
+ },
+ bind: function() {
+ var that = this, onSelectableClick;
+ onSelectableClick = _.bind(this._onSelectableClick, this);
+ this.$node.on("click.tt", this.selectors.selectable, onSelectableClick);
+ _.each(this.datasets, function(dataset) {
+ dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that);
+ });
+ return this;
+ },
+ isOpen: function isOpen() {
+ return this.$node.hasClass(this.classes.open);
+ },
+ open: function open() {
+ this.$node.addClass(this.classes.open);
+ },
+ close: function close() {
+ this.$node.removeClass(this.classes.open);
+ this._removeCursor();
+ },
+ setLanguageDirection: function setLanguageDirection(dir) {
+ this.$node.attr("dir", dir);
+ },
+ selectableRelativeToCursor: function selectableRelativeToCursor(delta) {
+ var $selectables, $oldCursor, oldIndex, newIndex;
+ $oldCursor = this.getActiveSelectable();
+ $selectables = this._getSelectables();
+ oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1;
+ newIndex = oldIndex + delta;
+ newIndex = (newIndex + 1) % ($selectables.length + 1) - 1;
+ newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex;
+ return newIndex === -1 ? null : $selectables.eq(newIndex);
+ },
+ setCursor: function setCursor($selectable) {
+ this._removeCursor();
+ if ($selectable = $selectable && $selectable.first()) {
+ $selectable.addClass(this.classes.cursor);
+ this._ensureVisible($selectable);
+ }
+ },
+ getSelectableData: function getSelectableData($el) {
+ return $el && $el.length ? Dataset.extractData($el) : null;
+ },
+ getActiveSelectable: function getActiveSelectable() {
+ var $selectable = this._getSelectables().filter(this.selectors.cursor).first();
+ return $selectable.length ? $selectable : null;
+ },
+ getTopSelectable: function getTopSelectable() {
+ var $selectable = this._getSelectables().first();
+ return $selectable.length ? $selectable : null;
+ },
+ update: function update(query) {
+ var isValidUpdate = query !== this.query;
+ if (isValidUpdate) {
+ this.query = query;
+ _.each(this.datasets, updateDataset);
+ }
+ return isValidUpdate;
+ function updateDataset(dataset) {
+ dataset.update(query);
+ }
+ },
+ empty: function empty() {
+ _.each(this.datasets, clearDataset);
+ this.query = null;
+ this.$node.addClass(this.classes.empty);
+ function clearDataset(dataset) {
+ dataset.clear();
+ }
+ },
+ destroy: function destroy() {
+ this.$node.off(".tt");
+ this.$node = $("<div>");
+ _.each(this.datasets, destroyDataset);
+ function destroyDataset(dataset) {
+ dataset.destroy();
+ }
+ }
+ });
+ return Menu;
+ }();
+ var DefaultMenu = function() {
+ "use strict";
+ var s = Menu.prototype;
+ function DefaultMenu() {
+ Menu.apply(this, [].slice.call(arguments, 0));
+ }
+ _.mixin(DefaultMenu.prototype, Menu.prototype, {
+ open: function open() {
+ !this._allDatasetsEmpty() && this._show();
+ return s.open.apply(this, [].slice.call(arguments, 0));
+ },
+ close: function close() {
+ this._hide();
+ return s.close.apply(this, [].slice.call(arguments, 0));
+ },
+ _onRendered: function onRendered() {
+ if (this._allDatasetsEmpty()) {
+ this._hide();
+ } else {
+ this.isOpen() && this._show();
+ }
+ return s._onRendered.apply(this, [].slice.call(arguments, 0));
+ },
+ _onCleared: function onCleared() {
+ if (this._allDatasetsEmpty()) {
+ this._hide();
+ } else {
+ this.isOpen() && this._show();
+ }
+ return s._onCleared.apply(this, [].slice.call(arguments, 0));
+ },
+ setLanguageDirection: function setLanguageDirection(dir) {
+ this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl);
+ return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0));
+ },
+ _hide: function hide() {
+ this.$node.hide();
+ },
+ _show: function show() {
+ this.$node.css("display", "block");
+ }
+ });
+ return DefaultMenu;
+ }();
+ var Typeahead = function() {
+ "use strict";
+ function Typeahead(o, www) {
+ var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged;
+ o = o || {};
+ if (!o.input) {
+ $.error("missing input");
+ }
+ if (!o.menu) {
+ $.error("missing menu");
+ }
+ if (!o.eventBus) {
+ $.error("missing event bus");
+ }
+ www.mixin(this);
+ this.eventBus = o.eventBus;
+ this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
+ this.input = o.input;
+ this.menu = o.menu;
+ this.enabled = true;
+ this.active = false;
+ this.input.hasFocus() && this.activate();
+ this.dir = this.input.getLangDir();
+ this._hacks();
+ this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this);
+ onFocused = c(this, "activate", "open", "_onFocused");
+ onBlurred = c(this, "deactivate", "_onBlurred");
+ onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed");
+ onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed");
+ onEscKeyed = c(this, "isActive", "_onEscKeyed");
+ onUpKeyed = c(this, "isActive", "open", "_onUpKeyed");
+ onDownKeyed = c(this, "isActive", "open", "_onDownKeyed");
+ onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed");
+ onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed");
+ onQueryChanged = c(this, "_openIfActive", "_onQueryChanged");
+ onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged");
+ this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this);
+ }
+ _.mixin(Typeahead.prototype, {
+ _hacks: function hacks() {
+ var $input, $menu;
+ $input = this.input.$input || $("<div>");
+ $menu = this.menu.$node || $("<div>");
+ $input.on("blur.tt", function($e) {
+ var active, isActive, hasActive;
+ active = document.activeElement;
+ isActive = $menu.is(active);
+ hasActive = $menu.has(active).length > 0;
+ if (_.isMsie() && (isActive || hasActive)) {
+ $e.preventDefault();
+ $e.stopImmediatePropagation();
+ _.defer(function() {
+ $input.focus();
+ });
+ }
+ });
+ $menu.on("mousedown.tt", function($e) {
+ $e.preventDefault();
+ });
+ },
+ _onSelectableClicked: function onSelectableClicked(type, $el) {
+ this.select($el);
+ },
+ _onDatasetCleared: function onDatasetCleared() {
+ this._updateHint();
+ },
+ _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
+ this._updateHint();
+ this.eventBus.trigger("render", suggestions, async, dataset);
+ },
+ _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
+ this.eventBus.trigger("asyncrequest", query, dataset);
+ },
+ _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
+ this.eventBus.trigger("asynccancel", query, dataset);
+ },
+ _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
+ this.eventBus.trigger("asyncreceive", query, dataset);
+ },
+ _onFocused: function onFocused() {
+ this._minLengthMet() && this.menu.update(this.input.getQuery());
+ },
+ _onBlurred: function onBlurred() {
+ if (this.input.hasQueryChangedSinceLastFocus()) {
+ this.eventBus.trigger("change", this.input.getQuery());
+ }
+ },
+ _onEnterKeyed: function onEnterKeyed(type, $e) {
+ var $selectable;
+ if ($selectable = this.menu.getActiveSelectable()) {
+ this.select($selectable) && $e.preventDefault();
+ }
+ },
+ _onTabKeyed: function onTabKeyed(type, $e) {
+ var $selectable;
+ if ($selectable = this.menu.getActiveSelectable()) {
+ this.select($selectable) && $e.preventDefault();
+ } else if ($selectable = this.menu.getTopSelectable()) {
+ this.autocomplete($selectable) && $e.preventDefault();
+ }
+ },
+ _onEscKeyed: function onEscKeyed() {
+ this.close();
+ },
+ _onUpKeyed: function onUpKeyed() {
+ this.moveCursor(-1);
+ },
+ _onDownKeyed: function onDownKeyed() {
+ this.moveCursor(+1);
+ },
+ _onLeftKeyed: function onLeftKeyed() {
+ if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
+ this.autocomplete(this.menu.getTopSelectable());
+ }
+ },
+ _onRightKeyed: function onRightKeyed() {
+ if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
+ this.autocomplete(this.menu.getTopSelectable());
+ }
+ },
+ _onQueryChanged: function onQueryChanged(e, query) {
+ this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
+ },
+ _onWhitespaceChanged: function onWhitespaceChanged() {
+ this._updateHint();
+ },
+ _onLangDirChanged: function onLangDirChanged(e, dir) {
+ if (this.dir !== dir) {
+ this.dir = dir;
+ this.menu.setLanguageDirection(dir);
+ }
+ },
+ _openIfActive: function openIfActive() {
+ this.isActive() && this.open();
+ },
+ _minLengthMet: function minLengthMet(query) {
+ query = _.isString(query) ? query : this.input.getQuery() || "";
+ return query.length >= this.minLength;
+ },
+ _updateHint: function updateHint() {
+ var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
+ $selectable = this.menu.getTopSelectable();
+ data = this.menu.getSelectableData($selectable);
+ val = this.input.getInputValue();
+ if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
+ query = Input.normalizeQuery(val);
+ escapedQuery = _.escapeRegExChars(query);
+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
+ match = frontMatchRegEx.exec(data.val);
+ match && this.input.setHint(val + match[1]);
+ } else {
+ this.input.clearHint();
+ }
+ },
+ isEnabled: function isEnabled() {
+ return this.enabled;
+ },
+ enable: function enable() {
+ this.enabled = true;
+ },
+ disable: function disable() {
+ this.enabled = false;
+ },
+ isActive: function isActive() {
+ return this.active;
+ },
+ activate: function activate() {
+ if (this.isActive()) {
+ return true;
+ } else if (!this.isEnabled() || this.eventBus.before("active")) {
+ return false;
+ } else {
+ this.active = true;
+ this.eventBus.trigger("active");
+ return true;
+ }
+ },
+ deactivate: function deactivate() {
+ if (!this.isActive()) {
+ return true;
+ } else if (this.eventBus.before("idle")) {
+ return false;
+ } else {
+ this.active = false;
+ this.close();
+ this.eventBus.trigger("idle");
+ return true;
+ }
+ },
+ isOpen: function isOpen() {
+ return this.menu.isOpen();
+ },
+ open: function open() {
+ if (!this.isOpen() && !this.eventBus.before("open")) {
+ this.menu.open();
+ this._updateHint();
+ this.eventBus.trigger("open");
+ }
+ return this.isOpen();
+ },
+ close: function close() {
+ if (this.isOpen() && !this.eventBus.before("close")) {
+ this.menu.close();
+ this.input.clearHint();
+ this.input.resetInputValue();
+ this.eventBus.trigger("close");
+ }
+ return !this.isOpen();
+ },
+ setVal: function setVal(val) {
+ this.input.setQuery(_.toStr(val));
+ },
+ getVal: function getVal() {
+ return this.input.getQuery();
+ },
+ select: function select($selectable) {
+ var data = this.menu.getSelectableData($selectable);
+ if (data && !this.eventBus.before("select", data.obj)) {
+ this.input.setQuery(data.val, true);
+ this.eventBus.trigger("select", data.obj);
+ this.close();
+ return true;
+ }
+ return false;
+ },
+ autocomplete: function autocomplete($selectable) {
+ var query, data, isValid;
+ query = this.input.getQuery();
+ data = this.menu.getSelectableData($selectable);
+ isValid = data && query !== data.val;
+ if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
+ this.input.setQuery(data.val);
+ this.eventBus.trigger("autocomplete", data.obj);
+ return true;
+ }
+ return false;
+ },
+ moveCursor: function moveCursor(delta) {
+ var query, $candidate, data, payload, cancelMove;
+ query = this.input.getQuery();
+ $candidate = this.menu.selectableRelativeToCursor(delta);
+ data = this.menu.getSelectableData($candidate);
+ payload = data ? data.obj : null;
+ cancelMove = this._minLengthMet() && this.menu.update(query);
+ if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
+ this.menu.setCursor($candidate);
+ if (data) {
+ this.input.setInputValue(data.val);
+ } else {
+ this.input.resetInputValue();
+ this._updateHint();
+ }
+ this.eventBus.trigger("cursorchange", payload);
+ return true;
+ }
+ return false;
+ },
+ destroy: function destroy() {
+ this.input.destroy();
+ this.menu.destroy();
+ }
+ });
+ return Typeahead;
+ function c(ctx) {
+ var methods = [].slice.call(arguments, 1);
+ return function() {
+ var args = [].slice.call(arguments);
+ _.each(methods, function(method) {
+ return ctx[method].apply(ctx, args);
+ });
+ };
+ }
+ }();
+ (function() {
+ "use strict";
+ var old, keys, methods;
+ old = $.fn.typeahead;
+ keys = {
+ www: "tt-www",
+ attrs: "tt-attrs",
+ typeahead: "tt-typeahead"
+ };
+ methods = {
+ initialize: function initialize(o, datasets) {
+ var www;
+ datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
+ o = o || {};
+ www = WWW(o.classNames);
+ return this.each(attach);
+ function attach() {
+ var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
+ _.each(datasets, function(d) {
+ d.highlight = !!o.highlight;
+ });
+ $input = $(this);
+ $wrapper = $(www.html.wrapper);
+ $hint = $elOrNull(o.hint);
+ $menu = $elOrNull(o.menu);
+ defaultHint = o.hint !== false && !$hint;
+ defaultMenu = o.menu !== false && !$menu;
+ defaultHint && ($hint = buildHintFromInput($input, www));
+ defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
+ $hint && $hint.val("");
+ $input = prepInput($input, www);
+ if (defaultHint || defaultMenu) {
+ $wrapper.css(www.css.wrapper);
+ $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
+ $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
+ }
+ MenuConstructor = defaultMenu ? DefaultMenu : Menu;
+ eventBus = new EventBus({
+ el: $input
+ });
+ input = new Input({
+ hint: $hint,
+ input: $input
+ }, www);
+ menu = new MenuConstructor({
+ node: $menu,
+ datasets: datasets
+ }, www);
+ typeahead = new Typeahead({
+ input: input,
+ menu: menu,
+ eventBus: eventBus,
+ minLength: o.minLength
+ }, www);
+ $input.data(keys.www, www);
+ $input.data(keys.typeahead, typeahead);
+ }
+ },
+ isEnabled: function isEnabled() {
+ var enabled;
+ ttEach(this.first(), function(t) {
+ enabled = t.isEnabled();
+ });
+ return enabled;
+ },
+ enable: function enable() {
+ ttEach(this, function(t) {
+ t.enable();
+ });
+ return this;
+ },
+ disable: function disable() {
+ ttEach(this, function(t) {
+ t.disable();
+ });
+ return this;
+ },
+ isActive: function isActive() {
+ var active;
+ ttEach(this.first(), function(t) {
+ active = t.isActive();
+ });
+ return active;
+ },
+ activate: function activate() {
+ ttEach(this, function(t) {
+ t.activate();
+ });
+ return this;
+ },
+ deactivate: function deactivate() {
+ ttEach(this, function(t) {
+ t.deactivate();
+ });
+ return this;
+ },
+ isOpen: function isOpen() {
+ var open;
+ ttEach(this.first(), function(t) {
+ open = t.isOpen();
+ });
+ return open;
+ },
+ open: function open() {
+ ttEach(this, function(t) {
+ t.open();
+ });
+ return this;
+ },
+ close: function close() {
+ ttEach(this, function(t) {
+ t.close();
+ });
+ return this;
+ },
+ select: function select(el) {
+ var success = false, $el = $(el);
+ ttEach(this.first(), function(t) {
+ success = t.select($el);
+ });
+ return success;
+ },
+ autocomplete: function autocomplete(el) {
+ var success = false, $el = $(el);
+ ttEach(this.first(), function(t) {
+ success = t.autocomplete($el);
+ });
+ return success;
+ },
+ moveCursor: function moveCursoe(delta) {
+ var success = false;
+ ttEach(this.first(), function(t) {
+ success = t.moveCursor(delta);
+ });
+ return success;
+ },
+ val: function val(newVal) {
+ var query;
+ if (!arguments.length) {
+ ttEach(this.first(), function(t) {
+ query = t.getVal();
+ });
+ return query;
+ } else {
+ ttEach(this, function(t) {
+ t.setVal(newVal);
+ });
+ return this;
+ }
+ },
+ destroy: function destroy() {
+ ttEach(this, function(typeahead, $input) {
+ revert($input);
+ typeahead.destroy();
+ });
+ return this;
+ }
+ };
+ $.fn.typeahead = function(method) {
+ if (methods[method]) {
+ return methods[method].apply(this, [].slice.call(arguments, 1));
+ } else {
+ return methods.initialize.apply(this, arguments);
+ }
+ };
+ $.fn.typeahead.noConflict = function noConflict() {
+ $.fn.typeahead = old;
+ return this;
+ };
+ function ttEach($els, fn) {
+ $els.each(function() {
+ var $input = $(this), typeahead;
+ (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
+ });
+ }
+ function buildHintFromInput($input, www) {
+ return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
+ autocomplete: "off",
+ spellcheck: "false",
+ tabindex: -1
+ });
+ }
+ function prepInput($input, www) {
+ $input.data(keys.attrs, {
+ dir: $input.attr("dir"),
+ autocomplete: $input.attr("autocomplete"),
+ spellcheck: $input.attr("spellcheck"),
+ style: $input.attr("style")
+ });
+ $input.addClass(www.classes.input).attr({
+ autocomplete: "off",
+ spellcheck: false
+ });
+ try {
+ !$input.attr("dir") && $input.attr("dir", "auto");
+ } catch (e) {}
+ return $input;
+ }
+ function getBackgroundStyles($el) {
+ return {
+ backgroundAttachment: $el.css("background-attachment"),
+ backgroundClip: $el.css("background-clip"),
+ backgroundColor: $el.css("background-color"),
+ backgroundImage: $el.css("background-image"),
+ backgroundOrigin: $el.css("background-origin"),
+ backgroundPosition: $el.css("background-position"),
+ backgroundRepeat: $el.css("background-repeat"),
+ backgroundSize: $el.css("background-size")
+ };
+ }
+ function revert($input) {
+ var www, $wrapper;
+ www = $input.data(keys.www);
+ $wrapper = $input.parent().filter(www.selectors.wrapper);
+ _.each($input.data(keys.attrs), function(val, key) {
+ _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
+ });
+ $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
+ if ($wrapper.length) {
+ $input.detach().insertAfter($wrapper);
+ $wrapper.remove();
+ }
+ }
+ function $elOrNull(obj) {
+ var isValid, $el;
+ isValid = _.isJQuery(obj) || _.isElement(obj);
+ $el = isValid ? $(obj).first() : [];
+ return $el.length ? $el : null;
+ }
+ })();
+}); \ No newline at end of file
diff --git a/debian/dconv/parser/__init__.py b/debian/dconv/parser/__init__.py
new file mode 100644
index 0000000..82b8522
--- /dev/null
+++ b/debian/dconv/parser/__init__.py
@@ -0,0 +1,81 @@
+__all__ = [
+ 'arguments',
+ 'example',
+ 'keyword',
+ 'seealso',
+ 'table',
+ 'underline'
+]
+
+
+class Parser:
+ def __init__(self, pctxt):
+ self.pctxt = pctxt
+
+ def parse(self, line):
+ return line
+
+class PContext:
+ def __init__(self, templates = None):
+ self.set_content_list([])
+ self.templates = templates
+
+ def set_content(self, content):
+ self.set_content_list(content.split("\n"))
+
+ def set_content_list(self, content):
+ self.lines = content
+ self.nblines = len(self.lines)
+ self.i = 0
+ self.stop = False
+
+ def get_lines(self):
+ return self.lines
+
+ def eat_lines(self):
+ count = 0
+ while self.has_more_lines() and self.lines[self.i].strip():
+ count += 1
+ self.next()
+ return count
+
+ def eat_empty_lines(self):
+ count = 0
+ while self.has_more_lines() and not self.lines[self.i].strip():
+ count += 1
+ self.next()
+ return count
+
+ def next(self, count=1):
+ self.i += count
+
+ def has_more_lines(self, offset=0):
+ return self.i + offset < self.nblines
+
+ def get_line(self, offset=0):
+ return self.lines[self.i + offset].rstrip()
+
+
+# Get the indentation of a line
+def get_indent(line):
+ indent = 0
+ length = len(line)
+ while indent < length and line[indent] == ' ':
+ indent += 1
+ return indent
+
+
+# Remove unneeded indentation
+def remove_indent(list):
+ # Detect the minimum indentation in the list
+ min_indent = -1
+ for line in list:
+ if not line.strip():
+ continue
+ indent = get_indent(line)
+ if min_indent < 0 or indent < min_indent:
+ min_indent = indent
+ # Realign the list content to remove the minimum indentation
+ if min_indent > 0:
+ for index, line in enumerate(list):
+ list[index] = line[min_indent:]
diff --git a/debian/dconv/parser/arguments.py b/debian/dconv/parser/arguments.py
new file mode 100644
index 0000000..096b269
--- /dev/null
+++ b/debian/dconv/parser/arguments.py
@@ -0,0 +1,132 @@
+import sys
+import re
+import parser
+
+'''
+TODO: Allow inner data parsing (this will allow to parse the examples provided in an arguments block)
+'''
+class Parser(parser.Parser):
+ def __init__(self, pctxt):
+ parser.Parser.__init__(self, pctxt)
+ #template = pctxt.templates.get_template("parser/arguments.tpl")
+ #self.replace = template.render().strip()
+
+ def parse(self, line):
+ #return re.sub(r'(Arguments *:)', self.replace, line)
+ pctxt = self.pctxt
+
+ result = re.search(r'(Arguments? *:)', line)
+ if result:
+ label = result.group(0)
+ content = []
+
+ desc_indent = False
+ desc = re.sub(r'.*Arguments? *:', '', line).strip()
+
+ indent = parser.get_indent(line)
+
+ pctxt.next()
+ pctxt.eat_empty_lines()
+
+ arglines = []
+ if desc != "none":
+ add_empty_lines = 0
+ while pctxt.has_more_lines() and (parser.get_indent(pctxt.get_line()) > indent):
+ for j in range(0, add_empty_lines):
+ arglines.append("")
+ arglines.append(pctxt.get_line())
+ pctxt.next()
+ add_empty_lines = pctxt.eat_empty_lines()
+ '''
+ print line
+
+ if parser.get_indent(line) == arg_indent:
+ argument = re.sub(r' *([^ ]+).*', r'\1', line)
+ if argument:
+ #content.append("<b>%s</b>" % argument)
+ arg_desc = [line.replace(argument, " " * len(self.unescape(argument)), 1)]
+ #arg_desc = re.sub(r'( *)([^ ]+)(.*)', r'\1<b>\2</b>\3', line)
+ arg_desc_indent = parser.get_indent(arg_desc[0])
+ arg_desc[0] = arg_desc[0][arg_indent:]
+ pctxt.next()
+ add_empty_lines = 0
+ while pctxt.has_more_lines and parser.get_indent(pctxt.get_line()) >= arg_indent:
+ for i in range(0, add_empty_lines):
+ arg_desc.append("")
+ arg_desc.append(pctxt.get_line()[arg_indent:])
+ pctxt.next()
+ add_empty_lines = pctxt.eat_empty_lines()
+ # TODO : reduce space at the beginnning
+ content.append({
+ 'name': argument,
+ 'desc': arg_desc
+ })
+ '''
+
+ if arglines:
+ new_arglines = []
+ #content = self.parse_args(arglines)
+ parser.remove_indent(arglines)
+ '''
+ pctxt2 = parser.PContext(pctxt.templates)
+ pctxt2.set_content_list(arglines)
+ while pctxt2.has_more_lines():
+ new_arglines.append(parser.example.Parser(pctxt2).parse(pctxt2.get_line()))
+ pctxt2.next()
+ arglines = new_arglines
+ '''
+
+ pctxt.stop = True
+
+ template = pctxt.templates.get_template("parser/arguments.tpl")
+ return template.render(
+ pctxt=pctxt,
+ label=label,
+ desc=desc,
+ content=arglines
+ #content=content
+ )
+ return line
+
+ return line
+
+'''
+ def parse_args(self, data):
+ args = []
+
+ pctxt = parser.PContext()
+ pctxt.set_content_list(data)
+
+ while pctxt.has_more_lines():
+ line = pctxt.get_line()
+ arg_indent = parser.get_indent(line)
+ argument = re.sub(r' *([^ ]+).*', r'\1', line)
+ if True or argument:
+ arg_desc = []
+ trailing_desc = line.replace(argument, " " * len(self.unescape(argument)), 1)[arg_indent:]
+ if trailing_desc.strip():
+ arg_desc.append(trailing_desc)
+ pctxt.next()
+ add_empty_lines = 0
+ while pctxt.has_more_lines() and parser.get_indent(pctxt.get_line()) > arg_indent:
+ for i in range(0, add_empty_lines):
+ arg_desc.append("")
+ arg_desc.append(pctxt.get_line()[arg_indent:])
+ pctxt.next()
+ add_empty_lines = pctxt.eat_empty_lines()
+
+ parser.remove_indent(arg_desc)
+
+ args.append({
+ 'name': argument,
+ 'desc': arg_desc
+ })
+ return args
+
+ def unescape(self, s):
+ s = s.replace("&lt;", "<")
+ s = s.replace("&gt;", ">")
+ # this has to be last:
+ s = s.replace("&amp;", "&")
+ return s
+'''
diff --git a/debian/dconv/parser/example.py b/debian/dconv/parser/example.py
new file mode 100644
index 0000000..3958992
--- /dev/null
+++ b/debian/dconv/parser/example.py
@@ -0,0 +1,77 @@
+import re
+import parser
+
+# Detect examples blocks
+class Parser(parser.Parser):
+ def __init__(self, pctxt):
+ parser.Parser.__init__(self, pctxt)
+ template = pctxt.templates.get_template("parser/example/comment.tpl")
+ self.comment = template.render(pctxt=pctxt).strip()
+
+
+ def parse(self, line):
+ pctxt = self.pctxt
+
+ result = re.search(r'^ *(Examples? *:)(.*)', line)
+ if result:
+ label = result.group(1)
+
+ desc_indent = False
+ desc = result.group(2).strip()
+
+ # Some examples have a description
+ if desc:
+ desc_indent = len(line) - len(desc)
+
+ indent = parser.get_indent(line)
+
+ if desc:
+ # And some description are on multiple lines
+ while pctxt.get_line(1) and parser.get_indent(pctxt.get_line(1)) == desc_indent:
+ desc += " " + pctxt.get_line(1).strip()
+ pctxt.next()
+
+ pctxt.next()
+ add_empty_line = pctxt.eat_empty_lines()
+
+ content = []
+
+ if parser.get_indent(pctxt.get_line()) > indent:
+ if desc:
+ desc = desc[0].upper() + desc[1:]
+ add_empty_line = 0
+ while pctxt.has_more_lines() and ((not pctxt.get_line()) or (parser.get_indent(pctxt.get_line()) > indent)):
+ if pctxt.get_line():
+ for j in range(0, add_empty_line):
+ content.append("")
+
+ content.append(re.sub(r'(#.*)$', self.comment, pctxt.get_line()))
+ add_empty_line = 0
+ else:
+ add_empty_line += 1
+ pctxt.next()
+ elif parser.get_indent(pctxt.get_line()) == indent:
+ # Simple example that can't have empty lines
+ if add_empty_line and desc:
+ # This means that the example was on the same line as the 'Example' tag
+ # and was not a description
+ content.append(" " * indent + desc)
+ desc = False
+ else:
+ while pctxt.has_more_lines() and (parser.get_indent(pctxt.get_line()) >= indent):
+ content.append(pctxt.get_line())
+ pctxt.next()
+ pctxt.eat_empty_lines() # Skip empty remaining lines
+
+ pctxt.stop = True
+
+ parser.remove_indent(content)
+
+ template = pctxt.templates.get_template("parser/example.tpl")
+ return template.render(
+ pctxt=pctxt,
+ label=label,
+ desc=desc,
+ content=content
+ )
+ return line
diff --git a/debian/dconv/parser/keyword.py b/debian/dconv/parser/keyword.py
new file mode 100644
index 0000000..f20944f
--- /dev/null
+++ b/debian/dconv/parser/keyword.py
@@ -0,0 +1,142 @@
+import re
+import parser
+from urllib.parse import quote
+
+class Parser(parser.Parser):
+ def __init__(self, pctxt):
+ parser.Parser.__init__(self, pctxt)
+ self.keywordPattern = re.compile(r'^(%s%s)(%s)' % (
+ '([a-z][a-z0-9\-\+_\.]*[a-z0-9\-\+_)])', # keyword
+ '( [a-z0-9\-_]+)*', # subkeywords
+ '(\([^ ]*\))?', # arg (ex: (<backend>), (<frontend>/<backend>), (<offset1>,<length>[,<offset2>]) ...
+ ))
+
+ def parse(self, line):
+ pctxt = self.pctxt
+ keywords = pctxt.keywords
+ keywordsCount = pctxt.keywordsCount
+ chapters = pctxt.chapters
+
+ res = ""
+
+ if line != "" and not re.match(r'^ ', line):
+ parsed = self.keywordPattern.match(line)
+ if parsed != None:
+ keyword = parsed.group(1)
+ arg = parsed.group(4)
+ parameters = line[len(keyword) + len(arg):]
+ if (parameters != "" and not re.match("^ +((&lt;|\[|\{|/).*|(: [a-z +]+))?(\(deprecated\))?$", parameters)):
+ # Dirty hack
+ # - parameters should only start with the characer "<", "[", "{", "/"
+ # - or a column (":") followed by a alpha keywords to identify fetching samples (optionally separated by the character "+")
+ # - or the string "(deprecated)" at the end
+ keyword = False
+ else:
+ splitKeyword = keyword.split(" ")
+
+ parameters = arg + parameters
+ else:
+ keyword = False
+
+ if keyword and (len(splitKeyword) <= 5):
+ toplevel = pctxt.details["toplevel"]
+ for j in range(0, len(splitKeyword)):
+ subKeyword = " ".join(splitKeyword[0:j + 1])
+ if subKeyword != "no":
+ if not subKeyword in keywords:
+ keywords[subKeyword] = set()
+ keywords[subKeyword].add(pctxt.details["chapter"])
+ res += '<a class="anchor" name="%s"></a>' % subKeyword
+ res += '<a class="anchor" name="%s-%s"></a>' % (toplevel, subKeyword)
+ res += '<a class="anchor" name="%s-%s"></a>' % (pctxt.details["chapter"], subKeyword)
+ res += '<a class="anchor" name="%s (%s)"></a>' % (subKeyword, chapters[toplevel]['title'])
+ res += '<a class="anchor" name="%s (%s)"></a>' % (subKeyword, chapters[pctxt.details["chapter"]]['title'])
+
+ deprecated = parameters.find("(deprecated)")
+ if deprecated != -1:
+ prefix = ""
+ suffix = ""
+ parameters = parameters.replace("(deprecated)", '<span class="label label-warning">(deprecated)</span>')
+ else:
+ prefix = ""
+ suffix = ""
+
+ nextline = pctxt.get_line(1)
+
+ while nextline.startswith(" "):
+ # Found parameters on the next line
+ parameters += "\n" + nextline
+ pctxt.next()
+ if pctxt.has_more_lines(1):
+ nextline = pctxt.get_line(1)
+ else:
+ nextline = ""
+
+
+ parameters = self.colorize(parameters)
+ res += '<div class="keyword">%s<b><a class="anchor" name="%s"></a><a href="#%s">%s</a></b>%s%s</div>' % (prefix, keyword, quote("%s-%s" % (pctxt.details["chapter"], keyword)), keyword, parameters, suffix)
+ pctxt.next()
+ pctxt.stop = True
+ elif line.startswith("/*"):
+ # Skip comments in the documentation
+ while not pctxt.get_line().endswith("*/"):
+ pctxt.next()
+ pctxt.next()
+ else:
+ # This is probably not a keyword but a text, ignore it
+ res += line
+ else:
+ res += line
+
+ return res
+
+ # Used to colorize keywords parameters
+ # TODO : use CSS styling
+ def colorize(self, text):
+ colorized = ""
+ tags = [
+ [ "[" , "]" , "#008" ],
+ [ "{" , "}" , "#800" ],
+ [ "&lt;", "&gt;", "#080" ],
+ ]
+ heap = []
+ pos = 0
+ while pos < len(text):
+ substring = text[pos:]
+ found = False
+ for tag in tags:
+ if substring.startswith(tag[0]):
+ # Opening tag
+ heap.append(tag)
+ colorized += '<span style="color: %s">%s' % (tag[2], substring[0:len(tag[0])])
+ pos += len(tag[0])
+ found = True
+ break
+ elif substring.startswith(tag[1]):
+ # Closing tag
+
+ # pop opening tags until the corresponding one is found
+ openingTag = False
+ while heap and openingTag != tag:
+ openingTag = heap.pop()
+ if openingTag != tag:
+ colorized += '</span>'
+ # all intermediate tags are now closed, we can display the tag
+ colorized += substring[0:len(tag[1])]
+ # and the close it if it was previously opened
+ if openingTag == tag:
+ colorized += '</span>'
+ pos += len(tag[1])
+ found = True
+ break
+ if not found:
+ colorized += substring[0]
+ pos += 1
+ # close all unterminated tags
+ while heap:
+ tag = heap.pop()
+ colorized += '</span>'
+
+ return colorized
+
+
diff --git a/debian/dconv/parser/seealso.py b/debian/dconv/parser/seealso.py
new file mode 100644
index 0000000..bbb53f9
--- /dev/null
+++ b/debian/dconv/parser/seealso.py
@@ -0,0 +1,32 @@
+import re
+import parser
+
+class Parser(parser.Parser):
+ def parse(self, line):
+ pctxt = self.pctxt
+
+ result = re.search(r'(See also *:)', line)
+ if result:
+ label = result.group(0)
+
+ desc = re.sub(r'.*See also *:', '', line).strip()
+
+ indent = parser.get_indent(line)
+
+ # Some descriptions are on multiple lines
+ while pctxt.has_more_lines(1) and parser.get_indent(pctxt.get_line(1)) >= indent:
+ desc += " " + pctxt.get_line(1).strip()
+ pctxt.next()
+
+ pctxt.eat_empty_lines()
+ pctxt.next()
+ pctxt.stop = True
+
+ template = pctxt.templates.get_template("parser/seealso.tpl")
+ return template.render(
+ pctxt=pctxt,
+ label=label,
+ desc=desc,
+ )
+
+ return line
diff --git a/debian/dconv/parser/table.py b/debian/dconv/parser/table.py
new file mode 100644
index 0000000..e2575b1
--- /dev/null
+++ b/debian/dconv/parser/table.py
@@ -0,0 +1,244 @@
+import re
+import sys
+import parser
+
+class Parser(parser.Parser):
+ def __init__(self, pctxt):
+ parser.Parser.__init__(self, pctxt)
+ self.table1Pattern = re.compile(r'^ *(-+\+)+-+')
+ self.table2Pattern = re.compile(r'^ *\+(-+\+)+')
+
+ def parse(self, line):
+ global document, keywords, keywordsCount, chapters, keyword_conflicts
+
+ pctxt = self.pctxt
+
+ if pctxt.context['headers']['subtitle'] != 'Configuration Manual':
+ # Quick exit
+ return line
+ elif pctxt.details['chapter'] == "4":
+ # BUG: the matrix in chapter 4. Proxies is not well displayed, we skip this chapter
+ return line
+
+ if pctxt.has_more_lines(1):
+ nextline = pctxt.get_line(1)
+ else:
+ nextline = ""
+
+ if self.table1Pattern.match(nextline):
+ # activate table rendering only for the Configuration Manual
+ lineSeparator = nextline
+ nbColumns = nextline.count("+") + 1
+ extraColumns = 0
+ print("Entering table mode (%d columns)" % nbColumns, file=sys.stderr)
+ table = []
+ if line.find("|") != -1:
+ row = []
+ while pctxt.has_more_lines():
+ line = pctxt.get_line()
+ if pctxt.has_more_lines(1):
+ nextline = pctxt.get_line(1)
+ else:
+ nextline = ""
+ if line == lineSeparator:
+ # New row
+ table.append(row)
+ row = []
+ if nextline.find("|") == -1:
+ break # End of table
+ else:
+ # Data
+ columns = line.split("|")
+ for j in range(0, len(columns)):
+ try:
+ if row[j]:
+ row[j] += "<br />"
+ row[j] += columns[j].strip()
+ except:
+ row.append(columns[j].strip())
+ pctxt.next()
+ else:
+ row = []
+ headers = nextline
+ while pctxt.has_more_lines():
+ line = pctxt.get_line()
+ if pctxt.has_more_lines(1):
+ nextline = pctxt.get_line(1)
+ else:
+ nextline = ""
+
+ if nextline == "":
+ if row: table.append(row)
+ break # End of table
+
+ if (line != lineSeparator) and (line[0] != "-"):
+ start = 0
+
+ if row and not line.startswith(" "):
+ # Row is complete, parse a new one
+ table.append(row)
+ row = []
+
+ tmprow = []
+ while start != -1:
+ end = headers.find("+", start)
+ if end == -1:
+ end = len(headers)
+
+ realend = end
+ if realend == len(headers):
+ realend = len(line)
+ else:
+ while realend < len(line) and line[realend] != " ":
+ realend += 1
+ end += 1
+
+ tmprow.append(line[start:realend])
+
+ start = end + 1
+ if start >= len(headers):
+ start = -1
+ for j in range(0, nbColumns):
+ try:
+ row[j] += tmprow[j].strip()
+ except:
+ row.append(tmprow[j].strip())
+
+ deprecated = row[0].endswith("(deprecated)")
+ if deprecated:
+ row[0] = row[0][: -len("(deprecated)")].rstrip()
+
+ nooption = row[1].startswith("(*)")
+ if nooption:
+ row[1] = row[1][len("(*)"):].strip()
+
+ if deprecated or nooption:
+ extraColumns = 1
+ extra = ""
+ if deprecated:
+ extra += '<span class="label label-warning">(deprecated)</span>'
+ if nooption:
+ extra += '<span>(*)</span>'
+ row.append(extra)
+
+ pctxt.next()
+ print("Leaving table mode", file=sys.stderr)
+ pctxt.next() # skip useless next line
+ pctxt.stop = True
+
+ return self.renderTable(table, nbColumns, pctxt.details["toplevel"])
+ # elif self.table2Pattern.match(line):
+ # return self.parse_table_format2()
+ elif line.find("May be used in sections") != -1:
+ nextline = pctxt.get_line(1)
+ rows = []
+ headers = line.split(":")
+ rows.append(headers[1].split("|"))
+ rows.append(nextline.split("|"))
+ table = {
+ "rows": rows,
+ "title": headers[0]
+ }
+ pctxt.next(2) # skip this previous table
+ pctxt.stop = True
+
+ return self.renderTable(table)
+
+ return line
+
+
+ def parse_table_format2(self):
+ pctxt = self.pctxt
+
+ linesep = pctxt.get_line()
+ rows = []
+
+ pctxt.next()
+ maxcols = 0
+ while pctxt.get_line().strip().startswith("|"):
+ row = pctxt.get_line().strip()[1:-1].split("|")
+ rows.append(row)
+ maxcols = max(maxcols, len(row))
+ pctxt.next()
+ if pctxt.get_line() == linesep:
+ # TODO : find a way to define a special style for next row
+ pctxt.next()
+ pctxt.stop = True
+
+ return self.renderTable(rows, maxcols)
+
+ # Render tables detected by the conversion parser
+ def renderTable(self, table, maxColumns = 0, toplevel = None):
+ pctxt = self.pctxt
+ template = pctxt.templates.get_template("parser/table.tpl")
+
+ res = ""
+
+ title = None
+ if isinstance(table, dict):
+ title = table["title"]
+ table = table["rows"]
+
+ if not maxColumns:
+ maxColumns = len(table[0])
+
+ rows = []
+
+ mode = "th"
+ headerLine = ""
+ hasKeywords = False
+ i = 0
+ for row in table:
+ line = ""
+
+ if i == 0:
+ row_template = pctxt.templates.get_template("parser/table/header.tpl")
+ else:
+ row_template = pctxt.templates.get_template("parser/table/row.tpl")
+
+ if i > 1 and (i - 1) % 20 == 0 and len(table) > 50:
+ # Repeat headers periodically for long tables
+ rows.append(headerLine)
+
+ j = 0
+ cols = []
+ for column in row:
+ if j >= maxColumns:
+ break
+
+ tplcol = {}
+
+ data = column.strip()
+ keyword = column
+ if j == 0 and i == 0 and keyword == 'keyword':
+ hasKeywords = True
+ if j == 0 and i != 0 and hasKeywords:
+ if keyword.startswith("[no] "):
+ keyword = keyword[len("[no] "):]
+ tplcol['toplevel'] = toplevel
+ tplcol['keyword'] = keyword
+ tplcol['extra'] = []
+ if j == 0 and len(row) > maxColumns:
+ for k in range(maxColumns, len(row)):
+ tplcol['extra'].append(row[k])
+ tplcol['data'] = data
+ cols.append(tplcol)
+ j += 1
+ mode = "td"
+
+ line = row_template.render(
+ pctxt=pctxt,
+ columns=cols
+ ).strip()
+ if i == 0:
+ headerLine = line
+
+ rows.append(line)
+
+ i += 1
+
+ return template.render(
+ pctxt=pctxt,
+ title=title,
+ rows=rows,
+ )
diff --git a/debian/dconv/parser/underline.py b/debian/dconv/parser/underline.py
new file mode 100644
index 0000000..3a2350c
--- /dev/null
+++ b/debian/dconv/parser/underline.py
@@ -0,0 +1,16 @@
+import parser
+
+class Parser(parser.Parser):
+ # Detect underlines
+ def parse(self, line):
+ pctxt = self.pctxt
+ if pctxt.has_more_lines(1):
+ nextline = pctxt.get_line(1)
+ if (len(line) > 0) and (len(nextline) > 0) and (nextline[0] == '-') and ("-" * len(line) == nextline):
+ template = pctxt.templates.get_template("parser/underline.tpl")
+ line = template.render(pctxt=pctxt, data=line).strip()
+ pctxt.next(2)
+ pctxt.eat_empty_lines()
+ pctxt.stop = True
+
+ return line
diff --git a/debian/dconv/templates/parser/arguments.tpl b/debian/dconv/templates/parser/arguments.tpl
new file mode 100644
index 0000000..b5f91e9
--- /dev/null
+++ b/debian/dconv/templates/parser/arguments.tpl
@@ -0,0 +1,9 @@
+<div class="separator">
+<span class="label label-info">${label}</span>\
+% if desc:
+ ${desc}
+% endif
+% if content:
+<pre class="prettyprint arguments">${"\n".join(content)}</pre>
+% endif
+</div>
diff --git a/debian/dconv/templates/parser/example.tpl b/debian/dconv/templates/parser/example.tpl
new file mode 100644
index 0000000..184b6dd
--- /dev/null
+++ b/debian/dconv/templates/parser/example.tpl
@@ -0,0 +1,12 @@
+<div class="separator">
+<span class="label label-success">${label}</span>
+<pre class="prettyprint">
+% if desc:
+<div class="example-desc">${desc}</div>\
+% endif
+<code>\
+% for line in content:
+${line}
+% endfor
+</code></pre>
+</div> \ No newline at end of file
diff --git a/debian/dconv/templates/parser/example/comment.tpl b/debian/dconv/templates/parser/example/comment.tpl
new file mode 100644
index 0000000..b51ec2d
--- /dev/null
+++ b/debian/dconv/templates/parser/example/comment.tpl
@@ -0,0 +1 @@
+<span class="comment">\1</span> \ No newline at end of file
diff --git a/debian/dconv/templates/parser/seealso.tpl b/debian/dconv/templates/parser/seealso.tpl
new file mode 100644
index 0000000..72cf5f9
--- /dev/null
+++ b/debian/dconv/templates/parser/seealso.tpl
@@ -0,0 +1 @@
+<div class="page-header"><b>${label}</b> ${desc}</div>
diff --git a/debian/dconv/templates/parser/table.tpl b/debian/dconv/templates/parser/table.tpl
new file mode 100644
index 0000000..0119176
--- /dev/null
+++ b/debian/dconv/templates/parser/table.tpl
@@ -0,0 +1,11 @@
+% if title:
+<div><p>${title} :</p>\
+% endif
+<table class="table table-bordered" border="0" cellspacing="0" cellpadding="0">
+% for row in rows:
+${row}
+% endfor
+</table>\
+% if title:
+</div>
+% endif \ No newline at end of file
diff --git a/debian/dconv/templates/parser/table/header.tpl b/debian/dconv/templates/parser/table/header.tpl
new file mode 100644
index 0000000..e84b47f
--- /dev/null
+++ b/debian/dconv/templates/parser/table/header.tpl
@@ -0,0 +1,6 @@
+<thead><tr>\
+% for col in columns:
+<% data = col['data'] %>\
+<th>${data}</th>\
+% endfor
+</tr></thead>
diff --git a/debian/dconv/templates/parser/table/row.tpl b/debian/dconv/templates/parser/table/row.tpl
new file mode 100644
index 0000000..e4f2bef
--- /dev/null
+++ b/debian/dconv/templates/parser/table/row.tpl
@@ -0,0 +1,36 @@
+<% from urllib.parse import quote %>
+<% base = pctxt.context['base'] %>
+<tr>\
+% for col in columns:
+<% data = col['data'] %>\
+<%
+ if data in ['yes']:
+ style = "class=\"alert-success pagination-centered\""
+ data = 'yes<br /><img src="%scss/check.png" alt="yes" title="yes" />' % base
+ elif data in ['no']:
+ style = "class=\"alert-error pagination-centered\""
+ data = 'no<br /><img src="%scss/cross.png" alt="no" title="no" />' % base
+ elif data in ['X']:
+ style = "class=\"pagination-centered\""
+ data = '<img src="%scss/check.png" alt="X" title="yes" />' % base
+ elif data in ['-']:
+ style = "class=\"pagination-centered\""
+ data = '&nbsp;'
+ elif data in ['*']:
+ style = "class=\"pagination-centered\""
+ else:
+ style = None
+%>\
+<td ${style}>\
+% if "keyword" in col:
+<a href="#${quote("%s-%s" % (col['toplevel'], col['keyword']))}">\
+% for extra in col['extra']:
+<span class="pull-right">${extra}</span>\
+% endfor
+${data}</a>\
+% else:
+${data}\
+% endif
+</td>\
+% endfor
+</tr>
diff --git a/debian/dconv/templates/parser/underline.tpl b/debian/dconv/templates/parser/underline.tpl
new file mode 100644
index 0000000..4f35f7e
--- /dev/null
+++ b/debian/dconv/templates/parser/underline.tpl
@@ -0,0 +1 @@
+<h5>${data}</h5>
diff --git a/debian/dconv/templates/summary.html b/debian/dconv/templates/summary.html
new file mode 100644
index 0000000..87c6414
--- /dev/null
+++ b/debian/dconv/templates/summary.html
@@ -0,0 +1,43 @@
+<a class="anchor" id="summary" name="summary"></a>
+<div class="page-header">
+ <h1 id="chapter-summary" data-target="summary">Summary</h1>
+</div>
+<div class="row">
+ <div class="col-md-6">
+ <% previousLevel = None %>
+ % for k in chapterIndexes:
+ <% chapter = chapters[k] %>
+ % if chapter['title']:
+ <%
+ if chapter['level'] == 1:
+ otag = "<b>"
+ etag = "</b>"
+ else:
+ otag = etag = ""
+ %>
+ % if chapter['chapter'] == '7':
+ ## Quick and dirty hack to split the summary in 2 columns
+ ## TODO : implement a generic way split the summary
+ </div><div class="col-md-6">
+ <% previousLevel = None %>
+ % endif
+ % if otag and previousLevel:
+ <br />
+ % endif
+ <div class="row">
+ <div class="col-md-2 pagination-right noheight">${otag}<small>${chapter['chapter']}.</small>${etag}</div>
+ <div class="col-md-10 noheight">
+ % for tab in range(1, chapter['level']):
+ <div class="tab">
+ % endfor
+ <a href="#${chapter['chapter']}">${otag}${chapter['title']}${etag}</a>
+ % for tab in range(1, chapter['level']):
+ </div>
+ % endfor
+ </div>
+ </div>
+ <% previousLevel = chapter['level'] %>
+ % endif
+ % endfor
+ </div>
+</div>
diff --git a/debian/dconv/templates/template.html b/debian/dconv/templates/template.html
new file mode 100644
index 0000000..c72b355
--- /dev/null
+++ b/debian/dconv/templates/template.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <title>${headers['title']} ${headers['version']} - ${headers['subtitle']}</title>
+ <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />
+ <link href="${base}css/page.css?${version}" rel="stylesheet" />
+ </head>
+ <body>
+ <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#menu">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="${base}index.html">${headers['title']} <small>${headers['subtitle']}</small></a>
+ </div>
+ <!-- /.navbar-header -->
+
+ <!-- Collect the nav links, forms, and other content for toggling -->
+ <div class="collapse navbar-collapse" id="menu">
+ <ul class="nav navbar-nav">
+ <li><a href="http://www.haproxy.org/">HAProxy home page</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Versions <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ ## TODO : provide a structure to dynamically generate per version links
+ <li class="dropdown-header">HAProxy 1.4</li>
+ <li><a href="${base}configuration-1.4.html">Configuration Manual <small>(stable)</small></a></li>
+ <li><a href="${base}snapshot/configuration-1.4.html">Configuration Manual <small>(snapshot)</small></a></li>
+ <li><a href="http://git.1wt.eu/git/haproxy-1.4.git/">GIT Repository</a></li>
+ <li><a href="http://www.haproxy.org/git/?p=haproxy-1.4.git">Browse repository</a></li>
+ <li><a href="http://www.haproxy.org/download/1.4/">Browse directory</a></li>
+ <li class="divider"></li>
+ <li class="dropdown-header">HAProxy 1.5</li>
+ <li><a href="${base}configuration-1.5.html">Configuration Manual <small>(stable)</small></a></li>
+ <li><a href="${base}snapshot/configuration-1.5.html">Configuration Manual <small>(snapshot)</small></a></li>
+ <li><a href="http://git.1wt.eu/git/haproxy-1.5.git/">GIT Repository</a></li>
+ <li><a href="http://www.haproxy.org/git/?p=haproxy-1.5.git">Browse repository</a></li>
+ <li><a href="http://www.haproxy.org/download/1.5/">Browse directory</a></li>
+ <li class="divider"></li>
+ <li class="dropdown-header">HAProxy 1.6</li>
+ <li><a href="${base}configuration-1.6.html">Configuration Manual <small>(stable)</small></a></li>
+ <li><a href="${base}snapshot/configuration-1.6.html">Configuration Manual <small>(snapshot)</small></a></li>
+ <li><a href="${base}intro-1.6.html">Starter Guide <small>(stable)</small></a></li>
+ <li><a href="${base}snapshot/intro-1.6.html">Starter Guide <small>(snapshot)</small></a></li>
+ <li><a href="http://git.1wt.eu/git/haproxy.git/">GIT Repository</a></li>
+ <li><a href="http://www.haproxy.org/git/?p=haproxy.git">Browse repository</a></li>
+ <li><a href="http://www.haproxy.org/download/1.6/">Browse directory</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </nav>
+ <!-- /.navbar-static-side -->
+
+ <div id="wrapper">
+
+ <div id="sidebar">
+ <form onsubmit="search(this.keyword.value); return false" role="form">
+ <div id="searchKeyword" class="form-group">
+ <input type="text" class="form-control typeahead" id="keyword" name="keyword" placeholder="Search..." autocomplete="off">
+ </div>
+ </form>
+ <p>
+ Keyboard navigation&nbsp;: <span id="keyboardNavStatus"></span>
+ </p>
+ <p>
+ When enabled, you can use <strong>left</strong> and <strong>right</strong> arrow keys to navigate between chapters.<br>
+ The feature is automatically disabled when the search field is focused.
+ </p>
+ <p class="text-right">
+ <small>Converted with <a href="https://github.com/cbonte/haproxy-dconv">haproxy-dconv</a> v<b>${version}</b> on <b>${date}</b></small>
+ </p>
+ </div>
+ <!-- /.sidebar -->
+
+ <div id="page-wrapper">
+ <div class="row">
+ <div class="col-lg-12">
+ <div class="text-center">
+ <h1>${headers['title']}</h1>
+ <h2>${headers['subtitle']}</h2>
+ <p><strong>${headers['version']}</strong></p>
+ <p>
+ <a href="http://www.haproxy.org/" title="HAProxy Home Page"><img src="${base}img/logo-med.png" /></a><br>
+ ${headers['author']}<br>
+ ${headers['date']}
+ </p>
+ </div>
+
+ ${document}
+ <br>
+ <hr>
+ <div class="text-right">
+ ${headers['title']} ${headers['version'].replace("version ", "")} &ndash; ${headers['subtitle']}<br>
+ <small>${headers['date']}, ${headers['author']}</small>
+ </div>
+ </div>
+ <!-- /.col-lg-12 -->
+ </div>
+ <!-- /.row -->
+ <div style="position: fixed; z-index: 1000; bottom: 0; left: 0; right: 0; padding: 10px">
+ <ul class="pager" style="margin: 0">
+ <li class="previous"><a id="previous" href="#"></a></li>
+ <li class="next"><a id="next" href="#"></a></li>
+ </ul>
+ </div>
+ </div>
+ <!-- /#page-wrapper -->
+
+ </div>
+ <!-- /#wrapper -->
+
+ <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/js/bootstrap.min.js"></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js"></script>
+ <script>
+ /* Keyword search */
+ var searchFocus = false
+ var keywords = [
+ "${'",\n\t\t\t\t"'.join(keywords)}"
+ ]
+
+ function updateKeyboardNavStatus() {
+ var status = searchFocus ? '<span class="label label-disabled">Disabled</span>' : '<span class="label label-success">Enabled</span>'
+ $('#keyboardNavStatus').html(status)
+ }
+
+ function search(keyword) {
+ if (keyword && !!~$.inArray(keyword, keywords)) {
+ window.location.hash = keyword
+ }
+ }
+ // constructs the suggestion engine
+ var kwbh = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ local: $.map(keywords, function(keyword) { return { value: keyword }; })
+ });
+ kwbh.initialize()
+
+ $('#searchKeyword .typeahead').typeahead({
+ hint: true,
+ highlight: true,
+ minLength: 1,
+ autoselect: true
+ },
+ {
+ name: 'keywords',
+ displayKey: 'value',
+ limit: keywords.length,
+ source: kwbh.ttAdapter()
+ }).focus(function() {
+ searchFocus = true
+ updateKeyboardNavStatus()
+ }).blur(function() {
+ searchFocus = false
+ updateKeyboardNavStatus()
+ }).bind('typeahead:selected', function ($e, datum) {
+ search(datum.value)
+ })
+
+ /* EXPERIMENTAL - Previous/Next navigation */
+ var headings = $(":header")
+ var previousTarget = false
+ var nextTarget = false
+ var $previous = $('#previous')
+ var $next = $('#next')
+ function refreshNavigation() {
+ var previous = false
+ var next = false
+ $.each(headings, function(item, value) {
+ var el = $(value)
+
+ // TODO : avoid target recalculation on each refresh
+ var target = el.attr('data-target')
+ if (! target) return true
+
+ var target_el = $('#' + target.replace(/\./, "\\."))
+ if (! target_el.attr('id')) return true
+
+ if (target_el.offset().top < $(window).scrollTop()) {
+ previous = el
+ }
+ if (target_el.offset().top - 1 > $(window).scrollTop()) {
+ next = el
+ }
+ if (next) return false
+ })
+
+ previousTarget = previous ? previous.attr('data-target') : 'top'
+ $previous.html(
+ previous && previousTarget ?
+ '<span class="glyphicon glyphicon-arrow-left"></span> ' + previous.text() :
+ '<span class="glyphicon glyphicon-arrow-up"></span> Top'
+ ).attr('href', '#' + previousTarget)
+
+ nextTarget = next ? next.attr('data-target') : 'bottom'
+ $next.html(
+ next && nextTarget ?
+ next.text() + ' <span class="glyphicon glyphicon-arrow-right"></span>' :
+ 'Bottom <span class="glyphicon glyphicon-arrow-down"></span>'
+ ).attr('href', '#' + nextTarget)
+ }
+
+ $(window).scroll(function () {
+ refreshNavigation()
+ });
+ $(document).ready(function() {
+ refreshNavigation()
+ updateKeyboardNavStatus()
+ });
+
+ /* EXPERIMENTAL - Enable keyboard navigation */
+ $(document).keydown(function(e){
+ if (searchFocus) return
+
+ switch(e.which) {
+ case 37: // left
+ window.location.hash = previousTarget ? previousTarget : 'top'
+ break
+
+ case 39: // right
+ window.location.hash = nextTarget ? nextTarget : 'bottom'
+ break
+
+ default: return // exit this handler for other keys
+ }
+ e.preventDefault()
+ })
+ </script>
+ ${footer}
+ <a class="anchor" name="bottom"></a>
+ </body>
+</html>
diff --git a/debian/dconv/tools/generate-docs.sh b/debian/dconv/tools/generate-docs.sh
new file mode 100755
index 0000000..36fdf1b
--- /dev/null
+++ b/debian/dconv/tools/generate-docs.sh
@@ -0,0 +1,177 @@
+#!/bin/bash
+
+PROJECT_HOME=$(dirname $(readlink -f $0))
+cd $PROJECT_HOME || exit 1
+
+WORK_DIR=$PROJECT_HOME/work
+
+function on_exit()
+{
+ echo "-- END $(date)"
+}
+
+function init()
+{
+ trap on_exit EXIT
+
+ echo
+ echo "-- START $(date)"
+ echo "PROJECT_HOME = $PROJECT_HOME"
+
+ echo "Preparing work directories..."
+ mkdir -p $WORK_DIR || exit 1
+ mkdir -p $WORK_DIR/haproxy || exit 1
+ mkdir -p $WORK_DIR/haproxy-dconv || exit 1
+
+ UPDATED=0
+ PUSH=0
+
+}
+
+# Needed as "git -C" is only available since git 1.8.5
+function git-C()
+{
+ _gitpath=$1
+ shift
+ echo "git --git-dir=$_gitpath/.git --work-tree=$_gitpath $@" >&2
+ git --git-dir=$_gitpath/.git --work-tree=$_gitpath "$@"
+}
+
+function fetch_haproxy_dconv()
+{
+ echo "Fetching latest haproxy-dconv public version..."
+ if [ ! -e $WORK_DIR/haproxy-dconv/master ];
+ then
+ git clone -v git://github.com/cbonte/haproxy-dconv.git $WORK_DIR/haproxy-dconv/master || exit 1
+ fi
+ GIT="git-C $WORK_DIR/haproxy-dconv/master"
+
+ OLD_MD5="$($GIT log -1 | md5sum) $($GIT describe --tags)"
+ $GIT checkout master && $GIT pull -v
+ version=$($GIT describe --tags)
+ version=${version%-g*}
+ NEW_MD5="$($GIT log -1 | md5sum) $($GIT describe --tags)"
+ if [ "$OLD_MD5" != "$NEW_MD5" ];
+ then
+ UPDATED=1
+ fi
+
+ echo "Fetching last haproxy-dconv public pages version..."
+ if [ ! -e $WORK_DIR/haproxy-dconv/gh-pages ];
+ then
+ cp -a $WORK_DIR/haproxy-dconv/master $WORK_DIR/haproxy-dconv/gh-pages || exit 1
+ fi
+ GIT="git-C $WORK_DIR/haproxy-dconv/gh-pages"
+
+ $GIT checkout gh-pages && $GIT pull -v
+}
+
+function fetch_haproxy()
+{
+ url=$1
+ path=$2
+
+ echo "Fetching HAProxy 1.4 repository..."
+ if [ ! -e $path ];
+ then
+ git clone -v $url $path || exit 1
+ fi
+ GIT="git-C $path"
+
+ $GIT checkout master && $GIT pull -v
+}
+
+function _generate_file()
+{
+ infile=$1
+ destfile=$2
+ git_version=$3
+ state=$4
+
+ $GIT checkout $git_version
+
+ if [ -e $gitpath/doc/$infile ];
+ then
+
+ git_version_simple=${git_version%-g*}
+ doc_version=$(tail -n1 $destfile 2>/dev/null | grep " git:" | sed 's/.* git:\([^ ]*\).*/\1/')
+ if [ $UPDATED -eq 1 -o "$git_version" != "$doc_version" ];
+ then
+ HTAG="VERSION-$(basename $gitpath | sed 's/[.]/\\&/g')"
+ if [ "$state" == "snapshot" ];
+ then
+ base=".."
+ HTAG="$HTAG-SNAPSHOT"
+ else
+ base="."
+ fi
+
+
+ $WORK_DIR/haproxy-dconv/master/haproxy-dconv.py -i $gitpath/doc/$infile -o $destfile --base=$base &&
+ echo "<!-- git:$git_version -->" >> $destfile &&
+ sed -i "s/\(<\!-- $HTAG -->\)\(.*\)\(<\!-- \/$HTAG -->\)/\1${git_version_simple}\3/" $docroot/index.html
+
+ else
+ echo "Already up to date."
+ fi
+
+ if [ "$doc_version" != "" -a "$git_version" != "$doc_version" ];
+ then
+ changelog=$($GIT log --oneline $doc_version..$git_version $gitpath/doc/$infile)
+ else
+ changelog=""
+ fi
+
+ GITDOC="git-C $docroot"
+ if [ "$($GITDOC status -s $destfile)" != "" ];
+ then
+ $GITDOC add $destfile &&
+ $GITDOC commit -m "Updating HAProxy $state $infile ${git_version_simple} generated by haproxy-dconv $version" -m "$changelog" $destfile $docroot/index.html &&
+ PUSH=1
+ fi
+ fi
+}
+
+function generate_docs()
+{
+ url=$1
+ gitpath=$2
+ docroot=$3
+ infile=$4
+ outfile=$5
+
+ fetch_haproxy $url $gitpath
+
+ GIT="git-C $gitpath"
+
+ $GIT checkout master
+ git_version=$($GIT describe --tags --match 'v*')
+ git_version_stable=${git_version%-*-g*}
+
+ echo "Generating snapshot version $git_version..."
+ _generate_file $infile $docroot/snapshot/$outfile $git_version snapshot
+
+ echo "Generating stable version $git_version..."
+ _generate_file $infile $docroot/$outfile $git_version_stable stable
+}
+
+function push()
+{
+ docroot=$1
+ GITDOC="git-C $docroot"
+
+ if [ $PUSH -eq 1 ];
+ then
+ $GITDOC push origin gh-pages
+ fi
+
+}
+
+
+init
+fetch_haproxy_dconv
+generate_docs http://git.1wt.eu/git/haproxy-1.4.git/ $WORK_DIR/haproxy/1.4 $WORK_DIR/haproxy-dconv/gh-pages configuration.txt configuration-1.4.html
+generate_docs http://git.1wt.eu/git/haproxy-1.5.git/ $WORK_DIR/haproxy/1.5 $WORK_DIR/haproxy-dconv/gh-pages configuration.txt configuration-1.5.html
+generate_docs http://git.1wt.eu/git/haproxy.git/ $WORK_DIR/haproxy/1.6 $WORK_DIR/haproxy-dconv/gh-pages configuration.txt configuration-1.6.html
+generate_docs http://git.1wt.eu/git/haproxy.git/ $WORK_DIR/haproxy/1.6 $WORK_DIR/haproxy-dconv/gh-pages intro.txt intro-1.6.html
+push $WORK_DIR/haproxy-dconv/gh-pages