#!/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])