From 504dd8ac2459fa93d22e2b4749ae91fa5da9c434 Mon Sep 17 00:00:00 2001 From: Taeyeon Mori Date: Fri, 11 Sep 2015 16:24:23 +0200 Subject: [PATCH] Add a tool to take the pain out of ln -s --- bin/lns | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100755 bin/lns diff --git a/bin/lns b/bin/lns new file mode 100755 index 0000000..c8ba4e3 --- /dev/null +++ b/bin/lns @@ -0,0 +1,74 @@ +#!/usr/bin/python +# (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))