Dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

75 lines
2.9 KiB

#!/usr/bin/env python3
# (c) 2015 Taeyeon Mori
# A simple script to make ln -s less painful
#
# Because of how symbolic links work (they're just a pathname that the OS reads and follows),
# the link target path must always be relative to the symlink's containing directory, OR
# an absolute path altogether. Unfortunately, ln -s does not automatically solve this issue
# which makes it painful to create a relative symlink somewhere outside the current working
# directory (It screws up shell completion for instance)
# This script performs the path transformation before creating the link and can therefore be
# used without worries of creating broken symlinks because one forgot to apply the correct
# relative adjustment to the target path
#
# ex:
# $ lns -v some-file dir/some-link
# ln -s '../some-file' 'dir/some-link'
# $ lns -v file ../derp/link # inside a directory called 'herp'
# ln -s '../herp/file' '../derp/link'
import os
import argparse
def transport_relpath(path, old_anchor, new_anchor):
"""
:brief: Transport a relative path from one anchor to another
Anchors must both be absolute paths, path must be relative (to old_anchor)
"""
return os.path.relpath(os.path.normpath(os.path.join(old_anchor, path)), new_anchor)
def verbose_symlink(target, dest):
print("ln -s '%s' '%s'" % (target, dest))
return os.symlink(target, dest)
def main(argv):
# Command line interface to make_symlink()
parser = argparse.ArgumentParser(prog=argv[0],
description="A 'ln -s' command that automatically translates relative target paths to be relative to the resulting symlink's containing directory when necessary.")
parser.add_argument("target", help="The file(s) to create symlink(s) to", nargs="+")
parser.add_argument("destination", help="The path of the symbolic link(s) to create")
parser.add_argument("-v", "--verbose", help="Print every operation",
dest="symlink", const=verbose_symlink, action="store_const", default=os.symlink)
args = parser.parse_args(argv[1:])
# Multiple
if len(args.target) > 1:
if not os.path.isdir(args.destination):
print("Destination must be an existing directory when multiple targets are passed!")
for target in args.target:
dest = os.path.join(args.destination, os.path.basename(target))
args.symlink(target if os.path.isabs(target) else transport_relpath(target, os.curdir, args.destination), dest)
# One
else:
target = args.target[0]
dest = ldir = args.destination
if os.path.isdir(dest):
dest = os.path.join(dest, os.path.basename(target))
else:
ldir = os.path.dirname(dest)
args.symlink(target if os.path.isabs(target) else transport_relpath(target, os.curdir, ldir), dest)
return 0
if __name__ == "__main__":
import sys
sys.exit(main(sys.argv))