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('&nbsp;')

    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">&nbsp;' % (
                        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 '&nbsp;&nbsp;</td>'
            col = col + 1

class EmptyCorner(PageComponent):
    def __call__(self):
        return '&nbsp;'
    
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