#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS

function display_help() {
    uecho "$scriptname: lancer une commande pour un ensemble d'arguments

USAGE
    $scriptname [options] [VAR=]expr cmd...
    $scriptname [options] [VAR=]items... -- cmd...

La commande est lancée avec la variable 'VAR', qui vaut par défaut 'item',
définie à la valeur courante de l'énumération. De plus, la variable 'index'
reçoit une valeur incrémentale commençant à 0.

La deuxième syntaxe appelée syntaxe alternative permet de spécifier un ensemble
d'éléments directement, mais nécessite l'utilisation du séparateur '--' pour
identifier où s'arrête la liste des éléments.

En plus de VAR, les variables file, dir, name, basename, ext et dotext sont
définies. Elle valent respectivement le chemin absolu du fichier, le répertoire
absolu du fichier, le nom du fichier, le nom de base sans extension, l'extension
sans le point et l'extension avec le point, e.g pour item=dir/file.ext on a
file=/path/to/dir/file.ext, dir=/path/to/dir, name=file.ext, basename=file,
ext=ext, dotext=.ext

Les 3 exemples suivants sont équivalents:
    $scriptname '*.c' cp %item dest/dir
    $scriptname item='*.c' cp %item dest/dir
    $scriptname *.c -- cp %item dest/dir

OPTIONS
    -b, --basedir BASEDIR
        Chercher les expressions -d, -f, -a à partir de BASEDIR au lieu du
        répertoire courant.
    -d, --dir
    -f, --file
    -a, --all
    -s, --string
        Ces options permettent de spécifier le type d'expression et la façon de
        les traiter. Avec -d, faire la correspondance de l'expression sur les
        répertoires uniquement. Avec -f, faire la correspondance sur les fichier
        uniquement. Avec -a, faire la correspondance sur les répertoires et les
        fichiers. Avec -s, évaluer l'expression avec la fonction 'eval' du
        shell.
        Si la syntaxe alternative est utilisée, le premier élément est évalué
        conformément à ces options *si et seulement s'il* est précédé d'une
        chaine 'VAR='. Les autres éléments sont pris tels-quels. e.g:
        cette commande affiche uniquement la chaine '*.c':
            $scriptname '*.c' --
        alors que celle-ci liste les fichiers qui ont l'extension '.c':
            $scriptname 'item=*.c' --
    -p, --parent
        Pour chaque fichier/répertoire, se placer dans le répertoire parent
        avant de lancer la commande. item est aussi modifié pour ne plus
        contenir que le nom de la cible (qui est désormais dans le répertoire
        courant)
        Cette option a la priorité sur --go
    -g, --go
        Si la cible est un répertoire, en faire le répertoire courant avant de
        lancer la commande. Si la cible est un fichier, faire comme --parent et
        se placer dans le répertoire du fichier avant de lancer la commande.
        Dans les deux cas, item est modifié pour ne plus contenir que le nom de
        la cible, soit '.' pour le répertoire courant, soit le nom du fichier
        dans le répertoire courant.
        Si cette option est mentionnée seule, elle implique --dir
    -x, --expand
    -n, --no-expand
        Reconnaitre et traiter (resp. ne pas reconnaitre) la syntaxe %var dans
        les arguments. C'est le cas par défaut, ce qui permet de simplifier
        l'écriture d'une ligne de commande. Pour écrire le caractère '%', il
        suffit de le doubler e.g %%item
        Si l'expansion est désactivée, il faut protéger le caractère \$ pour
        qu'il soit traité, e.g, avec les examples ci-dessus:
            $scriptname -n '*.c' 'cp \"\$item\" dest/dir'
    -t, --title
    --no-title
        Afficher (resp. ne pas afficher) chaque correspondance avant de lancer
        la commande. Par défaut, l'affichage est effectué."
}

basedir=
match=auto
changedir=
parentdir=
expand=1
title=1
args=(+
    --help '$exit_with display_help'
    -b:,--basedir: basedir=
    -d,--dir match=dir
    -f,-file match=file
    -a,--all match=all
    -p,--parent parentdir=1
    -g,--go changedir=1
    -s,--string match=string
    -x,--expand expand=1
    -n,--no-expand expand=
    -t,--title title=1
    --no-title title=
)
parse_args "$@"; set -- "${args[@]}"

if [ "$match" == auto ]; then
    if [ -n "$changedir" ]; then
        match=dir
    else
        match=all
    fi
fi

alt_syntax=
for sep in "$@"; do
    if [ "$sep" == -- ]; then
        alt_syntax=1
        break
    fi
done
suppl_items=()
varexpr="$1"; shift
if [ -n "$alt_syntax" ]; then
    while [ $# -gt 0 ]; do
        [ "$1" == -- ] && break
        array_add suppl_items "$1"
        shift
    done
    [ "$1" == -- ] && shift
fi
splitfsep2 "$varexpr" = varname expr
if [ -n "$alt_syntax" ]; then
    if [ -n "$varname" ]; then
        [ -n "$expr" ] || expr="*"
    elif [[ "$varexpr" == *=* ]]; then
        [ -n "$varname" ] || varname=item
        [ -n "$expr" ] || expr="*"
    else
        items=("$varexpr")
        varname=item
        expr=
    fi
else
    [ -n "$varname" ] || varname=item
    [ -n "$expr" ] || expr="*"
fi
if [ -n "$expr" ]; then
    if [ -n "$basedir" ]; then
        case "$match" in
        all) array_lsall items "$basedir" "$expr";;
        dir) array_lsdirs items "$basedir" "$expr";;
        file) array_lsfiles items "$basedir" "$expr";;
        string) eval "items=($expr)";;
        esac
    else
        basedir=.
        case "$match" in
        all) array_from_lines items "$(list_all "$basedir" "$expr")";;
        dir) array_from_lines items "$(list_dirs "$basedir" "$expr")";;
        file) array_from_lines items "$(list_files "$basedir" "$expr")";;
        string) eval "items=($expr)";;
        esac
    fi
fi
array_extend items suppl_items

cmd=("$@")
if [ ${#cmd[*]} -eq 0 ]; then
    if [ -n "$expand" ]; then
        cmd=(echo %item)
    else
        cmd=('echo "$item"')
    fi
fi

[ -n "$title" ] && einfo "${#items[@]} correspondance(s) trouvée(s)"
let index=0
for item in "${items[@]}"; do
    [ -n "$title" ] && etitle "$item"
    i="$item"
    setx file=abspath "$item"
    setx dir=dirname -- "$file"
    setx name=basename -- "$file"
    splitname "$name" basename ext
    [[ "$name" == *.* ]] && dotext=".$ext" || dotext=
    # ne pas changer les noms des variables sans modifier le script awk
    # ci-dessous
    __VARS=(i file dir name basename ext dotext)
    export "${__VARS[@]}"
    (
        if [ -n "$changedir" ]; then
            if [ -f "$item" ]; then
                parentdir=1
            elif [ ! -d "$item" ]; then
                ewarn "$item: n'est pas un répertoire"
                changedir=
            fi
        fi
        if [ -n "$parentdir" ]; then
            cd "$dir"
            item="$name"
        elif [ -n "$changedir" ]; then
            cd "$item"
            item=.
        fi
        i="$item"

        __VALUES=()
        for __VALUE in "${__VARS[@]}"; do
            array_add __VALUES "$__VALUE=${!__VALUE}"
        done

        if [ -n "$expand" ]; then
            array_from_lines cmd "$(awkrun -f "${__VALUES[@]}" xitem="$index" item="$item" varname="$varname" cmd[@] '
function replace_vars(src,             re, vs, dest) {
  re = "(%(" varname "|i|file|dir|name|basename|ext|dotext|index))"
  while (src != "") {
    if (match(src, "%%") != 0) {
      dest = dest substr(src, 1, RSTART)
      src = substr(src, RSTART + RLENGTH)
    } else if (match(src, "%" re) != 0) {
      dest = dest substr(src, 1, RSTART)
      src = substr(src, RSTART + RLENGTH)
    } else if (match(src, re, vs) != 0) {
      dest = dest substr(src, 1, RSTART - 1)
      src = substr(src, RSTART + RLENGTH)
    } else {
      break
    }
    var = vs[2]
    if (var == varname) dest = dest item
    else if (var == "i") dest = dest item
    else if (var == "file") dest = dest file
    else if (var == "dir") dest = dest dir
    else if (var == "name") dest = dest name
    else if (var == "basename") dest = dest basename
    else if (var == "ext") dest = dest ext
    else if (var == "dotext") dest = dest dotext
    else if (var == "index") dest = dest xitem
  }
  if (src != "") dest = dest src
  return dest
}
BEGIN {
  for (i=1; i<=cmd_count; i++) {
    arg = replace_vars(cmd[i])
    print arg
  }
}')"
            eval "export $(echo_setv "$varname" "$item")"
            "${cmd[@]}"
        else
            eval "export $(echo_setv "$varname" "$item"); ${cmd[*]}"
        fi
    )
    [ -n "$title" ] && eend "$item"
    let index=$index+1
done
