X-Git-Url: https://oss.titaniummirror.com/gitweb?a=blobdiff_plain;f=webber.py;h=ecd0351379decdc4a7ccc092ddd12057e424bfa6;hb=HEAD;hp=abb13d474004e1d8242c7232fa360a88917c4cc3;hpb=5b4e747f947e1a4c757fbcaed1f6d977374b208e;p=webber.git diff --git a/webber.py b/webber.py index abb13d4..ecd0351 100644 --- a/webber.py +++ b/webber.py @@ -1,5 +1,5 @@ # -*- coding: iso-8859-1 -*- -import sys, os, optparse, fnmatch, stat, re, time, types +import sys, os, optparse, fnmatch, stat, re, time, codecs from config import Holder @@ -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 "" % self.rel_path files = {} @@ -63,49 +74,72 @@ 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 #print self.keys() reKeywords = re.compile(r'(\S+)\s*:\s*(.*)') - #reIsoDate = re.compile(r'(\d\d\d\d)-(\d\d)-(\d\d)') - - def read_keywords(self, terminate_line=""): - """Opens the file and reads "key: value" pairs on the top of it. Returns - the open file handle for further processing by some plugins/read_*.py code.""" - f = open(self.path) - while True: - s = f.readline().strip() - if s==terminate_line: - break - m = self.reKeywords.match(s) - if not m: - warning("%s: wrong 'key: value' line '%s'" % (self.rel_path, s)) - break - key = m.group(1).lower() - val = m.group(2) - - if key == "mtime": - val = iso_to_time(val) - - if key == "ctime": - val = iso_to_time(val) - - if key == "title": - if not self.has_key("linktitle"): - self["linktitle"] = val - - #print self.rel_path, key, val - self[key] = val - return f + + def read(self, terminate_line=""): + f = codecs.open(self.path, "r", self.input_encoding) + + # Read keywords + read_keywords = True + txt = [] + for s in f.readlines(): + if read_keywords: + s = s.strip() + #print "kwd:", s + if s == terminate_line: + read_keywords = False + continue + + m = self.reKeywords.match(s) + if not m: + warning("%s: wrong 'key: value' line '%s'" % (self.rel_path, s)) + break + key = m.group(1).lower() + val = m.group(2) + + if key == "mtime": + val = iso_to_time(val) + + if key == "ctime": + val = iso_to_time(val) + + if key == "title": + if not self.has_key("linktitle"): + self["linktitle"] = val + + #print self.rel_path, key, val + self[key] = val + + 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 "" % 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: @@ -119,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 @@ -187,12 +225,17 @@ def relpath(base_path, target): def get_link_from(source, dest): - #print "get_link_from", source, dest - source = get_file_for(source) + if dest is None: + raise KeyError + if not isinstance(source, File): + source = get_file_for(source) if not source: + print "NO SOURCE" return "." - dest = get_file_for(dest) + if not isinstance(dest, File): + dest = get_file_for(dest) if not 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: @@ -204,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 @@ -230,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 = "" @@ -263,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 # @@ -312,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): @@ -410,17 +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(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 @@ -450,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) @@ -478,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 @@ -501,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, @@ -509,7 +570,7 @@ def walk_tree(dirpath): ) file.inheritFrom(direc) read_file(direc, file) - + walk(dirpath) @@ -520,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 @@ -536,9 +597,9 @@ reMacroArgs = re.compile(r''' = \s* (?: - "([^"]*)" # single-quoted + "([^"]*)" # single-quoted | - (\S+) # unquoted + (\S+) # unquoted ) )? ''', re.VERBOSE) @@ -558,7 +619,7 @@ def run_macros(file, contents): kw["file"] = file f = macros[name] s = f(kw) - if type(s) == types.UnicodeType: + if isinstance(s, unicode): s = s.encode("utf-8") return s else: @@ -566,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) @@ -626,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 @@ -637,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, @@ -666,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() @@ -704,7 +767,7 @@ def addoptions(params): return parser - + @set_hook("checkconfig", last=True) def checkconfig(params): # Ensure absolute paths that end in '/'. @@ -733,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")