diff --git a/ulib/.ulib_version b/ulib/.ulib_version index 98d9bcb..3c03207 100644 --- a/ulib/.ulib_version +++ b/ulib/.ulib_version @@ -1 +1 @@ -17 +18 diff --git a/ulib/base b/ulib/base index c1e2b5a..8d231c7 100644 --- a/ulib/base +++ b/ulib/base @@ -516,6 +516,19 @@ function array_fillrange() { done array_copy "$1" __af_vs } +function array_eq() { +# tester l'égalité des tableaux $1 et $2 + local __ae_c1="#$1[*]" __ae_c2="#$2[*]" + [ ${!__ae_c1} -eq ${!__ae_c2} ] || return 1 + local __ae_v1s="$1[@]" __ae_v1 + local __ae_v2 __ae_i=0 + for __ae_v1 in "${!__ae_v1s}"; do + __ae_v2="$2[$__ae_i]" + [ "$__ae_v1" == "${!__ae_v2}" ] || return 1 + __ae_i=$(($__ae_i + 1)) + done + return 0 +} function array_contains() { # tester si le tableau dont le nom est $1 contient la valeur $2 local __ac_v diff --git a/ulib/semver b/ulib/semver new file mode 100644 index 0000000..c3575bc --- /dev/null +++ b/ulib/semver @@ -0,0 +1,124 @@ +##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +## Versionnage sémantique +##@cooked nocomments +##@require base +uprovide semver +urequire base + +function semver_parse() { + local _ver="$1" _ma="${2:-major}" _mi="${3:-minor}" _pl="${4:-patchlevel}" _pr="${5:-prelease}" _md="${6:-metadata}" _va="${7:-valid}" + local _tmp + set_var "$_ma" "" + set_var "$_mi" "" + set_var "$_pl" "" + array_new "$_pr" + array_new "$_md" + set_var "$_va" "" + + # vérifier les caractères valides + [ -z "${_ver//[a-zA-Z0-9.+-]/}" ] || return 1 + + # extraire major + _tmp= + while [ "${_ver/#[0-9]/}" != "$_ver" ]; do + _tmp="$_tmp${_ver:0:1}" + _ver="${_ver:1}" + done + [ "${_ver:0:1}" == . ] || return 1 + _ver="${_ver:1}" + set_var "$_ma" "$_tmp" + + # extraire minor + _tmp= + while [ "${_ver/#[0-9]/}" != "$_ver" ]; do + _tmp="$_tmp${_ver:0:1}" + _ver="${_ver:1}" + done + [ "${_ver:0:1}" == . ] || return 1 + _ver="${_ver:1}" + set_var "$_mi" "$_tmp" + + # extraire patchlevel + _tmp= + while [ "${_ver/#[0-9]/}" != "$_ver" ]; do + _tmp="$_tmp${_ver:0:1}" + _ver="${_ver:1}" + done + [ -z "$_ver" -o "${_ver:0:1}" == - -o "${_ver:0:1}" == + ] || return 1 + set_var "$_pl" "$_tmp" + + # extraire prelease + if [ "${_ver:0:1}" == - ]; then + _ver="${_ver:1}" + if [[ "$_ver" == *+* ]]; then + _tmp="${_ver%%+*}" + _ver="+${_ver##*+}" + else + _tmp="$_ver" + _ver= + fi + array_split "$_pr" "$_tmp" . + fi + + # extraire metadata + if [ "${_ver:0:1}" == + ]; then + _ver="${_ver:1}" + [[ "$_ver" == *+* ]] && return 1 + _tmp="$_ver" + _ver= + array_split "$_md" "$_tmp" . + fi + + # on doit avoir tout analysé + [ -z "$_ver" ] || return 1 + + set_var "$_va" 1 + return 0 +} + +function semver_incmajor() { + set_var "$1" $((${!1} + 1)) + set_var "$2" 0 + set_var "$3" 0 + array_new "$4" +} + +function semver_incminor() { + set_var "$2" $((${!2} + 1)) + set_var "$3" 0 + array_new "$4" +} + +function semver_incpatchlevel() { + set_var "$3" $((${!3} + 1)) + array_new "$4" +} + +function semver_setprelease() { + # XXX analyser $1 et spliter avant de copier dans le tableau $5 + if [ "$1" ]; then + set_array "$5" @ "$1" + else + array_new "$5" + fi +} + +function semver_setmetadata() { + # XXX analyser $1 et spliter avant de copier dans le tableau $6 + if [ "$1" ]; then + set_array "$6" @ "$1" + else + array_new "$5" + fi +} + +function semver_build() { + echo_ "${!1}.${!2}.${!3}" + array_isempty "$4" || rawecho_ "-$(array_join "$4" .)" + array_isempty "$5" || rawecho_ "+$(array_join "$5" .)" + echo "" +} + +function semver_setvar() { + set_var "$1" "$(semver_build "$2" "$3" "$4" "$5" "$6")" +} diff --git a/uversion b/uversion new file mode 100755 index 0000000..5319e99 --- /dev/null +++ b/uversion @@ -0,0 +1,253 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname "$0")/ulib/ulib" || exit 1; urequire DEFAULTS semver + +function display_help() { + uecho "$scriptname: gérer des numéros de version + +USAGE + $scriptname [options] + +OPTIONS + -f, --file VERSIONFILE + Gérer le numéro de version se trouvant dans le fichier spécifié. Le + fichier est créé si nécessaire. C'est l'option par défaut si un fichier + nommé VERSION.txt se trouve dans le répertoire courant. + -s, --string VERSION + Prendre pour valeur de départ le numéro de version spécifié + + --show + Afficher le numéro de version. C'est l'action par défaut + --check + Vérifier que le numéro de version est conforme aux règles des versions + sémantiques (http://semver.org/) + --eq VERSION + --ne VERSION + --lt VERSION + --le VERSION + --gt VERSION + --ge VERSION + --same VERSION + --diff VERSION + Comparer avec la version spécifiée + eq, ne, lt, le, gt, ge ne testent pas la valeur des metadata + same et diff comparent aussi la valeur des metadata + -v, --set-version VERSION + Spécifier un nouveau numéro de version qui écrase la valeur actuelle. + Cette option ne devrait pas être utilisée parce qu'elle ne respecte pas + les règles du versionnage sémantique. + -u, --update + Mettre à jour le numéro de version. + + --menu + Afficher un menu permettant de choisir le composant de la version à + incrémenter + -x, --major + Augmenter le numéro de version majeure + -z, --minor + Augmenter le numéro de version mineure. C'est la valeur par défaut. + -p, --patchlevel + Augmenter le numéro de patch + + -l, --prelease ID + Spécifier un identifiant de prérelease, à ajouter au numéro de version. + -a, --alpha + -b, --beta + -r, --rc + Spécifier une pré-release de type alpha, beta, ou rc. Si la version est + déjà dans ce type, augmenter la dernière valeur numérique des composants + de l'identifiant, e.g. alpha deviant alpha.1, beta-1.2 devient beta-1.3, + rc1 devient rc2 + -R, --final, --release + Supprimer l'identifiant de prérelease + + -m, --metadata ID + Spécifier un identifiant de build, à ajouter au numéro de version." +} + +action=auto +source=auto +file= +version= +operator= +oversion= +setversion= +incversion= +setprelease= +setalpha= +setbeta= +setrc= +setfinal= +setmetadata= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -f:,--file: '$set@ file; source=' \ + -s:,--string: '$set@ version; source=' \ + --show action=show \ + --check action=check \ + --eq: '$action=compare; operator=eq; set@ oversion' \ + --ne: '$action=compare; operator=ne; set@ oversion' \ + --lt: '$action=compare; operator=lt; set@ oversion' \ + --le: '$action=compare; operator=le; set@ oversion' \ + --gt: '$action=compare; operator=gt; set@ oversion' \ + --ge: '$action=compare; operator=ge; set@ oversion' \ + -v:,--set-version: '$action=update; set@ setversion; incversion=' \ + -u,--update '$action=update; [ -z "$incversion" ] && incversion=auto' \ + --menu '$action=update; incversion=menu' \ + -x,--major '$action=update; incversion=major' \ + -z,--minor '$action=update; incversion=minor' \ + -p,--patchlevel '$action=update; incversion=patchlevel' \ + -l:,--prelease:,--prerelease: '$action=update; set@ setprelease' \ + -a,--alpha '$action=update; setalpha=1; setbeta=; setrc=; setfinal=' \ + -b,--beta '$action=update; setalpha=; setbeta=1; setrc=; setfinal=' \ + -r,--rc '$action=update; setalpha=; setbeta=; setrc=1; setfinal=' \ + -R,--release,--final '$action=update; setalpha=; setbeta=; setrc=; setfinal=1' \ + -m:,--metadata: '$action=update; set@ setmetadata' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +# Calculer la source +if [ "$source" == auto ]; then + for i in VERSION.txt version.txt; do + if [ -f "$i" ]; then + file="$i" + break + fi + done + [ -n "$file" ] || file=VERSION.txt +elif [ -n "$file" ]; then + [ "$action" == auto ] && action=update +fi +[ "$action" == auto ] && action=show + +# Lire la version +if [ -n "$file" ]; then + [ -f "$file" ] && version="$(<"$file")" + [ -n "$version" ] || version=0.0.0 +fi + +# Actions + +estepi "La version actuelle est $version" +[ "$action" == show ] && exit 0 + +# forcer le numéro de version +# XXX implémenter semver_setversion qui change juste la partie +# major.minor.patchlevel et ne touche pas au reste. une fois que cela sera fait, +# la ligne suivante doit être supprimée et le code de setversion doit être +# mis dans la partie "update" +[ -n "$setversion" ] && version="$setversion" + +semver_parse "$version" major minor patchlevel prelease metadata valid +if [ "$action" == check ]; then + [ -n "$valid" ] || die "Numéro de version invalide: $version" + +elif [ "$action" == compare ]; then + semver_parse "$oversion" omajor ominor opatchlevel oprelease ometadata ovalid + case "$operator" in + eq|same) + for var in valid major minor patchlevel; do + ovar="o$var" + [ "${!var}" == "${!ovar}" ] || exit 1 + done + array_eq prelease oprelease || exit 1 + if [ "$operator" == same ]; then + array_eq metadata ometadata || exit 1 + fi + exit 0 + ;; + ne|diff) + for var in valid major minor patchlevel; do + ovar="o$var" + [ "${!var}" != "${!ovar}" ] && exit 0 + done + ! array_eq prelease oprelease && exit 0 + if [ "$operator" == diff ]; then + ! array_eq metadata ometadata && exit 0 + fi + exit 1 + ;; + lt) + [ -z "$valid" -a -z "$ovalid" ] && exit 1 + [ "$major" -lt "$omajor" ] && exit 0 + [ "$major" -gt "$omajor" ] && exit 1 + [ "$minor" -lt "$ominor" ] && exit 0 + [ "$minor" -gt "$ominor" ] && exit 1 + [ "$patchlevel" -lt "$opatchlevel" ] && exit 0 + [ "$patchlevel" -gt "$opatchlevel" ] && exit 1 + # XXX tester prelease + exit 1 + ;; + le) + [ -z "$valid" -a -z "$ovalid" ] && exit 1 + [ "$major" -lt "$omajor" ] && exit 0 + [ "$major" -gt "$omajor" ] && exit 1 + [ "$minor" -lt "$ominor" ] && exit 0 + [ "$minor" -gt "$ominor" ] && exit 1 + [ "$patchlevel" -lt "$opatchlevel" ] && exit 0 + [ "$patchlevel" -gt "$opatchlevel" ] && exit 1 + # XXX tester prelease + exit 0 + ;; + gt) + [ -z "$valid" -a -z "$ovalid" ] && exit 1 + [ "$major" -lt "$omajor" ] && exit 1 + [ "$major" -gt "$omajor" ] && exit 0 + [ "$minor" -lt "$ominor" ] && exit 1 + [ "$minor" -gt "$ominor" ] && exit 0 + [ "$patchlevel" -lt "$opatchlevel" ] && exit 1 + [ "$patchlevel" -gt "$opatchlevel" ] && exit 0 + # XXX tester prelease + exit 1 + ;; + ge) + [ -z "$valid" -a -z "$ovalid" ] && exit 1 + [ "$major" -lt "$omajor" ] && exit 1 + [ "$major" -gt "$omajor" ] && exit 0 + [ "$minor" -lt "$ominor" ] && exit 1 + [ "$minor" -gt "$ominor" ] && exit 0 + [ "$patchlevel" -lt "$opatchlevel" ] && exit 1 + [ "$patchlevel" -gt "$opatchlevel" ] && exit 0 + # XXX tester prelease + exit 0 + ;; + esac + +elif [ "$action" == update ]; then + if [ -n "$file" -a ! -f "$file" ]; then + ask_yesno "Le fichier $(ppath "$file") n'existe pas. Faut-il le créer?" O || die + fi + + # incrémenter les numéros de version + [ "$incversion" == auto ] && incversion=minor #menu + case "$incversion" in + menu) eerror "non implémenté";; #XXX + major) semver_incmajor major minor patchlevel prelease metadata;; + minor) semver_incminor major minor patchlevel prelease metadata;; + patchlevel) semver_incpatchlevel major minor patchlevel prelease metadata;; + esac + + # spécifier prerelease + if [ -n "$setfinal" ]; then + semver_setprelease "" major minor patchlevel prelease metadata + elif [ -n "$setprelease" ]; then + semver_setprelease "$setprelease" major minor patchlevel prelease metadata + fi + if [ -n "$setalpha" ]; then + : + elif [ -n "$setbeta" ]; then + : + elif [ -n "$setrc" ]; then + : + fi + + # spécifier metadata + [ -n "$setmetadata" ] && semver_setmetadata "$setmetadata" major minor patchlevel prelease metadata + + semver_setvar version major minor patchlevel prelease metadata + if [ -n "$file" ]; then + echo "$version" >"$file" + fi + estepn "La nouvelle version est $version" +fi + +exit 0