Improve stowage

This commit is contained in:
2021-03-24 17:46:41 +00:00
parent 99fa73a4e3
commit 208ea18b29
2 changed files with 64 additions and 38 deletions

View File

@@ -1,9 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
stowage originally stowage, by Keith Gaughan <https://github.com/kgaughan/>
by Keith Gaughan <https://github.com/kgaughan/> modified by Andrew Williams <https://github.com/nikdoof/>
Stow, but in Python, and in a single file.
A dotfile package manager
Copyright (c) Keith Gaughan, 2017. Copyright (c) Keith Gaughan, 2017.
Copyright (c) Andrew Williams, 2021.
Permission is hereby granted, free of charge, to any person obtaining a copy of 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 this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to the Software without restriction, including without limitation the rights to
@@ -32,8 +36,8 @@ import sys
def add(args): def add(args):
target = path.realpath(args.target) target = path.realpath(args.target)
file_path = path.realpath(args.add) file_path = path.realpath(args.file)
package = path.realpath(args.packages[0]) package = path.realpath(os.path.join(args.repository, args.packages[0]))
if path.commonprefix([target, file_path]) != target: if path.commonprefix([target, file_path]) != target:
print(f"error: '{args.add}' not under '{args.target}'", print(f"error: '{args.add}' not under '{args.target}'",
file=sys.stderr) file=sys.stderr)
@@ -55,15 +59,16 @@ def add(args):
def install(args, is_excluded): def install(args, is_excluded):
for package in args.packages: for package in args.packages:
if not path.isdir(package): package_dir = os.path.join(args.repository, package)
if not path.isdir(package_dir):
print(f"no such package: {package}; skipping", file=sys.stderr) print(f"no such package: {package}; skipping", file=sys.stderr)
continue continue
for root, _, files in os.walk(package, followlinks=True): for root, _, files in os.walk(package_dir, followlinks=True):
files = [ files = [
filename for filename in files if not is_excluded(filename)] filename for filename in files if not is_excluded(filename)]
if len(files) == 0: if len(files) == 0:
continue continue
rest = root[len(package) + 1:] rest = root[len(package_dir) + 1:]
dest = path.join(args.target, rest) dest = path.join(args.target, rest)
if rest != "": if rest != "":
if args.verbose: if args.verbose:
@@ -90,15 +95,16 @@ def install(args, is_excluded):
def uninstall(args, is_excluded): def uninstall(args, is_excluded):
dirs = [] dirs = []
for package in args.packages: for package in args.packages:
if not path.isdir(package): package_dir = os.path.join(args.repository, package)
if not path.isdir(package_dir):
print(f"no such package: {package}; skipping", file=sys.stderr) print(f"no such package: {package}; skipping", file=sys.stderr)
continue continue
for root, _, files in os.walk(package, followlinks=True): for root, _, files in os.walk(package_dir, followlinks=True):
files = [ files = [
filename for filename in files if not is_excluded(filename)] filename for filename in files if not is_excluded(filename)]
if len(files) == 0: if len(files) == 0:
continue continue
rest = root[len(package) + 1:] rest = root[len(package_dir) + 1:]
dest = path.join(args.target, rest) dest = path.join(args.target, rest)
if rest != "": if rest != "":
dirs.append(dest) dirs.append(dest)
@@ -128,15 +134,23 @@ def uninstall(args, is_excluded):
def make_argparser(): def make_argparser():
parser = argparse.ArgumentParser(description="A symlink farm manager.") parser = argparse.ArgumentParser(description="A dotfile package manager.")
parser.add_argument("--verbose", "-v", parser.add_argument("--verbose", "-v",
action="store_true", help="Verbose output") action="store_true", help="Verbose output")
parser.add_argument("--dry-run", "-n",
action="store_true", help="Dry run.")
parser.add_argument( parser.add_argument(
"--target", "--target",
"-t", "-t",
default=os.path.expanduser('~'), default=os.path.expanduser('~'),
help="Target directory in which to place symlinks", help="Target directory in which to place symlinks",
) )
parser.add_argument(
"--repository",
"-r",
default=os.path.expanduser('~/.dotfiles'),
help="The location of the dotfile repository",
)
parser.add_argument( parser.add_argument(
"--exclude", "--exclude",
"-x", "-x",
@@ -145,46 +159,60 @@ def make_argparser():
metavar="GLOB", metavar="GLOB",
help="Glob pattern of files to exclude", help="Glob pattern of files to exclude",
) )
parser.add_argument("--dry-run", "-n",
action="store_true", help="Dry run.")
group = parser.add_mutually_exclusive_group(required=False) subparsers = parser.add_subparsers(dest='command', help='sub-command help')
group.add_argument(
"--uninstall",
"-D",
action="store_false",
dest="install",
help="Uninstall symlinks",
)
group.add_argument(
"--add", "-a", metavar="FILE", help="Stow files in a particular package"
)
parser.add_argument( # List
parser_add = subparsers.add_parser('list', help='List packages in the repository')
# Add
parser_add = subparsers.add_parser('add', help='Add a file to a package')
parser_add.add_argument(
"file", metavar="FILE", help="File to stow"
)
parser_add.add_argument(
"packages", metavar="PACKAGE", nargs="+", help="Packages to install" "packages", metavar="PACKAGE", nargs="+", help="Packages to install"
) )
# Uninstall
parser_uninstall = subparsers.add_parser('uninstall', help='Remove a package')
parser_uninstall.add_argument(
"packages", metavar="PACKAGE", nargs="+", help="Packages to uninstall"
)
# Install
parser_install = subparsers.add_parser('install', help='Install packages')
parser_install.add_argument(
"packages", metavar="PACKAGE", nargs="+", help="Packages to install"
)
return parser return parser
def main(): def main():
parser = make_argparser() parser = make_argparser()
args = parser.parse_args() args = parser.parse_args()
if args.dry_run:
args.verbose = True
exclude = [re.compile(fnmatch.translate(pattern)) exclude = [re.compile(fnmatch.translate(pattern))
for pattern in args.exclude] for pattern in args.exclude]
def is_excluded(filename): def is_excluded(filename):
return any(pattern.match(filename) for pattern in exclude) return any(pattern.match(filename) for pattern in exclude)
if args.add: if args.command == 'list':
for dir in os.listdir(args.repository):
if os.path.isdir(os.path.join(args.repository, dir)) and dir[0] != '.': print(dir)
elif args.command == 'add':
if len(args.packages) > 1: if len(args.packages) > 1:
parser.error("--add only works with a single package") parser.error("--add only works with a single package")
args.add = path.normpath(path.join(args.target, args.add)) args.file = path.normpath(path.join(args.target, args.file))
if not path.isfile(args.add): if not path.isfile(args.file):
parser.error(f"no such file: {args.add}") parser.error(f"no such file: {args.file}")
add(args) add(args)
elif args.install: elif args.command == 'install':
install(args, is_excluded) install(args, is_excluded)
else: elif args.command == 'uninstall':
uninstall(args, is_excluded) uninstall(args, is_excluded)

View File

@@ -10,18 +10,16 @@ fi
git clone https://github.com/nikdoof/dotfiles.git $HOME/.dotfiles > /dev/null git clone https://github.com/nikdoof/dotfiles.git $HOME/.dotfiles > /dev/null
# Clean bash files # Clean bash files
for file in .bash_profile .bashrc .bash_logout; do for file in .bash_profile .bashrc .bash_logout .zshrc; do
if [ -e $file ]; then if [ -e $file ]; then
rm -f $file rm -f $file
fi fi
done done
cd $HOME/.dotfiles/
# Stow the default packages # Stow the default packages
for package in bin bash; do for package in bin bash zsh; do
echo "Stowing ${package}" echo "Stowing ${package}"
./bin/bin/stowage $package $HOME/.dotfiles/bin/bin/stowage install $package
done done
echo "" echo ""
echo "Done, either source ~/.bash_profile or restart your shell." echo "Done, either source ~/.bash_profile / ~/.zshrc or restart your shell."