source: doc/user-guide/genhelp.py @ 16e4a9f

Last change on this file since 16e4a9f was 15a8c94, checked in by dequis <dx@…>, at 2016-12-26T00:18:55Z

genhelp: Slightly improve error messages on xml parse error

  • Property mode set to 100644
File size: 6.9 KB
RevLine 
[d6acf79]1#!/usr/bin/env python
[ad678a4]2
3# Usage: python genhelp.py input.xml output.txt
4# (Both python2 (>=2.5) or python3 work)
[1cef55f]5#
6# The shebang above isn't used, set the PYTHON environment variable
7# before running ./configure instead
[ad678a4]8
[d6acf79]9# This program is free software; you can redistribute it and/or
10# modify it under the terms of the GNU General Public License
11# as published by the Free Software Foundation; either version 2
12# of the License, or (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor,
22# Boston, MA  02110-1301, USA.
23
24
[fd45e859]25import os
[9740ce9]26import re
[ad678a4]27import sys
[9740ce9]28import xml.etree.ElementTree as ET
29
30NORMALIZE_RE = re.compile(r"([^<>\s\t])[\s\t]+([^<>\s\t])")
31
[d6acf79]32# Helpers
[9740ce9]33
34def normalize(x):
[d6acf79]35    """Normalize whitespace of a string.
36
37    The regexp turns any sequence of whitespace into a single space if it's in
38    the middle of the tag text, and then all newlines and tabs are removed,
39    keeping spaces.
40    """
41
[9740ce9]42    x = NORMALIZE_RE.sub(r"\1 \2", x or '')
43    return x.replace("\n", "").replace("\t", "")
44
[d6acf79]45def join(list):
46    """Turns any iterator into a string"""
47    return ''.join([str(x) for x in list])
48
49def fix_tree(tag, debug=False, lvl=''):
50    """Walks the XML tree and modifies it in-place fixing various details"""
51
52    # The include tags have an ugly namespace in the tag name. Simplify that.
[9740ce9]53    if tag.tag.count("XInclude"):
54        tag.tag = 'include'
55
[d6acf79]56    # Print a pretty tree-like representation of the processed tags
57    if debug:
58        print("%s<%s>%r" % (lvl, tag.tag, [tag.text, normalize(tag.text)]))
[9740ce9]59
60    for subtag in tag:
[d6acf79]61        fix_tree(subtag, debug, lvl + "  ")
[9740ce9]62
[d6acf79]63    if debug:
64        print("%s</%s>%r" % (lvl, tag.tag, [tag.tail, normalize(tag.tail)]))
[9740ce9]65
[d6acf79]66    # Actually normalize whitespace
[028ca92]67    if 'pre' not in tag.attrib:
68        tag.text = normalize(tag.text)
[9740ce9]69    tag.tail = normalize(tag.tail)
70
[d6acf79]71
72# Main logic
73
74def process_file(filename, parent=None):
[15a8c94]75    try:
76        tree = ET.parse(open(filename)).getroot()
77    except:
78        sys.stderr.write("\nException while processing %s\n" % filename)
79        raise
[9740ce9]80    fix_tree(tree)
81    return parse_tag(tree, parent)
82
83def parse_tag(tag, parent):
[d6acf79]84    """Calls a tag_... function based on the tag name"""
85
[9740ce9]86    fun = globals()["tag_%s" % tag.tag.replace("-", "_")]
87    return join(fun(tag, parent))
88
89def parse_subtags(tag, parent=None):
90    yield tag.text
91
92    for subtag in tag:
93        yield parse_tag(subtag, tag)
94
95    yield tag.tail
96
[d6acf79]97
98# Main tag handlers
99
[9740ce9]100def handle_subject(tag, parent):
[d6acf79]101    """Tag handler for preface, chapter, sect1 and sect2 (aliased below)"""
102
[9740ce9]103    yield '?%s\n' % tag.attrib['id']
104
105    first = True
106    for element in tag:
107        if element.tag in ["para", "variablelist", "simplelist",
108                           "command-list", "ircexample"]:
109            if not first:
[d6acf79]110                # Spaces between paragraphs
[9740ce9]111                yield "\n"
112            first = False
113
114            if element.attrib.get('title', ''):
115                yield element.attrib['title']
116                yield "\n"
117            yield join(parse_tag(element, tag)).rstrip("\n")
118            yield "\n"
119
120    yield "%\n"
121
122    for element in tag:
123        if element.tag in ["sect1", "sect2"]:
124            yield join(handle_subject(element, tag))
125
126    for element in tag.findall("bitlbee-command"):
127        yield join(handle_command(element))
128
129    for element in tag.findall("bitlbee-setting"):
130        yield join(handle_setting(element))
131
132def handle_command(tag, prefix=''):
[d6acf79]133    """Tag handler for <bitlbee-command> (called from handle_subject)"""
134
[9740ce9]135    this_cmd = prefix + tag.attrib['name']
136
137    yield "?%s\n" % this_cmd
138    for syntax in tag.findall("syntax"):
139        yield '\x02Syntax:\x02 %s\n' % syntax.text
140
141    yield "\n"
142    yield join(parse_subtags(tag.find("description"))).rstrip()
143    yield "\n"
144
145    for example in tag.findall("ircexample"):
146        yield "\n\x02Example:\x02\n"
147        yield join(parse_subtags(example)).rstrip()
148        yield "\n"
149
150    yield "%\n"
151
152    for element in tag.findall("bitlbee-command"):
153        yield join(handle_command(element, this_cmd + " "))
154
155def handle_setting(tag):
[d6acf79]156    """Tag handler for <bitlbee-setting> (called from handle_subject)"""
157
[9740ce9]158    yield "?set %s\n" % tag.attrib['name']
159    yield "\x02Type:\x02 %s\n" % tag.attrib["type"]
160    yield "\x02Scope:\x02 %s\n" % tag.attrib["scope"]
161
162    if tag.find("default") is not None:
163        yield "\x02Default:\x02 %s\n" % tag.findtext("default")
164
165    if tag.find("possible-values") is not None:
166        yield "\x02Possible Values:\x02 %s\n" % tag.findtext("possible-values")
167
168    yield "\n"
169    yield join(parse_subtags(tag.find("description"))).rstrip()
170    yield "\n%\n"
171
[d6acf79]172
173# Aliases for tags that behave like subjects
[9740ce9]174tag_preface = handle_subject
175tag_chapter = handle_subject
176tag_sect1 = handle_subject
177tag_sect2 = handle_subject
178
[d6acf79]179# Aliases for tags that don't have any special behavior
[9740ce9]180tag_ulink = parse_subtags
181tag_note = parse_subtags
182tag_book = parse_subtags
183tag_ircexample = parse_subtags
184
[d6acf79]185
186# Handlers for specific tags
187
[9740ce9]188def tag_include(tag, parent):
[d6acf79]189    return process_file(tag.attrib['href'], tag)
[9740ce9]190
191def tag_para(tag, parent):
192    return join(parse_subtags(tag)) + "\n\n"
193
194def tag_emphasis(tag, parent):
195    return "\x02%s\x02%s" % (tag.text, tag.tail)
196
197def tag_ircline(tag, parent):
198    return "\x02<%s>\x02 %s\n" % (tag.attrib['nick'], join(parse_subtags(tag)))
199
200def tag_ircaction(tag, parent):
201    return "\x02* %s\x02 %s\n" % (tag.attrib['nick'], join(parse_subtags(tag)))
202
203def tag_command_list(tag, parent):
204    yield "These are all root commands. See \x02help <command name>\x02 " \
205          "for more details on each command.\n\n"
206
207    for subtag in parent.findall("bitlbee-command"):
208        yield " * \x02%s\x02 - %s\n" % \
209            (subtag.attrib['name'],
210             subtag.findtext("short-description"))
211
212    yield "\nMost commands can be shortened. For example instead of " \
213          "\x02account list\x02, try \x02ac l\x02.\n\n"
214
215def tag_variablelist(tag, parent):
216    for subtag in tag:
217        yield " \x02%s\x02 - %s\n" % \
218            (subtag.findtext("term"),
219             join(parse_subtags(subtag.find("listitem/para"))))
220    yield '\n'
221
222def tag_simplelist(tag, parent):
223    for subtag in tag:
224        yield " - %s\n" % join(parse_subtags(subtag))
225    yield '\n'
226
[d6acf79]227
[9740ce9]228def main():
[ad678a4]229    if len(sys.argv) != 3:
230        print("Usage: python genhelp.py input.xml output.txt")
231        return
232
[fd45e859]233    # ensure that we really are in the same directory as the input file
234    os.chdir(os.path.dirname(os.path.abspath(sys.argv[1])))
235
[ad678a4]236    txt = process_file(sys.argv[1])
237    open(sys.argv[2], "w").write(txt)
[9740ce9]238
239if __name__ == '__main__':
240    main()
Note: See TracBrowser for help on using the repository browser.