aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xscripts/xattr_user_id109
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()