1#!/usr/bin/python 2 3# Audio Tools, a module and set of tools for manipulating audio data 4# Copyright (C) 2007-2014 Brian Langenberger 5 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 20import re 21import time 22from sys import version_info 23 24PY3 = version_info[0] >= 3 25 26 27WHITESPACE = re.compile(r'\s+') 28 29 30def subtag(node, name): 31 return [child for child in node.childNodes 32 if (hasattr(child, "nodeName") and 33 (child.nodeName == name))][0] 34 35 36def subtags(node, name): 37 return [child for child in node.childNodes 38 if (hasattr(child, "nodeName") and 39 (child.nodeName == name))] 40 41 42def text(node): 43 try: 44 return WHITESPACE.sub(u" ", node.childNodes[0].wholeText.strip()) 45 except IndexError: 46 return u"" 47 48 49def man_escape(s): 50 return s.replace('-', '\\-') 51 52 53if PY3: 54 def write_u(stream, unicode_string): 55 assert(isinstance(unicode_string, str)) 56 stream.write(unicode_string) 57else: 58 def write_u(stream, unicode_string): 59 assert(isinstance(unicode_string, unicode)) 60 stream.write(unicode_string.encode("utf-8")) 61 62 63class Manpage: 64 FIELDS = [ 65 ("%(track_number)2.2d", "the track's number on the CD"), 66 ("%(track_total)d", "the total number of tracks on the CD"), 67 ("%(album_number)d", "the CD's album number"), 68 ("%(album_total)d", "the total number of CDs in the set"), 69 ("%(album_track_number)s", "combination of album and track number"), 70 ("%(track_name)s", "the track's name"), 71 ("%(album_name)s", "the album's name"), 72 ("%(artist_name)s", "the track's artist name"), 73 ("%(performer_name)s", "the track's performer name"), 74 ("%(composer_name)s", "the track's composer name"), 75 ("%(conductor_name)s", "the track's conductor name"), 76 ("%(media)s", "the track's source media"), 77 ("%(ISRC)s", "the track's ISRC"), 78 ("%(catalog)s", "the track's catalog number"), 79 ("%(copyright)s", "the track's copyright information"), 80 ("%(publisher)s", "the track's publisher"), 81 ("%(year)s", "the track's publication year"), 82 ("%(date)s", "the track's original recording date"), 83 ("%(suffix)s", "the track's suffix"), 84 ("%(basename)s", "the track's original name, without suffix")] 85 86 def __init__(self, 87 utility=u"", 88 section=1, 89 name=u"", 90 title=u"", 91 synopsis=None, 92 description=u"", 93 author=u"", 94 options=None, 95 elements=None, 96 examples=None, 97 see_also=None): 98 self.utility = utility 99 self.section = int(section) 100 self.name = name 101 self.title = title 102 self.synopsis = synopsis 103 self.description = description 104 self.author = author 105 106 if options is not None: 107 self.options = options 108 else: 109 self.options = [] 110 111 if examples is not None: 112 self.examples = examples 113 else: 114 self.examples = [] 115 116 if elements is not None: 117 self.elements = elements 118 else: 119 self.elements = [] 120 121 if see_also is not None: 122 self.see_also = see_also 123 else: 124 self.see_also = [] 125 126 def __repr__(self): 127 return "Manpage(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % \ 128 (repr(self.utility), 129 repr(self.section), 130 repr(self.name), 131 repr(self.title), 132 repr(self.synopsis), 133 repr(self.description), 134 repr(self.author), 135 repr(self.options), 136 repr(self.elements), 137 repr(self.examples), 138 repr(self.see_also)) 139 140 def flatten_options(self): 141 for option_category in self.options: 142 for option in option_category.options: 143 yield option 144 145 @classmethod 146 def parse_file(cls, filename): 147 return cls.parse(xml.dom.minidom.parse(filename)) 148 149 @classmethod 150 def parse(cls, xml_dom): 151 manpage = xml_dom.getElementsByTagName(u"manpage")[0] 152 153 try: 154 synopsis = text(subtag(manpage, u"synopsis")) 155 except IndexError: 156 synopsis = None 157 158 options = [Options.parse(options) 159 for options in subtags(manpage, u"options")] 160 161 elements = [Element.parse(element) 162 for element in subtags(manpage, u"element")] 163 164 try: 165 examples = [Example.parse(example) 166 for example in subtags(subtag(manpage, 167 u"examples"), 168 u"example")] 169 except IndexError: 170 examples = None 171 172 return cls(utility=text(subtag(manpage, u"utility")), 173 section=text(subtag(manpage, u"section")), 174 name=text(subtag(manpage, u"name")), 175 title=text(subtag(manpage, u"title")), 176 synopsis=synopsis, 177 description=text(subtag(manpage, u"description")), 178 author=text(subtag(manpage, u"author")), 179 options=options, 180 elements=elements, 181 examples=examples) 182 183 def to_man(self, stream): 184 write_u(stream, 185 (u".TH \"%(utility)s\" %(section)d " + 186 u"\"%(date)s\" \"\" \"%(title)s\"\n") % 187 {"utility": self.utility.upper(), 188 "section": self.section, 189 "date": time.strftime("%B %Y", time.localtime()), 190 "title": self.title}) 191 write_u(stream, u".SH NAME\n") 192 write_u(stream, u"%(utility)s \\- %(name)s\n" % 193 {"utility": self.utility, 194 "name": self.name}) 195 if self.synopsis is not None: 196 write_u(stream, u".SH SYNOPSIS\n") 197 write_u(stream, u"%(utility)s %(synopsis)s\n" % 198 {"utility": self.utility, 199 "synopsis": self.synopsis}) 200 write_u(stream, u".SH DESCRIPTION\n") 201 write_u(stream, u".PP\n") 202 write_u(stream, self.description) 203 write_u(stream, u"\n") 204 for option in self.options: 205 option.to_man(stream) 206 for element in self.elements: 207 element.to_man(stream) 208 if len(self.examples) > 0: 209 if len(self.examples) > 1: 210 write_u(stream, u".SH EXAMPLES\n") 211 else: 212 write_u(stream, u".SH EXAMPLE\n") 213 for example in self.examples: 214 example.to_man(stream) 215 216 for option in self.flatten_options(): 217 if option.long_arg == 'format': 218 self.format_fields_man(stream) 219 break 220 221 self.see_also.sort(key=lambda x: x.utility) 222 223 if len(self.see_also) > 0: 224 write_u(stream, u".SH SEE ALSO\n") 225 # handle the trailing comma correctly 226 for page in self.see_also[0:-1]: 227 write_u(stream, u".BR %(utility)s (%(section)d),\n" % 228 {"utility": page.utility, 229 "section": page.section}) 230 231 write_u(stream, u".BR %(utility)s (%(section)d)\n" % 232 {"utility": self.see_also[-1].utility, 233 "section": self.see_also[-1].section}) 234 235 write_u(stream, u".SH AUTHOR\n") 236 write_u(stream, u"%(author)s\n" % {"author": self.author}) 237 238 def format_fields_man(self, stream): 239 write_u(stream, u".SH FORMAT STRING FIELDS\n") 240 write_u(stream, u".TS\n") 241 write_u(stream, u"tab(:);\n") 242 write_u(stream, u"| c s |\n") 243 write_u(stream, u"| c | c |\n") 244 write_u(stream, u"| r | l |.\n") 245 write_u(stream, u"_\n") 246 write_u(stream, u"Template Fields\n") 247 write_u(stream, u"Key:Value\n") 248 write_u(stream, u"=\n") 249 for (field, description) in self.FIELDS: 250 write_u(stream, u"\\fC%(field)s\\fR:%(description)s\n" % 251 {"field": field, 252 "description": description}) 253 write_u(stream, u"_\n") 254 write_u(stream, u".TE\n") 255 256 def to_html(self, stream): 257 write_u(stream, u'<div class="utility" id="%s">\n' % (self.utility)) 258 259 # display utility name 260 write_u(stream, u"<h2>%s</h2>\n" % (self.utility)) 261 262 # display utility description 263 write_u(stream, u"<p>%s</p>\n" % (self.description)) 264 265 # display options 266 for option_section in self.options: 267 option_section.to_html(stream) 268 269 # display additional sections 270 for element in self.elements: 271 element.to_html(stream) 272 273 # display examples 274 if len(self.examples) > 0: 275 write_u(stream, u'<dl class="examples">\n') 276 if len(self.examples) > 1: 277 write_u(stream, u"<dt>Examples</dt>\n") 278 else: 279 write_u(stream, u"<dt>Example</dt>\n") 280 write_u(stream, u"<dd>\n") 281 for example in self.examples: 282 example.to_html(stream) 283 write_u(stream, u"</dd>\n") 284 write_u(stream, u"</dl>\n") 285 286 write_u(stream, u'</div>\n') 287 288 289class Options: 290 def __init__(self, options, category=None): 291 self.options = options 292 self.category = category 293 294 def __repr__(self): 295 return "Options(%s, %s)" % \ 296 (self.options, 297 self.category) 298 299 @classmethod 300 def parse(cls, xml_dom): 301 if xml_dom.hasAttribute(u"category"): 302 category = xml_dom.getAttribute(u"category") 303 else: 304 category = None 305 306 return cls(options=[Option.parse(child) for child in 307 subtags(xml_dom, u"option")], 308 category=category) 309 310 def to_man(self, stream): 311 if self.category is None: 312 write_u(stream, u".SH OPTIONS\n") 313 else: 314 write_u(stream, u".SH %(category)s OPTIONS\n" % 315 {"category": self.category.upper()}) 316 for option in self.options: 317 option.to_man(stream) 318 319 def to_html(self, stream): 320 write_u(stream, u"<dl>\n") 321 if self.category is None: 322 write_u(stream, u"<dt>Options</dt>\n") 323 else: 324 write_u(stream, u"<dt>%s Options</dt>\n" % 325 self.category.capitalize()) 326 write_u(stream, u"<dd>\n") 327 write_u(stream, u"<dl>\n") 328 for option in self.options: 329 option.to_html(stream) 330 write_u(stream, u"</dl>\n") 331 write_u(stream, u"</dd>\n") 332 write_u(stream, u"</dl>\n") 333 334 335class Option: 336 def __init__(self, 337 short_arg=None, 338 long_arg=None, 339 arg_name=None, 340 description=None): 341 self.short_arg = short_arg 342 self.long_arg = long_arg 343 self.arg_name = arg_name 344 self.description = description 345 346 def __repr__(self): 347 return "Option(%s, %s, %s, %s)" % \ 348 (repr(self.short_arg), 349 repr(self.long_arg), 350 repr(self.arg_name), 351 repr(self.description)) 352 353 @classmethod 354 def parse(cls, xml_dom): 355 if xml_dom.hasAttribute("short"): 356 short_arg = xml_dom.getAttribute("short") 357 if len(short_arg) > 1: 358 raise ValueError("short arguments should be 1 character") 359 else: 360 short_arg = None 361 362 if xml_dom.hasAttribute("long"): 363 long_arg = xml_dom.getAttribute("long") 364 else: 365 long_arg = None 366 367 if xml_dom.hasAttribute("arg"): 368 arg_name = xml_dom.getAttribute("arg") 369 else: 370 arg_name = None 371 372 if len(xml_dom.childNodes) > 0: 373 description = WHITESPACE.sub( 374 u" ", xml_dom.childNodes[0].wholeText.strip()) 375 else: 376 description = None 377 378 return cls(short_arg=short_arg, 379 long_arg=long_arg, 380 arg_name=arg_name, 381 description=description) 382 383 def to_man(self, stream): 384 write_u(stream, u".TP\n") 385 if (self.short_arg is not None) and (self.long_arg is not None): 386 if self.arg_name is not None: 387 write_u(stream, 388 (u"\\fB\\-%(short_arg)s\\fR, " + 389 u"\\fB\\-\\-%(long_arg)s\\fR=" + 390 u"\\fI%(arg_name)s\\fR\n") % 391 {"short_arg": man_escape(self.short_arg), 392 "long_arg": man_escape(self.long_arg), 393 "arg_name": man_escape(self.arg_name.upper())}) 394 else: 395 write_u(stream, 396 (u"\\fB\\-%(short_arg)s\\fR, " + 397 u"\\fB\\-\\-%(long_arg)s\\fR\n") % 398 {"short_arg": man_escape(self.short_arg), 399 "long_arg": man_escape(self.long_arg)}) 400 elif self.short_arg is not None: 401 if self.arg_name is not None: 402 write_u(stream, 403 (u"\\fB\\-%(short_arg)s\\fR " + 404 u"\\fI%(arg_name)s\\fR\n") % 405 {"short_arg": man_escape(self.short_arg), 406 "arg_name": man_escape(self.arg_name.upper())}) 407 else: 408 write_u(stream, 409 u"\\fB\\-%(short_arg)s\\fR\n" % 410 {"short_arg": man_escape(self.short_arg)}) 411 elif self.long_arg is not None: 412 if self.arg_name is not None: 413 write_u(stream, 414 (u"\\fB\\-\\-%(long_arg)s\\fR" + 415 u"=\\fI%(arg_name)s\\fR\n") % 416 {"long_arg": man_escape(self.long_arg), 417 "arg_name": man_escape(self.arg_name.upper())}) 418 else: 419 write_u(stream, 420 u"\\fB\\-\\-%(long_arg)s\\fR\n" % 421 {"long_arg": man_escape(self.long_arg)}) 422 else: 423 raise ValueError("short arg or long arg must be present in option") 424 425 if self.description is not None: 426 write_u(stream, self.description) 427 write_u(stream, u"\n") 428 429 def to_html(self, stream): 430 write_u(stream, u"<dt>\n") 431 if (self.short_arg is not None) and (self.long_arg is not None): 432 if self.arg_name is not None: 433 write_u(stream, 434 (u"<b>-%(short_arg)s</b>, " + 435 u"<b>--%(long_arg)s</b>=" + 436 u"<i>%(arg_name)s</i>\n") % 437 {"short_arg": self.short_arg, 438 "long_arg": self.long_arg, 439 "arg_name": man_escape(self.arg_name.upper())}) 440 else: 441 write_u(stream, 442 (u"<b>-%(short_arg)s</b>, " + 443 u"<b>--%(long_arg)s</b>\n") % 444 {"short_arg": self.short_arg, 445 "long_arg": self.long_arg}) 446 elif self.short_arg is not None: 447 if self.arg_name is not None: 448 write_u(stream, 449 (u"<b>-%(short_arg)s</b> " + 450 u"<i>%(arg_name)s</i>\n") % 451 {"short_arg": self.short_arg, 452 "arg_name": self.arg_name.upper()}) 453 else: 454 write_u(stream, 455 u"<b>-%(short_arg)s\n" % {"short_arg": self.short_arg}) 456 elif self.long_arg is not None: 457 if self.arg_name is not None: 458 write_u(stream, 459 (u"<b>--%(long_arg)s</b>" + 460 u"=<i>%(arg_name)s</i>\n") % 461 {"long_arg": self.long_arg, 462 "arg_name": self.arg_name.upper()}) 463 else: 464 write_u(stream, 465 u"<b>--%(long_arg)s</b>\n" % 466 {"long_arg": self.long_arg}) 467 else: 468 raise ValueError("short arg or long arg must be present in option") 469 470 write_u(stream, u"</dt>\n") 471 472 if self.description is not None: 473 write_u(stream, u"<dd>%s</dd>\n" % (self.description)) 474 else: 475 write_u(stream, u"<dd></dd>\n") 476 477 478class Example: 479 def __init__(self, 480 description=u"", 481 commands=[]): 482 self.description = description 483 self.commands = commands 484 485 def __repr__(self): 486 return "Example(%s, %s)" % \ 487 (repr(self.description), 488 repr(self.commands)) 489 490 @classmethod 491 def parse(cls, xml_dom): 492 return cls(description=text(subtag(xml_dom, u"description")), 493 commands=map(Command.parse, subtags(xml_dom, u"command"))) 494 495 def to_man(self, stream): 496 write_u(stream, u".LP\n") 497 write_u(stream, self.description) # FIXME 498 write_u(stream, u"\n") 499 for command in self.commands: 500 command.to_man(stream) 501 502 def to_html(self, stream): 503 write_u(stream, u'<dl>\n') 504 write_u(stream, u"<dt>%s</dt>\n" % (self.description)) 505 write_u(stream, u"<dd>\n") 506 for command in self.commands: 507 command.to_html(stream) 508 write_u(stream, u"</dd>\n") 509 write_u(stream, u"</dl>\n") 510 511 512class Command: 513 def __init__(self, commandline, note=None): 514 self.commandline = commandline 515 self.note = note 516 517 def __repr__(self): 518 return "Command(%s, %s)" % (repr(self.commandline), 519 repr(self.note)) 520 521 @classmethod 522 def parse(cls, xml_dom): 523 if xml_dom.hasAttribute(u"note"): 524 note = xml_dom.getAttribute(u"note") 525 else: 526 note = None 527 528 return cls(commandline=text(xml_dom), 529 note=note) 530 531 def to_man(self, stream): 532 if self.note is not None: 533 write_u(stream, u".LP\n") 534 write_u(stream, self.note + u" :\n\n") 535 536 write_u(stream, u".IP\n") 537 write_u(stream, self.commandline) 538 write_u(stream, u"\n\n") 539 540 def to_html(self, stream): 541 if self.note is not None: 542 write_u(stream, 543 u'<span class="note">%s :</span><br>\n' % (self.note)) 544 545 write_u(stream, self.commandline) 546 write_u(stream, u"<br>\n") 547 548 549class Element_P: 550 def __init__(self, contents): 551 self.contents = contents 552 553 def __repr__(self): 554 return "Element_P(%s)" % (repr(self.contents)) 555 556 @classmethod 557 def parse(cls, xml_dom): 558 return cls(contents=text(xml_dom)) 559 560 def to_man(self, stream): 561 write_u(stream, self.contents) 562 write_u(stream, u"\n.PP\n") 563 564 def to_html(self, stream): 565 write_u(stream, u"<p>%s</p>" % (self.contents)) 566 567 568class Element_UL: 569 def __init__(self, list_items): 570 self.list_items = list_items 571 572 def __repr__(self): 573 return "Element_UL(%s)" % (repr(self.list_items)) 574 575 @classmethod 576 def parse(cls, xml_dom): 577 return cls(list_items=map(text, subtags(xml_dom, u"li"))) 578 579 def to_man(self, stream): 580 for item in self.list_items: 581 write_u(stream, u"\\[bu] ") 582 write_u(stream, item) 583 write_u(stream, u"\n") 584 write_u(stream, u".PP\n") 585 586 def to_html(self, stream): 587 write_u(stream, u"<ul>\n") 588 for item in self.list_items: 589 write_u(stream, u"<li>%s</li>\n" % (item)) 590 write_u(stream, u"</ul>\n") 591 592 593class Element_TABLE: 594 def __init__(self, rows): 595 self.rows = rows 596 597 def __repr__(self): 598 return "Element_TABLE(%s)" % (repr(self.rows)) 599 600 @classmethod 601 def parse(cls, xml_dom): 602 return cls(rows=[Element_TR.parse(tr) for tr in subtags(xml_dom, 603 u"tr")]) 604 605 def to_man(self, stream): 606 if len(self.rows) == 0: 607 return 608 609 if (len(set([len(row.columns) for row in self.rows 610 if row.tr_class in (TR_NORMAL, TR_HEADER)])) != 1): 611 raise ValueError("all rows must have the same number of columns") 612 else: 613 columns = len(self.rows[0].columns) 614 615 write_u(stream, u".TS\n") 616 write_u(stream, u"tab(:);\n") 617 write_u(stream, 618 u" ".join([u"l" for l in self.rows[0].columns]) + u".\n") 619 for row in self.rows: 620 row.to_man(stream) 621 write_u(stream, u".TE\n") 622 623 def to_html(self, stream): 624 if len(self.rows) == 0: 625 return 626 627 if (len({len(row.columns) for row in self.rows 628 if row.tr_class in (TR_NORMAL, TR_HEADER)}) != 1): 629 raise ValueError("all rows must have the same number of columns") 630 631 write_u(stream, u"<table>\n") 632 for (row, spans) in zip(self.rows, self.calculate_row_spans()): 633 row.to_html(stream, spans) 634 write_u(stream, u"</table>\n") 635 636 def calculate_row_spans(self): 637 # turn rows into arrays of "span" boolean values 638 row_spans = [] 639 for row in self.rows: 640 if row.tr_class in (TR_NORMAL, TR_HEADER): 641 row_spans.append([col.empty() for col in row.columns]) 642 elif row.tr_class == TR_DIVIDER: 643 row_spans.append([False] * len(row_spans[-1])) 644 645 # turn columns into arrays of integers containing the row span 646 columns = [list(self.calculate_span_column([row[i] for 647 row in row_spans])) 648 for i in xrange(len(row_spans[0]))] 649 650 # turn columns back into rows and return them 651 return zip(*columns) 652 653 def calculate_span_column(self, row_spans): 654 rows = None 655 for span in row_spans: 656 if span: 657 rows += 1 658 else: 659 if rows is not None: 660 yield rows 661 for i in xrange(rows - 1): 662 yield 0 663 rows = 1 664 665 if rows is not None: 666 yield rows 667 for i in xrange(rows - 1): 668 yield 0 669 670 671(TR_NORMAL, TR_HEADER, TR_DIVIDER) = range(3) 672 673 674class Element_TR: 675 def __init__(self, columns, tr_class): 676 self.columns = columns 677 self.tr_class = tr_class 678 679 def __repr__(self): 680 if self.tr_class in (TR_NORMAL, TR_HEADER): 681 return "Element_TR(%s, %s)" % (repr(self.columns), self.tr_class) 682 else: 683 return "Element_TR_DIVIDER()" 684 685 @classmethod 686 def parse(cls, xml_dom): 687 if xml_dom.hasAttribute("class"): 688 if xml_dom.getAttribute("class") == "header": 689 return cls(columns=[Element_TD.parse(tag) 690 for tag in subtags(xml_dom, u"td")], 691 tr_class=TR_HEADER) 692 elif xml_dom.getAttribute("class") == "divider": 693 return cls(columns=None, tr_class=TR_DIVIDER) 694 else: 695 raise ValueError("unsupported class \"%s\"" % 696 (xmldom_getAttribute("class"))) 697 else: 698 return cls(columns=[Element_TD.parse(tag) 699 for tag in subtags(xml_dom, u"td")], 700 tr_class=TR_NORMAL) 701 702 def to_man(self, stream): 703 if self.tr_class == TR_NORMAL: 704 write_u(stream, 705 u":".join(column.string() for column in self.columns) + 706 u"\n") 707 elif self.tr_class == TR_HEADER: 708 write_u(stream, 709 u":".join(u"\\fB%s\\fR" % (column.string()) 710 for column in self.columns) + 711 u"\n") 712 write_u(stream, u"_\n") 713 elif self.tr_class == TR_DIVIDER: 714 write_u(stream, u"_\n") 715 716 def column_widths(self): 717 if self.tr_class in (TR_NORMAL, TR_HEADER): 718 return [column.width() for column in self.columns] 719 else: 720 return None 721 722 def to_html(self, stream, rowspans): 723 if self.tr_class in (TR_NORMAL, TR_HEADER): 724 write_u(stream, u"<tr>\n") 725 for (column, span) in zip(self.columns, rowspans): 726 column.to_html(stream, self.tr_class == TR_HEADER, span) 727 write_u(stream, u"</tr>\n") 728 729 730class Element_TD: 731 def __init__(self, value): 732 self.value = value 733 734 def __repr__(self): 735 return "Element_TD(%s)" % (repr(self.value)) 736 737 @classmethod 738 def parse(cls, xml_dom): 739 try: 740 return cls(value=WHITESPACE.sub( 741 u" ", 742 xml_dom.childNodes[0].wholeText.strip())) 743 except IndexError: 744 return cls(value=None) 745 746 def empty(self): 747 return self.value is None 748 749 def string(self): 750 if self.value is not None: 751 return self.value.encode('ascii') 752 else: 753 return "\\^" 754 755 def to_man(self, stream): 756 stream.write(self.value) 757 758 def width(self): 759 if self.value is not None: 760 return len(self.value) 761 else: 762 return 0 763 764 def to_html(self, stream, header, rowspan): 765 if self.value is not None: 766 if rowspan > 1: 767 rowspan = u" rowspan=\"%d\"" % (rowspan) 768 else: 769 rowspan = u"" 770 771 if header: 772 write_u(stream, 773 u"<th%s>%s</th>" % (rowspan, self.value)) 774 else: 775 write_u(stream, 776 u"<td%s>%s</td>" % (rowspan, self.value)) 777 778 779class Element: 780 SUB_ELEMENTS = {u"p": Element_P, 781 u"ul": Element_UL, 782 u"table": Element_TABLE} 783 784 def __init__(self, name, elements): 785 self.name = name 786 self.elements = elements 787 788 def __repr__(self): 789 return "Element(%s, %s)" % (repr(self.name), repr(self.elements)) 790 791 @classmethod 792 def parse(cls, xml_dom): 793 if xml_dom.hasAttribute(u"name"): 794 name = xml_dom.getAttribute(u"name") 795 else: 796 raise ValueError("elements must have names") 797 798 elements = [] 799 for child in xml_dom.childNodes: 800 if hasattr(child, "tagName"): 801 if child.tagName in cls.SUB_ELEMENTS.keys(): 802 elements.append( 803 cls.SUB_ELEMENTS[child.tagName].parse(child)) 804 else: 805 raise ValueError("unsupported tag %s" % 806 (child.tagName.encode('ascii'))) 807 808 return cls(name=name, 809 elements=elements) 810 811 def to_man(self, stream): 812 write_u(stream, u".SH %s\n" % (self.name.upper())) 813 for element in self.elements: 814 element.to_man(stream) 815 816 def to_html(self, stream): 817 write_u(stream, u"<dl>\n") 818 write_u(stream, 819 u"<dt>%s</dt>\n" % 820 (u" ".join(part.capitalize() for part in self.name.split()))) 821 write_u(stream, u"<dd>\n") 822 for element in self.elements: 823 element.to_html(stream) 824 write_u(stream, u"</dd>\n") 825 write_u(stream, u"</dl>\n") 826 827 828if (__name__ == '__main__'): 829 import sys 830 import xml.dom.minidom 831 import argparse 832 833 parser = argparse.ArgumentParser(description="manual page generator") 834 835 parser.add_argument("-i", "--input", 836 dest="input", 837 help="the primary input XML file") 838 839 parser.add_argument("-t", "--type", 840 dest="type", 841 choices=("man", "html"), 842 default="man", 843 help="the output type") 844 845 parser.add_argument("see_also", 846 metavar="FILENAME", 847 nargs="*", 848 help="\"see also\" man pages") 849 850 options = parser.parse_args() 851 852 if options.input is not None: 853 main_page = Manpage.parse_file(options.input) 854 all_pages = [Manpage.parse_file(filename) 855 for filename in options.see_also] 856 main_page.see_also = [page for page in all_pages 857 if (page.utility != main_page.utility)] 858 859 if options.type == "man": 860 main_page.to_man(sys.stdout) 861 elif options.type == "html": 862 main_page.to_html(sys.stdout) 863