#!/usr/bin/env python # -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 """%(name)s: Gestion d'un fichier tiddlywiki.html USAGE %(name)s [-f tiddlywiki.html] action [options] """ try: True, False except NameError: True, False = 1, 0 import re, types from os import path from time import localtime from iso8859 import quote_entity, unquote_entity __all__ = ["ensure_unicode", "Tiddler", "TiddlyWiki", "list_tiddlers", "replace_tiddler", "delete_tiddler"] # XXX à configurer ARGS_ENCODING="latin-1" ###################################################################### # Classes et constantes de base RE_TAG = re.compile(r'<([^ >]+)') RE_ENDTAG = re.compile(r'</([^ ]+)>') RE_SOMETHING = re.compile(r'\S') RE_ATTR1 = re.compile(r'([^= ]+)="([^"]*)"') RE_ATTR2 = re.compile(r"([^= ]+)='([^']*)'") def parseDiv(data): attrs = {} text = '' # Parser <div mo = RE_TAG.search(data) if mo is None or mo.group(1) != 'div': return None, None, data data = data[mo.end():] # Parser attributs while True: # Sauter les espaces mo = RE_SOMETHING.search(data) if not mo: raise ValueError("Fin de flux") data = data[mo.start():] # Parser l'attribut mo = RE_ATTR1.match(data) or RE_ATTR2.match(data) or None if not mo: break name = mo.group(1) value = unquote_entity(mo.group(2)) attrs[name] = value data = data[mo.end():] # Fin de tag mo = RE_SOMETHING.search(data) if not mo: raise ValueError("Fin de flux") if data[:1] != '>': raise ValueError("Attendu >") data = data[1:] # Texte pos = data.find("<") if pos == -1: raise ValueError("Impossible de trouver le tag de fin") text = unquote_entity(data[:pos]) data = data[pos:] # Tag fermant if data[:6] != '</div>': raise ValueError("Attendu </div>") data = data[6:] return attrs, text, data def ensure_unicode(data): if data is None: return None elif type(data) is types.UnicodeType: return data else: return unicode(data, ARGS_ENCODING) def quote_nl(text): text = text.replace("\n", "\\n") return text def unquote_nl(text): text = text.replace("\\n", "\n") return text def get_now(): """Retourner la date actuelle dans le format attendu par le champ modified """ t = localtime() return '%04i%02i%02i%02i%02i' % t[0:5] class Tiddler: def __init__(self, tiddler=None, text=None, tags=None, modifier=None, modified=None, line=None): self.tiddler = ensure_unicode(tiddler) self.text = ensure_unicode(text) self.tags = ensure_unicode(tags) self.modifier = ensure_unicode(modifier) self.modified = ensure_unicode(modified) if line is not None: parse(line) def is_valid(self): """Retourner True si ce Tiddler est valide """ return self.tiddler is not None def parse(self, line): """Charger un Tiddler depuis line, et retourner le reste de la chaine qui n'a pas été parsé """ attrs, text, line = parseDiv(ensure_unicode(line)) if text is not None: self.tiddler = attrs["tiddler"] self.modified = attrs["modified"] self.modifier = attrs["modifier"] self.tags = attrs["tags"] self.text = unquote_nl(text) return line def to_line(self): """Retourner ce tiddler sous forme de ligne """ if not self.is_valid(): die("Il faut spécifier l'id du tiddler") line = u'<div' line += u' tiddler="%s"' % quote_entity(self.tiddler or '') line += u' modified="%s"' % quote_entity(self.modified or get_now()) line += u' modifier="%s"' % quote_entity(self.modifier or 'twlib') line += u' tags="%s"' % quote_entity(self.tags or '') line += u'>' line += quote_nl(quote_entity(self.text)) line += u'</div>' return line RE_SPACES = re.compile("\s+") def tags_contains(self, tag): return tag in self.RE_SPACES.split(self.tags) class TiddlyWiki: def __init__(self, twfile=None): self.twfile = None self.prefix = None self.suffix = None self.tsmap = None self.tslist = None if twfile is not None: self.load(twfile) RE_MAGIC = re.compile(r'<!--\nTiddlyWiki') PREFIX = '<div id="storeArea">' def load(self, twfile): inf = open(twfile, 'rb') data = inf.read() inf.close() # Vérifier qu'il s'agit bien d'un TiddlyWiki if not self.RE_MAGIC.search(data): raise ValueError("Fichier de format incorrect: ce n'est pas un tiddlywiki: %s" % twfile) self.twfile = twfile self.twpf = path.abspath(twfile) # Vérifier la présence du préfixe pos = data.find(self.PREFIX) if pos == -1: raise ValueError("Fichier de format incorrect: impossible de trouver le préfixe " + PREFIX) pos += len(self.PREFIX) self.prefix = data[:pos] data = unicode(data[pos:], "utf-8") self.tsmap = {} self.tslist = [] while True: t = Tiddler() data = t.parse(data) if not t.is_valid(): break self.tsmap[t.tiddler] = t self.tslist.append(t.tiddler) self.suffix = data.encode("utf-8") def save(self, twfile=None): if twfile is None: twfile = self.twfile if twfile is None: die("Il faut spécifier le fichier de sortie") outf = open(twfile, 'wb') outf.write(self.prefix) for tiddler in self.tslist: t = self.tsmap[tiddler] outf.write("\n") outf.write(t.to_line().encode("utf-8")) outf.write(self.suffix) outf.close() def __len__(self): return len(self.tslist) def __getitem__(self, key): if type(key) is int: return self.tslist[key] else: return self.tsmap[key] def get(self, key, default=None): return self.tsmap.get(key, default) def __setitem__(self, key, t): if type(key) is int: key = self.tslist[key] if key in self.tslist: self.tsmap[key] = t else: self.tsmap[key] = t self.tslist.append(key) def __delitem__(self, key): if type(key) is int: key = self.tslist[key] if key in self.tslist: del self.tsmap[key] self.tslist.remove(key) remove = __delitem__ def put(self, t): self[t.tiddler] = t ###################################################################### # Lister les tiddlers def list_tiddlers(twfile, args=None): """USAGE: list [tiddlers...] """ tw = TiddlyWiki(twfile) args = args or () if args: for tiddler in args: t = tw.get(tiddler, None) if t is not None: print "## tiddler=%s" % t.tiddler print "## modifier=%s" % t.modifier print "## modified=%s" % t.modified print "## tags=%s" % t.tags print print t.text else: ewarn("Tiddler inconnu: %s" % tiddler) else: for tiddler in tw: print tiddler ###################################################################### # Ajouter ou remplacer un tiddler def replace_tiddler(twfile, tiddler=None, text=None, tags=None, modifier=None, modified=None, args=None): """USAGE: replace [-t tags] [-m modifier] [-d modified] tiddler text """ tw = TiddlyWiki(twfile) args = args or () opts, args = get_args(args, [ ('t:', None, 'Spécifier tags'), ('m:', None, 'Spécifier modifier'), ('d:', None, 'Spécicier modified'), ]) for opt, value in opts: if opt == '-t': tags = value elif opt == '-m': modifier = value elif opt == '-d': modified = value i = 0 if tiddler is None: tiddler = args[i:i + 1] and ensure_unicode(args[i]) or None if tiddler is None: die("Il faut spécifier l'id du tiddler") i = i + 1 if text is None: text = args[i:i + 1] and ensure_unicode(args[i]) or None if text is None: die("Il faut spécifier le texte du tiddler") t = tw.get(tiddler, None) if t is None: t = Tiddler(tiddler, text, tags, modifier, modified) else: t.text = text if tags is not None: t.tags = tags if modifier is not None: t.modifier = modifier if modified is not None: t.modified = modified tw[tiddler] = t tw.save() ###################################################################### # Supprimer un tiddler def delete_tiddler(twfile, tiddler=None, args=None): """USAGE: delete tiddler """ tw = TiddlyWiki(twfile) args = args or () if tiddler is None: tiddler = args[0:1] and ensure_unicode(args[0]) or None if tiddler is None: die("Il faut spécifier l'id du tiddler") if tiddler in tw: del tw[tiddler] tw.save() else: die("Tiddler invalide: %s" % tiddler)