reflow

Преобликује пасусе у редове
git clone https://git.sr.ht/~strahinja/reflow
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

do (11096B)


      1 #!/usr/bin/env sh
      2 #
      3 # A minimal alternative to djb redo that doesn't support incremental builds.
      4 # For the full version, visit http://github.com/apenwarr/redo
      5 #
      6 # The author disclaims copyright to this source file and hereby places it in
      7 # the public domain. (2010 12 14; updated 2019 02 24)
      8 #
      9 USAGE="
     10 usage: do [-d] [-x] [-v] [-c] <targets...>
     11   -d  print extra debug messages (mostly about dependency checks)
     12   -v  run .do files with 'set -v'
     13   -x  run .do files with 'set -x'
     14   -c  clean up all old targets before starting
     15 
     16   Note: do is an implementation of redo that does *not* check dependencies.
     17   It will never rebuild a target it has already built, unless you use -c.
     18 "
     19 
     20 # CDPATH apparently causes unexpected 'cd' output on some platforms.
     21 unset CDPATH
     22 
     23 # By default, no output coloring.
     24 green=""
     25 bold=""
     26 plain=""
     27 
     28 if [ -n "$TERM" -a "$TERM" != "dumb" ] && tty <&2 >/dev/null 2>&1; then
     29 	green="$(printf '\033[32m')"
     30 	bold="$(printf '\033[1m')"
     31 	plain="$(printf '\033[m')"
     32 fi
     33 
     34 # The 'seq' command is not available on all platforms.
     35 _seq() {
     36 	local x=0 max="$1"
     37 	while [ "$x" -lt "$max" ]; do
     38 		x=$((x + 1))
     39 		echo "$x"
     40 	done
     41 }
     42 
     43 # Split $1 into a dir part ($_dirsplit_dir) and base filename ($_dirsplit_base)
     44 _dirsplit() {
     45 	_dirsplit_base=${1##*/}
     46 	_dirsplit_dir=${1%$_dirsplit_base}
     47 }
     48 
     49 # Like /usr/bin/dirname, but avoids a fork and uses _dirsplit semantics.
     50 qdirname() (
     51 	_dirsplit "$1"
     52 	dir=${_dirsplit_dir%/}
     53 	echo "${dir:-.}"
     54 )
     55 
     56 _dirsplit "$0"
     57 REDO=$(cd "$(pwd -P)" &&
     58 	cd "${_dirsplit_dir:-.}" &&
     59 	echo "$PWD/$_dirsplit_base")
     60 export REDO
     61 _cmd=$_dirsplit_base
     62 
     63 DO_TOP=
     64 if [ -z "$DO_BUILT" ]; then
     65 	export _do_opt_debug=
     66 	export _do_opt_exec=
     67 	export _do_opt_verbose=
     68 	export _do_opt_clean=
     69 fi
     70 while getopts 'dxvcj:h?' _opt; do
     71 	case $_opt in
     72 		d) _do_opt_debug=1 ;;
     73 		x) _do_opt_exec=x ;;
     74 		v) _do_opt_verbose=v ;;
     75 		c) _do_opt_clean=1 ;;
     76 		j) ;;  # silently ignore, for compat with real redo
     77 		\?|h|*) printf "%s" "$USAGE" >&2
     78 		   exit 99
     79 		   ;;
     80 	esac
     81 done
     82 shift "$((OPTIND - 1))"
     83 _debug() {
     84 	[ -z "$_do_opt_debug" ] || echo "$@" >&2
     85 }
     86 
     87 if [ -z "$DO_BUILT" -a "$_cmd" != "redo-whichdo" ]; then
     88 	DO_TOP=1
     89 	if [ "$#" -eq 0 ] && [ "$_cmd" = "do" -o "$_cmd" = "redo" ]; then
     90 		set all  # only toplevel redo has a default target
     91 	fi
     92 	export DO_STARTDIR="$(pwd -P)"
     93 	# If starting /bin/pwd != $PWD, this will fix it.
     94 	# That can happen when $PWD contains symlinks that the shell is
     95 	# trying helpfully (but unsuccessfully) to hide from the user.
     96 	cd "$DO_STARTDIR" || exit 99
     97 	export DO_BUILT="$PWD/.do_built"
     98 	if [ -z "$_do_opt_clean" -a -e "$DO_BUILT" ]; then
     99 		echo "do: Incremental mode. Use -c for clean rebuild." >&2
    100 	fi
    101 	: >>"$DO_BUILT"
    102 	sort -u "$DO_BUILT" >"$DO_BUILT.new"
    103 	while read f; do
    104 		[ -n "$_do_opt_clean" ] && printf "%s\0%s.did\0" "$f" "$f"
    105 		printf "%s.did.tmp\0" "$f"
    106 	done <"$DO_BUILT.new" |
    107 	xargs -0 rm -f 2>/dev/null
    108 	mv "$DO_BUILT.new" "$DO_BUILT"
    109 	export DO_PATH="$DO_BUILT.dir"
    110 	export PATH="$DO_PATH:$PATH"
    111 	rm -rf "$DO_PATH"
    112 	mkdir "$DO_PATH"
    113 	for d in redo redo-ifchange redo-whichdo; do
    114 		ln -s "$REDO" "$DO_PATH/$d"
    115 	done
    116 	for d in redo-ifcreate redo-stamp redo-always redo-ood \
    117 	    redo-targets redo-sources; do
    118 	        echo "#!/bin/sh" >"$DO_PATH/$d"
    119 		chmod a+rx "$DO_PATH/$d"
    120 	done
    121 fi
    122 
    123 
    124 # Chop the "file" part off a /path/to/file pathname.
    125 # Note that if the filename already ends in a /, we just remove the slash.
    126 _updir()
    127 {
    128 	local v="${1%/*}"
    129 	[ "$v" != "$1" ] && echo "$v"
    130 	# else "empty" which means we went past the root
    131 }
    132 
    133 
    134 # Returns true if $1 starts with $2.
    135 _startswith()
    136 {
    137 	[ "${1#"$2"}" != "$1" ]
    138 }
    139 
    140 
    141 # Returns true if $1 ends with $2.
    142 _endswith()
    143 {
    144 	[ "${1%"$2"}" != "$1" ]
    145 }
    146 
    147 
    148 # Prints $1 if it's absolute, or $2/$1 if $1 is not absolute.
    149 _abspath()
    150 {
    151 	local here="$2" there="$1"
    152 	if _startswith "$1" "/"; then
    153 		echo "$1"
    154 	else
    155 		echo "$2/$1"
    156 	fi
    157 }
    158 
    159 
    160 # Prints $1 as a path relative to $PWD (not starting with /).
    161 # If it already doesn't start with a /, doesn't change the string.
    162 _relpath()
    163 {
    164 	local here="$2" there="$1" out= hadslash=
    165 	#echo "RP start '$there' hs='$hadslash'" >&2
    166 	_startswith "$there" "/" || { echo "$there" && return; }
    167 	[ "$there" != "/" ] && _endswith "$there" "/" && hadslash=/
    168 	here=${here%/}/
    169 	while [ -n "$here" ]; do
    170 		#echo "RP out='$out' here='$here' there='$there'" >&2
    171 		[ "${here%/}" = "${there%/}" ] && there= && break;
    172 		[ "${there#$here}" != "$there" ] && break
    173 		out=../$out
    174 		_dirsplit "${here%/}"
    175 		here=$_dirsplit_dir
    176 	done
    177 	there=${there#$here}
    178 	if [ -n "$there" ]; then
    179 		echo "$out${there%/}$hadslash"
    180 	else
    181 		echo "${out%/}$hadslash"
    182 	fi
    183 }
    184 
    185 
    186 # Prints a "normalized relative" path, with ".." resolved where possible.
    187 # For example, a/b/../c will be reduced to just a/c.
    188 _normpath()
    189 (
    190 	local path="$1" relto="$2" out= isabs=
    191 	#echo "NP start '$path'" >&2
    192 	if _startswith "$path" "/"; then
    193 		isabs=1
    194 	else
    195 		path="${relto%/}/$path"
    196 	fi
    197 	set -f
    198 	IFS=/
    199 	for d in ${path%/}; do
    200 		#echo "NP out='$out' d='$d'" >&2
    201 		if [ "$d" = ".." ]; then
    202 			out=$(_updir "${out%/}")/
    203 		else
    204 			out=$out$d/
    205 		fi
    206 	done
    207 	#echo "NP out='$out' (done)" >&2
    208 	out=${out%/}
    209 	if [ -n "$isabs" ]; then
    210 		echo "${out:-/}"
    211 	else
    212 		_relpath "${out:-/}" "$relto"
    213 	fi
    214 )
    215 
    216 
    217 # Prints a "real" path, with all symlinks resolved where possible.
    218 _realpath()
    219 {
    220 	local path="$1" relto="$2" isabs= rest=
    221 	if _startswith "$path" "/"; then
    222 		isabs=1
    223 	else
    224 		path="${relto%/}/$path"
    225 	fi
    226 	(
    227 		for d in $(_seq 100); do
    228 			#echo "Trying: $PWD--$path" >&2
    229 			if cd -P "$path" 2>/dev/null; then
    230 				# success
    231 				pwd=$(pwd -P)
    232 				#echo "  chdir ok: $pwd--$rest" >&2
    233 				np=$(_normpath "${pwd%/}/$rest" "$relto")
    234 				if [ -n "$isabs" ]; then
    235 					echo "$np"
    236 				else
    237 					_relpath "$np" "$relto"
    238 				fi
    239 				break
    240 			fi
    241 			_dirsplit "${path%/}"
    242 			path=$_dirsplit_dir
    243 			rest="$_dirsplit_base/$rest"
    244 		done
    245 	)
    246 }
    247 
    248 
    249 # List the possible names for default*.do files in dir $1 matching the target
    250 # pattern in $2.  We stop searching when we find the first one that exists.
    251 _find_dofiles_pwd()
    252 {
    253 	local dodir="$1" dofile="$2"
    254 	_startswith "$dofile" "default." || dofile=${dofile#*.}
    255 	while :; do
    256 		dofile=default.${dofile#default.*.}
    257 		echo "$dodir$dofile"
    258 		[ -e "$dodir$dofile" ] && return 0
    259 		[ "$dofile" = default.do ] && break
    260 	done
    261 	return 1
    262 }
    263 
    264 
    265 # List the possible names for default*.do files in $PWD matching the target
    266 # pattern in $1.  We stop searching when we find the first name that works.
    267 # If there are no matches in $PWD, we'll search in .., and so on, to the root.
    268 _find_dofiles()
    269 {
    270 	local target="$1" dodir= dofile= newdir=
    271 	_debug "find_dofile: '$PWD' '$target'"
    272 	dofile="$target.do"
    273 	echo "$dofile"
    274 	[ -e "$dofile" ] && return 0
    275 
    276 	# Try default.*.do files, walking up the tree
    277 	_dirsplit "$dofile"
    278 	dodir=$_dirsplit_dir
    279 	dofile=$_dirsplit_base
    280 	[ -n "$dodir" ] && dodir=${dodir%/}/
    281 	[ -e "$dodir$dofile" ] && return 0
    282 	for i in $(_seq 100); do
    283 		[ -n "$dodir" ] && dodir=${dodir%/}/
    284 		#echo "_find_dofiles: '$dodir' '$dofile'" >&2
    285 		_find_dofiles_pwd "$dodir" "$dofile" && return 0
    286 		newdir=$(_realpath "${dodir}.." "$PWD")
    287 		[ "$newdir" = "$dodir" ] && break
    288 		dodir=$newdir
    289 	done
    290 	return 1
    291 }
    292 
    293 
    294 # Print the last .do file returned by _find_dofiles.
    295 # If that file exists, returns 0, else 1.
    296 _find_dofile()
    297 {
    298 	local files="$(_find_dofiles "$1")"
    299 	rv=$?
    300 	#echo "files='$files'" >&2
    301 	[ "$rv" -ne 0 ] && return $rv
    302 	echo "$files" | {
    303 		while read -r linex; do line=$linex; done
    304 		printf "%s\n" "$line"
    305 	}
    306 }
    307 
    308 
    309 # Actually run the given $dofile with the arguments in $@.
    310 # Note: you should always run this in a subshell.
    311 _run_dofile()
    312 {
    313 	export DO_DEPTH="$DO_DEPTH  "
    314 	export REDO_TARGET="$PWD/$target"
    315 	local line1
    316 	set -e
    317 	read line1 <"$PWD/$dofile" || true
    318 	cmd=${line1#"#!/"}
    319 	if [ "$cmd" != "$line1" ]; then
    320 		set -$_do_opt_verbose$_do_opt_exec
    321 		exec /$cmd "$PWD/$dofile" "$@"
    322 	else
    323 		set -$_do_opt_verbose$_do_opt_exec
    324 		# If $dofile is empty, "." might not change $? at
    325 		# all, so we clear it first with ":".
    326 		:; . "$PWD/$dofile"
    327 	fi
    328 }
    329 
    330 
    331 # Find and run the right .do file, starting in dir $1, for target $2,
    332 # providing a temporary output file as $3.  Renames the temp file to $2 when
    333 # done.
    334 _do()
    335 {
    336 	local dir="$1" target="$1$2" tmp="$1$2.redo.tmp" tdir=
    337 	local dopath= dodir= dofile= ext=
    338 	if [ "$_cmd" = "redo" ] ||
    339 	    ( [ ! -e "$target" -o -d "$target" ] &&
    340 	      [ ! -e "$target.did" ] ); then
    341 		printf '%sdo  %s%s%s%s\n' \
    342 			"$green" "$DO_DEPTH" "$bold" "$target" "$plain" >&2
    343 		dopath=$(_find_dofile "$target")
    344 		if [ ! -e "$dopath" ]; then
    345 			echo "do: $target: no .do file ($PWD)" >&2
    346 			return 1
    347 		fi
    348 		_dirsplit "$dopath"
    349 		dodir=$_dirsplit_dir dofile=$_dirsplit_base
    350 		if _startswith "$dofile" "default."; then
    351 			ext=${dofile#default}
    352 			ext=${ext%.do}
    353 		else
    354 			ext=
    355 		fi
    356 		target=$PWD/$target
    357 		tmp=$PWD/$tmp
    358 		cd "$dodir" || return 99
    359 		target=$(_relpath "$target" "$PWD") || return 98
    360 		tmp=$(_relpath "$tmp" "$PWD") || return 97
    361 		base=${target%$ext}
    362 		tdir=$(qdirname "$target")
    363 		[ ! -e "$DO_BUILT" ] || [ ! -w "$tdir/." ] ||
    364 		: >>"$target.did.tmp"
    365 		# $qtmp is a temporary file used to capture stdout.
    366 		# Since it might be accidentally deleted as a .do file
    367 		# does its work, we create it, then open two fds to it,
    368 		# then immediately delete the name.  We use one fd to
    369 		# redirect to stdout, and the other to read from after,
    370 		# because there's no way to fseek(fd, 0) in sh.
    371 		qtmp=$DO_PATH/do.$$.tmp
    372 		(
    373 			rm -f "$qtmp"
    374 			( _run_dofile "$target" "$base" "$tmp" >&3 3>&- 4<&- )
    375 			rv=$?
    376 			if [ $rv != 0 ]; then
    377 				printf "do: %s%s\n" "$DO_DEPTH" \
    378 					"$target: got exit code $rv" >&2
    379 				rm -f "$tmp.tmp" "$tmp.tmp2" "$target.did"
    380 				return $rv
    381 			fi
    382 			echo "$PWD/$target" >>"$DO_BUILT"
    383 			if [ ! -e "$tmp" ]; then
    384 				# if $3 wasn't created, copy from stdout file
    385 				cat <&4 >$tmp
    386 				# if that's zero length too, forget it
    387 				[ -s "$tmp" ] || rm -f "$tmp"
    388 			fi
    389 		) 3>$qtmp 4<$qtmp  # can't use "|| return" here...
    390 		# ...because "|| return" would mess up "set -e" inside the ()
    391 		# on some shells.  Running commands in "||" context, even
    392 		# deep inside, will stop "set -e" from functioning.
    393 		rv=$?
    394 		[ "$rv" = 0 ] || return "$rv"
    395 		mv "$tmp" "$target" 2>/dev/null
    396 		[ -e "$target.did.tmp" ] &&
    397 		mv "$target.did.tmp" "$target.did" ||
    398 		: >>"$target.did"
    399 	else
    400 		_debug "do  $DO_DEPTH$target exists." >&2
    401 	fi
    402 }
    403 
    404 
    405 # Implementation of the "redo" command.
    406 _redo()
    407 {
    408 	local i startdir="$PWD" dir base
    409 	set +e
    410 	for i in "$@"; do
    411 		i=$(_abspath "$i" "$startdir")
    412 		(
    413 			cd "$DO_STARTDIR" || return 99
    414 			i=$(_realpath "$(_relpath "$i" "$PWD")" "$PWD")
    415 			_dirsplit "$i"
    416 			dir=$_dirsplit_dir base=$_dirsplit_base
    417 			_do "$dir" "$base"
    418 		)
    419 		[ "$?" = 0 ] || return 1
    420 	done
    421 }
    422 
    423 
    424 # Implementation of the "redo-whichdo" command.
    425 _whichdo()
    426 {
    427 	_find_dofiles "$1"
    428 }
    429 
    430 
    431 case $_cmd in
    432 	do|redo|redo-ifchange) _redo "$@" ;;
    433 	redo-whichdo) _whichdo "$1" ;;
    434 	do.test) ;;
    435 	*) printf "do: '%s': unexpected redo command" "$_cmd" >&2; exit 99 ;;
    436 esac
    437 [ "$?" = 0 ] || exit 1
    438 
    439 if [ -n "$DO_TOP" ]; then
    440 	if [ -n "$_do_opt_clean" ]; then
    441 		echo "do: Removing stamp files..." >&2
    442 		[ ! -e "$DO_BUILT" ] ||
    443 		while read f; do printf "%s.did\0" "$f"; done <"$DO_BUILT" |
    444 		xargs -0 rm -f 2>/dev/null
    445 	fi
    446 fi