Re: class-private considered harmful
Rob Hooft <[email protected]> Sat, 24 Jul 2004 01:08:13 +0200
| Newsgroups | gmane.comp.web.ht2html.devel |
|---|---|
| Message-ID | <[email protected]> |
This is a multi-part message in MIME format.
--------------020505070002030904010107
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
Fred L. Drake, Jr. wrote:
> On Friday 23 July 2004 05:17 pm, David Goodger wrote:
> > My first thought is to replace class-private method and
> > attribute names with single-underscore names, qualified with the class
> > name. So Banner.__links becomes Banner._banner_links. Basically what
> > class-privacy does, but in a non-magical form.
>
> Actually, I'd be inclined to remove all the inheritance, and use aggregation
> and delegation instead. But that's an ever bigger task, and there's been
> little interest (at least from me) and no time.
I hacked away on this already in October 2003. Attached. Maybe someone
can use this as a starting point.
Rob
--------------020505070002030904010107
Content-Type: text/x-patch;
name="ht2html.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="ht2html.diff"
? Generator.py
? HooftGenerator.py
? pyinline.py
? pyinline.test
Index: Skeleton.py
===================================================================
RCS file: /cvsroot/ht2html/ht2html/Skeleton.py,v
retrieving revision 2.10
diff -u -r2.10 Skeleton.py
--- Skeleton.py 25 Jul 2003 04:59:52 -0000 2.10
+++ Skeleton.py 23 Jul 2004 23:06:02 -0000
@@ -11,8 +11,31 @@
import time
from cStringIO import StringIO
+class InternationalFileHandler:
+ # Interface
+ def get_targetfilename(self, inputfilename):
+ """Return a suitable output filename"""
+ root, ext, lang = self._splitfilename(inputfilename)
+ return root + '.html' + lang
+
+ def get_language(self, inputfilename, default=None):
+ root, ext, lang = self._splitfilename(inputfilename)
+ if lang == '':
+ return default
+ else:
+ return lang[1:] # Skip leading .
+
+ # Implementation
+ def _splitfilename(self, inputfilename):
+ root, ext = os.path.splitext(inputfilename)
+ if '.' in root:
+ root, lang = os.path.splitext(root)
+ else:
+ lang = ''
+ return root, ext, lang
-class Skeleton:
+
+class Skeleton(InternationalFileHandler):
#
# for sub-classes to override
#
@@ -173,8 +196,8 @@
return ''
else:
return 'margin: 0px;'
-
- # Call this method
+
+ # ht2html calls this method
def makepage(self):
banner = self.get_banner()
sidebar = self.get_sidebar()
@@ -255,7 +278,7 @@
def __do_body(self, body):
print '<!-- start of body cell -->'
- print '<td valign="top" width="%s%%" class="body"><br />' % (
+ print '<td valign="top" width="%s%%" class="body">' % (
self.get_banner_width())
print body
print '</td><!-- end of body cell -->'
Index: ht2html.py
===================================================================
RCS file: /cvsroot/ht2html/ht2html/ht2html.py,v
retrieving revision 2.3
diff -u -r2.3 ht2html.py
--- ht2html.py 25 Sep 2003 04:12:46 -0000 2.3
+++ ht2html.py 23 Jul 2004 23:06:02 -0000
@@ -182,12 +182,13 @@
def process_file(self, file):
if not self.quiet:
print 'Processing %s...' % file
- # get the target filename
- root, ext = os.path.splitext(file)
- htmlfile = root + '.html'
g = self.get_generator(file)
if g is None:
return
+
+ # get the target filename
+ htmlfile = g.get_targetfilename(file)
+
# deal with backups, first load the original file
try:
fp = open(htmlfile)
--------------020505070002030904010107
Content-Type: text/x-python;
name="Generator.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="Generator.py"
"""Classes to generate styles avoiding multiple inheritance
The following Headers are used from .ht files:
title: <text>
The title of the HTML page. If not present, the name of the
input file will be used as title.
wide-page: (yes|no)
If yes, no Sidebar will be rendered.
links: <words>
Space-separated list of filenames containing links for the
Sidebar. If not present, the file 'links.h' is used.
other-links: <html-text>
Inline links to be used in the Sidebar.
author: <text>
The name of the author of the page. If not present, an internal
default will be used.
author-email: <text>
The E-mail address of the author of the page. If not present, an
internal default will be used.
Links-files can contain two kinds of lines:
<h3>header</h3>
A header line
<li><a href="target">text</a> extratext
A link line
"""
import sys
import os
import time
import re
try:
from cStringIO import StringIO
except IOError:
from StringIO import StringIO
import rfc822
class HTParser(rfc822.Message):
def __init__(self, generator):
# Visit the generator to find some information
default_author = generator.get_author()
default_email = generator.get_email()
filename = generator.get_inputfilename()
#
self._filename = filename
self._fp = fp = open(filename)
self._extraheaders = {}
rfc822.Message.__init__(self, fp)
#
# Massage some standard headers we require.
#
# title
if not self.has_key('title'):
parts = self._filename.split(os.sep)
self._extraheaders['title'] = parts[-1]
# author
if not self.has_key('author'):
if default_author is not None:
author = default_author
else:
domainname = None
h, a, ip = gethostbyaddr(gethostbyname(gethostname()))
for host in [h] + a:
i = host.find('.')
if i > 0:
domainname = host[i+1:]
break
if domainname:
author = 'webmaster@' + domainname
else:
author = 'MUST SUPPLY AN AUTHOR'
self._extraheaders['author'] = author
# author email
if not self.has_key('author-email'):
if default_email is None:
default_email = self['author']
self._extraheaders['author-email'] = default_email
# override __getitem__ so we can access our own internal dictionary
def __getitem__(self, item):
try:
return rfc822.Message.__getitem__(self, item)
except KeyError:
return self._extraheaders[item]
# might be using an older rfc822
def get(self, name, default=None):
if self.has_key(name):
return self.getheader(name)
elif self._extraheaders.has_key(name):
return self._extraheaders[name]
else:
return default
class PageComponent:
"""Generic nonexisting page component. This can be used to represent no
banner, no corner, no sidebar"""
def __init__(self, generator):
"""Every page component is instantiated with the generator
as its only argument, such that it can retrieve from that
all the things it needs for its operation."""
pass
def __call__(self):
"""The instance is called to get the actual text contained in the
component"""
return None
class Sidebar(PageComponent):
"""This class is supposed to be used as a sidebar component in a
Generator class. It starts out empty, and can be filled using
add_XXX methods."""
def __init__(self, generator):
self._links = []
self._parser = generator._parser
self._lightshade = generator.get_lightshade()
self._darkshade = generator.get_darkshade()
self._bgcolor = generator.get_bgcolor()
self._language = generator.get_language()
# First define the three basic types of data: label, text or link
def add_label(self,text):
self._links.append(text)
def add_text(self,text,extra=None):
if extra is None:
self._links.append((None,text))
else:
self._links.append((None,text,extra))
def add_link(self,url,text,extra=None):
if extra is None:
self._links.append((url,text))
else:
self._links.append((url,text,extra))
# Define a number of derived datatypes
def add_empty(self):
self.add_text(' ')
def add_hr(self):
self.add_text('<hr>')
def add_imglink(self, url, filename, alt=None):
if alt is None:
self.add_link(url,'<center><img border="0" src="%s"></center>'%(filename))
else:
self.add_link(url,'<center><img alt="%s" border="0" src="%s"></center>'%(alt,filename))
def add_lastmodified(self, filename):
import stat
tm = os.stat(filename)[stat.ST_CTIME]
tm = time.localtime(tm)
self.add_text('<center><em>Last modified: '+time.strftime("%Y-%m-%d",tm)+'</em></center>')
def add_email(self,mailtitle='Email us'):
self.add_label(mailtitle)
author = self._parser.get('author') # guaranteed
email = self._parser.get('author-email', author)
self.add_link('mailto:' + email, author)
def add_linkfiles(self):
# Process all link files.
# We first look for a Links: header. If it is not present, we
# look for a file named 'links.language.h in the current directory.
# If that does not exist either, we look for links.h
# If the Links: header exists, it must explicitly mention links.h
linkfiles = self._parser.get('links', None)
if linkfiles is None:
interlinks = 'links.%s.h' % self._language
if os.path.exists(interlinks):
linkfiles = interlinks
else:
linkfiles = 'links.h'
for file in linkfiles.split():
try:
fp = open(file.strip())
except IOError:
continue
data = fp.read()
fp.close()
self._parse(data)
# Other-links header specifies more links in-lined
otherlinks = self._parser.get('other-links')
if otherlinks:
self._parse(otherlinks)
def fixlinks(self, linkfixer):
linkfixer.massage(self._links)
## Implementation ##
# regular expressions used in massage()
cre = re.compile(
r'(<h3>(?P<h3>.*?)</h3>)|'
r'(<li>(<a href="?(?P<link>[^<>"]*)"?>(?P<contents>[^<>]*)</a>)?)'
r'(?P<extra>[^\n]*)',
re.DOTALL | re.IGNORECASE)
def _parse(self, text):
"""Apply various bits of magic to the links in this list.
"""
start = 0
end = len(text)
while 1:
mo = self.cre.search(text, start)
if not mo:
break
mstart = mo.start(0)
h3, link, contents, extra = \
mo.group('h3', 'link', 'contents', 'extra')
if link is None:
link = ''
if contents is None:
contents = ''
if h3:
self.add_label(h3.strip())
elif extra:
L = [s.strip() for s in (link, contents)]
L.append(extra)
self.add_link(*tuple(L))
else:
L = [s.strip() for s in (link, contents)]
link = tuple(L)
self.add_link(*link)
start = mo.end(0)
def __call__(self):
stdout = sys.stdout
html = StringIO()
try:
sys.stdout = html
self._start_table()
self._do_link()
self._finish()
finally:
sys.stdout = stdout
return html.getvalue()
def _start_table(self):
print '<!-- start of sidebar table -->'
print '<table width="100%" border="0" cellspacing="0" cellpadding="3"'
print ' bgcolor="%s">' % self._bgcolor
def _finish(self):
print '</table><!-- end of sidebar table -->'
def _do_link(self):
done_one = 0
for item in self._links:
if type(item) != type(()):
# category header
if done_one:
# get some separation between header and last item
print '<tr><td bgcolor="%s"> ' % (
self._lightshade)
else:
done_one = 1
print '<tr><td bgcolor="%s"><b><font color="%s">' % (
self._darkshade, self._bgcolor)
print item
print '</font></b></td></tr>'
else:
if len(item) == 3:
url, text, extra = item
else:
url, text = item
extra = ''
if url is None:
s = text
else:
s = '<a href="%s">%s</a>' % (url, text)
print '<tr><td bgcolor="%s">' % self._lightshade
print '%s%s' % (s, extra)
print '</td></tr>'
class Banner(PageComponent):
def __init__(self, generator):
self._links = []
self._cols = 4
self._banner_attributes = generator.get_banner_attributes()
self._bgcolor = generator.get_bgcolor()
self._lightshade = generator.get_lightshade()
self._attributes = ''
# Generator interface
def set_cols(self, cols):
self._cols = cols
def set_attributes(self, attributes):
self._attributes = attributes
def set_links(self, links):
self._links = links
# Interface to ht2html
def __call__(self):
rows, leftover = divmod(len(self._links), self._cols)
if leftover:
rows = rows + 1
self._rows = rows
return self._get_text()
# Implementation
def _get_text(self):
stdout = sys.stdout
html = StringIO()
try:
sys.stdout = html
self._start_table()
self._do_table()
self._end_table()
finally:
sys.stdout = stdout
return html.getvalue()
def _start_table(self):
print '<!-- start of site links table -->'
print '<table width="100%" border="0"'
print self._attributes
print ' bgcolor="%s">' % (self._bgcolor)
print '<tr>'
def _end_table(self):
print '</tr>'
print '</table><!-- end of site links table -->'
def _do_table(self):
col = 0
for item in self._links:
if len(item) == 3:
url, text, extra = item
else:
url, text = item
extra = ''
if not url:
s = text + extra
else:
s = '<a href="%s">%s</a>%s' % (url, text, extra)
if col >= self._cols:
# break the row
print '</tr><tr>'
col = 0
print ' <td bgcolor="%s">' % self._lightshade
print s
print ' </td>'
col = col + 1
# fill rest of row with non-breaking spaces.
while col and col < self._cols:
print ' <td bgcolor="%s">' % self._lightshade
print ' </td>'
col = col + 1
class EmptyCorner(PageComponent):
def __call__(self):
return ' '
class LogoCorner(PageComponent):
def __init__(self, generator):
self._target = '/'
# Generator interface
def set_logo(self, image, alt=None):
self._image = image
self._alt = alt
def set_target(self, target):
self._target = target
# interface to ht2html
def __call__(self):
# It is important not to have newlines between the img tag and the end
# anchor and end center tags, otherwise layout gets messed up
if self._alt:
alt=' alt="%s"' % self._alt
else:
alt=''
return ('<center>'+
'<a href="%s">'%self._target+
'<img%s border="0" src="%s">' % (alt, self._image)+
'</a></center>')
debug = 0
class LinkFixer:
def __init__(self, myurl, rootdir='.', relthis='.'):
self._rootdir = rootdir
self._relthis = relthis
self.__myurl = self.normalize(myurl)
self._dict = {}
def normalize(self, url):
"""Take an url, and turn it into a filename the way a webserver
would do it."""
if url is None:
return
if url == './':
url = 'index.html'
elif url[-1] == '/':
url = url + 'index.html'
if url[0]=='/':
# For absolute path, do not add relpath
absurl = '/'.join([self._rootdir, url])
else:
absurl = '/'.join([self._rootdir, self._relthis, url])
# normalize the path, kind of the way os.path.normpath() does.
# urlparse ought to have something like this...
parts = []
for p in absurl.split('/'):
if p == '.' or (p == '' and len(parts) > 0):
continue
if p == '..' and len(parts) > 0:
del parts[-1]
parts.append(p)
absurl = '/'.join(parts)
return absurl
def set_dict(self,dict):
self._dict = dict
def massage(self, links, aboves=0):
"""Substitute in situ before massaging.
With aboves=1, boldify links if they are above myurl.
"""
for i in range(len(links)):
item = links[i]
if type(item) != type(()):
continue
if len(item) == 3:
url, text, extra = item
else:
url, text = item
extra = ''
# Perform string interpolation
try:
url = url % self._dict
except TypeError:
pass
if url:
absurl = self.normalize(url)
else:
absurl = None
if url is None:
links[i] = (url, text, extra)
elif absurl is None:
links[i] = (absurl, text, extra)
elif absurl == self.__myurl:
links[i] = (None, '<b>' + text + '</b>', extra)
elif aboves and self.above(absurl, self.__myurl):
links[i] = (url, '<b>' + text + '</b>', extra)
else:
links[i] = (url, text, extra)
def above(self, absurl, myurl):
"""Return true if absurl is above myurl."""
# Only do one level of checking, and don't match on the root, since
# that's always going to be above everything else.
myurl = self.normalize(myurl)
i = myurl.rfind('/')
j = absurl.rfind('/')
if i > 0 and j > 0 and \
absurl[:j] == myurl[:i] and \
myurl[i+1:] <> 'index.html':
return 1
return 0
from Skeleton import Skeleton
class Generator(Skeleton):
cornerclass = PageComponent
bannerclass = PageComponent
sidebarclass = PageComponent
parserclass = HTParser
linkfixerclass = LinkFixer
def __init__(self, file, rootdir, relthis):
self._filename = file
self._rootdir = rootdir
self._relthis = relthis
self.make_parser()
self.make_linkfixer()
self.make_components()
def get_language(self, filename=None):
if filename is None:
filename = self._filename
return Skeleton.get_language(self,filename)
def make_parser(self):
self._parser=self.parserclass(self)
def make_linkfixer(self):
root, ext, lang = self._splitfilename(self._filename)
self._linkfixer = self.linkfixerclass(root + ".html",
self._rootdir,
self._relthis)
def make_components(self):
self._corner=self.cornerclass(self)
self._banner=self.bannerclass(self)
self._sidebar=self.sidebarclass(self)
# Interface available to the component constructors
def get_title(self):
return self._parser.get('title')
def get_author(self):
if hasattr(self,'AUTHOR'):
return self.AUTHOR
else:
return None
def get_email(self):
if hasattr(self,'EMAIL'):
return self.EMAIL
else:
return None
def get_inputfilename(self):
return self._filename
# Interface available to ht2html
def get_sidebar(self):
if self._parser.get('wide-page', 'no').lower() == 'yes':
return None
# Delegate to the Sidebar component
return self._sidebar()
def get_banner(self):
# Delegate to the Banner component
return self._banner()
def get_corner(self):
# Delegate to the Corner component
return self._corner()
def get_body(self):
self._grokbody()
return self._body
def get_cont(self):
self._grokbody()
return self._cont
# Implementation
def _grokbody(self):
if not hasattr(self,'_body'):
text = self._parser.fp.read()
i = text.find('<!--table-stop-->')
if i >= 0:
self._body = text[:i]
self._cont = text[i+17:]
else:
# there is no wide body
self._body = text
self._cont = ''
--------------020505070002030904010107--
-------------------------------------------------------
This SF.Net email is sponsored by BEA Weblogic Workshop
FREE Java Enterprise J2EE developer tools!
Get your free copy of BEA WebLogic Workshop 8.1 today.
http://ads.osdn.com/?ad_id=4721&alloc_id=10040&op=click