initial commit.
This commit is contained in:
commit
72cbc59f70
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright 2020 "yafox"
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,26 @@
|
|||
lmr
|
||||
====
|
||||
|
||||
pronounced "lemur." an abbreviation of "link merge."
|
||||
|
||||
merges the contents of one directory into another using filesystem links.
|
||||
|
||||
can reverse a merge as long as the contents of the source directory have
|
||||
not changed since the merge was completed and no files were overwritten
|
||||
during the merge. if files were removed from the source directory, "dangling"
|
||||
symlinks will be left behind.
|
||||
|
||||
lmr exists for much the same reason as gnu stow, but has significantly fewer
|
||||
features and operates more like a recursive `ln` than a simplified `stow`.
|
||||
|
||||
lmr is implemented as a single, 80 SLOC long, POSIX-compliant shell script.
|
||||
|
||||
usage: lmr [-c] [-f] [-s] [-t|-u] [--] <src> <dest>
|
||||
|
||||
options:
|
||||
-c: check for file conflicts. link nothing.
|
||||
-f: force link creation, even if it overwrites files.
|
||||
-s: use symlinks instead of hard links.
|
||||
-t: exit if it seems a lmr has already been done between <src> and <dest>.
|
||||
-u: try to undo a previous lmr from <src> to <dest>.
|
||||
--: optional "end of flags" marker.
|
|
@ -0,0 +1,115 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
[ -n "$1" ] || { \
|
||||
echo "
|
||||
usage: $(basename $0) [-c] [-f] [-s] [-t|-u] [--] <src> <dest>
|
||||
|
||||
options:
|
||||
-c: check for file conflicts. link nothing.
|
||||
-f: force link creation, even if it overwrites files.
|
||||
-s: use symlinks instead of hard links.
|
||||
-t: exit if it seems a lmr has already been done between <src> and <dest>.
|
||||
-u: try to undo a previous lmr from <src> to <dest>.
|
||||
--: optional "end of flags" marker.
|
||||
" && exit 0; }
|
||||
|
||||
# if this is running on an interactive terminal (as opposed to in a script), if
|
||||
# tput is installed, and if tput knows some color codes, then set color values.
|
||||
if [ -t 1 ] && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
|
||||
_clr="$(tput sgr0)"
|
||||
_blu="$(tput setaf 6)"
|
||||
_ylw="$(tput setaf 3)"
|
||||
_red="$(tput setaf 1)"
|
||||
fi
|
||||
log() { echo "$_blu[LOG]$_clr $@"; }
|
||||
wrn() { echo "$_ylw[WRN]$_clr $@" >&2; }
|
||||
err() { echo "$_red[ERR]$_clr $@" >&2; exit 1; }
|
||||
|
||||
# -e checks if a file exists, but also dereferences symlinks and returns false
|
||||
# if the link's target does not exist. -L returns true if the file exists and
|
||||
# is a symlink; it does not dereference symlinks.
|
||||
exists() { [ -e "$1" ] || [ -L "$1" ]; }
|
||||
|
||||
linked() {
|
||||
# -ef returns true if the files exist and refer to the same file.
|
||||
{ [ -z "$soft" ] && [ "$1" -ef "$2" ]; } \
|
||||
|| [ "$1" = "$(readlink "$2" 2> /dev/null)" ] \
|
||||
|| return 1
|
||||
}
|
||||
export linked
|
||||
|
||||
while [ "$1" != "${1#-}" ]; do
|
||||
case "$1" in
|
||||
-c) check="yes";;
|
||||
-f) flags="$flags -f";;
|
||||
-s) soft="yes";;
|
||||
-t) test="yes";;
|
||||
-u) undo="yes";;
|
||||
--) break;;
|
||||
*) err "unrecognized option '$1'";;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
src="$1"
|
||||
dest="$2"
|
||||
|
||||
[ -d "$1" ] || err "not a directory: $1"
|
||||
[ -d "$2" ] || err "not a directory: $2"
|
||||
|
||||
if [ -z "$undo" ]; then
|
||||
[ -z "$soft" ] && flags="$flags -P" || flags="$flags -s"
|
||||
|
||||
find "$src" -type d ! -path "$src" -exec sh -c \
|
||||
"mkdir -p \$(echo \"$dest/\${0#$src}\" | tr -s '/')" {} \;
|
||||
|
||||
find "$src" \( -type f -o -type l \) | while read lnsrc; do
|
||||
lndest="$dest/${lnsrc#$src}"
|
||||
|
||||
if [ -z "$test" ]; then
|
||||
linked "$lnsrc" "$lndest" \
|
||||
&& exit 0 \
|
||||
|| exit 1
|
||||
|
||||
elif [ -z "$check" ]; then
|
||||
{ ! exists "$lndest"; } \
|
||||
|| linked "$lnsrc" "$lndest" \
|
||||
|| wrn "conflict: ${lnsrc#$src}"
|
||||
|
||||
else
|
||||
ln$flags "$lnsrc" "$lndest"
|
||||
fi
|
||||
done
|
||||
|
||||
else
|
||||
# delete all files in the destination directory which are either soft or
|
||||
# hard links (depending on whether the -s flag was set) to files in the
|
||||
# source directory.
|
||||
find "$src" \( -type f -o -type l \) | while read lnsrc; do
|
||||
lndest="$dest/${lnsrc#$src}"
|
||||
|
||||
exists "$lndest" || continue;
|
||||
|
||||
# only remove the file in the "destination" directory if it is a hard or
|
||||
# a soft link to the source file.
|
||||
{ ! linked "$lnsrc" "$lndest"; } || rm "$lndest"
|
||||
done
|
||||
|
||||
# recursively remove any empty directories in the destination directory
|
||||
# which also exist in the source directory. while no guarantee that these
|
||||
# were created by a previous lmr operation, this seems a decent heuristic.
|
||||
# reverse-sorting by path length to ensure "leaves" are processed first.
|
||||
find "$src" -type d ! -path "$src" \
|
||||
| awk '{ print length() "\t" $0 | "sort -nr" }' \
|
||||
| cut -f2 \
|
||||
| while read dir; do
|
||||
destdir="$dest/${dir#$src}"
|
||||
|
||||
# avoid "directory not empty" errors below.
|
||||
[ -d "$destdir" ] && [ -z "$(ls -A $destdir/)" ] || continue;
|
||||
|
||||
# rmdir will only delete empty directories.
|
||||
# use it in case the line above fails to detect something.
|
||||
rmdir "$destdir" || true # continue even if this fails for some reason.
|
||||
done
|
||||
fi
|
|
@ -0,0 +1,9 @@
|
|||
SRCDIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
PREFIX ?= /usr
|
||||
DESTDIR ?= $(PREFIX)/bin
|
||||
|
||||
install:
|
||||
cp -a $(SRCDIR)/lmr.sh $(DESTDIR)/lmr
|
||||
|
||||
uninstall:
|
||||
rm $(DESTDIR)/lmr
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SLOC is here defined as lines which are not comments or empty.
|
||||
|
||||
# find all *.sh files not under `pkg` that are not symbolic links, strip all
|
||||
# trailing whitespace, then all leading whitespace, then all lines starting
|
||||
# with '#', then all empty lines. then count the remaining lines.
|
||||
|
||||
find . -name "*.sh" ! -path "**/pkg/**" ! -type l \
|
||||
| xargs sed 's/[[:space:]]*$//g; s/^[[:space:]]*//g; s/^#.*$//g; /^$/d' \
|
||||
| wc -l - \
|
||||
| cut -d' ' -f1
|
||||
|
||||
# note that this script's SLOC is also included in the count.
|
Loading…
Reference in New Issue