source: doc/user-guide/genhelp.py @ 21a328ab

Last change on this file since 21a328ab 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
Line 
1#!/usr/bin/env python
2
3# Usage: python genhelp.py input.xml output.txt
4# (Both python2 (>=2.5) or python3 work)
5#
6# The shebang above isn't used, set the PYTHON environment variable
7# before running ./configure instead
8
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
25import os
26import re
27import sys
28import xml.etree.ElementTree as ET
29
30NORMALIZE_RE = re.compile(r"([^<>\s\t])[\s\t]+([^<>\s\t])")
31
32# Helpers
33
34def normalize(x):
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
42    x = NORMALIZE_RE.sub(r"\1 \2", x or '')
43    return x.replace("\n", "").replace("\t", "")
44
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.
53    if tag.tag.count("XInclude"):
54        tag.tag = 'include'
55
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)]))
59
60    for subtag in tag:
61        fix_tree(subtag, debug, lvl + "  ")
62
63    if debug:
64        print("%s</%s>%r" % (lvl, tag.tag, [tag.tail, normalize(tag.tail)]))
65
66    # Actually normalize whitespace
67    if 'pre' not in tag.attrib:
68        tag.text = normalize(tag.text)
69    tag.tail = normalize(tag.tail)
70
71
72# Main logic
73
74def process_file(filename, parent=None):
75    try:
76        tree = ET.parse(open(filename)).getroot()
77    except:
78        sys.stderr.write("\nException while processing %s\n" % filename)
79        raise
80    fix_tree(tree)
81    return parse_tag(tree, parent)
82
83def parse_tag(tag, parent):
84    """Calls a tag_... function based on the tag name"""
85
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
97
98# Main tag handlers
99
100def handle_subject(tag, parent):
101    """Tag handler for preface, chapter, sect1 and sect2 (aliased below)"""
102
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:
110                # Spaces between paragraphs
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=''):
133    """Tag handler for <bitlbee-command> (called from handle_subject)"""
134
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):
156    """Tag handler for <bitlbee-setting> (called from handle_subject)"""
157
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
172
173# Aliases for tags that behave like subjects
174tag_preface = handle_subject
175tag_chapter = handle_subject
176tag_sect1 = handle_subject
177tag_sect2 = handle_subject
178
179# Aliases for tags that don't have any special behavior
180tag_ulink = parse_subtags
181tag_note = parse_subtags
182tag_book = parse_subtags
183tag_ircexample = parse_subtags
184
185
186# Handlers for specific tags
187
188def tag_include(tag, parent):
189    return process_file(tag.attrib['href'], tag)
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
227
228def main():
229    if len(sys.argv) != 3:
230        print("Usage: python genhelp.py input.xml output.txt")
231        return
232
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
236    txt = process_file(sys.argv[1])
237    open(sys.argv[2], "w").write(txt)
238
239if __name__ == '__main__':
240    main()
Note: See TracBrowser for help on using the repository browser.