initial commit.

This commit is contained in:
yafox 2020-11-25 06:32:47 +00:00
commit 72cbc59f70
No known key found for this signature in database
GPG Key ID: B501C30B37F4806C
5 changed files with 184 additions and 0 deletions

20
LICENSE Normal file
View File

@ -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.

26
README Normal file
View File

@ -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.

115
lmr.sh Executable file
View File

@ -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

9
makefile Normal file
View File

@ -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

14
sloc.sh Executable file
View File

@ -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.