passmaker 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
passmaker/__init__.py ADDED
File without changes
passmaker/__main__.py ADDED
@@ -0,0 +1,209 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import functools
5
+ import secrets
6
+ import string
7
+ from typing import Callable, Sequence
8
+
9
+ import click
10
+ from click_option_group import optgroup
11
+
12
+ # Rule is a filter to an alphabet
13
+ type Rule = Callable[[str, set[str]], set[str]]
14
+
15
+ QWERTY: str = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
16
+
17
+
18
+ def no_duplicates(password: str, alphabet: set[str]) -> set[str]:
19
+ """Don't use the same character more than once in a password.
20
+
21
+ This rule removes from alphabet every char from password."""
22
+ if not password:
23
+ return alphabet
24
+
25
+ return alphabet - set(password)
26
+
27
+
28
+ def no_sequence_characters(password: str, alphabet: set[str]) -> set[str]:
29
+ """Don't use characters in password in a sequence, e.g. abc, 123, etc.
30
+
31
+ This rule enforces that the next symbol in password cannot be from
32
+ sequence, thus it's removed from alphabet."""
33
+
34
+ def _get_char(string: str, idx: int) -> str:
35
+ try:
36
+ return string[idx]
37
+ except IndexError:
38
+ return ""
39
+
40
+ if not password:
41
+ return alphabet
42
+
43
+ with contextlib.suppress(ValueError):
44
+ index = QWERTY.index(password[-1])
45
+
46
+ alphabet -= {
47
+ _get_char(QWERTY, index - 1),
48
+ _get_char(QWERTY, index + 1),
49
+ }
50
+
51
+ return alphabet - {chr(ord(password[-1]) - 1), chr(ord(password[-1]) + 1)}
52
+
53
+
54
+ def no_similar_characters(password: str, alphabet: set[str]) -> set[str]:
55
+ """Don't use similar characters in password, e.g. i, I, L, l, 1, etc.
56
+
57
+ This rule removes from alphabet these characters: i1Il|, oO0, `.:"""
58
+ return alphabet - set("i1Il|oO0`.:")
59
+
60
+
61
+ all_rules = [no_duplicates, no_sequence_characters, no_similar_characters]
62
+
63
+
64
+ def build_alphabet(
65
+ *,
66
+ digits: bool = True,
67
+ lowercase: bool = True,
68
+ uppercase: bool = True,
69
+ symbols: bool | str = True,
70
+ ) -> str:
71
+ alphabet = ""
72
+
73
+ if digits:
74
+ alphabet += string.digits
75
+ if lowercase:
76
+ alphabet += string.ascii_lowercase
77
+ if uppercase:
78
+ alphabet += string.ascii_uppercase
79
+ if symbols:
80
+ alphabet += (
81
+ symbols if isinstance(symbols, str) else string.punctuation
82
+ )
83
+
84
+ return alphabet
85
+
86
+
87
+ def generate_password(
88
+ length: int,
89
+ alphabet: str | None = None,
90
+ rules: Sequence[Rule] = all_rules,
91
+ ) -> str:
92
+ if alphabet is None:
93
+ alphabet = build_alphabet()
94
+
95
+ password = ""
96
+
97
+ for _ in range(length):
98
+ allowed_symbols = functools.reduce(
99
+ lambda alpha, rule: rule(password, alpha),
100
+ rules,
101
+ set(alphabet),
102
+ )
103
+
104
+ password += secrets.choice(list(allowed_symbols))
105
+
106
+ return password
107
+
108
+
109
+ @click.command()
110
+ @click.option(
111
+ "-l", "--length", default=22, type=int, help="Length of each password."
112
+ )
113
+ @click.option(
114
+ "-c",
115
+ "--count",
116
+ default=1,
117
+ type=int,
118
+ help="Count of passwords to generate in one take.",
119
+ )
120
+ @optgroup.group(
121
+ "Password alphabet",
122
+ help="Options controlling what set of symbols can be used in a password.",
123
+ )
124
+ @optgroup.option(
125
+ "-d/-D",
126
+ "--digits/--no-digits",
127
+ "use_digits",
128
+ default=True,
129
+ help="Where to use digits in password or no (default: yes)",
130
+ )
131
+ @optgroup.option(
132
+ "-a/-A",
133
+ "--alpha/--no-alpha",
134
+ "use_alpha",
135
+ default=True,
136
+ help="Where to use ASCII alphabet symbols in password or no "
137
+ "(default: yes)",
138
+ )
139
+ @optgroup.option(
140
+ "-S",
141
+ "--no-symbols",
142
+ default=False,
143
+ help="Don't use punctuation symbols for password generation "
144
+ "(overrides 'symbols' option).",
145
+ )
146
+ @optgroup.option(
147
+ "-s",
148
+ "--symbols",
149
+ type=str,
150
+ help="Use these exact symbols for password generation.",
151
+ )
152
+ @optgroup.group(
153
+ "Generation rules", help="Set of rules to control password generation."
154
+ )
155
+ @optgroup.option(
156
+ "--no-duplicates/--duplicates",
157
+ "use_duplicates",
158
+ default=True,
159
+ help="Allow or disallow duplicate characters.",
160
+ )
161
+ @optgroup.option(
162
+ "--no-sequence/--sequence",
163
+ "use_sequence",
164
+ default=True,
165
+ help="Allow or disallow sequential characters.",
166
+ )
167
+ @optgroup.option(
168
+ "--no-similar/--similar",
169
+ "use_similar",
170
+ default=True,
171
+ help="Allow or disallow similar looking characters.",
172
+ )
173
+ def main( # noqa: PLR0913
174
+ length: int,
175
+ count: int,
176
+ *,
177
+ use_digits: bool,
178
+ use_alpha: bool,
179
+ symbols: str,
180
+ no_symbols: bool,
181
+ use_duplicates: bool,
182
+ use_sequence: bool,
183
+ use_similar: bool,
184
+ ) -> None:
185
+ """Simple password generator. Safe to use, generates quite strong
186
+ passwords.
187
+ """
188
+ alphabet = build_alphabet(
189
+ digits=use_digits,
190
+ lowercase=use_alpha,
191
+ uppercase=use_alpha,
192
+ symbols=False if no_symbols else symbols or True,
193
+ )
194
+
195
+ rules = []
196
+
197
+ if use_duplicates:
198
+ rules.append(no_duplicates)
199
+ if use_sequence:
200
+ rules.append(no_sequence_characters)
201
+ if use_similar:
202
+ rules.append(no_similar_characters)
203
+
204
+ for _ in range(count):
205
+ print(generate_password(length, alphabet=alphabet, rules=rules)) # noqa: T201
206
+
207
+
208
+ if __name__ == "__main__":
209
+ main()
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: passmaker
3
+ Version: 0.1.0
4
+ Summary: Generate strong passwords with ease and peace.
5
+ Author-email: Soucelover <mr.ktotov@gmail.com>
6
+ Requires-Python: >=3.14
7
+ Requires-Dist: click-option-group>=0.5.9
8
+ Requires-Dist: click>=8.3.1
@@ -0,0 +1,6 @@
1
+ passmaker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ passmaker/__main__.py,sha256=Xf46YaxRbSkPAPJTKhKQIw830JDAhUYHIxgJmmU-pbQ,5274
3
+ passmaker-0.1.0.dist-info/METADATA,sha256=mvcxsGmjidwK9TMjDqBsLz8Yi1WZSswZ6Fw9jTddtDE,249
4
+ passmaker-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ passmaker-0.1.0.dist-info/entry_points.txt,sha256=yP7kgl6dQ8HOszYiyx1BfDw4Ax_zT7wMvyYIFDnsOEk,54
6
+ passmaker-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ passmaker = passmaker.__main__:main