From 2aeba5cb28cdc3e9723ce6457243878fcf949174 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Fri, 3 Jan 2020 16:30:33 +0100 Subject: add dv --- bin/dv | 556 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bin/wd | 239 ++++++++++++++++++++++++++++ 2 files changed, 795 insertions(+) create mode 100755 bin/dv create mode 100755 bin/wd diff --git a/bin/dv b/bin/dv new file mode 100755 index 0000000..0737295 --- /dev/null +++ b/bin/dv @@ -0,0 +1,556 @@ +#!/bin/sh + +# Local dv (aka sim) + +dv_version='dv-0.23 Copyright 2013-2014 Marc Vertes, Philippe Bergheaud' +unset CDPATH +export LC_ALL=C IFS=' +' + +anc() { + usage 'anc [Version]' 'print ancestor' && return + getdv .dv || die no item + case $1 in + (""|.|$PWD) + [ "$Opt_N" ] && Opt_N=$(($Opt_N - 1)) + [ "$Opt_N" ] && info anc $R2 $Opt_N || echo $R2 + ;; + (*) info anc $1 ${Opt_N:-1};; + esac +} + +cache() { + isremote "$1" || return + o=$1 _d=$cacheprefix/$1; pd=${_d%/*}; ppd=${pd%/*} t=$pd/.${_d##*/} + [ -d "$ppd" ] || mkdir -p "$ppd" + [ -L "$ppd/.dv." ] || ln -s . "$ppd/.dv." + [ -f "$_d/.dv" ] && return + [ -d "$pd" ] || mkdir -p "$pd" + set -- $pd/*; shift $(($# - 1)) + [ -d "$1" ] && ropt="--link-dest=../${1##*/}" || ropt= + rsync -aDS $ropt "$o/" "$t" && mv "$t" "$_d" +} + +client() { + usage 'client [Version]' 'print clients' && return + getdv .dv || die no item + case $1 in + (""|.|$PWD) + while getpdir .dv .. + do + cd "$R1" + getdv ".dv" + [ "$OptM" ] && echo "$R2 -> $PWD" || echo "$R2" + ((Opt_N = Opt_N-1)) || break + done ;; + (*) info cli $1 ${Opt_N:-9999} | sort -u ;; + esac +} + +clone() { + usage 'clone [-A Author] [-m Msg] Version [Dir]' \ + 'Copy version to dir' && return + [ "$1" ] || die missing argument + [ "$3" ] && die too many arguments + getpdir .dv . && root=$R1 && getdv $root/.dv && lib=$R1 item=${R2%/*} + new_version=${1##*/} + if [ "$new_version" = "$1" ] + then + getdv .dv && lib=$R1 new_item=${R2%/*} || die no item + else + v=${1%/$new_version} + new_item=${v##*/} + [ "$new_item" = "$v" ] || new_lib=${v%/$new_item} + fi + [ "$PWD" = "$root" ] && [ "$item" != "$new_item" ] && + die "already a clone of $item" + lib=${lib:-$new_lib} + [ "$lib" ] || die no lib + [ "$new_lib" -a "$new_lib" != "$lib" ] && die "already in lib: $lib" + if [ "$new_version" = 0 ] # Init item in lib + then + initlib "$lib" + getdvinfo "$lib" || die "could not lock $lib" + mkdir -p /tmp/dv.$$/$new_item/0 + dv_write $new_item/0 $new_item/0 >/tmp/dv.$$/$new_item/0/.dv + { cat /tmp/dv.$$/$new_item/0/.dv; echo; } >>/tmp/dv.$$/.dvinfo.0 + ln -f /tmp/dv.$$/.dvinfo.0 /tmp/dv.$$/.dvinfo.1 + rsync -a /tmp/dv.$$/ "$lib" + fi + if [ ! "$2" ] # Use the item name as default directory (a la git) + then + set -- "$1" "${1%/*}" + set -- "$1" "${2##*/}" + echo cloning into "$2" + fi + [ ! -d "$2" ] && mkdir "$2" + cd "$2" || die cannot chdir to "$2" + # Rename existing supplier directories to allow supplier symlinks + find . -name .dv | while read l + do + l=${l%/.dv} && [ "$l" = "." ] && continue + mv "$l" "$l.dvold" + done + # Sync from lib, starting from most client item, up to last supplier + d=. s=$new_item/$new_version + while [ "$d" ] + do + [ -L "$d" ] && { + rm -f "$d" + [ -d "$d.dvold" ] && mv "$d.dvold" "$d" + } + cache "$lib/$s" && pref=$cacheprefix/ || pref= + rsync -aDS "$pref$lib/$s/" "$d" + dv_header "$d" Anc "$s" + dv_header "$d" Lib "$lib" + # Find next symlink pointing to a supplier + d= l= + eval "$(find . -type l | while read -r l + do + f=$(readlink "$l") + case $f in + (*/.dv./*) echo "d=\"$l\" s=\"${f#*/.dv./}\"" + break;; + esac + done)" + done +} + +desc() { + usage 'desc [Version]' 'print descendants' && return + getdv .dv || die no item + case $1 in + (""|.|$PWD) ;; + (*) info desc $1 ;; + esac +} + +die() { echo "$0: fatal: $@" >&2; exit 1; } + +diff() { + usage 'diff [-x pat] [-F File] [V1 [V2]]' 'Print differences' && return + getpdir .dv && getdv "$R1/.dv" || die not in a clone + lib=$R1 item=${R2%/*} old=${R2#*/} + [ "$lib" = "" ] && [ -L "../../.dv." ] && lib=${PWD%/*/*} + if [ "$2" ] + then + case $2 in + (.) new=. new_is_clone=1 ;; + (*) new=$lib/$item/$2 ;; + esac + cache "$new" && np=$cacheprefix/ || np= + old=$lib/$item/$1 + elif [ "$1" ] + then + case $1 in + (.) new=. new_is_clone=1 ;; + (*) new=$lib/$item/$1 ;; + esac + cache "$new" && np=$cacheprefix/ || np= + getdv "$np$new/.dv" && old=${R1:-$lib}/$R2 + else + new=$PWD new_is_clone=1 + cache "$new" && np=$cacheprefix/ || np= + getdv "$np$new/.dv" && old=${R1:-$lib}/$R2 + fi + cache "$old" && op=$cacheprefix/ || op= + [ "$Opts" ] || printf "old %s\nnew %s\n" "$old" "$new" + # Itemized diff for all suppliers, deepest first + cd "$np$new" && for f in $(find . -type f -name .dv | sort -r) + do + d=${f%/.dv} + [ "$d" = . ] && prefix= || prefix=${d#./}/ + getdv "$f" && cache "$lib/$R2" + [ "$new_is_clone" ] && dest=$np$d || dest=$prefix$d + diffdir "$op$lib/$R2" "$dest" + Optx="$Optx --exclude=${d##*/}" + done +} + +# Usage: diffdir oldpath newpath +# Print differences between 2 directories +diffdir() { + [ -f "$2/.dvignore" ] && xf=--exclude-from=$2/.dvignore || xf= + rsync -aDSniv $xf --delete --exclude=".dv.*/" $Optx "$2/" "$1" | + awk -v OptF="${OptF#./}" -v prefix=$prefix ' + NF == 0 {exit} + NR < 2 || /\/*\.dv$/ || /\/$/ {next} + # Match an itemized status for all versions of rsync -i + $1 !~ /^[<>ch.*][fdLDS+][.+?cstpoguaxz]+$/ {next} + {key = $1; file = substr($0, length(key) + 2)} + OptF && OptF != file {next} + key == "*deleting" {print "deleted " prefix file; next} + substr(key, 3, 7) == "+++++++" {print "created " prefix file; next} + { # Avoid false positive if only mtime is changed. + of = "'$1'/" file; gsub("'\''", "'\'\\\\\'\''", of) + nf = "'$2'/" file; gsub("'\''", "'\'\\\\\'\''", nf) + if (substr(key, 2, 1) == "L") { # Symlink + src = target = file + sub(/.* -> /, "", target); + sub(/ -> .*/, "", src); + "readlink '$2'/" src | getline otarget + if (target != otarget) + print "changed " src + } else if (system("cmp -s '\''" of "'\'\ \''" nf "'\''")) + print "changed " file + }' + # Or: scan key for file/link size, checksum or permission change + # rsync -c is required + #key ~ /[cps]/ { print "changed " prefix file }' +} + +dv_write() { + cat <<- EOT + From $(username) + Date: $(date +"%F %T %z") + Version: $1 + Anc: $2 + EOT + + [ "$OptA" ] && echo "Author: $OptA" + printf "\n%s\n" "$Optm" +} + +dv_header() { + awk -v val="$3" '/^'$2':/ {print "'$2': " val; done = 1; next} + NF == 0 && done == 0 {print "'$2': " val; done = 1} + {print}' $1/.dv >$1/.dv.$$ && mv $1/.dv.$$ $1/.dv +} + +# Usage: getdv path +# Return lib in R1, anc in R2, version in R3 +getdv() { + R1= R2= + while read -r line + do + case $line in + ("Lib: "*) R1=${line#Lib: };; + ("Anc: "*) R2=${line#Anc: };; + ("Version: "*) R3=${line#Version: };; + esac + done <$1 + [ "$R1" -o "$R2" ] +} + +# Usage: getdvinfo lib +# Get exclusive write access to a dvlib, for new version commit +getdvinfo() { + [ -d /tmp/dv.$$ ] || mkdir /tmp/dv.$$ + [ "$Optn" ] && { rsync "$1/.dvinfo.1" /tmp/dv.$$/.dvinfo.0; return; } + t=0 + for i in 1 2 3 4 5 + do + rsync --remove-source-files "$1/.dvinfo.0" /tmp/dv.$$/ && break + t=$(($t + $i)) && sleep $t + done + test -f /tmp/dv.$$/.dvinfo.0 +} + +putdvinfo() { + [ "$Optn" ] && return + ln -f /tmp/dv.$$/.dvinfo.0 /tmp/dv.$$/.dvinfo.1 + rsync -a /tmp/dv.$$/.dvinfo.[01] "$1/" + isremote "$1" && cp /tmp/dv.$$/.dvinfo.0 "$cacheprefix/$1/" +} + +# Usage: getpdir file [path] +# Return absolute parent dir of path or PWD containing file +getpdir() { + R1="$([ -d "${2:-.}" ] && cd "${2:-.}" && pwd)" + while [ "$R1" ] + do + [ -f "$R1/$1" ] && return || R1=${R1%/*} + done + return 1 +} + +# Usage: getprd file [path] +# Return relative path to parent directory containing file +getprd() { + R2="$(cd "${2:-.}" && pwd)"; R2=${R2%/*} R1=.. + while [ "$R2" ] + do + [ -f "$R2/$1" ] && return || R2=${R2%/*} R1=$R1/.. + done + return 1 +} + +info() { + usage info && return + case $2 in + (*:*) lib=${2%/*/*} ;; + (*) getpdir .dv && getdv $R1/.dv && lib=$R1 || die no dvlib found + esac + case $2 in # $2 specifies: + (*/*/*) v=${2%/*/*}; v=${2#$v/} ;; # lib/item/version + (*/*) v=$2 ;; # item/version + (*) v=${R2%/*}/$2 ;; # version + esac + isremote "$lib" && { + p=$cacheprefix + [ -d "$p/$lib" ] || mkdir -p "$p/$lib" + [ "$Optl" ] || rsync -a "$lib/.dvinfo.1" "$p/$lib/" + } || p= + info_query $1 $v $3 <$p/$lib/.dvinfo.1 +} + +info_query() +{ + awk -v arg0=$1 -v arg1=$2 -v arg2=$3 ' + /^From / { + if (v) msg[v] = var["Body"] + delete var + var["From"] = substr($0, 6) + header = 1 + next + } + header == 1 && NF == 0 { + v = var["Version"]; a = var["Anc"]; s = var["Sup"] + if (a != v) { + anc[v] = a; desc[a] = desc[a] ? desc[a] " " v : v + } + n = split(s, as) + for (i = 1; i <= n; i++) { + cli[as[i]] = cli[as[i]] ? cli[as[i]] " " v : v + } + sup[v] = s; msg[v] = var["Msg"]; date[v] = var["Date"] + if (var["Root"]) root[v] = var["Root"] + header = 0 + next + } + header == 1 { + i = index($0, ":") + var[substr($0, 1, i-1)] = substr($0, i+2) + next + } + header == 0 { + if ($0 ~ />+From /) sub(/>/, "") + var["Body"] = var["Body"] ? var["Body"] "\n" $0 : $0 + } + END { + if (v) msg[v] = var["Body"] + if (arg0 == "graph") anc_graph() + else if (arg0 == "anc") query_anc(arg1, arg2) + else if (arg0 == "sup") query_sup(arg1, arg2) + else if (arg0 == "cli") query_cli(arg1, arg2) + else if (arg0 == "log") print msg[arg1] + else if (arg0 == "from") print from[arg1] + else if (arg0 == "date") print date[arg1] + else if (arg0 == "desc") printl(desc[arg1]) + } + function anc_graph() { + print "digraph G {" + for (v in anc) print "\"" anc[v] "\" -> \"" v "\"" + print "}" + } + function query_anc(v, n) { while (n-- > 0) v = anc[v]; print v } + function query_cli(v, n) { + while (n-- > 0 && v != "") { + num = split(v, av); v = "" + for (i = 1; i <= num; i++) { + printl(cli[av[i]]) + v = v ? v " " cli[av[i]] : cli[av[i]] + } + } + } + function query_sup(v, n) { + while (n-- > 0 && v != "") { + num = split(v, av); v = "" + for (i = 1; i <= num; i++) { + printl(sup[av[i]]) + v = v ? v " " sup[av[i]] : sup[av[i]] + } + } + } + function printl(l, i, n) { + n = split(l, al) + for (i = 1; i <= n; i++) + if (root[al[i]]) + print al[i] " -> '"$PWD/"'" root[al[i]] + else + print al[i] + }' +} + +initlib() { + rsync --list-only "$1/.dv." >/dev/null 2>&1 && return + mkdir -p /tmp/dv.$$ + ln -s . /tmp/dv.$$/.dv. + >/tmp/dv.$$/.dvinfo.0 + >/tmp/dv.$$/.dvinfo.1 + chmod g+w /tmp/dv.$$/.dvinfo.[01] + rsync -a /tmp/dv.$$/ "$1" +} + +isremote() { case $1 in (*:*) return;; esac; return 1; } + +help_all() { + printf "$dv_version\nUsage: dv command [options] [args]\n" + Opth=1; for c in $Cmdlist; do $c; done +} + +newversion() { + [ "$OptV" ] && R1=$OptV && return + case $OptB in + ('') R1=$1 ;; + (*[-/]*) die 'illegal character [-/] in branch name' ;; + (*[0-9.]) die 'illegal end character [0-9.] in branch name' ;; + (*) case $1 in (*-${OptB}[1-9]*) R1=$1;; (*) R1=$1-${OptB}0;; esac ;; + esac + R2=${R1##*[!0-9]} + optb=$Optb + [ "$optb" ] && R3=.1 optb=${optb%b} || R3= R1=${R1%$R2}$(($R2 + 1)) + while [ "${optb}" ]; do optb=${optb%b} R3=.0$R3; done + R1=$R1$R3 + while grep -q "^Version: $item/$R1\$" /tmp/dv.$$/.dvinfo.0 + do + R2=${R1##*[!0-9]} + [ "$R2" ] && R1=${R1%$R2}$(($R2 - 1)).1 || R1=${R1}1 + done +} + +patch() { + usage 'patch [-x pat] [-F File] [Dir|Version]' \ + 'Print patch diff from ancestor' && return + diff "$@" | awk -v wdflag="-v${OptD:+3}${OptN:+1}${OptO:+2}" ' + { key = $1; file = substr($0, length(key) + 2) } + key == "old" { old = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } + key == "new" { new = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } + { + of = old "/" file; gsub("'\''", "'\'\\\\\'\''", of) + nf = new "/" file; gsub("'\''", "'\'\\\\\'\''", nf) + system("diff -Naup '\''" of "'\'\ \''" nf "'\''; echo") + }' +} + +save() { + usage 'save [-bln] [-A Author] [-m msg] [-x pat] [-B Branch|-V Version] [Dir]'\ + 'Save dir into a new version' && return + [ "$2" ] && die 'too many arguments' + getpdir .dv "$1" || die "not in a clone: ${1:-$PWD}" && root=$R1 + cd "$root" + getdv .dv && lib=$R1 && getdvinfo "$lib" + # Process supplier subdirs from the deepest up to "." (most client) + for d in $(find . -type f -name .dv | sort -r) + do + d=${d%/.dv} + cd "$root/$d" + OptF= save1 "$d" && nanc=$R1 && echo $nanc + [ "$d" = . ] && continue # not a supplier, done + + # Replace supplier subdir by symlink in lib + getprd .dv && s=$R1/../.dv./$nanc + t=${d##*/}; t=.dv.$t + mv "$root/$d" "$root/${d%/*}/$t" + ln -s "$s" "$root/$d" + done + putdvinfo "$lib" + # Restore supplier subdirs and remove links to lib, deepest first + find . -type d -name ".dv.*" | sort -r | while read d + do + t=${d##*/.dv.}; t=${d%/*}/$t + rm -f "$t" && mv "$d" "$t" + done +} + +save1() { + getdv .dv && lib=$R1 anc=$R2 && item=${anc%/*} old=${anc#*/} + [ "$(diffdir "$lib/$anc" .)" ] || { R1=$anc; return; } + newversion $old && new=$item/$R1 + [ "$Optn" ] || dv_write "$new" "$anc" >.dv + # Compute list of direct suppliers, for caching in .dvinfo.0 + lsup=$(find . -type l | while read -r l + do + case $l in (*/.dv.*) continue ;; esac + f=$(readlink "$l") + case $f in (*/.dv./*) printf "%s " ${f#*/.dv./} ;; esac + done) + [ ! "$Optn" ] && [ "$lsup" ] && dv_header . Sup "$lsup" + { cat .dv; echo; } >>/tmp/dv.$$/.dvinfo.0 + [ -f ".dvignore" ] && xf=--exclude-from=.dvignore || xf= + rsync -aDS$Optn$Optv --link-dest=../$old $xf --exclude=".dv.*/" \ + ./ "$lib/$new" + R1=$new + [ "$Optn" ] && return + isremote "$lib" && rsync -aDS --link-dest=../$old --exclude=".dv.*/" \ + ./ "$cacheprefix/$lib/$new" + dv_header . Anc "$new" + dv_header . Lib "$lib" +} + +supplier() { + usage 'supplier [Version]' 'print suppliers' && return + getdv .dv || die no item + case $1 in + (""|.|$PWD) # create a temporary dvinfo file, query it + V=$R2; find . -name .dv | + while read dv + do + [ "$dv" = "./.dv" ] && continue + # get the supplier + getdv "$dv"; v=$R2; dv=${dv%/.dv} + # get the client + getpdir .dv "${dv%/*}"; getdv "$R1/.dv" + # declare the client as supplier + printf "From \nVersion: %s\nSup: %s\n" "$v" "$R2" + [ "$OptM" ] && echo "Root: ${dv#./}"; echo + done | info_query cli $V ${Opt_N:-9999} ;; + (*) info sup $1 ${Opt_N:-9999} ;; + esac | sort -u +} + +usage() { [ "$Opth" ] && printf " %-34s %s\n" "$1" "$2"; } + +username() { + case $(uname -s) in + (Darwin) id -P | awk -v FS=: '{print $8}' ;; + (*) awk -v FS=[:,] '/^'$USER':/ {print $5; exit}' /etc/passwd ;; + esac +} + +wdiff() { + usage 'wdiff [-DNO] [-x pat] [-F File] [Dir|Version]' \ + 'Print word diffs from ancestor' && return + diff "$@" | + awk -v wdflag="-v${OptD:+3}${OptN:+1}${OptO:+2}" ' + { key = $1; file = substr($0, length(key) + 2) } + key == "old" { old = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } + key == "new" { new = (file ~ /:/ ? "'$cacheprefix'/" : "") file; next } + { + of = old "/" file; gsub("'\''", "'\'\\\\\'\''", of) + nf = new "/" file; gsub("'\''", "'\'\\\\\'\''", nf) + system("wd " wdflag " '\''" of "'\'\ \''" nf "'\''; echo") + }' | less -FCimnqGrX -j5 -h0 +/'\[0' +} + +cacheprefix=$HOME/.cache/dv +Cmdlist='anc client clone desc diff info patch save supplier wdiff' +while getopts :nvV opt # Parse global options +do + case $opt in + (V) echo "$dv_version"; exit ;; + (*) help_all; exit ;; + esac +done +shift $((OPTIND - 1)) +[ $1 ] && C=$1 && shift 1 || { help_all; exit 1; } +for c in $Cmdlist +do + case $c in + ($C|_$C) cmd=$c; break;; + ($C*) [ $cmd ] && die ambiguous command $C || cmd=$c;; + esac +done +while getopts :0123456789A:bB:F:hl:m:MnNOsvV:x: opt # Parse command options +do + case $opt in + ([0-9]) Opt_N=$Opt_N$opt;; + ([bhlnMNOsv]) eval Opt$opt=\${Opt$opt}$opt ;; + ([ABFmV]) eval Opt$opt=\$OPTARG ;; + (x) Optx="$Optx --exclude=$OPTARG" ;; + (*) Opth=1; $cmd; exit 1;; + esac +done +shift $(($OPTIND - 1)) +trap "rm -rf /tmp/dv.$$" EXIT +[ "$cmd" ] || die "no command \"$C\"" && $cmd "$@" diff --git a/bin/wd b/bin/wd new file mode 100755 index 0000000..74e67de --- /dev/null +++ b/bin/wd @@ -0,0 +1,239 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2008 Oligem.com. All rights reserved. +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +# wd, word diff + +usage() +{ + echo 'NAME + wd - word differences +SYNOPSIS + wd [-123NvV] [-w|-x|-y|-z Mark] Oldfile Newfile +OPTIONS + -1 inhibit output of deleted words + -2 inhibit output of inserted words + -3 inhibit output of common words + -N treat absent files as empty + -O Optimal markup. Default marks are redundant, but + make less(1) happy for colorizing. + -V print version and exit + -v print a header line with old and new filenames. + -w Mark mark beginning of old word + -x Mark mark end of old word + -y Mark mark beginning of new word + -z Mark mark end of new word +AUTHORS + Philippe Bergheaud and Marc Vertes' >&2 + + exit 2 +} + +istextfile() +{ + diff /dev/null "$1" 2>/dev/null | awk '/differ/ {exit 1} {exit 0}' +} + +opt1=0 opt2=0 opt3=0 +red=""; red="$red[01;31m" +blue=""; blue="$blue[01;34m" +green=""; green="$green[01;35m" +white=""; white="$white[00m" +opth=$green optw=$blue optx=$white opty=$red optz=$white + +while getopts :123L:NOuVvw:x:y:z: opt +do + case $opt in + ([123NOuv]) eval opt$opt=1 ;; + ([Lwxyz]) eval "opt$opt=\"$OPTARG\"" ;; + (V) echo wd-0.6; exit ;; + (*) usage ;; + esac +done +shift $((OPTIND - 1)) + +[ $# -eq 2 ] || usage + +if [ $opt1 -eq 0 -a $opt2 -ne 0 ] +then + # show old: swap files and colors + old_file=$2 new_file=$1 + opt2=0 opt1=1 opty=$optw optz=$optx +else + old_file=$1 new_file=$2 +fi + +[ -f "$old_file" ] || { [ "$Optn" ] && old_file=/dev/null || exit; } +[ -f "$new_file" ] || { [ "$OptN" ] && new_file=/dev/null || exit; } +istextfile $new_file || exit + +old_word_file=/tmp/old_word_file.$$ +trap 'rm -f $old_word_file' EXIT + +tr -s '[:blank:]' '\n' <$old_file >$old_word_file +tr -s '[:blank:]' '\n' <$new_file | +diff -B --new-group-format='n %dF %dL +' --old-group-format='o %df %dl +' --line-format= "$old_word_file" - | +awk -v optv="$optv" -v new_file="$new_file" -v old_file="$old_file" \ + -v optO="$optO" -v opt1=$opt1 -v opt2=$opt2 -v opt3=$opt3 -v opth="$opth" \ + -v optw="$optw" -v optx="$optx" -v opty="$opty" -v optz="$optz" ' +function print_tblank(file, blank1, display, resume) +{ + if (!display) return + if (tblank ~ /\n/) { + if (prev_file != file && resume) { + if (blank1) + printf("%s%s", tblank, blank1) + else + printf(" ") + } else + printf("%s%s", tblank, blank1) + } else { + if (prev_file && prev_file != file) + printf(" ") + else + printf("%s%s", tblank, blank1) + } + prev_file = file + tblank = "" +} + +function print_word(file, begin, end, blank, word, display, bmark, emark) +{ + bmark_printed = 0 + emark_toprint = 0 + while (W[file] != end) { + if (i[file] > nf[file]) { + if (getline line nf[file]) { + if (W[file] == end) + tblank = tblank "\n" + else { + print "" + tblank = "" + } + } + if (W[file] == end) + break + } + if (emark_toprint) + printf("%s", emark) + w[file] = end + 1 +} + +function same_group(begin, end) +{ + print_word(old_file, w[old_file], w[old_file] + end - begin, + oblank, oword, 0, "", "") + print_word(new_file, begin, end, nblank, nword, !opt3, "", "") +} + +function old_group(begin, end) +{ + if (begin > w[old_file]) + same_group(w[new_file], w[new_file] + begin - w[old_file] - 1) + print_word(old_file, begin, end, oblank, oword, !opt1, optw, optx) +} + +function new_group(begin, end) +{ + if (begin > w[new_file]) same_group(w[new_file], begin - 1) + print_word(new_file, begin, end, nblank, nword, !opt2, opty, optz) +} + +BEGIN { + # index of old word read, to be printed + W[old_file] = 0; w[old_file] = 1 + + # index of new word read, to be printed + W[new_file] = 0; w[new_file] = 1 + + # initialize the for loop on old lines + i[old_file] = 0; nf[old_file] = -1 + + # initialize the for loop on new lines + i[new_file] = 0; nf[new_file] = -1 + + oblank[1] = ""; oword[1] = "" # old blank and old word arrays + nblank[1] = ""; nword[1] = "" # new blank and new word arrays + + # trailing blank, replaced by a space after the last word + # of an old group when followed by a word on the same line + tblank = "" + prev_file = "" # previous file + if (optv) print opth "### wd " optw old_file " " opty new_file optx +} +/^n/ { new_group($2, $3) } # display common and new words +/^o/ { old_group($2, $3) } # display common and old words +END { # display common words, up to the end of file + if ((!opt1 || !opt2) && opt3) + print "" + print_word(new_file, w[new_file], -1, nblank, nword, !opt3, "", "") +}' | +less -CimqGrX -j9 -h0 +/'\[01'; echo -n '' +echo -n '' # force color reset -- cgit v1.2.3