941 lines
32 KiB
Python
941 lines
32 KiB
Python
|
#!/usr/bin/env python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
u"""%(scriptname)s: gérer un TiddlyWiki
|
||
|
|
||
|
USAGE
|
||
|
%(scriptname)s [options] addtext|addfile|remove|list|edit
|
||
|
|
||
|
OPTIONS
|
||
|
-c twrc
|
||
|
Charger le fichier de configuration twrc au lieu de la valeur par
|
||
|
défaut (~/.utools/twrc)
|
||
|
-f twfile.html
|
||
|
Utiliser twfile.html au lieu de chercher un fichier par défaut dans le
|
||
|
répertoire courant. Les noms considérés par défaut sont TODO.html,
|
||
|
project_wiki.html et TiddlyWiki.html"""
|
||
|
|
||
|
__all__ = ('Tiddler', 'TiddlyWiki', 'TwCLI')
|
||
|
|
||
|
import os, sys, re
|
||
|
from os import path
|
||
|
from getopt import getopt
|
||
|
from time import localtime
|
||
|
from types import IntType, LongType, StringTypes
|
||
|
from xml.dom.minidom import parseString
|
||
|
|
||
|
from pyutools.config import ShConfigFile
|
||
|
from pyutools.dates import datef, TW_DATEF
|
||
|
from pyutools.iso8859 import quote_attr, quote_html, unquote_but_html
|
||
|
from pyutools import scriptdir, scriptname, dict, isnum, isstr, isseq
|
||
|
from pyutools import get_stdin_encoding, get_editor_encoding
|
||
|
from pyutools import ensure_unicode, uprint, enote, eerror, die, get_colored
|
||
|
from pyutools import edit_template
|
||
|
|
||
|
_marker = []
|
||
|
|
||
|
TW_PARAM_PATTERN = re.compile(
|
||
|
r'(?:' +
|
||
|
r'(?:"((?:(?:\\")|[^"])+)")|' + # "value"
|
||
|
r"(?:'((?:(?:\\')|[^'])+)')|" + # 'value'
|
||
|
r'(?:\[\[(.*?)\]\])|' + # [[space separated values]]
|
||
|
r'(?:(\{\{.*?\}\}))|' + # {{braced values}}
|
||
|
r'(?:([^"' + "'" + r'\s]\S*))|' + # value
|
||
|
r'(?:"")|' + # ""
|
||
|
r"(?:'')" + # ''
|
||
|
r')'
|
||
|
)
|
||
|
def twParseParams(s):
|
||
|
params = []
|
||
|
for a, b, c, d, e in TW_PARAM_PATTERN.findall(s):
|
||
|
value = a or b or c or d or e
|
||
|
if value: params.append(value)
|
||
|
return params
|
||
|
|
||
|
def twParseTags(s):
|
||
|
tags = {}
|
||
|
for tag in twParseParams(s):
|
||
|
tags[tag] = None
|
||
|
return tags.keys()
|
||
|
|
||
|
TW_BRACED_VALUE = re.compile(r'\{\{.*\}\}$')
|
||
|
TW_VALUE = re.compile(r'[^"' + "'" + r'\s]\S*$')
|
||
|
def twFormatTags(tags):
|
||
|
ss = []
|
||
|
for tag in tags:
|
||
|
if TW_BRACED_VALUE.match(tag): ss.append(tag)
|
||
|
elif TW_VALUE.match(tag): ss.append(tag)
|
||
|
else: ss.append("[[%s]]" % tag)
|
||
|
return " ".join(ss)
|
||
|
|
||
|
class Tiddler:
|
||
|
parent = None # Instance de TiddlyWiki parent de ce Tiddler
|
||
|
title = u''
|
||
|
modifier = u''
|
||
|
creation_date = None
|
||
|
modification_date = None
|
||
|
tags = None
|
||
|
attrs = None
|
||
|
changecount = 0
|
||
|
text = u''
|
||
|
modified = False
|
||
|
|
||
|
def __init__(self, element=None, parent=None):
|
||
|
self.creation_date = self.modification_date = datef(TW_DATEF)
|
||
|
self.tags = []
|
||
|
self.attrs = {}
|
||
|
if parent is not None:
|
||
|
self.parent = parent
|
||
|
if element is not None:
|
||
|
self._parseElement(element)
|
||
|
|
||
|
ATTRS = ['title', 'modifier', 'modification_date', 'tags', 'creation_date', 'changecount']
|
||
|
ATTRS1_MAP = {'tiddler': 'title', 'modified': 'modification_date'}
|
||
|
ATTRS1_REVMAP = {'title': 'tiddler', 'modification_date': 'modified', 'creation_date': None, 'changecount': None}
|
||
|
ATTRS2_MAP = {'modified': 'modification_date', 'created': 'creation_date'}
|
||
|
ATTRS2_REVMAP = {'modification_date': 'modified', 'creation_date': 'created'}
|
||
|
|
||
|
def __unquote1(self, s):
|
||
|
s = s.replace('\\n', '\n')
|
||
|
return s
|
||
|
def __quote1(self, s):
|
||
|
s = s.replace('\n', '\\n')
|
||
|
return s
|
||
|
|
||
|
def __parseElement1(self, div, attrs):
|
||
|
if attrs is not None:
|
||
|
for i in range(attrs.length):
|
||
|
name = div.attributes.item(i).name
|
||
|
value = div.attributes.item(i).value
|
||
|
name = self.ATTRS1_MAP.get(name, name)
|
||
|
if name == 'tags': # cas particulier
|
||
|
value = twParseTags(value)
|
||
|
if name in self.ATTRS: setattr(self, name, value)
|
||
|
else: self.attrs[name] = value
|
||
|
textNode = div.firstChild
|
||
|
if textNode is not None:
|
||
|
self.text = self.__unquote1(textNode.data)
|
||
|
|
||
|
def __formatElement1(self):
|
||
|
s = u'<div'
|
||
|
for name in self.ATTRS:
|
||
|
value = getattr(self, name)
|
||
|
name = self.ATTRS1_REVMAP.get(name, name)
|
||
|
if name is None: continue
|
||
|
if name == 'tags': value = twFormatTags(value)
|
||
|
s += ' %s="%s"' % (name, quote_attr(unicode(value)))
|
||
|
for name, value in self.attrs.items():
|
||
|
s += ' %s="%s"' % (name, quote_attr(unicode(value)))
|
||
|
s += '>'
|
||
|
s += quote_html(self.__quote1(self.text))
|
||
|
s += '</div>'
|
||
|
return s
|
||
|
|
||
|
def __parseElement2(self, div, attrs):
|
||
|
if attrs is not None:
|
||
|
for i in range(attrs.length):
|
||
|
name = attrs.item(i).name
|
||
|
value = div.attributes.item(i).value
|
||
|
name = self.ATTRS2_MAP.get(name, name)
|
||
|
if name == 'tags': # cas particulier
|
||
|
value = twParseTags(value)
|
||
|
if name in self.ATTRS: setattr(self, name, value)
|
||
|
else: self.attrs[name] = value
|
||
|
textNode = div.getElementsByTagName('pre')[0].firstChild
|
||
|
if textNode is not None:
|
||
|
self.text = textNode.data
|
||
|
|
||
|
def __formatElement2(self):
|
||
|
s = u'<div'
|
||
|
for name in self.ATTRS:
|
||
|
value = getattr(self, name)
|
||
|
name = self.ATTRS2_REVMAP.get(name, name)
|
||
|
if name is None: continue
|
||
|
if name == 'tags': value = twFormatTags(value)
|
||
|
s += ' %s="%s"' % (name, quote_attr(unicode(value)))
|
||
|
for name, value in self.attrs.items():
|
||
|
s += ' %s="%s"' % (name, quote_attr(unicode(value)))
|
||
|
s += '>\n<pre>'
|
||
|
s += quote_html(self.text)
|
||
|
s += '</pre>\n</div>'
|
||
|
return s
|
||
|
|
||
|
def _parseElement(self, div, version=None):
|
||
|
attrs = div.attributes
|
||
|
if version is None:
|
||
|
if attrs.getNamedItem('title') is not None: version = 2
|
||
|
else: version = 1
|
||
|
if self.parent is not None:
|
||
|
self.parent.set_version(version)
|
||
|
if version == 2: return self.__parseElement2(div, attrs)
|
||
|
elif version == 1: return self.__parseElement1(div, attrs)
|
||
|
|
||
|
def _formatElement(self, version=None):
|
||
|
if version is None and self.parent is not None:
|
||
|
version = self.parent.get_version()
|
||
|
if version == 2: return self.__formatElement2()
|
||
|
elif version == 1: return self.__formatElement1()
|
||
|
|
||
|
def is_modified(self):
|
||
|
return self.modified
|
||
|
def set_modified(self):
|
||
|
self.modification_date = datef(TW_DATEF)
|
||
|
self.changecount = int(self.changecount) + 1
|
||
|
self.modified = False
|
||
|
|
||
|
def get_changecount(self):
|
||
|
return self.changecount
|
||
|
def set_changecount(self, changecount):
|
||
|
if changecount != self.changecount:
|
||
|
self.changecount = int(changecount)
|
||
|
self.modified = True
|
||
|
|
||
|
def get_title(self):
|
||
|
return self.title
|
||
|
def set_title(self, title):
|
||
|
if title != self.title:
|
||
|
self.title = ensure_unicode(title)
|
||
|
self.modified = True
|
||
|
|
||
|
def get_modifier(self):
|
||
|
return self.modifier
|
||
|
def set_modifier(self, modifier):
|
||
|
if modifier != self.modifier:
|
||
|
self.modifier = modifier
|
||
|
self.modified = True
|
||
|
|
||
|
def get_creation_date(self):
|
||
|
return self.creation_date
|
||
|
def set_creation_date(self, creation_date):
|
||
|
if creation_date != self.creation_date:
|
||
|
self.creation_date = creation_date
|
||
|
self.modified = True
|
||
|
|
||
|
def get_modification_date(self):
|
||
|
return self.modification_date
|
||
|
def set_modification_date(self, modification_date):
|
||
|
if modification_date != self.modification_date:
|
||
|
self.modification_date = modification_date
|
||
|
self.modified = True
|
||
|
|
||
|
def get_tags(self):
|
||
|
return self.tags
|
||
|
def set_tags(self, tags):
|
||
|
if type(tags) in StringTypes: tags = twParseTags(tags)
|
||
|
if set(tags) != set(self.tags):
|
||
|
self.tags = tags
|
||
|
self.modified = True
|
||
|
def add_tag(self, tag):
|
||
|
if tag not in self.tags:
|
||
|
self.tags.append(tag)
|
||
|
self.modified = True
|
||
|
def remove_tag(self, tag):
|
||
|
if tag in self.tags:
|
||
|
self.tags.remove(tag)
|
||
|
self.modified = True
|
||
|
|
||
|
def get_attrs(self):
|
||
|
return self.attrs
|
||
|
def set_attrs(self, attrs):
|
||
|
if attrs != self.attrs:
|
||
|
self.attrs = attrs
|
||
|
self.modified = True
|
||
|
|
||
|
def get_text(self):
|
||
|
return self.text
|
||
|
def set_text(self, text):
|
||
|
if text != self.text:
|
||
|
self.text = ensure_unicode(text)
|
||
|
self.modified = True
|
||
|
|
||
|
def __repr__(self):#XXX
|
||
|
s = self.get_title()
|
||
|
if self.get_tags():
|
||
|
s += "[%s]" % ",".join(self.get_tags())
|
||
|
return s
|
||
|
|
||
|
class TiddlyWiki:
|
||
|
DEFAULT_NAMES = ('TODO.html', 'project_wiki.html', 'TiddlyWiki.html')
|
||
|
DEFAULT_NAME = DEFAULT_NAMES[1]
|
||
|
|
||
|
valid = False
|
||
|
file = pf = dirname = filename = None
|
||
|
|
||
|
version = 2
|
||
|
byname = None
|
||
|
tiddlers = None
|
||
|
|
||
|
def __init__(self, src=None, raise_exception=False, default_names=None):
|
||
|
self.byname = {}
|
||
|
self.tiddlers = []
|
||
|
|
||
|
if default_names is None:
|
||
|
default_names = self.DEFAULT_NAMES
|
||
|
if src is None:
|
||
|
for name in default_names:
|
||
|
if path.exists(name):
|
||
|
src = name
|
||
|
break
|
||
|
elif path.isdir(src):
|
||
|
dir = src
|
||
|
src = None
|
||
|
for name in default_names:
|
||
|
pf = path.join(dir, name)
|
||
|
if path.exists(pf):
|
||
|
src = pf
|
||
|
break
|
||
|
if src is None:
|
||
|
if raise_exception:
|
||
|
raise IOError("Impossible de trouver un TiddlyWiki par défaut")
|
||
|
self.__update_file(self.DEFAULT_NAME)
|
||
|
else:
|
||
|
self.__update_file(src)
|
||
|
self.load(raise_exception=raise_exception)
|
||
|
|
||
|
def __update_file(self, file):
|
||
|
self.file = file
|
||
|
self.pf = path.abspath(self.file)
|
||
|
self.dirname, self.filename = path.split(self.pf)
|
||
|
|
||
|
MAGIC_PATTERN = re.compile(r'<!--\nTiddlyWiki')
|
||
|
START_SAVE_AREA = '<div id="storeArea">'
|
||
|
END_SAVE_AREA = '<!--POST-STOREAREA-->'
|
||
|
START_DIV = '<div '
|
||
|
END_DIV = '</div>'
|
||
|
|
||
|
def __load_prefix_and_suffix(self, pf, raise_exception=True):
|
||
|
try:
|
||
|
inf = open(pf, 'rb')
|
||
|
try:
|
||
|
data = inf.read()
|
||
|
finally:
|
||
|
inf.close()
|
||
|
|
||
|
## Vérifier qu'il s'agit bien d'un tiddlywiki
|
||
|
if not self.MAGIC_PATTERN.search(data):
|
||
|
raise ValueError("Fichier de format incorrect: ce n'est pas un tiddlywiki: %s" % self.file)
|
||
|
|
||
|
## Vérifier la présence du storeArea
|
||
|
# début
|
||
|
pos = data.find(self.START_SAVE_AREA)
|
||
|
if pos == -1:
|
||
|
raise ValueError("%s: Impossible de trouver la zone de stockage" % self.file)
|
||
|
pos += len(self.START_SAVE_AREA)
|
||
|
self.prefix = data[:pos]
|
||
|
data = data[pos:]
|
||
|
# fin
|
||
|
pos = 0
|
||
|
while True:
|
||
|
poss = data.find(self.START_DIV, pos)
|
||
|
pose = data.find(self.END_DIV, pos)
|
||
|
if pose == -1:
|
||
|
raise ValueError("%s: erreur de format à %i" % (self.file, pos))
|
||
|
if poss != -1 and poss < pose:
|
||
|
# <div....</div>
|
||
|
pos = pose + len(self.END_DIV)
|
||
|
else:
|
||
|
# end_save_area
|
||
|
break
|
||
|
pos = data.find(self.END_DIV, pos)
|
||
|
if pos == -1:
|
||
|
raise ValueError("%s: Impossible de trouver la fin de la zone de stockage" % self.file)
|
||
|
self.suffix = data[pos:]
|
||
|
data = data[:pos]
|
||
|
return True, data
|
||
|
except:
|
||
|
if raise_exception: raise
|
||
|
return False, None
|
||
|
|
||
|
def load(self, file=None, raise_exception=True):
|
||
|
if file is not None: self.__update_file(file)
|
||
|
|
||
|
self.valid = False
|
||
|
data = self.__load_prefix_and_suffix(self.pf, raise_exception)[1]
|
||
|
|
||
|
# charger les données
|
||
|
self.valid = True
|
||
|
|
||
|
self.byname = {}
|
||
|
self.tiddlers = []
|
||
|
|
||
|
data = self.START_SAVE_AREA + data + self.END_DIV
|
||
|
data = unquote_but_html(data, "utf-8") # HACK: nécessaire car minidom
|
||
|
# ne supporte pas les entities. utf-8 est codé en dur parce qu'on sait que
|
||
|
# TiddlyWiki enregistre dans ce codec.
|
||
|
dom = parseString(data)
|
||
|
for node in dom.documentElement.getElementsByTagName('div'):
|
||
|
self.add(Tiddler(element=node, parent=self))
|
||
|
|
||
|
return True
|
||
|
|
||
|
def is_valid(self):
|
||
|
return self.valid
|
||
|
|
||
|
def save(self, file=None, templatefile=None, set_modified=True, raise_exception=True):
|
||
|
if templatefile is None and self.file is None:
|
||
|
templatefile = path.join(path.split(__file__)[0], 'empty.html')
|
||
|
if templatefile is None:
|
||
|
if not hasattr(self, 'prefix') or not hasattr(self, 'suffix'):
|
||
|
raise IOError("Etat inconsistant: il faut le préfixe et le suffixe")
|
||
|
else:
|
||
|
self.__load_prefix_and_suffix(templatefile, raise_exception)
|
||
|
|
||
|
if file is not None: self.__update_file(file)
|
||
|
tmppf = self.pf + '.tmp'
|
||
|
|
||
|
try:
|
||
|
outf = open(tmppf, 'wb')
|
||
|
try:
|
||
|
outf.write(self.prefix)
|
||
|
for tiddler in self.tiddlers:
|
||
|
if tiddler.is_modified() and set_modified:
|
||
|
tiddler.set_modified()
|
||
|
outf.write(tiddler._formatElement().encode("utf-8"))
|
||
|
outf.write("\n")
|
||
|
outf.write(self.suffix)
|
||
|
finally:
|
||
|
outf.close()
|
||
|
os.rename(tmppf, self.pf)
|
||
|
except:
|
||
|
if raise_exception: raise
|
||
|
|
||
|
def set_version(self, version):
|
||
|
self.version = version
|
||
|
def get_version(self):
|
||
|
return self.version
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self.tiddlers)
|
||
|
def has_key(self, title):
|
||
|
return self.byname.has_key(title)
|
||
|
def __getitem__(self, indexOrTitle, default=_marker):
|
||
|
if isnum(indexOrTitle):
|
||
|
return self.tiddlers[indexOrTitle]
|
||
|
else:
|
||
|
if default is _marker: return self.byname[indexOrTitle]
|
||
|
else: return self.byname.get(indexOrTitle, default)
|
||
|
get = __getitem__
|
||
|
def add(self, tiddler):
|
||
|
if not isinstance(tiddler, Tiddler):
|
||
|
raise ValueError("value doit être une instance de Tiddler")
|
||
|
title = tiddler.get_title()
|
||
|
if title in self.byname:
|
||
|
del self.tiddlers[self.byname[title]]
|
||
|
tiddler.parent = self
|
||
|
self.tiddlers.append(tiddler)
|
||
|
self.byname[title] = tiddler
|
||
|
def __delitem__(self, indexOrTitle):
|
||
|
if isnum(indexOrTitle):
|
||
|
tiddler = self.tiddlers[indexOrTitle]
|
||
|
index = indexOrTitle
|
||
|
else:
|
||
|
tiddler = self.byname[indexOrTitle]
|
||
|
index = self.tiddlers.index(tiddler)
|
||
|
del self.byname[tiddler.get_title()]
|
||
|
del self.tiddlers[index]
|
||
|
|
||
|
def __repr__(self):
|
||
|
return repr(map(lambda t: t.get_title(), self.tiddlers))
|
||
|
|
||
|
################################################################################
|
||
|
|
||
|
class TwrcFile(ShConfigFile):
|
||
|
TWRC = "twrc"
|
||
|
|
||
|
def __init__(self, file=None, raise_exception=True):
|
||
|
if file is None:
|
||
|
testdir = path.join(scriptdir, "test")
|
||
|
utoolsrcdir = path.join(path.expanduser("~"), ".utools")
|
||
|
if path.isdir(testdir): file = path.join(testdir, self.TWRC)
|
||
|
else: file = path.join(utoolsrcdir, self.TWRC)
|
||
|
raise_exception = False
|
||
|
ShConfigFile.__init__(self, file=file, raise_exception=raise_exception)
|
||
|
|
||
|
DEFAULT_NAMES = "default_names"
|
||
|
MODIFIER = "modifier"
|
||
|
|
||
|
def __p(self, p, refdir=None):
|
||
|
if refdir is not None:
|
||
|
if not path.isdir(refdir): refdir = path.split(refdir)[0]
|
||
|
p = path.normpath(path.expanduser(p))
|
||
|
if refdir is not None:
|
||
|
p = path.join(refdir, p)
|
||
|
return p
|
||
|
|
||
|
COMMA_PATTERN = re.compile(r'\s*,\s*')
|
||
|
def __vs(self, vs):
|
||
|
if isstr(vs): vs = self.COMMA_PATTERN.split(vs)
|
||
|
return tuple(vs)
|
||
|
def __csv(self, csv):
|
||
|
if isseq(csv): csv = ','.join(csv)
|
||
|
return ensure_unicode(csv)
|
||
|
|
||
|
def get_default_names(self):
|
||
|
if self.has_key(self.DEFAULT_NAMES): return self.__vs(self[self.DEFAULT_NAMES])
|
||
|
else: return TiddlyWiki.DEFAULT_NAMES
|
||
|
def set_default_names(self, default_names=None):
|
||
|
if default_names is None:
|
||
|
if self.has_key(self.DEFAULT_NAMES): del self[self.DEFAULT_NAMES]
|
||
|
else:
|
||
|
self[self.DEFAULT_NAMES] = self.__csv(default_names)
|
||
|
|
||
|
def get_modifier(self):
|
||
|
if self.has_key(self.MODIFIER): return self[self.MODIFIER]
|
||
|
else: return os.environ.get('USER', 'TiddlyWiki.py')
|
||
|
def set_modifier(self, modifier=None):
|
||
|
if modifier is None:
|
||
|
if self.has_key(self.MODIFIER): del self[self.MODIFIER]
|
||
|
else:
|
||
|
self[self.MODIFIER] = modifier
|
||
|
|
||
|
class TwCLI:
|
||
|
twrc = None
|
||
|
|
||
|
def __newTiddlyWiki(self, file=None):
|
||
|
return TiddlyWiki(file, default_names=self.twrc.get_default_names(), raise_exception=True)
|
||
|
|
||
|
twfile = None
|
||
|
def __twfile(self):
|
||
|
if self.twfile is None:
|
||
|
self.twfile = self.__newTiddlyWiki()
|
||
|
return self.twfile
|
||
|
|
||
|
def __init__(self, twrc=None):
|
||
|
if not isinstance(twrc, TwrcFile):
|
||
|
twrc = TwrcFile(twrc)
|
||
|
self.twrc = twrc
|
||
|
|
||
|
CONFOPT = 'c:f:'
|
||
|
CONFLOPT = ['config=', 'file=']
|
||
|
|
||
|
def is_global_option(self, opt, value):
|
||
|
if opt in ('-c', '--config'):
|
||
|
self.twrc = TwrcFile(value)
|
||
|
elif opt in ('-f', '--file'):
|
||
|
self.twfile = self.__newTiddlyWiki(value)
|
||
|
else:
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
def ADDTEXT(self,
|
||
|
title=None, text=None,
|
||
|
modifier=None,
|
||
|
set_tags=None, add_tags=None, remove_tags=None,
|
||
|
encoding=None,
|
||
|
argv=(), scriptname=None,
|
||
|
**kw):
|
||
|
u"""%(scriptname)s: Créer ou mettre à jour un tiddler
|
||
|
|
||
|
USAGE
|
||
|
%(scriptname)s [options] title
|
||
|
|
||
|
OPTIONS
|
||
|
-m text
|
||
|
Si le texte du tiddler est spécifié, on ne lance pas d'éditeur
|
||
|
-u modifier
|
||
|
Spécifier le nom de l'utilisateur qui fait la modification. Par défaut,
|
||
|
il s'agit de $USER
|
||
|
-t tag
|
||
|
tag: ajouter un tag, -tag: enlever un tag
|
||
|
-e encoding
|
||
|
Spécifier l'encoding du titre et du texte s'il sont spécifiés sur la
|
||
|
ligne de commande. Par défaut, on considère que les données sont
|
||
|
encodées en %(default_encoding)s"""
|
||
|
default_encoding = get_stdin_encoding()
|
||
|
opts, argv = getopt(argv,
|
||
|
self.CONFOPT + 'hm:u:t:e:',
|
||
|
self.CONFLOPT + ['help', 'text=', 'modifier=', 'tag=', 'encoding='])
|
||
|
for opt, value in opts:
|
||
|
if self.is_global_option(opt, value):
|
||
|
pass
|
||
|
elif opt in ('-h', '--help'):
|
||
|
uprint(self.LIST.__doc__ % locals())
|
||
|
sys.exit(0)
|
||
|
elif opt in ('-m', '--text'):
|
||
|
text = value
|
||
|
elif opt in ('-u', '--modifier'):
|
||
|
modifier = value
|
||
|
elif opt in ('-t', '--tag'):
|
||
|
if value.startswith('-'):
|
||
|
if remove_tags is None: remove_tags = []
|
||
|
remove_tags.append(value[1:])
|
||
|
else:
|
||
|
if value.startswith('+'): value = value[1:]
|
||
|
if add_tags is None: add_tags = []
|
||
|
add_tags.append(value)
|
||
|
elif opt in ('-e', '--encoding'):
|
||
|
encoding = value
|
||
|
|
||
|
if title is None:
|
||
|
if not argv:
|
||
|
raise ValueError("Il faut spécifier un titre pour le nouveau tiddler")
|
||
|
title = argv[0]
|
||
|
|
||
|
edit = False
|
||
|
if text is None:
|
||
|
text = ''
|
||
|
edit = True
|
||
|
if modifier is None:
|
||
|
modifier = self.twrc.get_modifier()
|
||
|
if encoding is None:
|
||
|
encoding = default_encoding
|
||
|
title = ensure_unicode(title, encoding)
|
||
|
text = ensure_unicode(text, encoding)
|
||
|
|
||
|
twfile = self.__twfile()
|
||
|
|
||
|
new_tiddler = False
|
||
|
tiddler = twfile.get(title, None)
|
||
|
if tiddler is None:
|
||
|
new_tiddler = True
|
||
|
tiddler = Tiddler()
|
||
|
twfile.add(tiddler)
|
||
|
|
||
|
tiddler.set_title(title)
|
||
|
tiddler.set_text(text)
|
||
|
tiddler.set_modifier(modifier)
|
||
|
if set_tags is not None:
|
||
|
tiddler.set_tags(set_tags)
|
||
|
if add_tags is not None:
|
||
|
for tag in add_tags:
|
||
|
tiddler.add_tag(tag)
|
||
|
if remove_tags is not None:
|
||
|
for tag in remove_tags:
|
||
|
tiddler.remove_tag(tag)
|
||
|
|
||
|
if tiddler.is_modified():
|
||
|
twfile.save()
|
||
|
if edit:
|
||
|
self.EDIT(tiddler=tiddler)
|
||
|
else:
|
||
|
if new_tiddler: enote(u"Ajout d'un nouveau tiddler '%s'" % title)
|
||
|
else: enote(u"Mise à jour du tiddler '%s'" % title)
|
||
|
|
||
|
def ADDFILE(self,
|
||
|
file=None, title=None,
|
||
|
modifier=None,
|
||
|
set_tags=None, add_tags=None, remove_tags=None,
|
||
|
encoding=None,
|
||
|
argv=(), scriptname=None,
|
||
|
**kw):
|
||
|
u"""%(scriptname)s: Créer ou mettre à jour un tiddler à partir d'un fichier
|
||
|
|
||
|
USAGE
|
||
|
%(scriptname)s [options] /path/to/file
|
||
|
|
||
|
OPTIONS
|
||
|
-n title
|
||
|
Spécifier le titre du tiddler. Par défaut, il s'agit du nom du fichier
|
||
|
-u modifier
|
||
|
Spécifier le nom de l'utilisateur qui fait la modification. Par défaut,
|
||
|
il s'agit de $USER
|
||
|
-t tag
|
||
|
Ajouter un tag
|
||
|
Si le fichier a l'extension .js, on ajoute automatiquement le tag
|
||
|
systemConfig, sauf si un tag est spécifié
|
||
|
-e encoding
|
||
|
Spécifier l'encoding du fichier. Par défaut, on lit en %(default_encoding)s"""
|
||
|
default_encoding = get_editor_encoding()
|
||
|
opts, argv = getopt(argv,
|
||
|
self.CONFOPT + 'hn:u:t:e:',
|
||
|
self.CONFLOPT + ['help', 'title=', 'modifier=', 'tag=', 'encoding='])
|
||
|
for opt, value in opts:
|
||
|
if self.is_global_option(opt, value):
|
||
|
pass
|
||
|
elif opt in ('-h', '--help'):
|
||
|
uprint(self.LIST.__doc__ % locals())
|
||
|
sys.exit(0)
|
||
|
elif opt in ('-n', '--title'):
|
||
|
title = value
|
||
|
elif opt in ('-u', '--modifier'):
|
||
|
modifier = value
|
||
|
elif opt in ('-t', '--tag'):
|
||
|
if value.startswith('-'):
|
||
|
if remove_tags is None: remove_tags = []
|
||
|
remove_tags.append(value[1:])
|
||
|
else:
|
||
|
if value.startswith('+'): value = value[1:]
|
||
|
if add_tags is None: add_tags = []
|
||
|
add_tags.append(value)
|
||
|
elif opt in ('-e', '--encoding'):
|
||
|
encoding = value
|
||
|
|
||
|
if file is None:
|
||
|
if not argv:
|
||
|
raise ValueError("Il faut spécifier un fichier à importer")
|
||
|
file = argv[0]
|
||
|
if not path.exists(file):
|
||
|
raise IOError("Fichier inexistant: %s" % file)
|
||
|
|
||
|
if title is None:
|
||
|
title = file
|
||
|
if modifier is None:
|
||
|
modifier = os.environ.get('USER', 'TiddlyWiki.py')
|
||
|
if path.splitext(file)[1] == '.js':
|
||
|
if set_tags is not None:
|
||
|
set_tags.append('systemConfig')
|
||
|
else:
|
||
|
if add_tags is None: add_tags = []
|
||
|
add_tags.append('systemConfig')
|
||
|
if encoding is None:
|
||
|
encoding = defaut_encoding
|
||
|
|
||
|
inf = open(file, 'rb')
|
||
|
try:
|
||
|
text = ensure_unicode(inf.read(), encoding)
|
||
|
finally:
|
||
|
inf.close()
|
||
|
|
||
|
twfile = self.__twfile()
|
||
|
|
||
|
new_tiddler = False
|
||
|
tiddler = twfile.get(title, None)
|
||
|
if tiddler is None:
|
||
|
new_tiddler = True
|
||
|
tiddler = Tiddler()
|
||
|
twfile.add(tiddler)
|
||
|
|
||
|
tiddler.set_title(title)
|
||
|
tiddler.set_modifier(modifier)
|
||
|
if set_tags is not None:
|
||
|
tiddler.set_tags(set_tags)
|
||
|
if add_tags is not None:
|
||
|
for tag in add_tags:
|
||
|
tiddler.add_tag(tag)
|
||
|
if remove_tags is not None:
|
||
|
for tag in remove_tags:
|
||
|
tiddler.remove_tag(tag)
|
||
|
tiddler.set_text(text)
|
||
|
|
||
|
if tiddler.is_modified():
|
||
|
twfile.save()
|
||
|
if new_tiddler: enote(u"Ajout d'un nouveau tiddler '%s'" % title)
|
||
|
else: enote(u"Mise à jour du tiddler '%s'" % title)
|
||
|
|
||
|
def REMOVE(self,
|
||
|
title=None,
|
||
|
argv=(), scriptname=None,
|
||
|
**kw):
|
||
|
u"""%(scriptname)s: Supprimer un tiddler
|
||
|
|
||
|
USAGE
|
||
|
%(scriptname)s title"""
|
||
|
opts, argv = getopt(argv,
|
||
|
self.CONFOPT + 'hn:',
|
||
|
self.CONFLOPT + ['help', 'title='])
|
||
|
for opt, value in opts:
|
||
|
if self.is_global_option(opt, value):
|
||
|
pass
|
||
|
elif opt in ('-h', '--help'):
|
||
|
uprint(self.LIST.__doc__ % locals())
|
||
|
sys.exit(0)
|
||
|
elif opt in ('-n', '--title'):
|
||
|
title = value
|
||
|
|
||
|
if title is None:
|
||
|
if not argv:
|
||
|
raise ValueError("Il faut spécifier le tiddler à supprimer")
|
||
|
title = argv[0]
|
||
|
|
||
|
twfile = self.__twfile()
|
||
|
if twfile.has_key(title):
|
||
|
del twfile[title]
|
||
|
twfile.save()
|
||
|
enote(u"Suppression du tiddler '%s'" % title)
|
||
|
|
||
|
def LIST(self,
|
||
|
showtext=False,
|
||
|
argv=(), scriptname=None,
|
||
|
**kw):
|
||
|
u"""%(scriptname)s: Lister les tiddlers
|
||
|
|
||
|
USAGE
|
||
|
%(scriptname)s
|
||
|
|
||
|
OPTIONS
|
||
|
-l Afficher aussi le contenu des tiddlers"""
|
||
|
opts, argv = getopt(argv,
|
||
|
self.CONFOPT + 'hl',
|
||
|
self.CONFLOPT + ['help', 'show-text'])
|
||
|
for opt, value in opts:
|
||
|
if self.is_global_option(opt, value):
|
||
|
pass
|
||
|
elif opt in ('-h', '--help'):
|
||
|
uprint(self.LIST.__doc__ % locals())
|
||
|
sys.exit(0)
|
||
|
elif opt in ('-l', '--show-text'):
|
||
|
showtext = True
|
||
|
|
||
|
twfile = self.__twfile()
|
||
|
if showtext:
|
||
|
for tiddler in twfile:
|
||
|
title = get_colored(u'>>> ' + tiddler.get_title(), 'b')
|
||
|
if tiddler.get_tags():
|
||
|
title += " [%s]" % ', '.join(map(lambda t: get_colored(tiddler, 'y'), tiddler.get_tags()))
|
||
|
uprint(title)
|
||
|
|
||
|
text = tiddler.get_text()
|
||
|
if text: uprint(text)
|
||
|
else:
|
||
|
for tiddler in twfile:
|
||
|
uprint(tiddler.get_title())
|
||
|
|
||
|
EDIT_TEMPLATE = u"""
|
||
|
EDIT: ----------------------------------------------------------------
|
||
|
EDIT: Saisissez ou modifiez le titre et le corps du tiddler.
|
||
|
EDIT:
|
||
|
EDIT: - Les lignes commencant par 'EDIT:' seront supprimées automatiquement
|
||
|
EDIT: - La ligne tags: peut être modifiée si nécessaire.
|
||
|
EDIT:
|
||
|
EDIT: ----------------------------------------------------------------"""
|
||
|
|
||
|
TAGS_PATTERN = re.compile(r'##\s*tags:\s*')
|
||
|
|
||
|
def __nblines(self, s):
|
||
|
lines = s.split("\n")
|
||
|
nblines = len(lines)
|
||
|
if not lines[-1]: nblines -= 1
|
||
|
return nblines
|
||
|
|
||
|
def EDIT(self,
|
||
|
title=None,
|
||
|
tiddler=None,
|
||
|
argv=(), scriptname=None,
|
||
|
**kw):
|
||
|
u"""%(scriptname)s: Editer un tiddler
|
||
|
|
||
|
USAGE
|
||
|
%(scriptname)s title"""
|
||
|
opts, argv = getopt(argv,
|
||
|
self.CONFOPT + 'h',
|
||
|
self.CONFLOPT + ['help'])
|
||
|
for opt, value in opts:
|
||
|
if self.is_global_option(opt, value):
|
||
|
pass
|
||
|
elif opt in ('-h', '--help'):
|
||
|
uprint(self.LIST.__doc__ % locals())
|
||
|
sys.exit(0)
|
||
|
|
||
|
twfile = self.__twfile()
|
||
|
if tiddler is None:
|
||
|
if title is None:
|
||
|
if not argv:
|
||
|
raise ValueError("Il faut spécifier le tiddler à éditer")
|
||
|
title = argv[0]
|
||
|
tiddler = twfile.get(title, None)
|
||
|
if tiddler is None:
|
||
|
raise ValueError("Tiddler non trouvé: %s" % title)
|
||
|
|
||
|
template = u""
|
||
|
template += u"## tags: %s\n" % twFormatTags(tiddler.get_tags())
|
||
|
template += u"\n"
|
||
|
|
||
|
title = tiddler.get_title()
|
||
|
template += u"%s\n" % title
|
||
|
setline = self.__nblines(template)
|
||
|
setcol = len(title)
|
||
|
|
||
|
text = tiddler.get_text()
|
||
|
if text: template += u"\n%s\n" % text
|
||
|
|
||
|
template += self.EDIT_TEMPLATE
|
||
|
lines = edit_template(template, 'EDIT:', setline, setcol).split('\n')
|
||
|
|
||
|
new_tags = []
|
||
|
parsing_tags = True
|
||
|
skip_empty = True
|
||
|
text = []
|
||
|
for line in lines:
|
||
|
if skip_empty and not line: continue
|
||
|
if parsing_tags:
|
||
|
mot = self.TAGS_PATTERN.match(line)
|
||
|
if mot is not None:
|
||
|
new_tags.extend(twParseTags(line[mot.end():]))
|
||
|
continue
|
||
|
else:
|
||
|
parsing_tags = False
|
||
|
skip_empty = False
|
||
|
text.append(line)
|
||
|
text = "\n".join(text)
|
||
|
pos = text.find('\n\n')
|
||
|
if pos == -1:
|
||
|
title = text.replace('\n', ' ')
|
||
|
text = u''
|
||
|
else:
|
||
|
title = text[:pos].replace('\n', ' ')
|
||
|
text = text[pos + 2:]
|
||
|
|
||
|
tiddler.set_tags(new_tags)
|
||
|
tiddler.set_title(title)
|
||
|
tiddler.set_text(text)
|
||
|
|
||
|
if tiddler.is_modified():
|
||
|
twfile.save()
|
||
|
enote(u"Mise à jour du tiddler '%s'" % title)
|
||
|
|
||
|
################################################################################
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
debug = False
|
||
|
action = None
|
||
|
argv = sys.argv[1:]
|
||
|
twCLI = TwCLI()
|
||
|
|
||
|
# Essayer de determiner l'action avec le nom du script
|
||
|
if scriptname in ('twa', ):
|
||
|
action = 'addtext'
|
||
|
elif scriptname in ('twf', ):
|
||
|
action = 'addfile'
|
||
|
elif scriptname in ('twl', 'twll',):
|
||
|
if scriptname == 'twll': argv.insert(0, '-l')
|
||
|
action = 'list'
|
||
|
elif scriptname in ('twe', ):
|
||
|
action = 'edit'
|
||
|
|
||
|
if action is None:
|
||
|
opts, argv = getopt(argv,
|
||
|
TwCLI.CONFOPT + 'hD',
|
||
|
TwCLI.CONFLOPT + ['help', 'debug'])
|
||
|
for opt, value in opts:
|
||
|
if opt in ('-h', '--help'):
|
||
|
uprint(__doc__ % dict(scriptname=scriptname))
|
||
|
sys.exit(0)
|
||
|
elif twCLI.is_global_option(opt, value):
|
||
|
pass
|
||
|
elif opt in ('-D', '--debug'):
|
||
|
debug = True
|
||
|
|
||
|
if not argv:
|
||
|
uprint(__doc__ % dict(scriptname=scriptname))
|
||
|
sys.exit(0)
|
||
|
|
||
|
action, argv = argv[0], argv[1:]
|
||
|
if action in ('addtext', 'add', 'a'):
|
||
|
action = 'addtext'
|
||
|
elif action in ('addfile', 'file', 'f'):
|
||
|
action = 'addfile'
|
||
|
elif action in ('remove', 'r'):
|
||
|
action = 'remove'
|
||
|
elif action in ('list', 'l', 'll'):
|
||
|
if action == 'll': argv.insert(0, '-l')
|
||
|
action = 'list'
|
||
|
elif action in ('edit', 'e'):
|
||
|
action = 'edit'
|
||
|
else:
|
||
|
eerror("Action inconnue: %s" % action)
|
||
|
sys.exit(1)
|
||
|
|
||
|
if scriptname in ('TiddlyWiki.py', 'tw'):
|
||
|
# pour l'affichage de l'aide
|
||
|
scriptname = '%s %s' % (scriptname, action)
|
||
|
|
||
|
try:
|
||
|
apply(getattr(twCLI, action.upper()), (), {'argv': argv, 'scriptname': scriptname})
|
||
|
except Exception, e:
|
||
|
if debug:
|
||
|
eerror(e[0])
|
||
|
import traceback
|
||
|
traceback.print_exc()
|
||
|
else:
|
||
|
die(e[0])
|