nutools/lib/pyulib/migrate/tasks1/tiddlywiki2.py

294 lines
9.0 KiB
Python
Raw Normal View History

2013-08-27 15:14:44 +04:00
#!/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)