#!/usr/bin/env python
# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

u"""%(name)s - Gérer les releases d'un projet
USAGE
    %(name)s [options] [projdir]
"""

import os, sys, string, re, shutil
from os import path
from time import time, localtime

from ulib.all import *
from ulib.vcs import *
from ulib.ext.tarfile import tarfile
from uapps.update_inc import update_inc_params, update_inc
from pyutools.TODO import TODOs
from pyutools.wo import wo_projname_and_projtype

def uappspath(file):
    """Exprimer un fichier par rapport au répertoire de uapps.
    XXX il vaut mieux déplacer la logique dans un module, et exprimer le fichier
    par rapport à la position du module
    """
    uappsdir = path.join(sys.prefix, 'lib', 'python' + sys.version[:3], 'site-packages', 'uapps')
    return path.join(uappsdir, file)

##################################################
### Méthodes de gestion des paramètres

# Note: les paramètres peuvent être donnés sur la ligne de commande, dans un
# fichier release.conf, et ont une valeur par défaut

## On construit les structures au fur et à mesure
# Paramètres valides dans release.conf et méthodes associées
params_for_release_conf = {}

# Valeurs par défauts
params_defaults = {}

# Paramètres sur la ligne de commande
argsdesc = [
    # Afficher l'aide
    ('h', 'help', u"Afficher l'aide"),
    ]

## Gestion des versions
uver = 'release_update_version'
sver = 'force_version'

def set_uver(p, value=False): p[uver] = is_yes(value)
def set_sver(p, value): p[sver] = value

params_for_release_conf.update({uver: set_uver})
params_defaults.update({uver: False, sver: None})
argsdesc.extend([
    ('n', 'keep-version', u"Ne pas changer la version (par défaut)"),
    ('u', 'update-version', u"Mettre à jour la version"),
    ('v:', 'set-version=', u"Spécifier une nouvelle version"),
    ])

## suivi des changements
log = 'release_log_changes'
todorc = 'release_todorc'

def set_log(p, value=False): p[log] = is_yes(value)
def set_todorc(p, value): p[todorc] = path.abspath(value)

params_for_release_conf.update({log: set_log, todorc: set_todorc})
params_defaults.update({log: True, todorc: None})
argsdesc.extend([
    (None, 'log-changes', u"Saisir les changements pour une nouvelle version (par défaut)"),
    (None, 'no-log-changes', u"Ne pas saisir les changements"),
    (None, 'todorc=', u"Spécifier le fichier de configuration pour la gestion des TODOs"),
    ])

## gestion des tags (avec CVS)
tag = 'release_tag_in_vcs'
stag = 'force_tag'

def set_tag(p, value=False): p[tag] = is_yes(value)
def set_stag(p, value): p[stag] = value

params_for_release_conf.update({tag: set_tag})
params_defaults.update({tag: True, stag: None})
argsdesc.extend([
    (None, 'tag', u"tagger les sources avec le nom du projet et la version (par défaut)"),
    (None, 'no-tag', u"Ne pas tagger les sources"),
    (None, 'set-tag', u"Spécifier le tag à utiliser"),
    ])

## gestion des fichiers de licence
lic = 'release_include_license'
slic = 'force_license'

def set_lic(p, value=False): p[lic] = is_yes(value)
def set_slic(p, value): p[slic] = value

params_for_release_conf.update({lic: set_lic})
params_defaults.update({lic: True, slic: None})
argsdesc.extend([
    (None, 'include-license', u"Inclure un fichier de licence"),
    (None, 'no-include-license', u"Ne pas inclure le fichier de licence"),
    (None, 'set-license', u"Choisir le fichier de licence à utiliser"),
    (None, 'ur', u"Spécifier la licence pour les produits de l'université de la Réunion"),
    (None, 'jclain', u"Spécifier la licence pour les produits de Jephté CLAIN"),
    ])

## création de l'archive
bp = 'release_base_path'
exn = 'release_exclude_names'
exp = 'release_exclude_paths'
an = 'archive_name'
asrc = 'archive_source'
sas = 'source_archive_suffix'
ab = 'archive_binary'
bas = 'binary_archive_suffix'
aj = 'archive_javadoc'
jdas = 'archive_suffix'

def set_bp(p, value): p[bp] = value
def set_exn(p, value):
    if not p.has_key(exn): p[exn] = []
    if ':' in value:
        values = string.split(value, ':')
    else:
        values = [value]
    for value in values:
        list_set(p[exn], value)
def set_exp(p, value):
    if not p.has_key(exp): p[exp] = []
    if ':' in value:
        values = string.split(value, ':')
    else:
        values = [value]
    for value in values:
        list_set(p[exp], value)
def set_an(p, value): p[an] = value
def set_asrc(p, value=False): p[asrc] = is_yes(value)
def set_sas(p, value): p[sas] = value
def set_ab(p, value=False): p[ab] = is_yes(value)
def set_bas(p, value): p[bas] = value
def set_aj(p, value=False): p[aj] = is_yes(value)
def set_jdas(p, value): p[jdas] = value

params_for_release_conf.update({bp: set_bp, exn: set_exn, exp: set_exp})
params_defaults.update({bp: '@@projname@@', #'@@projname@@-@@projver@@',
                        exn: [], exp: [], an: None,
                        asrc: False, sas: '',
                        ab: False, bas: '',
                        aj: False, jdas: '-api'
                        })
argsdesc.extend([
    ('c', 'archive-source', u"Créer l'archive du source"),
    ('B', 'archive-binary', u"Créer l'archive du produit binaire"),
    ('J', 'archive-javadoc', u"Créer l'archive de la javadoc"),
    ('b:', 'base-path=', u"Dans l'archive générée, répertoire de base"),
    ('x:', 'exclude-names=', u"Noms de fichiers à exclure de l'archive"),
    ('X:', 'exclude-paths=', u"Répertoires à exclure de l'archive"),
    ])

# ensembles nommés d'options
pyp = 'release_use_python_defaults'
zop = 'release_use_zope_defaults'
jawop = 'release_use_jawo_defaults'

def set_pyp(p, value=False):
    if is_yes(value):
        p[pyp] = True
        p[zop] = p[jawop] = False
        set_tag(p, True)
        set_log(p, True)
        set_lic(p, True)
        set_exn(p, 'CVS/')
        set_exn(p, '.svn/')
        set_exn(p, '*.pyc')
        set_exn(p, '*~')
        set_exn(p, '.DS_Store')
def set_zop(p, value=False):
    if is_yes(value):
        set_pyp(True)
        p[zop] = True
        p[pyp] = p[jawop] = False
        p[bp] = 'lib/python/Products/@@projname@@' #'lib/python/Products/@@projname@@-@@projver@@'
def set_jawop(p, value=False):
    if is_yes(value):
        p[jawop] = True
        p[pyp] = p[zop] = False
        set_tag(p, True)
        set_log(p, True)
        set_lic(p, True)
        set_sas(p, '-src')
        set_exn(p, 'CVS/')
        set_exn(p, '.svn/')
        set_exn(p, '*~')
        set_exn(p, '.DS_Store')
        set_exp(p, 'build/')
        set_exp(p, '*.pbproj/*.pbxuser')

params_for_release_conf.update({pyp: set_pyp, zop: set_zop, jawop: set_jawop})
params_defaults.update({pyp: False, zop: False, jawop: False})
argsdesc.extend([
    (None, 'auto', u"Autodétecter le type de projet (par défaut)"),
    (None, 'no-auto', u"Ne pas autodétecter le type de projet"),
    ('P', 'python-product', u"Choisir les options par défaut pour un projet Python"),
    ('Z', 'zope-product', u"Choisir les options par défaut pour un projet Zope"),
    ('W', 'jawo-product', u"Choisir les options par défaut pour un projet jabuild/wobuild"),
    ])

def is_auto(opt): return opt in ('--auto', )
def isnot_auto(opt): return opt in ('--no-auto', '-P', '--python-product',
                                    '-Z', '--zope-product',
                                    '-W', '--jawo-product',
                                    )

# support de update_inc
ui = 'update_inc'

def set_ui(p, value=False, basedir=None):
    p[ui] = None
    if is_yes(value):
        if basedir is None: basedir = os.getcwd()
        cf = ShConfigFile(path.join(basedir, '.uinst.conf'), raise_exception=False)
        if cf.is_valid():
            # on force le mode quiet avec -q en attendant que update_inc supporte set_verbosity()
            options = split_args(cf.get('update_inc_options', '') + ' -q') + split_args(cf.get('update_inc_args', ''))
            p_ui, args_ui = update_inc_params(options, basedir=basedir)
            p[ui] = (p_ui, args_ui)
def fold_maybe(p, vcsdir=None):
    if p[ui]:
        pr, args = p[ui]
        if vcsdir is None:
            update_inc(pr, args, 'fold')
            return True
        else: return vcsdir.fold(pr, args, 'fold')
def unfold_maybe(p, vcsdir=None):
    if p[ui]:
        pr, args = p[ui]
        if vcsdir is None: update_inc(pr, args, 'unfold')
        else: vcsdir.unfold(pr, args, 'unfold')

params_for_release_conf.update({ui: set_ui})
params_defaults.update({ui: False})
argsdesc.extend([
    (None, 'update_inc', u"Activer le support de update_inc"),
    ])

# autres paramètres qui sont utilisés par release()
cver = 'version_txt_created?'
clog = 'changes_txt_created?'
clic = 'license_txt_created?'
cd = 'curdir'
pn = 'projname'
pt = 'projtype'

def set_cver(p, value=False): p[cver] = is_yes(value)
def set_clog(p, value=False): p[clog] = is_yes(value)
def set_clic(p, value=False): p[clic] = is_yes(value)
def set_cd(p, value): p[cd] = value
def set_pn(p, value): p[pn] = value
def set_pt(p, value): p[pt] = value

params_for_release_conf.update({})

params_defaults.update({cver: False,clog: False, clic: False, cd: None,
                        pn: None, pt: None,
                        })
argsdesc.extend([])

##################################################
# Méthodes utilitaires
def list_set(list, value):
    """insérer une valeur dans une liste si elle n'y est pas déjà
    """
    if value not in list:
        list.append(value)

def strip_release_from_version(v):
    """Supprimer la date de release dd/mm/yyyy d'un numéro de version
    """
    v = string.strip(v)
    mo = re.match(r'(.*)-r[0-9]+/[0-9]+/[0-9]+$', v)
    if mo is not None:
        v = mo.group(1)
    return v

def _inc_last(v):
    v0, v1 = re.match(r'^(.*?)([0-9]*)$', v).groups()
    v1 = str(int(v1 or 0) + 1)
    return v0 + v1

def increment_version(v):
    """Incrémenter le dernier élément d'un numéro de version, et retourner les
    différentes propositions possibles.
    """
    vs = []
    # [0]: ajouter un chiffre
    vs.append(v + '.1')
    # [1]: incrémenter le dernier
    vs.append(_inc_last(v))
    # [2]: incrémenter l'avant dernier
    if v.count('.') > 1:
        pos = v.rfind('.')
        vs.append(_inc_last(v[:pos]))

    return vs

def expand_projname_and_version(s, p):
    s = string.replace(s, "@@projname@@", p[pn])
    s = string.replace(s, "@@projver@@", p[sver])
    return s

##################################################
# Méthodes pour la création d'une archive
def tarpath(p):
    """normaliser un chemin pour utilisation dans un fichier tar: il ne doit pas
    se terminer par /, les séparateurs doivent être / et non \ et le répertoire
    courant '.' est remplacé par ''
    """
    p = path.normpath(p)
    p = string.replace(p, '\\', '/')
    while p[-1:] == '/': p = p[:-1]
    if p == '.': p = ''
    return p

def normalize_paths(paths):
    """Normaliser tous les chemins de la liste paths.

    Retourner une liste de tuples (tpath, apath), où tpath est le chemin
    normalisé pour le fichier tar (séparateurs '/'), et apath est le chemin
    absolu sur le système de fichier.
    """
    return map(lambda p: (tarpath(p), path.abspath(p)), paths)

def select_files(p, exclude_names=(), exclude_paths=(), basepath=None):
    """Construire récursivement la liste de tous les fichiers à partir du
    répertoire p, en excluant les répertoires dont le nom est donné dans
    excludes_names, ou dont le chemin relativement à p est donné dans
    exclude_paths

    on ne suit pas les liens symboliques.

    Note: on exclue automatiquement les fichiers qui sont 'binaires'
    """
    if basepath is None:
        return select_files(p, exclude_names, exclude_paths, p)
    else:
        files = []
        for file in os.listdir(p):
            pf = path.join(p, file) # chemin absolu
            relpf = pf[len(basepath) + 1:] # chemin relativement à basepath. +1 pour le '/'

            ignore = 0
            for pattern in exclude_names:
                if matches_name(pattern, file, p):
                    ignore = 1
            else:
                for pattern in exclude_paths:
                    if matches_name(pattern, relpf, basepath):
                        ignore = 1
            if not ignore:
                files.append(pf)
                if path.isdir(pf) and not path.islink(pf):
                    files.extend(select_files(pf, exclude_names, exclude_paths, basepath))
        return files

def create_archive(p, paths, altbp=None):
    """Créer l'archive p[an].tgz dans le répertoire courant avec les
    répertoires énumérés dans paths
    """
    archive = tarfile.open(p[an] + '.tgz', 'w:gz')
    archive.posix = 0

    if altbp is None: altbp = p[bp]
    basepath = altbp and altbp + '/' or ''
    for tp, ap in paths:
        tp = tp and tp + '/' or ''
        files = select_files(ap, p[exn], p[exp])
        for file in files:
            tfile = basepath + tp + string.replace(file[len(ap) + 1:], '\\', '/')
            archive.add(file, tfile, False)

    archive.close()

##################################################
# L'application release

def print_help(name=None):
    if name is None:
        name = path.split(sys.argv[0])[1]
    print __doc__ % vars()
    print "OPTIONS"
    for argdesc in argsdesc:
        so = argdesc[0:1] and argdesc[0] or None
        if so is not None: so = '-' + so[:1]

        lo = argdesc[1:2] and argdesc[1] or None
        if lo is not None: lo = (so is not None and ', ' or '') + '--' + lo

        desc = argdesc[2:3] and argdesc[2] or None

        if so is not None or lo is not None:
            print "    %s%s" % (so or '', lo or '')
            if desc is not None: print "        %s" % desc

def release(p, *paths):
    release_date = time()

    # Obtenir, vérifier et/ou normaliser les chemins
    paths = list(paths)

    curdir = p[cd]
    if curdir is None: die(u"curdir doit être spécifié")
    projname = p[pn]
    if projname is None: die(u"Il faut donner le nom du projet")
    projtype = p[pt]

    tp0, ap0 = paths[0]

    ### obtenir le numéro de version courant et incrémenter le numéro de version
    maj_version = False # True si la version a été changée

    # calculer le nom du fichier qui contient la version
    version_txt = 'VERSION.txt'
    if not path.isfile(path.join(ap0, version_txt)):
        if path.isfile(path.join(ap0, 'version.txt')):
            version_txt = 'version.txt'
    aversion_txt = path.join(ap0, version_txt)

    # lire la version qui se trouve dans le fichier
    if not path.isfile(aversion_txt):
        if not ask_yesquit(u"Le fichier %s n'existe pas. Faut-il le créer?" % version_txt, True, minlevel=I_INTER):
            raise Exit, 1
        set_cver(p, True)

    vf = TextFile(aversion_txt, raise_exception=False)
    if vf.lines:
        v = strip_release_from_version(vf.lines[0])
        einfo(u"La version actuelle est %s" % v)
    else:
        v = ''
        if p[uver]:
            v = '0.0'
            einfo(u"Pas d'information de version. on commence à 0.1")
        elif p[sver] is not None:
            einfo(u"Pas d'information de version. On commence à %s (forcé)" % p[sver])
        else:
            einfo(u"Pas d'information de version.")

    vs = None
    if p[sver] is None:
        if p[uver]:
            vs = increment_version(v)
            v = vs[1]
            maj_version = True
        p[sver] = v
    else:
        maj_version = p[sver] != v

    if maj_version:
        if vs is None:
            if not ask_yesquit(u"Mettre à jour la version vers %s?" % p[sver], True):
                raise Exit, 1
        else:
            if check_verbosity(I_NORMAL):
                for i in range(len(vs)):
                    einfo(u"%i - Passer à la version %s" % (i, vs[i]))
                r = read_value(u"Mettre à jour la version vers %s? [Oq]" % p[sver], default="O", refuse_empty=False)
                v = None
                if re.match(r'\d+$', r) is not None:
                    i = int(r)
                    if i >= 0 and i < len(vs):
                        v = vs[i]
                elif is_yes(r):
                    v = vs[1]

                if v is None: raise Exit, 1
                else: p[sver] = v

        vf.lines[0:1] = [p[sver] + '-r' + datef(FR_DATEF, release_date)]
        vf.writelines()

    # mettre à jour p[bp] qui peut contenir des tags @@projname@@ et @@projver@@
    p[bp] = expand_projname_and_version(tarpath(p[bp]), p)

    ### enregistrer les changements, seulement si on met à jour la version
    if p[log] and maj_version:
        if not ask_yesquit(u"Saisir le changelog pour la version %s?" % p[sver], True, minlevel=I_INTER):
            raise Exit, 1

        template = u"""
-EDIT-: ----------------------------------------------------------------------
-EDIT-: Saisissez une description de la distribution %(projname)s-%(version)s
-EDIT-:
-EDIT-: Les lignes commencant par '-EDIT-:' sont automatiquement supprimees
-EDIT-: dans le fichier de sortie (%(filename)s)
-EDIT-:
-EDIT-: ----------------------------------------------------------------------
"""
        project = path.split(ap0)[1]
        # Calculer automatiquement les tâches qui doivent être incluses dans le log
        ts = TODOs(p[todorc])
        ts.purge(project)
        released = ts.list_releaseable(project)

        mark_released = False
        if released:
            mark_released = True
            template = template + u"""\
-EDIT-: Pour information, les informations suivantes seront automatiquement
-EDIT-: ajoutées à la description de la distribution:
-EDIT-:
"""
            lines = []
            for ds in map(lambda d: d.to_string(), released):
                ds = ds.replace("%", "%%")
                lines.append("\n".join(map(lambda l: "-EDIT-: " + l, string.split(ds, "\n"))))
            template = template + "\n-EDIT-: ----\n".join(lines)

        todofile = ts.get_todofile(project)
        if todofile is not None and todofile.can_release():
            # éditer le fichier
            change_lines = edit_template(template % {'projname': projname,
                                                     'version': p[sver],
                                                     'filename': todofile.get_file()},
                                         '-EDIT-:')
            todofile.release(released, release_date, p[sver], change_lines.join())
            todofile.save()

        else:
            # calculer le nom du fichier qui contient les changements
            changes_txt = 'CHANGES.txt'
            if not path.exists(path.join(ap0, changes_txt)):
                if path.exists(path.join(ap0, 'changes.txt')):
                    changes_txt = changes.txt
            achanges_txt = path.join(ap0, changes_txt)

            if not path.exists(achanges_txt):
                set_clog(p, True)

            # éditer le fichier
            change_lines = edit_template(template % {'projname': projname,
                                                     'version': p[sver],
                                                     'filename': changes_txt},
                                         '-EDIT-:')
            if released:
                first = False
                if change_lines: change_lines.append("")
                else: first = True
                for todo in released:
                    if first: first = False
                    else: change_lines.append("----")
                    change_lines.extend(string.split(todo.to_string(), "\n"))

            # ajouter le changelog
            cf = TextFile(achanges_txt, raise_exception=False)

            if cf.lines and string.strip(cf.lines[-1]):
                cf.lines.append("")
            title = u"%s    %s-%s" % (datef(FRHM_DATEF, release_date), projname, p[sver])
            cf.lines[0:0] = [title, "=" * len(title), ""] + change_lines + (cf.lines and [""] or [])

            cf.writelines()

        ts.mark_released(project)

    ### ajouter le fichier de licence
    alicense_txt = None
    if p[lic] and maj_version:
        # calculer le nom du fichier qui contient la licence
        license_txt = 'LICENSE.txt'
        if not path.exists(path.join(ap0, license_txt)):
            if path.exists(path.join(ap0, 'license.txt')):
                license_txt = 'license.txt'
        alicense_txt = path.join(ap0, license_txt)

        if not path.exists(alicense_txt):
            set_clic(p, True)

        # si on a spécifié un fichier de license, forcer l'écrasement. Sinon, on
        # ne copie la license que si elle n'existe pas déjà
        copy_license = False
        if p[slic] is None:
            if p[clic]:
                copy_license = True
                p[slic] = uappspath('ur_license.txt') # par défaut
        if p[slic] is not None:
            copy_license = True
            if dirname(p[slic]) == '':
                # si on donne juste le nom du fichier, et qu'il n'existe pas
                # dans le répertoire courant, l'exprimer par rapport à au
                # répertoire du module uapps. XXX cf la documentation de
                # uappspath() pour voir ce qu'il faut corriger
                if not path.isfile(path.join(curdir, p[slic])):
                    p[slic] = tmp = uappspath(p[slic])
                    if not path.isfile(tmp):
                        tmp = tmp + '_license.txt'
                        if path.exists(tmp): p[slic] = tmp
                ## si on donne juste le nom du fichier, et qu'il n'existe pas
                ## dans le répertoire courant, l'exprimer par rapport à
                ## au répertoire de ce module
                #if not path.isfile(path.join(curdir, p[slic])):
                #    p[slic] = path.join(dirname(__file__), p[slic])
                #    if not path.isfile(p[slic]):
                #        tmp = p[slic] + '_license.txt'
                #        if path.exists(tmp):
                #            p[slic] = tmp
            if not path.isfile(p[slic]):
                die(u"Fichier de licence inexistant: %s" % p[slic])

        if copy_license:
            if not ask_yesquit(u"Copier le fichier de licence %s?" % basename(p[slic]), True, minlevel=I_INTER):
                raise Exit, 1

            if not path.exists(alicense_txt) or not path.samefile(p[slic], alicense_txt):
                shutil.copy(p[slic], alicense_txt)
                p[slic] = alicense_txt

            lf = TextFile(p[slic])
            if lf.grep(r'.*(@@projname@@|@@year@@)'):
                lf.replace('@@projname@@', projname)
                lf.replace('@@year@@', datef(YEAR_DATEF))
                lf.writelines()

    ### tag des fichiers si VCS est activé et si la version a été modifiée
    if p[tag] and maj_version and is_vcsdir(ap0):
        vcsdir = get_vcsdir(ap0)
        vcsdir.set_tag_rootdir(False)
        options = dict(trace=True, quiet=True, auto_update=False)

        # déterminer le nom du tag
        if p[stag] is None:
            p[stag] = '%s-%s' % (projname, string.replace(p[sver], '.', '-'))

        if ask_yesno(u"Taguer avec %s?" % p[stag], True, minlevel=I_INTER):
            os.chdir(ap0)

            # avant vcs, il faut faire un fold si on supporte update_inc
            estep(u"Suppression des inclusions")
            folded = fold_maybe(p, vcsdir)

            estep(u"Marquage des fichiers avec le tag %s" % p[stag])
            if p[cver]: vcsdir.add(version_txt, **options)
            if p[clog]: vcsdir.add(changes_txt, **options)
            if p[clic]: vcsdir.add(license_txt, **options)

            message = u"Création de la distribution pour le tag %s" % p[stag]
            vcsdir.commit(message, **options)
            vcsdir.tag(p[stag], message, **options)

            # après vcs, il faut faire un unfold si on supporte update_inc
            if folded:
                estep(u"Mise à jour des inclusions")
                unfold_maybe(p, vcsdir)

    ### Créer la distribution si cela a été demandé
    # vérifier la cohérence de certains paramètres
    if p[asrc] or p[ab] or p[aj]:
        if (p[ab] or p[aj]) and not p[jawop]:
            ewarn(u"La création d'une archive binaire/javadoc n'est supportée que pour les projets WebObjects")

        # avant de créer l'archive, unfold si on supporte update_inc
        unfold_maybe(p)

        # contruire l'archive du source
        p[an] = '%s-%s%s' % (projname, p[sver], p[sas])
        if ask_yesno(u"Créer l'archive %s.tgz?" % p[an], True, minlevel=I_INTER):
            if p[bp] != '':
                # si on a spécifié un chemin de base, inclure la racine de façon relative
                paths[0] = ('', paths[0][1])
            estep(u"Création de l'archive %s.tgz" % p[an])
            create_archive(p, paths)

        # construire l'archive du binaire
        if p[ab] and p[jawop]:
            archive_created = False
            for suffix in ('', '.woa', '.framework'):
                projdir = projname + suffix
                p[an] = '%s-%s%s' % (projdir, p[sver], p[bas])
                aprojdir = path.join(ap0, 'build', projdir)
                if path.isdir(aprojdir):
                    if ask_yesno(u"Créer l'archive des binaires %s.tgz?" % p[an], True, minlevel=I_INTER):
                        estep(u"Création de l'archive %s.tgz" % p[an])
                        create_archive(p, [(projdir, aprojdir)], '')
                    archive_created = True
            if not archive_created:
                eerror(u"Imposssible de créer l'archive binaire: il faut construire le projet d'abord")

        # construire l'archive de la documentation
        if p[aj] and p[jawop]:
            p[an] = '%s-%s%s' % (projname, p[sver], p[jdas])
            aprojdir = path.join(ap0, 'build', 'api')
            if path.isdir(aprojdir):
                if ask_yesno(u"Créer l'archive de la documentation %s.tgz?" % p[an], True, minlevel=I_INTER):
                    estep(u"Création de l'archive %s.tgz" % p[an])
                    create_archive(p, [(projdir, aprojdir)])
            else:
                eerror(u"Répertoire de la javadoc introuvable. L'archive n'a pas été générée")

def run_release():
    ### Lire les arguments
    options, longoptions = build_options(argsdesc)
    opts, paths = get_args(None, options, longoptions)
    p = {}

    # todorc est traité à part, parce qu'il peut s'agir d'un répertoire relatif
    index = 0
    for opt, value in opts:
        if opt in ('--todorc', ):
            set_todorc(p, value)
            del opts[index]
            break
        index = index + 1

    ### Vérifier et normaliser les chemins
    if not paths: paths = ['.']
    paths = normalize_paths(paths)
    tp0, ap0 = paths[0]

    curdir = os.getcwd()
    if curdir[:len(ap0)] == ap0:
        # nous sommes dans le répertoire de la distribution, se placer au-dessus
        os.chdir(path.split(ap0)[0])
    p[cd] = curdir

    ### Lecture des paramètres
    # Si nécessaire, auto-détecter le type de projet
    # ne pas autodétecter s'il existe un fichier .uinst.conf
    rcf = ShConfigFile(path.join(ap0, '.uinst.conf'), raise_exception=False)

    projname = projtype = None
    projecttype_autodetected = False
    autodetect_projecttype = not rcf.is_valid()
    for opt, value in opts:
        if is_auto(opt): autodetect_projecttype = True
        elif isnot_auto(opt): autodetect_projecttype = False

    if autodetect_projecttype:
        projname, projtype = wo_projname_and_projtype(ap0)
        if projtype in ('woapplication', 'woframework'):
            projecttype_autodetected = True
            set_jawop(p, True)

    if projname is None:
        # sinon, le nom de base de l'archive est le nom du répertoire
        projname = path.split(ap0)[1]
    p[pn] = projname
    p[pt] = projtype

    # Tout d'abord, essayer de lire les paramètres dans un fichier .uinst.conf
    if rcf.is_valid():
        for key, set_func in params_for_release_conf.items():
            if not rcf.has_key(key): continue

            value = rcf[key]
            if key == ui:
                # cas particulier: ui demande le répertoire de base
                set_func(p, value, ap0)
            else:
                set_func(p, value)

    # puis lire les paramètres en ligne de commande
    for opt, value in opts:
        if opt in ('-h', '--help'):
            print_help()
            sys.exit(0)

        elif opt in ('-n', '--keep-version'): set_uver(p, False)
        elif opt in ('-u', '--update-version'): set_uver(p, True)
        elif opt in ('-v', '--set-version'): set_sver(p, value)

        elif opt in ('--log-changes', ): set_log(p, True)
        elif opt in ('--no-log-changes', ): set_log(p, False)

        elif opt in ('--tag', ): set_tag(p, True)
        elif opt in ('--no-tag', ): set_tag(p, False)
        elif opt in ('--set-tag', ): set_stag(p, value)

        elif opt in ('--include-license', ): set_lic(p, True)
        elif opt in ('--no-include-license', ): set_lic(p, False)
        elif opt in ('--set-license', ): set_slic(p, value)
        elif opt in ('--ur', ): set_slic(p, 'ur_license.txt')
        elif opt in ('--jclain', ): set_slic(p, 'jclain_license.txt')

        elif opt in ('-c', '--archive-source'): set_asrc(p, True)
        elif opt in ('-B', '--archive-binary'): set_ab(p, True)
        elif opt in ('-J', '--archive-javadoc'): set_aj(p, True)
        elif opt in ('-b', '--base-path'): set_bp(p, value)
        elif opt in ('-x', '--exclude-names'): set_exn(p, value)
        elif opt in ('-X', '--exclude-paths'): set_exp(p, value)

        elif opt in ('-P', '--python-product'): set_pyp(p, True)
        elif opt in ('-Z', '--zope-product'): set_zop(p, True)
        elif opt in ('-W', '--jawo-product'): set_jawop(p, True)

        elif opt in ('--update_inc', ): set_ui(p, True, ap0)

    # compléter avec les valeurs par défaut des paramètres
    for key, value in params_defaults.items():
        if not p.has_key(key):
            p[key] = value

    # lancer l'application
    try:
        exitcode = apply(release, tuple([p] + paths))
    except Exit, exitcode:
        pass
    sys.exit(exitcode)

if __name__ == '__main__':
    run_release()
