microcms

Microcms shows how easy it is to perform simple content management tasks using custom python scripts. Microcms searches for and reads files ending in .chtml in and under the current directory and processes them to output a file with a .html extension. Microcms uses plugins (dymanically loaded python scripts) to accomplish specific tasks.

usage is (run from the root directory of the site):

python microcms.py

microcms invokes built-in processing when it encounters the following markup.

Microcms is easy to understand, extend and use because more complicated functionailty has been isolated in plugins.

Microcms plugins

Plugin scripts are located in the plugins sub-directory relative to microcms.py. Scripts may implement either or both of the following hook functions:

The following plugins have been implemented to provide functionality which generates this web site:

Code for microcms is shown below:

microcms.py
# microcms.py - tiny file based CMS for static web sites
# Copyright (c) 2009 Niall McCarroll  
# Distributed under the MIT/X11 License (http://www.mccarroll.net/snippets/license.txt)

# functionality:
#   expand ./.. in HTML files to the relative path to the top level directory 
#   transform HTML content using plugins

# usage:
#   cd <root-directory-of-site>
#   python microcms.py 
   
import sys
import os
import re
import copy
import codecs
from tinplate import tinplate

rootdir = None
plugins = {}
plugin_consumers = {}
plugin_processors = {}

def loadplugins():
    global plugins
    global plugin_processors
    global plugin_consumers
    pluginsdir = os.path.join(os.path.split(sys.argv[0])[0],'plugins')
    for f in os.listdir(pluginsdir):
        if f.endswith(".py"):
            mname = os.path.splitext(f)[0]    
            try:
                plugin = __import__("plugins",globals(),locals(),[mname],-1)
                plugins[mname] = plugin
            except Exception, ex:
                print f+":"+str(ex)
                pass

    for plugin in plugins:
        try:
            processfn = getattr(getattr(plugins[plugin],plugin),'process')
            plugin_processors[plugin] = processfn
            print "Loaded processor function from module:"+plugin
        except Exception, ex:
            pass 
        try:
            consumefn = getattr(getattr(plugins[plugin],plugin),'consume')
            plugin_consumers[plugin] = consumefn
            print "Loaded consumer function from module:"+plugin
        except Exception, ex:
            pass        

filter = ['.chtml']

pat1 = re.compile(r'(.*)<microcms([^>]*)>(.*)')
pat2 = re.compile(r'\s*([a-zA-Z_]*)\s*=\s*\"([^\"]*)\"(.*)')

def parse_microcms(line):
    mat = pat1.match(line)
    if mat:
        attrs = {}
        attrtxt = mat.group(2)
        mat2 = pat2.match(attrtxt)
        while mat2:
            aname = mat2.group(1)
            avalue = mat2.group(2)
            attrtxt = mat2.group(3)
            mat2 = pat2.match(attrtxt)
            attrs[aname] = avalue
        return (mat.group(1),attrs,mat.group(3))
    else:
        return None

class cmsdirectory:

    def __init__(self,name,files,directories):
        self.name = name
        self.files = files
        self.directories = directories
        self.indexfile = None
        for d in self.directories:
            d.parentdir = self
        for f in self.files:
            f.parentdir = self
            if os.path.split(f.path)[1] == "index.chtml":
                self.indexfile = f
        self.parentdir = None

    def depth(self):
        depth = 0
        d = self.parentdir
        while d != None:
            d = d.parentdir
            depth += 1
            
        return depth
        
    def getName(self):
        return self.name
             

class cmsfile:

    def __init__(self,path,outpath):
        self.path = path
        self.outpath = outpath
        self.metadata = None
        self.parentdir = None
       
    def depth(self):
        if self.parentdir:
            return self.parentdir.depth()
        else:
            return 0
        
def expand_env(line,env):
    for k in env:
        pos = 0
        while True:
            pos = line.find("$"+k,pos)
            if pos == -1:
                break
            if pos == 0 or line[pos-1] != "$":
                line = line[:pos]+env[k]+line[pos+len(k)+1:]
                pos += len(env[k])
            else:
                line = line[:pos]+line[pos+1:]
                pos += len(k)   
    return line 
                
def rewritecontents(content,cfile,original_env,extpath=None):
    lines = content.split("\n")
    
    result = ''    
    
    for line in lines:
        
        env = copy.copy(original_env)

        rootpath = "."
        for x in xrange(0,cfile.parentdir.depth()):
            rootpath = os.path.join(rootpath,"..")
        env["root"] = rootpath
        
        line = expand_env(line,env)
        microcms_cmd = parse_microcms(line)
        if microcms_cmd:
            pre = microcms_cmd[0]
            attrs = microcms_cmd[1]
            if "path" in attrs and extpath:
                attrs["path"] = os.path.join(extpath,attrs["path"])
            post = microcms_cmd[2]
            func = attrs["function"]
            
            inc = ''
            
            # search for a matching plugin and invoke it
            global plugin_processors
            if func in plugin_processors:
                inc = plugin_processors[func](cfile,attrs)
            else:
                inc += "microcms error: could not load plugin: "+func
            epath = None
            if "path" in attrs:
                epath = os.path.split(attrs["path"])[0]
                
            # rewrite the content returned from the plugin
            for attr in attrs:
                env[attr] = attrs[attr]
                    
            result += pre+rewritecontents(inc,cfile,env,epath)+post        
        else:
            result += line
        
        result += "\n"    
   
    return result

def rewritefile(path,cfile,original_env):
    print path
    fin = codecs.open(path, encoding='utf-8', mode='r')
    content = fin.read()
    fin.close()
    return rewritecontents(content,cfile,original_env)
    
def rewrite(cfile):
    fout = codecs.open(cfile.outpath, encoding='utf-8', mode='w')
    fout.write(rewritefile(cfile.path,cfile,{}))
    fout.close()  
    
def scanfiles(path,norecurse=False):
    
    directories = []
    files = []
    dname = ""
    pathsplit = os.path.split(path)
    if len(pathsplit)>1:
        dname = pathsplit[1]
 
    if not norecurse:
        for f in os.listdir(path):
            fpath = os.path.join(path,f)
            if os.path.isdir(fpath):
                subdir = scanfiles(fpath)
                if subdir != None:
                    directories.append(subdir)

    for f in os.listdir(path):
        fpath = os.path.join(path,f)
        
        if fpath.startswith("./"):
            fpath = fpath[2:]
        if os.path.isfile(fpath):
            (root,ext) = os.path.splitext(fpath)
            outpath = root+".html"
           
            if ext in filter:
                relpath = os.path.join(os.path.split(path)[1],os.path.split(outpath)[1])
                
                print "processing:"+fpath
                cfile = cmsfile(fpath,outpath)
                files.append(cfile)

                fin = open(fpath,"r")
                contents = fin.read()
                for func in sorted(plugin_consumers):
                    plugin_consumers[func](cfile,contents)
                
              
    if len(files)>0 or len(directories)>0:
        return cmsdirectory(dname,files,directories)
    else:
        return None

def processfiles(directory):
    
    for f in directory.files:
        rewrite(f)

    for d in directory.directories:
        processfiles(d)

if __name__ == '__main__':
    norecurse = False
    if len(sys.argv)>1 and sys.argv[1] == "-norecurse":
        norecurse = True
    loadplugins()
    rootdir = scanfiles(".",norecurse)
    processfiles(rootdir)   
 
Anti-spam check
Leave a comment