#!/usr/bin/env python # Siter # Generate full johnj.com Web site from content markup, navigation skeletons, CSS, etc. # John Jacobsen, NPX Designs, Inc., john@mail.npxdesigns.com # (C) 2007 NPX Designs, Inc. # Started: Sun Sep 30 14:01:22 2007 import optparse, os, sys, re from xml.dom import minidom import Image class NavTree: def __init__(self, url=None, txt=None, isHomeLink=False): self.items = [] self.linkURL = url self.linkTxt = txt self.isHomeLink = isHomeLink def __str__(self): return self.pr(0) def pr(self, indent=0): s = "'%s'->%s\n" % (self.linkTxt, self.linkURL) for x in self.items: s += ' '*indent + x.pr(indent+3) return s class SourceHTML: """ Object for parsing directory source HTML files (gallery.html or post.html) """ def __init__(self, f): # Get content content = open(f).read() # Get title self.title = "" m = re.search("(.*?)", content, re.I) if(m): self.title = re.sub("\r", "\n", m.group(1)) # Get body self.bodyHTML = "" m = re.search("(.*?)", content, re.I | re.S) if(m): self.bodyHTML = re.sub("\r", "\n", m.group(1)) self.bodyHTML = self.bodyHTML.strip() # No leading or trailing whitespace # Get hidden "square thumbs" indicator self.squareThumbs = False m = re.search("<\!--\s*?square-thumbs\s*?-->", content, re.I | re.S) if(m): self.squareThumbs = True # Helper functions used by e.g. Siter class ................................ def parseNavContent(node): """ Recursively traverse XML navigation content, picking out list items, names and links. Store in recursive NavTree object """ top = NavTree() while node: if node.__dict__.has_key("tagName"): if node._attrs.has_key("link"): link = node._attrs["link"].value else: link = None if node.tagName == "item": top.items.append(NavTree(link, node._attrs["name"].value)) if node.tagName == "list": x = parseNavContent(node.firstChild) if node._attrs.has_key("name"): x.linkURL = link x.linkTxt = node._attrs["name"].value top.items.append(x) node = node.nextSibling return top def copyRightText(): return """\ """ def sanitize(s): """ 'Sanitize' string (strip off illegal unicode characters) """ ret = "" for c in s: if ord(c)>127: ret += '?' else: ret += c return ret def isHome(d): return os.path.isdir(d) and \ os.path.exists(os.path.join(d, "home.html")) def isGallery(d): return os.path.isdir(d) and \ os.path.exists(os.path.join(d, "gallery.html")) def isPost(d): return os.path.isdir(d) and \ os.path.exists(os.path.join(d, "post.html")) def isTargetImg(f): if re.search(r'-thumb\.', f, re.I): return False if re.search(r'-display\.', f, re.I): return False if re.search(r'-inline\.', f, re.I): return False if re.search(r'\.jpg', f, re.I): return True if re.search(r'\.png', f, re.I): return True return False def thumbName(f): return os.path.splitext(f)[0] + "-thumb.jpg" def displayName(f): return os.path.splitext(f)[0] + "-display.jpg" def inlineName(f): return os.path.splitext(f)[0] + "-inline.jpg" def countSlashes(f): return len(f.split('/'))-1 def relSlash(top, dir): """ Add '../' for every level for which is beneath """ topDepth = countSlashes(top) dirDepth = countSlashes(dir) ret = "" for i in range(0, dirDepth-topDepth): ret = "../"+ret return ret def relPath(top, dir, f): """ Get path name for file relative to position in site based on """ return relSlash(top, dir)+f def isExternal(link): if link is None: return if re.search('^http:', link): return True return False def adjustLink(top, dir, link): """ Add any required "../" to make navbar point to correct file relative to ; don't fix external links """ if link is None: return link if isExternal(link): return link # FIXME: handle e.g. mailto: URIs return relSlash(top, dir) + link def showThisLinkList(top, dir, link): """ Determine whether to show list described by or not, based on whether the correct subdirectories match """ if link is None: return True topList = top.split('/') dirList = dir.split('/') linkList = link.split('/') linkList.pop() # Take off HTML file while topList and dirList and topList[0] == dirList[0]: topList.pop(0) dirList.pop(0) if len(linkList) == 0: return False for i in range(0, len(linkList)): if i >= len(dirList): return False if dirList[i] != linkList[i]: return False return True def createNavMarkup(top, dir, divClass, navTree): return "
\n" % divClass + createNavTree(top, dir, navTree) + "
\n" def createNavTree(top, dir, navTree): """ Generate navigation snippet for relative to root directory based on navigation content. """ if navTree is None: return "" highlight = showThisLinkList(top, dir, navTree.linkURL) if navTree.linkTxt == "Home": highlight = False # Kludge if len(navTree.items) < 1: if navTree.linkTxt: classTxt = "" if highlight: classTxt = "active" # Can't be both active and external! if isExternal(navTree.linkURL): classTxt = "external" if navTree.linkURL is None: return navTree.linkTxt return "%s" % (adjustLink(top, dir, navTree.linkURL), classTxt, navTree.linkTxt) else: return "" txt = "" if navTree.linkTxt: txt += "%s" % (adjustLink(top, dir, navTree.linkURL), highlight and "active" or "", navTree.linkTxt) # Only show directory if it's appropriate for this context: if(highlight): txt += "
    \n" for item in navTree.items: txt += "
  • "+createNavTree(top, dir, item)+"
  • \n" txt += "
\n" return txt class Siter: """ Object for converting content in a directory into a Web site """ def __init__(self, targetDir, verbose, replace): self.targetDir = targetDir self.verbose = verbose self.foundHome = False self.homeOK = False self.imagesOK = True self.siteCSS = None self.siteTitle = "Site" self.siteNav = None self.doReplace = replace self.parsedNav = None def processTopDir(self, navXML='nav.xml'): """ Spider top-level directory, locating site CSS file and navigation content """ navAbs = os.path.join(self.targetDir, navXML) if not os.path.exists(navXML): print "WARNING: Site navigation content '%s' not found!" % navXML else: self.siteNav = parseNavContent(minidom.parse(navAbs).childNodes[0].childNodes[0]) cssFile = 'site.css' cssAbs = os.path.join(self.targetDir, cssFile) if not os.path.exists(cssAbs): print "WARNING: Site CSS file '%s' not found!" % cssAbs else: self.siteCSS = cssFile homeSrc = os.path.join(self.targetDir, 'home') if os.path.exists(homeSrc): self.processHomeContent(homeSrc) self.processDirectory(self.targetDir) def header(title, cssfile): """ Generate header content given title and CSS stylesheet name """ if cssfile: css = "" % cssfile else: css = "" return """ %s %s """ % (title, css) header = staticmethod(header) def body(navContent, otherContent, bodyClass=None): """ Generate body markup given nav bar content and main content """ if bodyClass: bodyTag = "" % bodyClass else: bodyTag = "" return """\ %s

johnj.com

%s
%s
""" % (bodyTag, navContent, sanitize(otherContent)) body = staticmethod(body) def footer(): """ Generate closing HTML tag """ return "" footer = staticmethod(footer) def writeHomePageHTML(self, title, content): outfile = os.path.join(self.targetDir, 'index.html') out = file(outfile, "w") print >>out, Siter.header(title, self.siteCSS) print >>out, Siter.body(createNavMarkup(self.targetDir, self.targetDir, "home nav", self.siteNav), content, "home") print >>out, Siter.footer() out.close() def writeGalleryHTML(self, dir, title, content, imgHtml): outfile = os.path.join(dir, 'index.html') out = file(outfile, "w") print >>out, Siter.header(title, relPath(self.targetDir, dir, self.siteCSS)) print >>out, Siter.body(createNavMarkup(self.targetDir, dir, "nav", self.siteNav), imgHtml+content) print >>out, Siter.footer() out.close() def writePostHTML(self, dir, title, content): outfile = os.path.join(dir, 'index.html') out = file(outfile, "w") print >>out, Siter.header(title, relPath(self.targetDir, dir, self.siteCSS)) print >>out, Siter.body(createNavMarkup(self.targetDir, dir, "nav", self.siteNav), content) print >>out, Siter.footer() out.close() def processHomeContent(self, f): """ Process home page content stored in directory 'f' Write the home page to the top level directory """ if self.verbose: print "Processing home page content in '%s'" % f self.foundHome = True srcFile = os.path.join(f, 'home.html') if not os.path.exists(srcFile): print "WARNING: source HTML file not found for directory '%s'!" % f else: s = SourceHTML(srcFile) self.writeHomePageHTML(s.title, s.bodyHTML) self.siteTitle = s.title self.homeOK = True def createImageThumbnail(self, xsiz, ysiz, f, out, suffix, doSquare=False): out = os.path.splitext(f)[0] + "-" + suffix if re.search('%s$' % suffix, f) or \ (not self.doReplace and os.path.exists(out)): return if self.verbose: print "%s -> %s" % (f, out) try: im = Image.open(f) x, y = im.size if doSquare: x1, y1 = x, y if x>y: x1 = y1 else: y1 = x1 xlo = (x-x1)/2 xhi = (x-x1)/2 + x1 ylo = (y-y1)/2 yhi = (y-y1)/2 + y1 region = im.crop((xlo, ylo, xhi, yhi)) else: region = im region.thumbnail((xsiz, ysiz), Image.ANTIALIAS) region.save(out, "JPEG") return except IOError, e: print "WARNING: Can't create %s: %s" % (out, e) self.imagesOK = False return def prepImages(self, galDir, squareThumbs): """ Prepare any necessary images for gallery content """ if self.verbose: print "Prepping images for", galDir files = os.listdir(galDir) ret = "
\n" imgCount = 1 for f in files: if not isTargetImg(f): continue thumb = thumbName(f) display = displayName(f) if self.verbose: print "\t%s -> %s, %s" % (f, thumb, display) self.createImageThumbnail(1000, 100, os.path.join(galDir, f), os.path.join(galDir, thumb), "thumb.jpg", squareThumbs) self.createImageThumbnail(300, 300, os.path.join(galDir, f), os.path.join(galDir, thumb), "inline.jpg", False) self.createImageThumbnail(10000, 800, os.path.join(galDir, f), os.path.join(galDir, display), "display.jpg", False) ret += '\n' % (display, thumb) # if (imgCount % 3) == 0: ret += '
' imgCount += 1 ret += "
\n" return ret def processGalleryContent(self, galDir): """ Process gallery content found in directory 'galDir'; write index.html to same directory. """ if self.verbose: print "Processing gallery content in '%s'" % galDir srcFile = os.path.join(galDir, 'gallery.html') if not os.path.exists(srcFile): title = "%s: Gallery '%s'" % (self.siteTitle, galDir) text = "

Gallery '%s'

" % galDir else: s = SourceHTML(srcFile) title = "%s: %s" % (self.siteTitle, s.title) text = "
\n%s\n%s\n
\n" \ % (s.bodyHTML, copyRightText()) squareThumbs = s.squareThumbs imgHtml = self.prepImages(galDir, squareThumbs) self.writeGalleryHTML(galDir, title, text, imgHtml) def processPostContent(self, f): """ Process text and/or image 'post'-like content in directory 'f'; write index.html to same directory """ if self.verbose: print "Processing 'post' content in '%s'" % f srcFile = os.path.join(f, 'post.html') if not os.path.exists(srcFile): title = "%s: %s" % (self.siteTitle, f) text = "

'%s'

" % f else: s = SourceHTML(srcFile) title = "%s: %s" % (self.siteTitle, s.title) text = "
\n%s\n%s\n
\n" \ % (s.bodyHTML, copyRightText()) self.writePostHTML(f, title, text) def processDirectory(self, dir): """ Recursively spider directory, processing any found content """ l = os.listdir(dir) for filly in l: f = os.path.join(dir, filly) if os.path.isdir(f): if isHome(f): pass elif isGallery(f): self.processGalleryContent(f) self.processDirectory(f) elif isPost(f): self.processPostContent(f) self.processDirectory(f) else: self.processDirectory(f) def main(): p = optparse.OptionParser() p.add_option("-d", "--parse-directory", action="store", dest="targetDir", type="string", help="Directory to parse") p.add_option("-v", "--verbose", action="store_true", dest="verbose", help="Show verbose output") p.add_option("-r", "--replace", action="store_true", dest="doReplace", help="Replace all generated images") p.set_defaults(targetDir = ".", verbose = False, doReplace = False) opt, args = p.parse_args() m = Siter(opt.targetDir, opt.verbose, opt.doReplace) m.processTopDir() if not m.foundHome: print "WARNING: home directory for directory tree '%s' not found!" % opt.targetDir if not m.homeOK: print "WARNING: could not generate home page content!" if not m.imagesOK: print "WARNING: had trouble generating one or more images!" if __name__ == "__main__": main()