]> oss.titaniummirror.com Git - webber.git/blobdiff - webber.py
Fix directories key error in get_path_to_root
[webber.git] / webber.py
index 194ce5cf3e93e4667f7f79245842948a19d2d6e4..ecd0351379decdc4a7ccc092ddd12057e424bfa6 100644 (file)
--- a/webber.py
+++ b/webber.py
@@ -11,21 +11,21 @@ from config import Holder
 
 __all__ = [
        # Globals
-       "cfg",          # configuration from webber.ini
-       "directories",  # global hash of directories, by rel_path
-       "files",        # global hash of files, by rel_path
-       "functions",    # all exported template functions
+       "cfg",                  # configuration from webber.ini
+       "directories",  # global hash of directories, by rel_path
+       "files",                # global hash of files, by rel_path
+       "functions",    # all exported template functions
 
        # Functions
-       "set_hook",     # decorator for hook-functions
-       "set_macro",    # define macro
+       "set_hook",             # decorator for hook-functions
+       "set_macro",    # define macro
        "set_function", # define functions for the template
        "get_file_for",
        "get_link_from",
        "get_current_file", # because mako-called functions cannot access the
-                       # current File object
+                                       # current File object
        "get_program_directory",
-       "log",          # misc logging functions
+       "log",                  # misc logging functions
        "info",
        "warning",
        "error",
@@ -50,7 +50,18 @@ class Directory(Holder):
 
        def __init__(self, **kw):
                Holder.__init__(self, **kw)
-               directories[kw["rel_path"]] = self
+               kw["rel_path"] = self
+               if self.rel_path == "":
+                       self.rel_path = "."
+               directories[self.rel_path] = self
+               try:
+                       self.load(os.path.join(self.abs_path, "directory.conf"))
+                       #print self
+               except IOError:
+                       pass
+
+       def __repr__(self):
+               return "<Directory %s>" % self.rel_path
 
 
 files = {}
@@ -63,6 +74,7 @@ class File(Holder):
                Holder.__init__(self, **kw)
                files[kw["rel_path"]] = self
                self.render = None
+               self.contents = None
                mtime = os.stat(self.path)[stat.ST_MTIME]
                self.mtime = mtime
                self.ctime = mtime
@@ -80,7 +92,7 @@ class File(Holder):
                        if read_keywords:
                                s = s.strip()
                                #print "kwd:", s
-                               if s==terminate_line:
+                               if s == terminate_line:
                                        read_keywords = False
                                        continue
 
@@ -107,14 +119,27 @@ class File(Holder):
                                continue
                        #print "txt:", s.rstrip().encode("iso-8859-1")
                        txt.append(s)
+
+               # Warn about a bogus time entries
+               if self.mtime < self.ctime:
+                       log('%s: modification time cannot be before creation time' % self.rel_path)
+                       self.ctime = self.mtime
+
+               # Warn about long titles / long linktitles
+               if len(self.linktitle) > 20:
+                       log('%s: define a shorter linktitle' % self.rel_path)
+
                self.contents = "".join(txt)
 
+       def __repr__(self):
+               return "<File %s>" % self.rel_path
+
 
 _get_file_for_cache = {}
 def get_file_for(name):
        """webber.files is an hash of File objects, but keyed on the real file name.
        This function returns a File object for a specific linktitle."""
-       
+
        try:
                return _get_file_for_cache[name]
        except:
@@ -128,6 +153,10 @@ def get_file_for(name):
                                #print "  via linktitle:", s
                                _get_file_for_cache[name] = f
                                return f
+                       if f.title == name:
+                               #print "  via title:", s
+                               _get_file_for_cache[name] = f
+                               return f
                except:
                        pass
                # Allow exact match as well
@@ -196,8 +225,8 @@ def relpath(base_path, target):
 
 
 def get_link_from(source, dest):
-       #print "get_link_from", source, dest
-       #print source
+       if dest is None:
+               raise KeyError
        if not isinstance(source, File):
                source = get_file_for(source)
        if not source:
@@ -206,7 +235,7 @@ def get_link_from(source, dest):
        if not isinstance(dest, File):
                dest = get_file_for(dest)
        if not dest:
-               print "NO DEST"
+               warning("unknown link from %s to %s" % (source.rel_path, dest))
                return "."
        rel_path = relpath(directories[source.direc].abs_path, directories[dest.direc].abs_path)
        try:
@@ -218,7 +247,7 @@ def get_link_from(source, dest):
        if rel_path.startswith("./"):
                rel_path = rel_path[2:]
        #print "  from path:", source.out_path
-       #print "  to path:  ", out_path
+       #print "  to path:      ", out_path
        #print "  rel path: ", rel_path
        return rel_path
 
@@ -244,14 +273,14 @@ def get_program_directory():
 #
 #  Logging
 #
-#      1    Error
-#      2    Warning
-#      3    Info
-#      4    Log
+#      1        Error
+#      2        Warning
+#      3        Info
+#      4        Log
 #      5... Debug
 #
 def log(s, level=4):
-       if level>4:
+       if level > 4:
                indent = " " * (level-4)
        else:
                indent = ""
@@ -277,43 +306,43 @@ def info(s):
 
 # IkiWiki does something like this:
 # At startup:
-#      getopt               modify ARGV
-#      checkconfig          check configuration
-#      refresh              allow plugins to build source files
+#      getopt                           modify ARGV
+#      checkconfig                      check configuration
+#      refresh                          allow plugins to build source files
 # While scanning files:
-#      needsbuild           detect if page needs to be rebuild
-#      filter               arbitrary changes
-#      scan                 collect metadata
+#      needsbuild                       detect if page needs to be rebuild
+#      filter                           arbitrary changes
+#      scan                             collect metadata
 # While rendering files:
-#      filter               arbitrary changes
-#      preprocess           execute macros
-#      linkify              change wikilinks into links
-#      htmlize              turns text into html
-#      sanitize             sanitize html
-#      templatefile         allows changing of the template on a per-file basis
-#      pagetemplate         fill template with page
-#      format               similar to sanitize, but act on whole page body
+#      filter                           arbitrary changes
+#      preprocess                       execute macros
+#      linkify                          change wikilinks into links
+#      htmlize                          turns text into html
+#      sanitize                         sanitize html
+#      templatefile             allows changing of the template on a per-file basis
+#      pagetemplate             fill template with page
+#      format                           similar to sanitize, but act on whole page body
 # At the end:
-#      savestate            plugins can save their state
+#      savestate                        plugins can save their state
 #
 #
 # We do something like this:
 #
 # At startup:
-#      addoptions           allow plugins to add command-line options
-#      checkconfig          check configuration
-#      start                
+#      addoptions                       allow plugins to add command-line options
+#      checkconfig                      check configuration
+#      start
 # While reading files:
-#      read                 ask any reader (plugins!) to read the file
-#      filter               ask anybody to filter the contents
+#      read                             ask any reader (plugins!) to read the file
+#      filter                           ask anybody to filter the contents
 # While scanning files:
-#      scan                 called per file, let plugins act on file data
-#      scan_done            Allows post-processing of scanned data
+#      scan                             called per file, let plugins act on file data
+#      scan_done                        Allows post-processing of scanned data
 # While rendering files:
-#      htmlize              turns text into html-part
-#      linkify              convert link macros to HTML
-#      pagetemplate         ask template engine (plugin!) to generate HTML out
-#                           of template and body part
+#      htmlize                          turns text into html-part
+#      linkify                          convert link macros to HTML
+#      pagetemplate             ask template engine (plugin!) to generate HTML out
+#                                               of template and body part
 # At the end:
 #      finish
 #
@@ -326,13 +355,11 @@ hooks = {}
 def load_plugins():
        """Loads all plugins in the plugins directory."""
        sys.path.append(os.path.join(get_program_directory(), "plugins"))
+       if cfg.has_key("plugin_dirs"):
+               for s in cfg.plugin_dirs:
+                       sys.path.append(s)
        for s in cfg.plugins:
-               #print "import:", s
-               #try:
                exec "import %s" % s
-               #except:
-               #       print "Could not import plugin '%s'" % s
-               #       sys.exit(1)
 
 
 def set_hook(name, last=False):
@@ -424,21 +451,30 @@ def iso_to_time(val):
                try:
                        t = time.strptime(val, "%Y-%m-%d")
                except ValueError:
-                       warning("%s: wrong ISO format in '%s'" % (self.rel_path, s))
+                       warning("wrong ISO format in '%s'" % val)
        return int(time.mktime(t))
 
 @set_function("format_date")
-def format_date(timestamp):
-       return time.strftime(cfg.date_format, time.localtime(timestamp))
+def format_date(timestamp, format=None):
+       if not format:
+               format = cfg.date_format
+       return time.strftime(format, time.localtime(timestamp))
 
 @set_function("get_time")
-def get_time():
-       return format_date(time.time())
+def get_time(format=None):
+       return format_date(time.time(), format)
 
 @set_function("get_current_file")
 def get_current_file():
        return current_file
 
+@set_function("get_path_to_root")
+def get_path_to_root():
+       rel_path = relpath(directories[current_file.direc].abs_path, directories['.'].abs_path)
+       rel_path = os.path.join(rel_path, os.path.split("")[1])
+       if rel_path[-1] == "/":
+               rel_path = rel_path[:-1]
+       return rel_path
 
 
 
@@ -468,9 +504,9 @@ def read_file(direc, file):
                return_holder=False)
        if not contents:
                return
+       file.contents = contents
 
        log("filtering file %s" % file.rel_path, level=6)
-       file.contents = contents
        res = run_hooks("filter",
                direc=direc,
                file=file)
@@ -496,13 +532,13 @@ def walk_tree(dirpath):
                direc.inheritFrom(cfg)
 
                if not rel_path: rel_path = "."
-               log("reading directory %s" % rel_path, level=4)
+               log("reading directory %s" % rel_path, level=5)
 
                for s in os.listdir(dirpath):
                        full_path = os.path.join(dirpath, s)
                        ok = True
                        if os.path.isdir(full_path):
-                               for e in cfg.exclude_dir:
+                               for e in cfg.exclude_dirs:
                                        if fnmatch.fnmatchcase(s, e):
                                                log("ignoring directory %s" % s, level=7)
                                                ok = False
@@ -519,6 +555,13 @@ def walk_tree(dirpath):
                                if ok:
                                        #print "FILE", s
                                        rel_path = relpath(cfg.in_dir, full_path)
+                                       # Allow paths to be specified in exclude_files:
+                                       for e in cfg.exclude_files:
+                                               if fnmatch.fnmatch(rel_path, e):
+                                                       log("ignoring file %s" % rel_path, level=7)
+                                                       ok = False
+                                                       break
+                               if ok:
                                        log("reading file %s" % rel_path, level=5)
                                        file = File(
                                                path = full_path,
@@ -527,7 +570,7 @@ def walk_tree(dirpath):
                                        )
                                        file.inheritFrom(direc)
                                        read_file(direc, file)
-                                               
+
        walk(dirpath)
 
 
@@ -538,14 +581,14 @@ def walk_tree(dirpath):
 #
 
 reMacro = re.compile(r'''
-       \[\[\!                  # Begin of macro
+       \[\[\!                                  # Begin of macro
        \s*
-       ([^\s\]]+)              # Macro name
+       ([^\s\]]+)                              # Macro name
        (?:
-               \s+                 # optional space
-        ([^\]]+)            # optional argumens
+               \s+                                     # optional space
+               ([^\]]+)                        # optional argumens
        )?
-       \]\]                    # End of macro
+       \]\]                                    # End of macro
        ''', re.VERBOSE)
 reMacroArgs = re.compile(r'''
        ([-_\w]+)                               # parameter name
@@ -554,9 +597,9 @@ reMacroArgs = re.compile(r'''
                =
                \s*
                (?:
-                       "([^"]*)"       # single-quoted
+                       "([^"]*)"               # single-quoted
                |
-                       (\S+)           # unquoted
+                       (\S+)                   # unquoted
                )
        )?
        ''', re.VERBOSE)
@@ -584,21 +627,27 @@ def run_macros(file, contents):
        s = reMacro.sub(do_macro, contents)
        #print s
        return s
-       
+
 
 def scan_files():
        info("Scanning files ...")
 
        for s in files:
                file = files[s]
-               try:
-                       # Just check if the file has contents
-                       contents = file.contents
-               except:
+               if not file.has_key("contents"):
                        continue
+#              try:
+#                      # Just check if the file has contents
+#                      contents = file.contents
+#              except:
+#                      continue
 
                direc = directories[file.direc]
 
+               # "calculate" output file name
+               if file.render and file.render == "html":
+                       file.out_path = os.path.splitext(s)[0] + ".html"
+
                run_hooks("scan",
                        direc=direc,
                        file=file)
@@ -644,9 +693,6 @@ def render_files():
                        continue
                file.contents = contents
 
-               # Output-Filename "berechnen"
-               file.out_path = os.path.splitext(fname_in)[0] + ".html"
-
        for fname_in in files:
                file = files[fname_in]
                current_file = file
@@ -655,18 +701,16 @@ def render_files():
                        continue
                direc = directories[file.direc]
 
-               contents = run_hooks("linkify",
+               run_hooks("linkify",
                        direc=direc,
                        file=file,
-                       return_holder=False)
+                       return_holder=True)
                #print "contents after 'linkify':", contents
-               if not contents:
+               if not file.contents:
                        continue
-               file.contents = contents
 
-               # TODO: einige Fragmente sollen u.U. in eine andere
-               # Webseite eingebaut werden und sollten daher nicht in
-               # ein HTML-File landen
+               # TODO: make it possible to render also "fragments", e.g.
+               # parts that don't end up immediately in a the HTML file.
                contents = run_hooks("pagetemplate",
                        direc=direc,
                        file=file,
@@ -684,12 +728,13 @@ def render_files():
                except OSError:
                        pass
 
-               # TODO: evtl. überprüfen, ob contents == f.read(), dann nicht schreiben
+               # TODO: check if contents == f.read(). In this case we don't
+               # need to save. Probably overkill.
                log("writing file %s" % fname_out, level=6)
                f = open(fname_out, "w")
                f.write(contents)
                f.close()
-               # TODO: Time-Stamps setzen?
+               os.utime(fname_out, (file.mtime, file.mtime))
 
                #print file.mtime, file.get("ctime","?")
                #print direc.keys()
@@ -722,7 +767,7 @@ def addoptions(params):
 
        return parser
 
-       
+
 @set_hook("checkconfig", last=True)
 def checkconfig(params):
        # Ensure absolute paths that end in '/'.
@@ -751,7 +796,12 @@ def main():
        # link contents of webber.ini into cfg and set some defaults,
        # then let plugins fixup things in cfg.*
        cfg.inheritFrom(options)
-       cfg.setDefault("exclude_dir", ["plugins"])
+       cfg.setDefault("exclude_dirs", [])
+       cfg.setDefault("exclude_files", ["webber.conf", "directory.conf", "*.tmpl"])
+       cfg.setDefault("copy_files", [])
+       cfg.setDefault("input_encoding", "iso-8859-1")
+       cfg.setDefault("output_encoding", "iso-8859-1")
+       cfg.setDefault("template", "default")
        run_hooks("checkconfig")
 
        run_hooks("start")