File: ManualPage.py

package info (click to toggle)
python-gendoc 0.73-8
  • links: PTS
  • area: main
  • in suites: woody
  • size: 312 kB
  • ctags: 845
  • sloc: python: 2,610; makefile: 124; sh: 26
file content (588 lines) | stat: -rw-r--r-- 16,965 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
#
# FILE            $Id: ManualPage.py,v 1.12 1998/02/04 18:18:18 dlarsson Exp $
#
# DESCRIPTION     Manual page abstraction.
#
# AUTHOR          SEISY/LKSB Daniel Larsson
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of ABB Industrial Systems
# not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior permission.
#
# ABB INDUSTRIAL SYSTEMS DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS, IN NO EVENT SHALL ABB INDUSTRIAL SYSTEMS BE LIABLE
# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# Copyright (C) ABB Industrial Systems AB, 1996
# Unpublished work.  All Rights Reserved.
# 
# HISTORY:
# $Log: /Gendoc/ManualPage.py $
# 
# 2     98-04-01 14:12 Daniel
# Fixes from mcfletch.
# 
# 1     98-04-01 13:15 Daniel
# Revision 1.12  1998/02/04 18:18:18  dlarsson
# Fix from R. Friedrich; replacing of links fouled up if the length of the
# rendered link was less than the original link length.
#
# Revision 1.11  1998/01/19 11:41:09  dlarsson
# Cosmetic change
#
# Revision 1.10  1996/09/06 19:12:07  omfadmin
# Removed surrounding characters on markups.
#
# Revision 1.9  1996/09/06  09:54:57  omfadmin
# Replaced setext markup with structured text markup.
#
# Revision 1.8  1996/08/26  20:29:07  omfadmin
# Added support for numbered and nested lists.
#      "        for definition lists.
#
# Revision 1.7  1996/07/11  16:38:09  omfadmin
# Split render_hyperlink into two, internal links and setext links.
# Caused problems since it was use to run on code sections and variables
# were sometimes assumed to be setext links.
#
# Revision 1.6  1996/07/10  03:03:10  omfadmin
# - Added set_author() method.
# - Fixed bug when replacing internal hyperlinks.
# - Fixed problem with method sections beeing reused.
#
# Revision 1.5  1996/07/08  05:15:41  omfadmin
# Added top(), previous() and next() for navigation buttons
# (primarily for HTML, but other formatters can use it too).
#
# Revision 1.4  1996/06/24  09:54:16  omfadmin
# Added support for setext style hyperlinks.
#
# Revision 1.3  1996/06/21  00:06:46  omfadmin
# - Fixed the hyperlink regular expression. It matched too much when multiple
#   links occurred on the same line. ':' is now forbidden in links and link
#   text.
# - Converts hyperlinks not only in code, but in paragraphs too.
#
# Revision 1.2  1996/06/13  17:50:33  omfadmin
# Simplified implementing formatters somewhat by moving code to find
# markups and hyperlinks to here.
#
# Revision 1.1  1995/04/15  13:36:11  dlarsson
# Initial revision
#
#

__version__ = "$Revision: 2 $"

import regex
import docregex

error = 'ManualPage.error'

# Verbosity level
VERBOSE = 0

def message(string, level=1):
    if VERBOSE >= level:
	print string


#
# NOTE: The syntax for hyperlinks expressed here is *only* an
# internal syntax. You're not supposed to know about this.
#
# Regular expression for hyperlinks.
# The _hyperlink expression groups a match into the following
# parts:
#   1   'HREF='
#   2   The hyperlink
#   3   '::'
#   4   The hyperlink text
#   5   '/HREF'
#
# The syntax is similar to the HTML construct, but not identical to make
# the substitution process by the HTML formatter easier.
#                                       ____1____    ____2____  
hyperlink_regex = regex.compile('HREF=\\([^:]*\\)::\\([^:]*\\)/HREF')


# Simulate enum type
PARAGRAPH,	\
CODE,		\
UN_LIST,	\
OR_LIST,        \
DEF_LIST,       \
SECTION	= tuple(range(1, 7))

def convert(formatter, text):
    """Convert list item text."""
    if type(text) == type(''):
	return formatter.convert_text(text)
    else: # Assume it is a tuple (listtype, list)
	return text[0], map(lambda text, f=formatter:convert(f, text), text[1])
	    

class ManualPage:
    """Abstract representation of manual page.

    This class enables you to create an abstract representation
    of a manual page. A manual page consists of a number of sections,
    possibly nested, and each section contain some paragraphs, lists
    and/or code snippets. To format the manual page, you must pass
    it a formatter instance. Formatters translate the manual page
    into a concrete syntax, such as HTML, MIF or UNIX manual page
    format.
    """

    # Available markups
    MARKUPS = [ ('strong',      docregex.strong_regex),
		('quoted',      docregex.code_regex),
		('emphasis',    docregex.emph_regex) ]

    def __init__(self, name):
	"""Initialize object."""

	self.author = None

	# References to other manpages
	self.topp = None
	self.prev = None
	self.nxt = None

	# These members hold the document
	self._name_ = name
	self._doctree_ = None
	self._cursection_ = None
	self._references_ = {}

	# Current level is set while rendering the document.
	# The level is the depth of the section nesting.
	# The method 'cur_level()' can be used by formatters
	# to find out at what level they currently are.
	self._cur_level_ = 0

	# Mapping from segment type to rendering function
	self._render_ = {
	    PARAGRAPH: self._render_paragraph,
	    CODE:      self._render_code,
	    UN_LIST:   self._render_list,
	    OR_LIST:   self._render_list,
	    DEF_LIST:  self._render_deflist,
	    SECTION:   self._render_subsection
	    }

	# Maps docregex hyperlinks to URLs
	self.links = {}

#    def __repr__(self):
#	return 'ManualPage for '+self._name_

    def name(self):
	"Name of this manual page"
	return self._name_

    def title(self, title, *marker):
	"""Set document title.

	~marker~ can be used to add the	title to an index. The marker
	is sent untouched to subclasses."""

	# Format: (level, supersection, title string, parts, marker)
	self._doctree_ = (0, None, title, [], marker)
	self._cursection_ = self._doctree_

    def set_author(self, author):
	"""Set the author of the software module."""

	self.author = author

    def top(self, top):
	"""Set up link to top manpage.

	If ~top~ is None, no link is set up"""

	self.topp = top

    def previous(self, prev):
	"""Set up link to previous manpage.

	If ~prev~ is None, no link is set up"""

	self.prev = prev

    def next(self, next):
	"""Set up link to next manpage.

	If ~next~ is None, no link is set up"""

	self.nxt = next

    def cur_level(self):
	"""The current section level during rendering.
	
	Intended for formatters."""
	return self._cur_level_

    def section(self, section, super_section=None, *marker, **kw):
	"""Add a new section.
	
	Sections can be added under other
	sections by giving a section object.
	
	RETURNS
	Either a newly created or a found section object.
	"""
	# If no section is given, assume it is a top section, i.e put
	# it directly under the doc tree root.
	if not super_section:
	    super_section = self._doctree_

	# Is there already is a section with the same name under
	# 'super_section'?
	sect = None
	if not kw.has_key('search') or kw['search']:
	    sect = self._find_section_(section, super_section)

	if sect:
	    sect = sect[0][1]
	else:
	    # No section found at current level. Search one level
	    # up also before creating a new one. This might not
	    # be the most logical thing to do??
	    sect = self._find_section_(section, super_section[1])

	    if sect:
		sect = sect[0][1]
		super_section = super_section[1]
	    else:
		# Ok, create a new empty section. The format is:
		#   (section level, supersection, section title, list of contents)
		sect = (super_section[0]+1, super_section, section, [], marker)
		self._add_to_section_(super_section, SECTION, sect)

	self._cursection_ = sect
	return self._cursection_

    def paragraph(self, para, section=None):
	"""Add a new paragraph under 'section'.
	
	If no section is given, use the section returned from the most
	recent	call to 'section(...)'
	"""
	self._add_to_section_(section, PARAGRAPH, para)

    def code(self, code, section=None):
	"""Add a new code paragraph under 'section'.
	
	If no section is given, use the section returned from the most
	recent call to 'section(...)'
	"""
	self._add_to_section_(section, CODE, code)

    def list(self, listtype, list, section=None):
	"""Add a new list under 'section'.
	
	If no section is given, use the section returned from the most
	recent call to 'section(...)'
	"""
	self._add_to_section_(section, listtype, (listtype, list))

    def deflist(self, list, section=None):
	"""Add a new definition list under 'section'.
	
	If no section is given, use the section returned from the most
	recent call to 'section(...)'
	"""
	self._add_to_section_(section, DEF_LIST, list)

    def add_hyperlink(self, url, text):
	"""Add docregex link definition.

	~url~ is the URL, and ~text~ is the docregex link name."""

	self.links[text] = url


    def render(self, formatter):
	"""Render the manual page.
	
	The render function traverses the manual page and lets the
	formatter render each part.
	
	RETURNS
	The formatted manual page as a string.
	"""
	
	# Pick up data from the root
	lvl, none, title, children, marker = self._doctree_

	# Render the title
	formatter.render_title(self, title, marker)

	# Render navigation links
	formatter.render_navigation(self.topp, self.prev, self.nxt)

	# Then render all the children
	for child in children:
	    apply(self._render_[child[0]], (formatter,)+tuple(child[1:]))

	# Finalizing document
	return formatter.end()

    def reference(self, ref, text=None):
	"""Return a reference to 'ref' with the text 'text'."""
	if not text:
	    text = ref

	return 'HREF=%s::%s/HREF' % (ref, text)


    # ---  Private Methods  ---
    def _find_section_recursive_(self, section_name, section=None):
	"""Search recursively for a section 'section_name' starting
	at 'section'."""
		
	# If section is not given, start at root
	if section == None:
	    section = self._doctree_

	# Search for section at this level
	sect = self._find_section_(section_name, section)
	if sect:
	    return sect
	else:
	    # Loop through all subsections and search them recursively
	    for sect in filter(lambda s: s[0] == SECTION, section[3]):
		sect = self._find_section_recursive_(section_name, sect[1])
		if sect:
		    return sect
	return None

    def _find_section_(self, section, super_section):
	"""Search for 'section' under 'super_section'. Only sections
	at the level below 'super_section' is searched."""
	if super_section:
	    return filter(lambda s, new=section: s[0] == SECTION and s[1][2] == new, super_section[3])
	else:
	    return None


    def _add_to_section_(self, sect, *node):
	"""Add a new element under 'section'.		
	
	If no section is given, use the section returned from the most
	recent call to 'section(...)'
	"""
	if not sect:
	    sect = self._cursection_
	sect[3].append(node)

    def _render_subsection(self, formatter, args):
	"Render a subsection."
	lvl, super, sect, children, marker = args

	# If section has no children, skip it
	if not children:
	    return

	# Set current level. This information might be needed
	# when rendering children (e.g. indentation)
	self._cur_level_ = lvl

	# First, render this section
	message("--- emitting section: %s\n " % sect)
	formatter.render_section(self, lvl, sect, marker)

	# then render the section's children
	for child in children:
	    apply(self._render_[child[0]], (formatter,)+tuple(child[1:]))


    def _render_paragraph(self, formatter, para):
	# First, let the formatter massage the text, such as
	# escaping certain characters. We must do this before
	# translating the hyperlinks, since otherwise it gets
	# pretty difficult for the HTML formatter to distinguish
	# '<' characters used in hyperlinks and those used in
	# the text.
	try:
	    para = formatter.convert_text(para)
	except AttributeError:
	    # convert_text probably not implemented. Ignore.
	    pass

	# Create hyperlinks
	para = self._render_internal_hyperlink(formatter, para)
	para = self._render_docregex_hyperlink(formatter, para)

	# Make markup substitutions
	para = self._render_markup(formatter, para)

	# Ok, substitutions are done. Render the paragraph.
	message("--- emitting paragraph: %s\n " % para)
	formatter.render_paragraph(self, para)

    def _render_code(self, formatter, code):
	try:
	    code = formatter.convert_text(code)
	except AttributeError:
	    # convert_text probably not implemented. Ignore.
	    pass

	# Create hyperlinks
	code = self._render_internal_hyperlink(formatter, code)

	message("--- emitting code: %s\n " % code)
	formatter.render_code(self, code)

    def _render_list(self, formatter, list):
	try:
	    list = convert(formatter, list)
	except AttributeError:
	    # convert_text probably not implemented. Ignore.
	    pass
	formatter.render_list(self, list)

    def _render_deflist(self, formatter, listt):
	ltype, list = listt
	try:
	    for i in range(len(list)):
		list[i] = formatter.convert_text(list[i][0]) , formatter.convert_text(list[i][1])
	except AttributeError:
	    # convert_text probably not implemented. Ignore.
	    pass

	formatter.render_deflist(self, list)

    def _render_markup(self, formatter, text):
	# Translate docregex markup into formatter specific markup
	for markup, regexp in self.MARKUPS:
	    index = length = 0
	    while index != -1 and index + length < len(text):
		index = regexp.search(text, index+length)
		if index >= 0:

		    matched_text = regexp.group(0)
		    length = len(matched_text)

		    prefix = regexp.group(1)
		    marked_text = regexp.group(2)

		    # Ok, we found a markup. Ask formatter for a
		    # substitution, and proceed.
		    mtd_name = 'render_'+markup

		    message('--- Found markup %s for "%s"' % (markup, marked_text), 2)
		    
		    if hasattr(formatter, mtd_name):
			method = getattr(formatter, mtd_name)
			marked_text = method(marked_text)

		    # Append punctuation back to string.
		    # Ugh, nasty hack :-(
		    if regexp == docregex.code_regex:
			marked_text = marked_text + regexp.group(4)
		    else:
			marked_text = marked_text + regexp.group(3)
		    text = text[:index] + prefix + marked_text + text[index+length:]

	return text

    def _render_internal_hyperlink(self, formatter, text):
	# Search for hyperlinks, and ask the formatter how
	# to translate these.
	index = 0
	link_len = 0
	rendered_link = ''
	while 1:
	    index = hyperlink_regex.search(text, index+len(rendered_link))

	    if index == -1: break

	    link_len = len(hyperlink_regex.group(0))

	    # Group 1 is the link, group 2 the link text
	    link, link_text = hyperlink_regex.group(1), hyperlink_regex.group(2)

	    # If there is a section in this manual
	    # with the same name as the link, make
	    # it an internal link, otherwise, assume
	    # it is external.
	    try:
		if self._find_section_recursive_(link):
		    rendered_link = formatter.render_internal_link(link, link_text)
		else:
		    rendered_link = formatter.render_external_link(link, link_text)
	    except AttributeError, m:
		rendered_link = link_text

	    # Substitute the found hyperlink with the rendered one
	    text = text[:index] + rendered_link + text[index+link_len:]

	return text


    def _render_docregex_hyperlink(self, formatter, text):
	from docregex import hypertext_regex
	import regsub

	index = 0
	while index >= 0:
	    index = hypertext_regex.search(text, index)
	    if index == -1: break

	    link_len = len(hypertext_regex.group(0))

	    try:
		prefix, link_text, suffix = hypertext_regex.group(1,2,3)
		link = self.links[link_text]
		try:
		    rendered_link = formatter.render_external_link(link, link_text)
		except AttributeError, m:
		    rendered_link = link_text
	    except KeyError, key:
		# We found a string "text" which obviously wasn't
		# a link. Regard it as just quoted text.
		rendered_link = '"' + link_text + '"'
		

	    # Substitute the found hyperlink with the rendered one
	    text = text[:index] + \
		   prefix + rendered_link + suffix + \
		   text[index+link_len:]

	    # Move index past the found regex
	    if link_len > len(rendered_link):
		index = index + len(rendered_link)
	    else:
		index = index + len(rendered_link) - link_len

	return text


def test():
    from ManFormatter import ASCIIFormatter
    doc = ManualPage('Test')
    txt = "Hej hopp _Daniel_. This is ~italic~. This is **bold**."
    txt2 = "This_is_a_link_."
    doc.title('TEST')
    doc.paragraph(txt)
    doc.paragraph(txt2)
    
    form = ASCIIFormatter()
    print doc.render(form)

def test2():
    from ManFormatter import HTMLFormatter
    

if __name__ == '__main__':
    test()