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
|
#
# Copyright (C) 2000 Stephen Davies
# Copyright (C) 2000 Stefan Seefeld
# All rights reserved.
# Licensed to the public under the terms of the GNU LGPL (>= 2),
# see the file COPYING for details.
#
"""
View base class, contains base functionality and common interface for all Views.
"""
from Synopsis.Processor import Parametrized, Parameter
from Synopsis import Util
from Tags import *
import os.path, cStringIO
class Format(Parametrized):
"""Default and base class for formatting a view layout. The Format
class basically defines the HTML used at the start and end of the view.
The default creates an XHTML compliant header and footer with a proper
title, and link to the stylesheet."""
def init(self, processor, prefix):
self.prefix = prefix
def view_header(self, os, title, body, headextra, view):
"""Called to output the view header to the given output stream.
@param os a file-like object (use os.write())
@param title the title of this view
@param body the body tag, which may contain extra parameters such as
onLoad scripts, and may also be empty eg: for the frames index
@param headextra extra html to put in the head section, such as
scripts
"""
os.write('<?xml version="1.0" encoding="iso-8859-1"?>\n')
os.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n')
os.write(' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
os.write('<html xmlns="http://www.w3.org/1999/xhtml" lang="en">\n')
os.write('<!-- ' + view.filename() + ' -->\n')
os.write('<!-- this view was generated by ' + view.__class__.__name__ + ' -->\n')
os.write("<head>\n")
os.write('<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type"/>')
os.write(entity('title','Synopsis - '+ title) + '\n')
css = self.prefix + 'style.css'
os.write(solotag('link', type='text/css', rel='stylesheet', href=css) + '\n')
os.write(headextra)
os.write("</head>\n%s\n"%body)
def view_footer(self, os, body):
"""Called to output the view footer to the given output stream.
@param os a file-like object (use os.write())
@param body the close body tag, which may be empty eg: for the frames
index
"""
os.write("\n%s\n</html>\n"%body)
class Template(Format):
"""Format subclass that uses a template file to define the HTML header
and footer for each view."""
template = Parameter('', 'the html template file')
copy_files = Parameter([], 'a list of files to be copied into the output dir')
def init(self, processor, prefix):
Format.init(self, processor, prefix)
self.__re_body = re.compile('<body(?P<params>([ \t\n]+[-a-zA-Z0-9]+=("[^"]*"|\'[^\']*\'|[^ \t\n>]*))*)>', re.I)
self.__re_closebody = re.compile('</body>', re.I)
self.__re_closehead = re.compile('</head>', re.I)
self.__title_tag = '@TITLE@'
self.__content_tag = '@CONTENT@'
for file in self.copy_files:
processor.file_layout.copy_file(file, file)
self.load_file()
def load_file(self):
"""Loads and parses the template file"""
f = open(self.template, 'rt')
text = f.read(1024*64) # arbitrary max limit of 64kb
f.close()
# Find the content tag
content_index = text.find(self.__content_tag)
if content_index == -1:
print "Fatal: content tag '%s' not found in template file!"%self.__content_tag
raise SystemError, "Content tag not found"
header = text[:content_index]
# Find the title (doesn't matter if not found)
self.__title_index = text.find(self.__title_tag)
if self.__title_index != -1:
# Remove the title tag
header = header[:self.__title_index] + \
header[self.__title_index+len(self.__title_tag):]
# Find the close head tag
mo = self.__re_closehead.search(header)
if mo: self.__headextra_index = mo.start()
else: self.__headextra_index = -1
# Find the body tag
mo = self.__re_body.search(header)
if not mo:
print "Fatal: body tag not found in template file!"
print "(if you are sure there is one, this may be a bug in Synopsis)"
raise SystemError, "Body tag not found"
if mo.group('params'): self.__body_params = mo.group('params')
else: self.__body_params = ''
self.__body_index = mo.start()
header = header[:mo.start()] + header[mo.end():]
# Store the header
self.__header = header
footer = text[content_index+len(self.__content_tag):]
# Find the close body tag
mo = self.__re_closebody.search(footer)
if not mo:
print "Fatal: close body tag not found in template file"
raise SystemError, "Close body tag not found"
self.__closebody_index = mo.start()
footer = footer[:mo.start()] + footer[mo.end():]
self.__footer = footer
def write(self, os, text):
"""Writes the text to the output stream, replaceing @PREFIX@ with the
prefix for this file"""
sections = string.split(text, '@PREFIX@')
os.write(string.join(sections, self.prefix))
def view_header(self, os, title, body, headextra, view):
"""Formats the header using the template file"""
if not body: return Format.view_header(self, os, title, body, headextra)
header = self.__header
index = 0
if self.__title_index != -1:
self.write(os, header[:self.__title_index])
self.write(os, title)
index = self.__title_index
if self.__headextra_index != -1:
self.write(os, header[index:self.__headextra_index])
self.write(os, headextra)
index = self.__headextra_index
self.write(os, header[index:self.__body_index])
if body:
if body[-1] == '>':
self.write(os, body[:-1]+self.__body_params+body[-1])
else:
# Hmmmm... Should not happen, perhaps use regex?
self.write(os, body)
self.write(os, header[self.__body_index:])
def view_footer(self, os, body):
"""Formats the footer using the template file"""
if not body: return Format.view_footer(self, os, body)
footer = self.__footer
self.write(os, footer[:self.__closebody_index])
self.write(os, body)
self.write(os, footer[self.__closebody_index:])
class View(Parametrized):
"""Base class for a Views. The base class provides a common interface, and
also handles common operations such as opening the file, and delegating
the view formatting to a strategy class.
@see Format"""
template = Parameter(Format(), 'the object that provides the html template for the view')
def register(self, processor):
"""Registers this View class with the processor."""
self.processor = processor
self.__os = None
def filename(self):
"Polymorphic method returning the filename associated with the view"
return ''
def title(self):
"Polymorphic method returning the title associated with the view"
return ''
def os(self):
"Returns the output stream opened with start_file"
return self.__os
def write(self, str):
"""Writes the given string to the currently opened file"""
self.__os.write(str)
def register_filenames(self, start):
"""Registers filenames for each file this View will generate, given
the starting Scope."""
pass
def get_toc(self, start):
"""Retrieves the TOC for this view. This method assumes that the view
generates info for the the whole AST, which could be the Scope,
the Source (source code) or the XRef (cross reference info).
The default implementation returns None. Start is the declaration to
start processing from, which could be the global namespace."""
pass
def process(self, start):
"""Process the given Scope recursively. This is the method which is
called to actually create the files, so you probably want to override
it ;)"""
pass
def open_file(self):
"""Returns a new output stream. This template method is for internal
use only, but may be overriden in derived classes.
The default joins output dir and self.filename()
and uses Util.open()"""
return Util.open(os.path.join(self.processor.output, self.filename()))
def close_file(self):
"""Closes the internal output stream. This template method is for
internal use only, but may be overriden in derived classes."""
self.__os.close()
self.__os = None
def start_file(self, body='<body>', headextra=''):
"""Start a new file with given filename, title and body. This method
opens a file for writing, and writes the html header crap at the top.
You must specify a title, which is prepended with the project name.
The body argument is optional, and it is preferred to use stylesheets
for that sort of stuff. You may want to put an onLoad handler in it
though in which case that's the place to do it. The opened file is
stored and can be accessed using the os() method."""
self.__os = self.open_file()
prefix = rel(self.filename(), '')
self.template.init(self.processor, prefix)
self.template.view_header(self.__os, self.title(), body, headextra, self)
def end_file(self, body='</body>'):
"""Close the file using given close body tag. The default is
just a close body tag, but if you specify '' then nothing will be
written (useful for a frames view)"""
self.template.view_footer(self.__os, body)
self.close_file()
def reference(self, name, scope, label=None, **keys):
"""Returns a reference to the given name. The name is a scoped name,
and the optional label is an alternative name to use as the link text.
The name is looked up in the TOC so the link may not be local. The
optional keys are appended as attributes to the A tag."""
if not label: label = escape(Util.ccolonName(name, scope))
entry = self.processor.toc[name]
if entry: return apply(href, (rel(self.filename(), entry.link), label), keys)
return label or ''
class BufferView(View):
"""A view that writes to a string buffer."""
def _take_control(self):
self.open_file = lambda s=self: BufferView.open_file(s)
self.close_file = lambda s=self: BufferView.close_file(s)
self.get_buffer = lambda s=self: BufferView.get_buffer(s)
def open_file(self):
"Returns a new StringIO"
return cStringIO.StringIO()
def close_file(self):
"Does nothing."
pass
def get_buffer(self):
"""Returns the view as a string, then deletes the internal buffer"""
view = self.os().getvalue()
# NOW we do the close
View.close_file(self)
return view
|