-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenp
More file actions
executable file
·130 lines (109 loc) · 3.27 KB
/
genp
File metadata and controls
executable file
·130 lines (109 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env -S uv --quiet run --no-project --script --
# https://peps.python.org/pep-0723/
# https://github.com/astral-sh/uv
# /// script
# requires-python = ">=3.14,<4"
# dependencies = [
# ]
# ///
import argparse, shutil, string, collections
def main(argv: list[str] | None =None) -> None:
opts = _parse_args(argv)
per_line: int = (opts.width + 1) // (opts.length + 1)
with random_generator() as fo:
passcodes = [genp(fo, opts.length, opts.alphabet) for i in range(opts.number)]
while passcodes:
print(" ".join(passcodes[:per_line]))
passcodes = passcodes[per_line:]
def random_generator():
# https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Special_designs
return open("/dev/random", "rb")
def genp(fo, length, alphabet):
assert len(alphabet) <= 255
return "".join(alphabet[x % len(alphabet)] for x in fo.read(length))
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
passcode_type_defaults = collections.namedtuple("passcode_type_defaults", ["alphabet", "length"])
passcode_types = {
"pin": passcode_type_defaults(
alphabet=string.digits,
length=6,
),
"password": passcode_type_defaults(
alphabet=(string.ascii_lowercase + string.ascii_uppercase + string.digits),
length=32,
),
"punctuation": passcode_type_defaults(
alphabet=string.punctuation.encode("ascii").translate(None, b"'\"").decode("ascii"),
length=32,
),
"passwordwithpunctuation": passcode_type_defaults(
alphabet=(string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation.encode("ascii").translate(None, b"'\"").decode("ascii")),
length=32,
),
"hex": passcode_type_defaults(
alphabet=(string.digits + string.ascii_uppercase[:6]),
length=32,
),
}
parser = argparse.ArgumentParser(prog=(argv[0] if argv is not None else None))
parser.add_argument(
"--type", "-t",
dest="type",
choices=passcode_types.keys(),
default="password"
)
parser.add_argument(
"--alphabet", "-a",
dest="alphabet",
action="store",
type=str,
metavar="CHARACTERS",
default=None
)
parser.add_argument(
"--length", "-l",
dest="length",
action="store",
type=int,
metavar="N",
default=None,
help="how many digits in each PIN"
)
parser.add_argument(
"--width", "-w",
dest="width",
action="store",
type=int,
metavar="N",
default=None,
help="print no more than specified chars per line"
)
parser.add_argument(
"--number", "-n",
dest="number",
action="store",
type=int,
metavar="N",
default=None,
help="how many PINs to generate"
)
opts = parser.parse_args(argv[1:] if argv is not None else None)
if opts.alphabet is None:
opts.alphabet = passcode_types[opts.type].alphabet
if opts.length is None:
opts.length = passcode_types[opts.type].length
screen_size = shutil.get_terminal_size()
if opts.width is None:
opts.width = screen_size.columns
if opts.width < opts.length:
parser.error("width can not be less than length")
if opts.number is None:
per_line = (opts.width + 1) // (opts.length + 1)
opts.number = (screen_size.lines - 2) * per_line
if opts.number % per_line:
opts.number = opts.number + per_line - opts.number % per_line
if not opts.alphabet:
parser.error("alphabet can not be empty")
return opts
if __name__ == '__main__':
main()