1#!/usr/local/bin/python3.8
2# -*- coding: UTF-8 -*-
3
4import sys
5import re
6import commands
7import warnings
8
9errors = 0
10def warn(msg, error=None):
11    global errors
12    errors += 1
13    if error is None:
14        warnings.warn("-- "+str(errors)+" --\n  "+msg, RuntimeWarning, 2)
15    else:
16        warnings.warn("-- "+str(errors)+" --\n  "+msg+
17                      "\n  error was "+str(error), RuntimeWarning, 2)
18#fu
19
20# beware, stupid python interprets backslashes in repl only partially!
21def s(string, pattern, repl, count=0):
22    return re.sub(pattern, repl, string, count)
23def m(string, pattern):
24    return re.match(pattern, string)
25def sorted_keys(dict):
26    keys = dict.keys()
27    keys.sort()
28    return keys
29
30# we make up a few formatter routines to help in the processing:
31def html2docbook(text):
32    """ the C comment may contain html markup - simulate with docbook tags """
33    return (
34        s(s(s(s(s(s(s(s(s(s(s(text,
35                              r"<br\s*/?>",""),
36                            r"(</?)em>",r"\1emphasis>"),
37                          r"<code>","<userinput>"),
38                        r"</code>","</userinput>"),
39                      r"<link>","<function>"),
40                    r"</link>","</function>"),
41                  r"(?s)\s*</screen>","</screen>"),
42#               r"<ul>","</para><itemizedlist>"),
43#             r"</ul>","</itemizedlist><para>"),
44#           r"<li>","<listitem><para>"),
45#         r"</li>","</para></listitem>\n"),
46                r"<ul>","</para><programlisting>\n"),
47              r"</ul>","</programlisting><para>"),
48            r"<li>",""),
49          r"</li>",""))
50def paramdef2html(text):
51    return s(s(s(s(s(text,
52                     r"\s+<paramdef>", r"\n<nobr>"),
53                   r"<paramdef>",r"<nobr>"),
54                 r"</paramdef>",r"</nobr>"),
55               r"<parameters>",r"\n <code>"),
56             r"</parameters>",r"</code>\n")
57def section2html(text):
58    mapping = { "<screen>" : "<pre>", "</screen>" : "</pre>",
59                "<para>" : "<p>", "</para>" : "</p>" ,
60                "<function>" : "<link>", "</function>" : "</link>" }
61    for m in mapping:
62        text = text.replace(m, mapping[m])
63    return text
64def html(text):
65    return section2html(paramdef2html(text))
66def cdata1(text):
67    return text.replace("&",  "&amp;")
68def cdata31(text):
69    text = text.replace("<","&lt;")
70    return text.replace(">","&gt;")
71def cdata3(text):
72    return cdata31(cdata1(text))
73def cdata43(text):
74    return text.replace("\"", "&quot;")
75def cdata41(text):
76    return cdata43(cdata31(text))
77def cdata4(text):
78    return cdata43(cdata3(text))
79def markup_as_screen41 (text):
80    """ used for non-star lines in comment blocks """
81    return " <screen> " + s(cdata41(text), r"(?m)^", r" ") +" </screen> "
82
83def file_comment2section(text):
84    """ convert a C comment into a series of <para> and <screen> parts """
85    return ("<para>\n"+
86            s(s(s(s(s(s(s(text,
87                          r"(?s){<([\w\.\-]+\@[\w\.\-]+\w\w)>",
88                          r"&lt;\1&gt;"),
89                        r"(?mx) ^\s?\s?\s? ([^\*\s]+ .*) $",
90                        lambda x : markup_as_screen41 (x.group(1))),
91                      r"(?mx) ^\s*[*]\s* $", r" \n</para><para>\n"),
92                    r"(?mx) ^\s?\s?\s?\* (.*) $", r" \1 "),
93                  r"(?sx) </screen>(\s*)<screen> ", r"\1"),
94                r"(?sx) <([^<>\;]+\@[^<>\;]+)> ", r"<email>\1</email>"),
95              r"(?sx) \&lt\;([^<>\&\;]+\@[^<>\&\;]+)\&gt\; ",
96              r"<email>\1</email>") + "\n</para>")
97def func_comment2section(text):
98    """ convert a C comment into a series of <para> and <screen> parts
99        and sanitize a few markups already present in the comment text
100    """
101    return ("<para>\n"+
102            s(s(s(s(s(s(s(s(s(s(s(text,
103                                  r"<c>",r"<code>"),   r"</c>", r"</code>"),
104                              r"(?mx) ^\s?\s?\s? ([^\*\s]+.*)",
105                              lambda x: markup_as_screen41 (x.group(1))),
106                            r"(?mx) ^\s?\s?\s?\* (.*) $", r" <br /> \1"),
107                          r"(?mx) ^\s*<br\s*\/>\s* $", r"\n</para><para>\n"),
108                        r"<<",r"&lt;"),   r">>",r"&gt;"),
109                    r"(?sx) (</?para>\s*)<br\s*\/?>",r"\1"),
110                  r"(?sx) (</?para>\s*)<br\s*\/?>",r"\1"),
111                r"(?sx) (<br\s*\/?>\s*)<br\s*\/?>",r"\1"),
112              r"(?sx) <\/screen>(\s*)<screen>",r"\1") + "\n</para>")
113def markup_link_syntax(text):
114    """ markup the link-syntax ` => somewhere ` in the text block """
115    return (
116        s(s(s(s(text,
117                r"(?mx) (^|\s)\=\>\"([^\"]*)\"", r"\1<link>\2</link>"),
118              r"(?mx) (^|\s)\=\>\'([^\"]*)\'", r"\1<link>\2</link>"),
119            r"(?mx) (^|\s)\=\>\s(\w[\w.]*\w)\b", r"\1<link>\2</link>"),
120          r"(?mx) (^|\s)\=\>\s([^\s\,\.\!\?\:\;\<\>\&\'\=\-]+)",
121          r"\1<link>\2</link>"))
122def this_function_link(text, name):
123    return s(text, r"(?sx) (T|t)his \s (function|procedure) ", lambda x
124             : "<function>"+x.group(1)+"he "+name+" "+x.group(2)+"</function>")
125
126# -----------------------------------------------------------------------
127class Options:
128    var = {}
129    def __getattr__(self, name):
130        if not name in self.var: return None
131        return self.var[name]
132    def __setattr__(self, name, value):
133        self.var[name] = value
134#end
135
136o = Options()
137o.verbose = 0
138
139o.version = s( commands.getoutput(
140    """ grep -i "^version *:" *.spec 2>/dev/null |
141        sed -e "s/[Vv]ersion *: *//" """),  r"\s*",r"")
142o.package = s(commands.getoutput(
143    """ grep -i "^name *:" *.spec 2>/dev/null |
144        sed -e "s/[Nn]ame *: *//" """),     r"\s*",r"")
145
146if not len(o.version):
147    o.version = commands.getoutput(""" date +%Y.%m.%d """)
148if not len(o.package):
149    o.package = "_project"
150
151o.suffix = "-doc3"
152o.mainheader = o.package+".h"
153
154class File:
155    def __init__(self, filename):
156        self.name = filename
157        self.mainheader = o.mainheader
158        self.authors = ""
159        self.copyright = ""
160    def __getattr__(self, name):
161        """ defend against program to break on uninited members """
162        if name in self.__dict__: return self.__dict__[name]
163        warn("no such member: "+name); return None
164    def set_author(self, text):
165        if self.authors:
166            self.authors += "\n"
167        self.authors += text
168        return text
169    def set_copyright(self, text):
170        self.copyright = text
171        return text
172
173class InputFiles:
174    """ for each set of input files we can create an object
175        it does correspond with a single html-output page and
176        a single docbook <reference> master page to be output
177    """
178    def __init__(self):
179        # the id will tell us in which order
180        # we did meet each function definition
181        self.id = 1000
182        self.files = [] # file_list
183        self.funcs = [] # func_list: of hidden class FuncDeclaration
184        self.file = None # current file
185    def new_File(self, name):
186        self.file = File(name)
187        self.files.append(self.file)
188        return self.file
189    def next_id(self):
190        id = self.id ; self.id += 1
191        return id
192    def add_function_declaration(self, comment, prototype):
193        class FuncDeclaration:    # note that both decl.comment and
194            pass                  # decl.prototype are in cdata1 format
195        func = FuncDeclaration()
196        func.file = self.file
197        func.comment = s(comment, # need to take out email-style markups
198                         r"<([\w\.\-]+\@[\w\.\-]+\w\w)>", r"&lt;\1&gt;")
199        func.prototype = prototype
200        func.id = all.next_id()
201        self.funcs.append(func)
202        # print id
203        return prototype
204
205def scan_options (options, list):
206    def encode(text):
207        return s(s(text, r"¬",  r"&#AC;"), r"\*/",r"¬")
208    def decode(text):
209        return s(text, r"¬", r"*/")
210
211    for name in options:
212        found = m(name, r"^(\w+)=(.*)")
213        if found:
214            o.var[found.group(1)] = found.group(2)
215            continue
216        #else
217        try:
218            input = open(name, "r")
219        except IOError as error:
220            warn(#...... (scan_options) ...............
221                "can not open input file: "+name, error)
222            continue
223        text = input.read() ; input.close()
224        text = encode (cdata1 (text))
225
226        file = list.new_File(name)
227
228        # cut per-function comment block
229        text = s(text, r"(?x) [/][*][*](?=\s) ([^¬]+) ¬ ([^\{\}\;\#]+) [\{\;]",
230                 lambda x : list.add_function_declaration(
231            decode(x.group(1)), decode(x.group(2))))
232
233        # cut per-file comment block
234        found = m(text, r"(?sx)  [/][*]+(?=\s) ([^¬]+) ¬ "
235                  r"(?:\s*\#define\s*\S+)*"
236                  r"(\s*\#include\s*<[^<>]*>(?:\s*//[^\n]*)?)")
237        if found:
238            file.comment = decode(found.group(1))
239            file.include = cdata31(found.group(2))
240        else:
241            file.comment = None
242            file.include = None
243            found = m(text, r"(?sx)  ^ [/][*]+(?=\s) ([^¬]+) ¬ ")
244            if found:
245                file.comment = decode(found.group(1))
246        #fi
247        # throw away the rest - further processing on memorized strings only
248
249    return None
250
251all = InputFiles()
252scan_options (sys.argv[1:], all)
253
254if not o.docbookfile:
255    o.docbookfile = o.package+o.suffix+".docbook"
256if not o.libhtmlfile:
257    o.libhtmlfile = o.package+o.suffix+".html"
258if not o.dumpdocfile:
259    o.dumpdocfile = o.package+o.suffix+".dxml"
260
261# ...........................................................................
262# check out information in the file.comment section
263
264def all_files_comment2section(list):
265    for file in list:
266        if file.comment is None: continue
267        file.section = file_comment2section(file.comment)
268
269        file.section = s(
270            file.section, r"(?sx) \b[Aa]uthor\s*:(.*</email>) ", lambda x
271            : "<author>" + file.set_author(x.group(1)) + "</author>")
272        file.section = s(
273            file.section, r"(?sx) \b[Cc]opyright\s*:([^<>]*)</para> ",lambda x
274            : "<copyright>" + file.set_copyright(x.group(1)) + "</copyright>")
275        # if "file" in file.name: print >> sys.stderr, file.comment # 2.3
276    #od
277all_files_comment2section(all.files)
278
279# -----------------------------------------------------------------------
280
281class Function:
282    " <prespec>void* </><namespec>hello</><namespec> (int) const</callspec> "
283    def __init__(self):
284        self.prespec  = ""
285        self.namespec = ""
286        self.callspec = ""
287        self.name = ""
288#    def set(self, **defines):
289#        name = defines.keys()[0]
290#        self.__dict__[name] = defines[name]
291#        return defines[name]
292#    def cut(self, **defines):
293#        name = defines.keys()[0]
294#        self.__dict__[name] += defines[name]
295#        return ""
296    def __getattr__(self, name):
297        """ defend against program exit on members being not inited """
298        if name in self.__dict__: return self.__dict__[name]
299        warn("no such member: "+name); return None
300    def dict(self):
301        return self.__dict__
302    def dict_sorted_keys(self):
303        keys = self.__dict__.keys()
304        keys.sort()
305        return keys
306    def parse(self, prototype):
307        found = m(prototype, r"(?sx) ^(.*[^.]) \b(\w[\w.]*\w)\b (\s*\(.*) $ ")
308        if found:
309            self.prespec = found.group(1).lstrip()
310            self.namespec = found.group(2)
311            self.callspec = found.group(3).lstrip()
312            self.name = self.namespec.strip()
313            return self.name
314        return None
315
316# pass 1 of per-func strings ...............................................
317# (a) cut prototype into prespec/namespec/callspec
318# (b) cut out first line of comment as headline information
319# (c) sanitize rest of comment block into proper docbook formatted .body
320#
321# do this while copying strings from all.funcs to function_list
322# and remember the original order in name_list
323
324def markup_callspec(text):
325    return (
326        s(s(s(s(s(text,
327                  r"(?sx) ^([^\(\)]*)\(", r"\1<parameters>(<paramdef>",1),
328                r"(?sx) \)([^\(\)]*)$", r"</paramdef>)</parameters>\1",1),
329              r"(?sx) , ", r"</paramdef>,<paramdef>"),
330            r"(?sx) <paramdef>(\s+) ", r"\1<paramdef>"),
331          r"(?sx) (\s+)</paramdef>", r"</paramdef>\1"))
332
333def parse_all_functions(func_list): # list of FunctionDeclarations
334    """ parse all FunctionDeclarations and create a list of Functions """
335    list = []
336    for func in all.funcs:
337        function = Function()
338        if not function.parse (func.prototype): continue
339
340        list.append(function)
341
342        function.body = markup_link_syntax(func.comment)
343        if "\n" not in function.body: # single-line comment is the head
344            function.head = function.body
345            function.body = ""
346        else: # cut comment in first-line and only keep the rest as descr body
347            function.head = s(function.body,  r"(?sx) ^([^\n]*\n).*",r"\1",1)
348            function.body = s(function.body,  r"(?sx)  ^[^\n]*\n",   r"",  1)
349        #fi
350        if m(function.head, r"(?sx) ^\s*$ "): # empty head line, autofill here
351            function.head = s("("+func.file.name+")", r"[.][.][/]", r"")
352
353        function.body = func_comment2section(function.body)
354        function.src = func # keep a back reference
355
356        # add extra docbook markups to callspec in $fn-hash
357        function.callspec = markup_callspec (function.callspec)
358    #od
359    return list
360function_list = parse_all_functions(all.funcs)
361
362def examine_head_anchors(func_list):
363    """ .into tells later steps which func-name is the leader of a man
364        page and that this func should add its descriptions over there. """
365    for function in func_list:
366        function.into = None
367        function.seealso = None
368
369        found = m(function.head, r"(?sx) ^ \s* <link>(\w[\w.]*\w)<\/link>")
370        # if found and found.group(1) in func_list.names:
371        if found and found.group(1):
372            function.into = found.group(1)
373
374        def set_seealso(f, value):
375            f.seealso = value
376            return value
377        function.head = s(function.head, r"(.*)also:(.*)", lambda x
378                          : set_seealso(function, x.group(2)) and x.group(1))
379        if function.seealso and None:
380            print("function[" + function.name + "].seealso=" + function.seealso)
381examine_head_anchors(function_list)
382
383# =============================================================== HTML =====
384
385def find_by_name(func_list, name):
386    for func in func_list:
387        if func.name == name:
388            return func
389    #od
390    return None
391#fu
392
393class HtmlFunction:
394    def __init__(self, func):
395        self.src = func.src
396        self.into = func.into
397        self.name = func.name
398        self.toc_line = paramdef2html(
399            "  <td valign=\"top\"><code>"+func.prespec+"</code></td>\n"+
400            "  <td valign=\"top\">&nbsp;&nbsp;</td>\n"+
401            "  <td valign=\"top\"><a href=\"#"+func.name+"\">\n"+
402            "                       <code>"+func.namespec+"</code>"+
403            "  </a></td>\n"+
404            "  <td valign=\"top\">&nbsp;&nbsp;</td>\n"+
405            "  <td valign=\"top\">"+func.callspec+"</td>\n")
406        self.synopsis = paramdef2html(
407            "  <code>"+func.prespec+"</code>\n"+
408            "  <br /><b><code>"+func.namespec+"</code></b>\n"+
409            "   &nbsp; <code>"+func.callspec+"</code>\n")
410        self.anchor = "<a name=\""+func.name+"\" />"
411        self.section = "<para><em> &nbsp;"+func.head+"\n"+ \
412                       "\n</em></para>"+section2html(func.body)
413#class
414
415class HtmlFunctionFamily(HtmlFunction):
416    def __init__(page, func):
417        HtmlFunction.__init__(page, func)
418        page.toc_line_list = [ page.toc_line ]
419        # page.html_txt     = page.synopsis
420        page.synopsis_list = [ page.synopsis ]
421        page.anchor_list   = [ page.anchor ]
422        page.section_list  = [ this_function_link(page.section, func.name) ]
423
424def ensure_name(text, name):
425    adds = "<small><code>"+name+"</code></small> -"
426    match = r"(?sx) .*>[^<>]*\b" + name + r"\b[^<>]*<.*"
427    found = m(text, match)
428    if found: return text
429    found = m(text, r".*<p(ara)?>.*")
430    if found: return s(text, r"(<p(ara)?>)", r"\1"+adds, 1)
431    return adds+text
432
433def combined_html_pages(func_list):
434    """ and now add descriptions of non-leader entries (html-mode) """
435    combined = {}
436
437    for func in func_list: # assemble leader pages
438        if func.into is not None: continue
439        combined[func.name] =  HtmlFunctionFamily(func)
440
441    for func in func_list:
442        if func.into is None: continue
443        if func.into not in combined :
444            warn(#......... (combine_html_pages) ..............
445                "function '"+func.name+"'s into => '"+func.into+
446                "\n: no such target function: "+func.into)
447            combined[func.name] = HtmlFunctionFamily(func)
448            continue
449        #fi
450        page = HtmlFunction(func)
451        into = combined[func.into]
452        into.toc_line_list.append( page.toc_line )
453        into.anchor_list.append( page.anchor )
454        into.synopsis_list.append( page.synopsis )
455        into.section_list.append(
456            s(ensure_name(this_function_link(section2html( func.body ),
457                                             func.name), func.name),
458              r"(?sx) (</?para>\s*) <br\s*\/>", r"\1"))
459    return combined.values()
460html_pages = combined_html_pages(function_list)
461
462def html_resolve_links_on_page(text, list):
463    """ link ref-names of a page with its endpoint on the same html page"""
464    def html_link (name , extra):
465        """ make <link>s to <href> of correct target or make it <code> """
466        if find_by_name(list, name) is None:
467            return "<code>"+name+extra+"</code>"
468        else:
469            return "<a href=\"#"+name+"\"><code>"+name+extra+"</code></a>"
470    #fu html_link
471    return s(s(text, r"(?sx) <link>(\w+)([^<>]*)<\/link> ",
472               lambda x : html_link(x.group(1),x.group(2))),
473             r"(?sx) \-\> ", r"<small>-&gt;</small>") # just sanitize..
474#fu html_resolve_links
475
476class HtmlPage:
477    def __init__(self):
478        self.toc = ""
479        self.txt = ""
480        self.package = o.package
481        self.version = o.version
482    def page_text(self):
483        """ render .toc and .txt parts into proper <html> page """
484        T = ""
485        T += "<html><head>"
486        T += "<title>"+self.package+"autodoc documentation </title>"
487        T += "</head>\n<body>\n"
488        T += "\n<h1>"+self.package+" <small><small><i>- "+self.version
489        T += "</i></small></small></h1>"
490        T += "\n<table border=0 cellspacing=2 cellpadding=0>"
491        T +=  self.toc
492        T += "\n</table>"
493        T += "\n<h3>Documentation</h3>\n\n<dl>"
494        T += html_resolve_links_on_page(self.txt, function_list)
495        T += "\n</dl>\n</body></html>\n"
496        return T
497    def add_page_map(self, list):
498        """ generate the index-block at the start of the onepage-html file """
499        keys = list.keys()
500        keys.sort()
501        for name in keys:
502            self.toc += "<tr valign=\"top\">\n"+ \
503                        "\n</tr><tr valign=\"top\">\n".join(
504                list[name].toc_line_list)+"</tr>\n"
505            self.txt += "\n<dt>"+" ".join(list[name].anchor_list)
506            self.txt += "\n"+"\n<br />".join(list[name].synopsis_list)+"<dt>"
507            self.txt += "\n<dd>\n"+"\n".join(list[name].section_list)
508            self.txt += ("\n<p align=\"right\">"+
509                         "<small>("+list[name].src.file.name+")</small>"+
510                         "</p></dd>")
511    def add_page_list(self, functions):
512        """ generate the index-block at the start of the onepage-html file """
513        mapp = {}
514        for func in functions:
515            mapp[func.name] = func
516        #od
517        self.add_page_map(mapp)
518#end
519
520html = HtmlPage()
521# html.add_function_dict(Fn)
522# html.add_function_list(Fn.sort.values())
523html.add_page_list(html_pages)
524
525# and finally print the html-formatted output
526try:
527    F = open(o.libhtmlfile, "w")
528except IOError as error:
529    warn(# ............. open(o.libhtmlfile, "w") ..............
530        "can not open html output file: "+o.libhtmlfile, error)
531else:
532    print(html.page_text(), file=F)
533    F.close()
534#fi
535
536# ========================================================== DOCBOOK =====
537# let's go for the pure docbook, a reference type master for all man pages
538
539class RefPage:
540    def __init__(self, func):
541        """ initialize the fields needed for a man page entry - the fields are
542           named after the docbook-markup that encloses (!!) the text we store
543           the entries like X.refhint = "hello" will be printed therefore as
544           <refhint>hello</refhint>. Names with underscores are only used as
545           temporaries but they are memorized, perhaps for later usage. """
546        self.refhint = "\n<!--========= "+func.name+" (3) ===========-->\n"
547        self.refentry = None
548        self.refentry_date = o.version.strip()        # //refentryinfo/date
549        self.refentry_productname = o.package.strip() # //refentryinfo/prod*
550        self.refentry_title = None                    # //refentryinfo/title
551        self.refentryinfo = None                      # override
552        self.manvolnum = "3"                         # //refmeta/manvolnum
553        self.refentrytitle = None                    # //refmeta/refentrytitle
554        self.refmeta = None                          # override
555        self.refpurpose = None                       # //refnamediv/refpurpose
556        self.refname = None                          # //refnamediv/refname
557        self.refname_list = []
558        self.refnamediv = None                       # override
559        self.mainheader = func.src.file.mainheader
560        self.includes = func.src.file.include
561        self.funcsynopsisinfo = ""       # //funcsynopsisdiv/funcsynopsisinfo
562        self.funcsynopsis = None         # //funcsynopsisdiv/funcsynopsis
563        self.funcsynopsis_list = []
564        self.description = None
565        self.description_list = []
566        # optional sections
567        self.authors_list = []           # //sect1[authors]/listitem
568        self.authors = None              # override
569        self.copyright = None
570        self.copyright_list = []
571        self.seealso = None
572        self.seealso_list = []
573        if  func.seealso:
574            self.seealso_list.append(func.seealso)
575        # func.func references
576        self.func = func
577        self.file_authors = None
578        if  func.src.file.authors:
579            self.file_authors = func.src.file.authors
580        self.file_copyright = None
581        if  func.src.file.copyright:
582            self.file_copyright = func.src.file.copyright
583    #fu
584    def refentryinfo_text(page):
585        """ the manvol formatter wants to render a footer line and header line
586            on each manpage and such info is set in <refentryinfo> """
587        if page.refentryinfo:
588            return page.refentryinfo
589        if page.refentry_date and \
590           page.refentry_productname and \
591           page.refentry_title: return (
592            "\n <date>"+page.refentry_date+"</date>"+
593            "\n <productname>"+page.refentry_productname+"</productname>"+
594            "\n <title>"+page.refentry_title+"</title>")
595        if page.refentry_date and \
596           page.refentry_productname: return (
597            "\n <date>"+page.refentry_date+"</date>"+
598            "\n <productname>"+page.refentry_productname+"</productname>")
599        return ""
600    def refmeta_text(page):
601        """ the manvol formatter needs to know the filename of the manpage to
602            be made up and these parts are set in <refmeta> actually """
603        if page.refmeta:
604            return page.refmeta
605        if page.manvolnum and page.refentrytitle:
606            return (
607                "\n <refentrytitle>"+page.refentrytitle+"</refentrytitle>"+
608                "\n <manvolnum>"+page.manvolnum+"</manvolnum>")
609        if page.manvolnum and page.func.name:
610            return (
611                "\n <refentrytitle>"+page.func.name+"</refentrytitle>"+
612                "\n <manvolnum>"+page.manvolnum+"</manvolnum>")
613        return ""
614    def refnamediv_text(page):
615        """ the manvol formatter prints a header line with a <refpurpose> line
616            and <refname>'d functions that are described later. For each of
617            the <refname>s listed here, a mangpage is generated, and for each
618            of the <refname>!=<refentrytitle> then a symlink is created """
619        if page.refnamediv:
620            return page.refnamediv
621        if page.refpurpose and page.refname:
622            return ("\n <refname>"+page.refname+'</refname>'+
623                    "\n <refpurpose>"+page.refpurpose+" </refpurpose>")
624        if page.refpurpose and page.refname_list:
625            T = ""
626            for refname in page.refname_list:
627                T += "\n <refname>"+refname+'</refname>'
628            T += "\n <refpurpose>"+page.refpurpose+" </refpurpose>"
629            return T
630        return ""
631    def funcsynopsisdiv_text(page):
632        """ refsynopsisdiv shall be between the manvol mangemaent information
633            and the reference page description blocks """
634        T=""
635        if page.funcsynopsis:
636            T += "\n<funcsynopsis>"
637            if page.funcsynopsisinfo:
638                T += "\n<funcsynopsisinfo>"+    page.funcsynopsisinfo + \
639                     "\n</funcsynopsisinfo>\n"
640            T += page.funcsynopsis + \
641                 "\n</funcsynopsis>\n"
642        if page.funcsynopsis_list:
643            T += "\n<funcsynopsis>"
644            if page.funcsynopsisinfo:
645                T += "\n<funcsynopsisinfo>"+    page.funcsynopsisinfo + \
646                     "\n</funcsynopsisinfo>\n"
647            for funcsynopsis in page.funcsynopsis_list:
648                T += funcsynopsis
649            T += "\n</funcsynopsis>\n"
650        #fi
651        return T
652    def description_text(page):
653        """ the description section on a manpage is the main part. Here
654            it is generated from the per-function comment area. """
655        if page.description:
656            return page.description
657        if page.description_list:
658            T = ""
659            for description in page.description_list:
660                if not description: continue
661                T += description
662            if T: return T
663        return ""
664    def authors_text(page):
665        """ part of the footer sections on a manpage and a description of
666            original authors. We prever an itimizedlist to let the manvol
667            show a nice vertical aligment of authors of this ref item """
668        if page.authors:
669            return page.authors
670        if page.authors_list:
671            T = "<itemizedlist>"
672            previous=""
673            for authors in page.authors_list:
674                if not authors: continue
675                if previous == authors: continue
676                T += "\n <listitem><para>"+authors+"</para></listitem>"
677                previous = authors
678            T += "</itemizedlist>"
679            return T
680        if page.authors:
681            return page.authors
682        return ""
683    def copyright_text(page):
684        """ the copyright section is almost last on a manpage and purely
685            optional. We list the part of the per-file copyright info """
686        if page.copyright:
687            return page.copyright
688        """ we only return the first valid instead of merging them """
689        if page.copyright_list:
690            T = ""
691            for copyright in page.copyright_list:
692                if not copyright: continue
693                return copyright # !!!
694        return ""
695    def seealso_text(page):
696        """ the last section on a manpage is called 'SEE ALSO' usually and
697            contains a comma-separated list of references. Some manpage
698            viewers can parse these and convert them into hyperlinks """
699        if page.seealso:
700            return page.seealso
701        if page.seealso_list:
702            T = ""
703            for seealso in page.seealso_list:
704                if not seealso: continue
705                if T: T += ", "
706                T += seealso
707            if T: return T
708        return ""
709    def refentry_text(page, id=None):
710        """ combine fields into a proper docbook refentry """
711        if id is None:
712            id = page.refentry
713        if id:
714            T = '<refentry id="'+id+'">'
715        else:
716            T = '<refentry>' # this is an error
717
718        if page.refentryinfo_text():
719            T += "\n<refentryinfo>"+       page.refentryinfo_text()+ \
720                 "\n</refentryinfo>\n"
721        if page.refmeta_text():
722            T += "\n<refmeta>"+            page.refmeta_text() + \
723                 "\n</refmeta>\n"
724        if page.refnamediv_text():
725            T += "\n<refnamediv>"+         page.refnamediv_text() + \
726                 "\n</refnamediv>\n"
727        if page.funcsynopsisdiv_text():
728            T += "\n<refsynopsisdiv>\n"+   page.funcsynopsisdiv_text()+ \
729                 "\n</refsynopsisdiv>\n"
730        if page.description_text():
731            T += "\n<refsect1><title>Description</title> " + \
732                 page.description_text() + "\n</refsect1>"
733        if page.authors_text():
734            T += "\n<refsect1><title>Author</title> " + \
735                 page.authors_text() + "\n</refsect1>"
736        if page.copyright_text():
737            T += "\n<refsect1><title>Copyright</title> " + \
738                 page.copyright_text() + "\n</refsect1>\n"
739        if page.seealso_text():
740            T += "\n<refsect1><title>See Also</title><para> " + \
741                 page.seealso_text() + "\n</para></refsect1>\n"
742
743        T +=  "\n</refentry>\n"
744        return T
745    #fu
746#end
747
748# -----------------------------------------------------------------------
749class FunctionRefPage(RefPage):
750    def reinit(page):
751        """ here we parse the input function for its values """
752        if page.func.into:
753            page.refhint = "\n              <!-- see "+page.func.into+" -->\n"
754        #fi
755        page.refentry = page.func.name               # //refentry@id
756        page.refentry_title = page.func.name.strip() # //refentryinfo/title
757        page.refentrytitle = page.func.name          # //refmeta/refentrytitle
758        if page.includes:
759            page.funcsynopsisinfo += "\n"+page.includes
760        if not page.funcsynopsisinfo:
761            page.funcsynopsisinfo="\n"+' #include &lt;'+page.mainheader+'&gt;'
762        page.refpurpose = page.func.head
763        page.refname = page.func.name
764
765        def funcsynopsis_of(func):
766            return (
767                "\n <funcprototype>\n <funcdef>"+func.prespec+
768                " <function>"+func.name+"</function></funcdef>"+
769                "\n"+s(s(s(func.callspec,
770                           r"<parameters>\s*\(",r" "),
771                         r"\)\s*</parameters>",r" "),
772                       r"</paramdef>\s*,\s*",r"</paramdef>\n ")+
773                " </funcprototype>")
774        page.funcsynopsis = funcsynopsis_of(page.func)
775
776        page.description = (
777            html2docbook(this_function_link(page.func.body, page.func.name)))
778
779        if page.file_authors:
780            def add_authors(page, ename, email):
781                page.authors_list.append( ename+' '+email )
782                return ename+email
783            s(page.file_authors,
784              r"(?sx) \s* ([^<>]*) (<email>[^<>]*</email>) ", lambda x
785              : add_authors(page, x.group(1), x.group(2)))
786        #fi
787
788        if page.file_copyright:
789            page.copyright = "<screen>\n"+page.file_copyright+"</screen>\n"
790        #fi
791        return page
792    def __init__(page,func):
793        RefPage.__init__(page, func)
794        FunctionRefPage.reinit(page)
795
796def refpage_list_from_function_list(funclist):
797    list = []
798    mapp = {}
799    for func in funclist:
800        mapp[func.name] = func
801    #od
802    for func in funclist:
803        page = FunctionRefPage(func)
804        if func.into and func.into not in mapp:
805            warn (# ............ (refpage_list_from_function_list) .......
806                "page '"+page.func.name+"' has no target => "+
807                "'"+page.func.into+"'"
808                "\n: going to reset .into of Function '"+page.func.name+"'")
809            func.into = None
810        #fi
811        list.append(FunctionRefPage(func))
812    return list
813#fu
814
815# ordered list of pages
816refpage_list = refpage_list_from_function_list(function_list)
817
818class FunctionFamilyRefPage(RefPage):
819    def __init__(self, page):
820        RefPage.__init__(self, page.func)
821        self.seealso_list = [] # reset
822        self.refhint_list = []
823    def refhint_list_text(page):
824        T = ""
825        for hint in page.refhint_list:
826            T += hint
827        return T
828    def refentry_text(page):
829        return page.refhint_list_text() + "\n" + \
830               RefPage.refentry_text(page)
831    pass
832
833def docbook_pages_recombine(pagelist):
834    """ take a list of RefPages and create a new list where sections are
835        recombined in a way that their description is listed on the same
836        page and the manvol formatter creates symlinks to the combined
837        function description page - use the attribute 'into' to guide the
838        processing here as each of these will be removed from the output
839        list. If no into-pages are there then the returned list should
840        render to the very same output text like the input list would do """
841
842    list = []
843    combined = {}
844    for orig in pagelist:
845        if orig.func.into: continue
846        page = FunctionFamilyRefPage(orig)
847        combined[orig.func.name] = page ; list.append(page)
848
849        page.refentry = orig.refentry              # //refentry@id
850        page.refentry_title = orig.refentrytitle   # //refentryinfo/title
851        page.refentrytitle = orig.refentrytitle    # //refmeta/refentrytitle
852        page.includes = orig.includes
853        page.funcsynopsisinfo = orig.funcsynopsisinfo
854        page.refpurpose = orig.refpurpose
855        if orig.refhint:
856            page.refhint_list.append( orig.refhint )
857        if orig.refname:
858            page.refname_list.append( orig.refname )
859        elif orig.refname_list:
860            page.refname_list.extend( orig.refname_list )
861        if orig.funcsynopsis:
862            page.funcsynopsis_list.append( orig.funcsynopsis )
863        elif orig.refname_list:
864            page.funcsynopsis_list.extend( orig.funcsynopsis_list )
865        if orig.description:
866            page.description_list.append( orig.description )
867        elif orig.refname_list:
868            page.description_list.extend( orig.description_list )
869        if orig.seealso:
870            page.seealso_list.append( orig.seealso )
871        elif orig.seealso_list:
872            page.seealso_list.extend( orig.seealso_list )
873        if orig.authors:
874            page.authors_list.append( orig.authors )
875        elif orig.authors_list:
876            page.authors_list.extend( orig.authors_list )
877        if orig.copyright:
878            page.copyright_list.append( orig.copyright )
879        elif orig.refname_list:
880            page.copyright_list.extend( orig.copyright_list )
881    #od
882    for orig in pagelist:
883        if not orig.func.into: continue
884        if orig.func.into not in combined:
885            warn("page for '"+orig.func.name+
886                 "' has no target => '"+orig.func.into+"'")
887            page = FunctionFamilyRefPage(orig)
888        else:
889            page = combined[orig.func.into]
890
891        if orig.refname:
892            page.refname_list.append( orig.refname )
893        elif orig.refname_list:
894            page.refname_list.extend( orig.refname_list )
895        if orig.funcsynopsis:
896            page.funcsynopsis_list.append( orig.funcsynopsis )
897        elif orig.refname_list:
898            page.funcsynopsis_list.extend( orig.funcsynopsis_list )
899        if orig.description:
900            page.description_list.append( orig.description )
901        elif orig.refname_list:
902            page.description_list.extend( orig.description_list )
903        if orig.seealso:
904            page.seealso_list.append( orig.seealso )
905        elif orig.seealso_list:
906            page.seealso_list.extend( orig.seealso_list )
907        if orig.authors:
908            page.authors_list.append( orig.authors )
909        elif orig.authors_list:
910            page.authors_list.extend( orig.authors_list )
911        if orig.copyright:
912            page.copyright_list.append( orig.copyright )
913        elif orig.refname_list:
914            page.copyright_list.extend( orig.copyright_list )
915    #od
916    return list
917#fu
918
919combined_pages = docbook_pages_recombine(pagelist = refpage_list)
920
921# -----------------------------------------------------------------------
922
923class HeaderRefPage(RefPage):
924    pass
925
926def docbook_refpages_perheader(page_list): # headerlist
927    " creating the per-header manpage - a combination of function man pages "
928    header = {}
929    for page in page_list:
930        assert not page.func.into
931        file = page.func.src.file.mainheader # short for the mainheader index
932        if file not in header:
933            header[file] = HeaderRefPage(page.func)
934            header[file].id = s(file, r"[^\w\.]","-")
935            header[file].refentry = header[file].id
936            header[file].refentryinfo = None
937            header[file].refentry_date = page.refentry_date
938            header[file].refentry_productname = (
939                "the library "+page.refentry_productname)
940            header[file].manvolnum = page.manvolnum
941            header[file].refentrytitle = file
942            header[file].funcsynopsis = ""
943        if 1: # or += or if not header[file].refnamediv:
944            header[file].refpurpose = " library "
945            header[file].refname = header[file].id
946
947        if not header[file].funcsynopsisinfo and page.funcsynopsisinfo:
948            header[file].funcsynopsisinfo  = page.funcsynopsisinfo
949        if page.funcsynopsis:
950            header[file].funcsynopsis  += "\n"+page.funcsynopsis
951        if not header[file].copyright and page.copyright:
952            header[file].copyright = page.copyright
953        if not header[file].authors and page.authors:
954            header[file].authors = page.authors
955        if not header[file].authors and page.authors_list:
956            header[file].authors_list = page.authors_list
957        if not header[file].description:
958            found = m(commands.getoutput("cat "+o.package+".spec"),
959                      r"(?s)\%description\b([^\%]*)\%")
960            if found:
961                header[file].description = found.group(1)
962            elif not header[file].description:
963                header[file].description = "<para>" + (
964                    page.refentry_productname + " library") + "</para>";
965            #fi
966        #fi
967    #od
968    return header#list
969#fu
970
971def leaders(pagelist):
972    list = []
973    for page in pagelist:
974        if page.func.into : continue
975        list.append(page)
976    return list
977header_refpages = docbook_refpages_perheader(leaders(refpage_list))
978
979# -----------------------------------------------------------------------
980# printing the docbook file is a two-phase process - we spit out the
981# leader pages first - later we add more pages with _refstart pointing
982# to the leader page, so that xmlto will add the functions there. Only the
983# leader page contains some extra info needed for troff page processing.
984
985doctype = '<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"'
986doctype += "\n     "
987doctype += '"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">'+"\n"
988
989try:
990    F = open(o.docbookfile,"w")
991except IOError as error:
992    warn("can not open docbook output file: "+o.docbookfile, error)
993else:
994    print(doctype + '<reference><title>Manual Pages</title>', file=F)
995
996    for page in combined_pages:
997        print(page.refentry_text(), file=F)
998    #od
999
1000    for page in header_refpages.values():
1001        if not page.refentry: continue
1002        print("\n<!-- _______ "+page.id+" _______ -->" + page.refentry_text(), file=F)
1003    #od
1004
1005    print("\n</reference>\n", file=F)
1006    F.close()
1007#fi
1008
1009# _____________________________________________________________________
1010try:
1011    F = open( o.dumpdocfile, "w")
1012except IOError as error:
1013    warn ("can not open"+o.dumpdocfile,error)
1014else:
1015    for func in function_list:
1016        name = func.name
1017        print("<fn id=\""+name+"\">"+"<!-- FOR \""+name+"\" -->\n", file=F)
1018        for H in sorted_keys(func.dict()):
1019            print("<"+H+" name=\""+name+"\">" + str(func.dict()[H]) + "</"+H+">", file=F)
1020        #od
1021        print("</fn><!-- END \""+name+"\" -->\n\n", file=F)
1022    #od
1023    F.close();
1024#fi
1025
1026if errors: sys.exit(errors)
1027