2021-02-24 10:55:56 +04:00
|
|
|
#!/usr/bin/env python2
|
2013-08-27 15:14:44 +04:00
|
|
|
# -*- 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)
|
2015-08-20 07:58:17 +04:00
|
|
|
|
2013-08-27 15:14:44 +04:00
|
|
|
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
|
2015-08-20 07:58:17 +04:00
|
|
|
|
2013-08-27 15:14:44 +04:00
|
|
|
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")
|
2015-08-20 07:58:17 +04:00
|
|
|
|
2013-08-27 15:14:44 +04:00
|
|
|
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)
|
2015-08-20 07:58:17 +04:00
|
|
|
|
2013-08-27 15:14:44 +04:00
|
|
|
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)
|
2015-08-20 07:58:17 +04:00
|
|
|
|