diff options
| -rwxr-xr-x | scripts/xattr_user_id | 109 | 
1 files changed, 109 insertions, 0 deletions
diff --git a/scripts/xattr_user_id b/scripts/xattr_user_id new file mode 100755 index 0000000..28a1d99 --- /dev/null +++ b/scripts/xattr_user_id @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# Copyright 2020 vg +# SPDX-License-Identifier: MIT + +''' +Description +=========== + +Tag given files with a unique incrementing id. + +State File +========== + +State file records the last used id as a simple string and can be used as +a starting point when recalling this program. Otherwise the program starts +from 1 (== last used id is 0). The same starting point is assumed when +the state file does not exists. + +If no filename are given as positional command line argument, read filenames +from stdin. + +If given files are already tagged, they are just displayed, not +rettagged/renumbered unless -f is given. + +Usage +===== + +Usage: {progname} [--state STATE_FILE] [-z] [-f] [--] [FILENAME...] +       {progname} -h|--help + +Options: + +  -s, --state STATE_FILE +        Records and read back last used id from/to this file + +  -h, --help +        Show the complete help + +  -z +        Null instead of LF as filename separator for in and out + +  -f +        Force. Rewrite already tagged files. + +''' + +### standard modules +import contextlib +import functools +import itertools +import operator +import os +import sys + +### external modules +import docopt + + +# This script is not compatible below python3.7.2, always abort (not in main +# since the syntax itself can cause the script to fail later inconveniently). +assert sys.hexversion >= 0x03070200 + + +def stream_split(sep='\n', stream=sys.stdin, *, rem='', chunksize=4096): +    if stream.isatty(): +        # workaround the double Ctrl-D issue for interactive entry where perf +        # should not be an issue thus reading 1 by 1 is ok. +        chunksize = 1 +    for data in iter(functools.partial(stream.read, chunksize), ''): +        splitted = ''.join((rem, data)).split(sep) +        for part in itertools.islice(splitted, len(splitted)-1): +            yield part +        rem = splitted[-1] +    yield rem + + +def filenames(arg_filenames, separator): +    from itertools import repeat +    yield from zip(repeat(False), +            filter(bool, arg_filenames or stream_split(separator))) + + +def main(): +    args = docopt.docopt(__doc__.format(progname=os.path.basename(sys.argv[0]))) + +    last_used_id = 0 +    if args['--state']: +        with contextlib.suppress(FileNotFoundError): +            with open(args['--state'], encoding='utf8') as stream: +                last_used_id = int(stream.read()) + +    separator = '\0' if args['-z'] else '\n' +    for untagged, filename in filenames(args['FILENAME'], separator): +        try: +            file_tag_id = int(os.getxattr(filename, 'user.id').decode('utf8')) +        except OSError as err: +            untagged = True +        if untagged or args['-f']: +            last_used_id += 1 +            os.setxattr(filename, 'user.id', str(last_used_id).encode('utf8')) +            file_tag_id = last_used_id +        print(file_tag_id, filename, end=separator) + +    if args['--state']: +        with open(args['--state'], 'w', encoding='utf8') as stream: +            print(last_used_id, file=stream) + + +main()  | 
