changeset 254:655f9e4bc6f2

genpwd: allow grouping of the output
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 13 Feb 2025 11:57:32 +0100
parents 4314ee20927a
children d852559df523
files cutils/genpwd.py
diffstat 1 files changed, 86 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/cutils/genpwd.py	Sat Feb 08 15:09:32 2025 +0100
+++ b/cutils/genpwd.py	Thu Feb 13 11:57:32 2025 +0100
@@ -101,11 +101,38 @@
         help="For some repertoires make OUTPUT-LENGTH the number of bytes"
              " that is to be read from random sources instead of output bytes")
     aparser.add_argument(
+        "--group", "-G", action="store_true",
+        help="""Group the results. If no "--group-sep" or "--group-size" is
+given use their respective defaults.""")
+    aparser.add_argument(
+        "--group-sep", action="store", default=None, metavar="GROUP-SEP",
+        help="""Group the result using GROUP-SEP as separator.
+Option "--group" is implied.
+Default when grouping is enabled is the SPACE character ` '.""")
+    aparser.add_argument(
+        "--group-size", action="store", type=int, default=None,
+        metavar="GROUP-SIZE",
+        help="""Group the result using a group size of GROUP-SIZE characters.
+Option "--group" is implied.
+Default when grouping is enabled is 6.""")
+    aparser.add_argument(
         "req_length", metavar="OUTPUT-LENGTH", type=int,
         help="The required length of the generated output")
 
     opts = aparser.parse_args(args=argv)
 
+    grouper = None
+    if opts.group or opts.group_sep is not None or opts.group_size is not None:
+        if opts.group_sep is None:
+            gsep = b' '
+        else:
+            if PY2:
+                gsep = opts.group_sep
+            else:
+                gsep = opts.group_sep.encode("utf8")
+        gsize = 6 if opts.group_size is None else opts.group_size
+        grouper = make_grouper(sep=gsep, size=gsize)
+
     if opts.repertoire:
         try:
             repertoire = (opts.repertoire
@@ -113,54 +140,69 @@
                           else opts.repertoire.encode("iso-8859-15"))
         except UnicodeError:
             raise ValueError("non ISO-8859-15 character in given repertoire")
-        pwd = gen_from_repertoire(opts.req_length, repertoire)
+        pwd = gen_from_repertoire(opts.req_length, repertoire, grouper=grouper)
     elif opts.algorithm == "web":
-        pwd = gen_from_repertoire(opts.req_length, WEB_CHARS)
+        pwd = gen_from_repertoire(
+            opts.req_length, WEB_CHARS, grouper=grouper)
     elif opts.algorithm == "safe-web":
-        pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS)
+        pwd = gen_from_repertoire(
+            opts.req_length, SAFE_WEB_CHARS, grouper=grouper)
     elif opts.algorithm == "safe-web-2":
-        pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS_2)
+        pwd = gen_from_repertoire(
+            opts.req_length, SAFE_WEB_CHARS_2, grouper=grouper)
     elif opts.algorithm == "uri":
-        pwd = gen_from_repertoire(opts.req_length, URI_CHARS)
+        pwd = gen_from_repertoire(
+            opts.req_length, URI_CHARS, grouper=grouper)
     elif opts.algorithm == "safe-uri":
-        pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS)
+        pwd = gen_from_repertoire(
+            opts.req_length, SAFE_URI_CHARS, grouper=grouper)
     elif opts.algorithm == "safe-uri-2":
-        pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS_2)
+        pwd = gen_from_repertoire(
+            opts.req_length, SAFE_URI_CHARS_2, grouper=grouper)
     elif opts.algorithm == "ascii":
-        pwd = gen_from_repertoire(opts.req_length, FULL_ASCII)
+        pwd = gen_from_repertoire(
+            opts.req_length, FULL_ASCII, grouper=grouper)
     elif opts.algorithm == "safe-ascii":
-        pwd = gen_from_repertoire(opts.req_length, SAFE_ASCII)
+        pwd = gen_from_repertoire(
+            opts.req_length, SAFE_ASCII, grouper=grouper)
     elif opts.algorithm == "alnum":
-        pwd = gen_from_repertoire(opts.req_length, ALNUM)
+        pwd = gen_from_repertoire(opts.req_length, ALNUM, grouper=grouper)
     elif opts.algorithm == "safe-alnum":
-        pwd = gen_from_repertoire(opts.req_length, SAFE_ALNUM)
+        pwd = gen_from_repertoire(
+            opts.req_length, SAFE_ALNUM, grouper=grouper)
     elif opts.algorithm == "bin-base64":
         encoder = base64.b64encode
         pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
-                      rstrip_chars=b"=")
+                      rstrip_chars=b"=", grouper=grouper)
     elif opts.algorithm == "bin-urlsafe-base64":
         encoder = base64.urlsafe_b64encode
         pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
-                      rstrip_chars=b"=")
+                      rstrip_chars=b"=", grouper=grouper)
     elif opts.algorithm == "bin-base32":
         encoder = base64.b32encode
         pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
-                      rstrip_chars=b"=")
+                      rstrip_chars=b"=", grouper=grouper)
     elif opts.algorithm == "bin-ascii85":
         encoder = base64.a85encode
-        pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder)
+        pwd = gen_bin(
+            opts.req_length, opts.use_bin_length, encoder, grouper=grouper)
     elif opts.algorithm == "bin-hex":
         encoder = binascii.hexlify
-        pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder)
+        pwd = gen_bin(
+            opts.req_length, opts.use_bin_length, encoder, grouper=grouper)
     else:
         raise NotImplementedError("algorithm not yet implemented: %s"
                                   % opts.algorithm)
-    if opts.use_bin_length:
+    if opts.group or opts.group_size or opts.group_sep:
         if len(pwd) < opts.req_length:
             raise AssertionError("internal length mismatch")
     else:
-        if len(pwd) != opts.req_length:
-            raise AssertionError("internal length mismatch")
+        if opts.use_bin_length:
+            if len(pwd) < opts.req_length:
+                raise AssertionError("internal length mismatch")
+        else:
+            if len(pwd) != opts.req_length:
+                raise AssertionError("internal length mismatch")
     if PY2:
         print(pwd)
         sys.stdout.flush()
@@ -170,7 +212,7 @@
         sys.stdout.buffer.flush()
 
 
-def gen_from_repertoire(length, repertoire):
+def gen_from_repertoire(length, repertoire, grouper=None):
     """Select `length` characters randomly from given character repertoire
     `repertoire`.
 
@@ -190,15 +232,37 @@
         pwd = b''.join(pwd)
     else:
         pwd = bytes(pwd)
+    if grouper:
+        pwd = grouper(pwd)
     return pwd
 
 
-def gen_bin(length, use_bin_length, encoder, rstrip_chars=None):
+def gen_bin(length, use_bin_length, encoder, rstrip_chars=None, grouper=None):
     """Generate from :func:`os.urandom` and just encode with given `encoder`.
 
     """
     pwd = encoder(os.urandom(length))
-    return pwd.rstrip(rstrip_chars) if use_bin_length else pwd[:length]
+    pwd = pwd.rstrip(rstrip_chars) if use_bin_length else pwd[:length]
+    if grouper:
+        pwd = grouper(pwd)
+    return pwd
+
+
+def make_grouper(sep=b' ', size=6):
+
+    def _grouper(pwd):
+        if not pwd or size <= 0:
+            return pwd
+        assert isinstance(pwd, bytes)
+        groups = []
+        idx = 0
+        while idx < len(pwd):
+            groups.append(pwd[idx:idx+size])
+            idx += size
+        return sep.join(groups)
+
+    assert isinstance(sep, bytes)
+    return _grouper
 
 
 if __name__ == "__main__":