Skip to content

Commit abbd319

Browse files
committed
Project 20 solution
1 parent 4eb02e1 commit abbd319

File tree

2 files changed

+224
-44
lines changed

2 files changed

+224
-44
lines changed

20_password/password.py

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Author : Luke McGuire <[email protected]>
4+
Date : 2024-07-02
5+
Purpose: Password maker
6+
"""
7+
8+
import argparse
9+
import random
10+
import string
11+
12+
13+
# --------------------------------------------------
14+
def get_args():
15+
"""Get command-line arguments"""
16+
17+
parser = argparse.ArgumentParser(
18+
description="Password maker",
19+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
20+
)
21+
22+
parser.add_argument(
23+
"file",
24+
help="Input file(s)",
25+
metavar="FILE",
26+
type=argparse.FileType("rt"),
27+
nargs="+",
28+
)
29+
30+
parser.add_argument(
31+
"-n",
32+
"--num",
33+
help="Number of passwords to generate",
34+
metavar="int",
35+
type=int,
36+
default=3,
37+
)
38+
39+
parser.add_argument(
40+
"-w",
41+
"--num_words",
42+
help="Number of words per password",
43+
metavar="int",
44+
type=int,
45+
default=4,
46+
)
47+
48+
parser.add_argument(
49+
"-m",
50+
"--min_word_len",
51+
help="Minimum length of word",
52+
metavar="int",
53+
type=int,
54+
default=3,
55+
)
56+
57+
parser.add_argument(
58+
"-x",
59+
"--max_word_len",
60+
help="Maximum length of word",
61+
metavar="int",
62+
type=int,
63+
default=6,
64+
)
65+
66+
parser.add_argument(
67+
"-s", "--seed", help="Seed for random", metavar="int", type=int, default=None
68+
)
69+
70+
parser.add_argument(
71+
"-l", "--l33t", help="Obfuscate the password", action="store_true"
72+
)
73+
74+
return parser.parse_args()
75+
76+
77+
# --------------------------------------------------
78+
def choose(char: str) -> str:
79+
"""Choose a random case for a character"""
80+
81+
return random.choice([char.lower(), char.upper()])
82+
83+
84+
# --------------------------------------------------
85+
def test_choose():
86+
"""Test choose"""
87+
88+
state = random.getstate()
89+
random.seed(1)
90+
assert choose("a") == "a"
91+
assert choose("b") == "b"
92+
assert choose("c") == "C"
93+
assert choose("d") == "d"
94+
random.setstate(state)
95+
96+
97+
# --------------------------------------------------
98+
def ransom(passwd: str) -> str:
99+
"""Randomly capitalize and lowercase letters ala a ransom note"""
100+
101+
return "".join(map(choose, passwd))
102+
103+
104+
# --------------------------------------------------
105+
def test_ransom():
106+
"""Test ransom function"""
107+
108+
state = random.getstate()
109+
random.seed(1)
110+
assert ransom("Money") == "moNeY"
111+
assert ransom("Dollars") == "DOLlaRs"
112+
random.setstate(state)
113+
114+
115+
# --------------------------------------------------
116+
def l33t(passwd: str) -> str:
117+
"""Obfuscate the password using l33t rules"""
118+
l33t_table = {"a": "@", "A": "4", "O": "0", "t": "+", "E": "3", "I": "1", "S": "5"}
119+
ransom_note = ransom(passwd)
120+
l33ted = ransom_note.translate(str.maketrans(l33t_table))
121+
return l33ted + random.choice(string.punctuation)
122+
123+
124+
# --------------------------------------------------
125+
def test_l33t():
126+
"""Test l33t function"""
127+
128+
state = random.getstate()
129+
random.seed(1)
130+
assert l33t("Money") == "moNeY{"
131+
assert l33t("Dollars") == "D0ll4r5`"
132+
random.setstate(state)
133+
134+
135+
# --------------------------------------------------
136+
def clean(word):
137+
"""Remove non-alphanumeric characters fro word"""
138+
139+
return "".join(filter(str.isalpha, word))
140+
141+
142+
# --------------------------------------------------
143+
def test_clean():
144+
"""Test clean function"""
145+
146+
assert clean("") == ""
147+
assert clean("states,") == "states"
148+
assert clean("Don't") == "Dont"
149+
150+
151+
# --------------------------------------------------
152+
def main():
153+
"""Make a jazz noise here"""
154+
155+
args = get_args()
156+
random.seed(args.seed)
157+
words = set()
158+
159+
def word_len(word):
160+
"""filter words list to match length requirements"""
161+
return args.min_word_len <= len(word) <= args.max_word_len
162+
163+
for fh in args.file:
164+
for line in fh:
165+
for word in filter(word_len, map(clean, line.title().split())):
166+
words.add(word)
167+
168+
words = sorted(words)
169+
passwords = ["".join(random.sample(words, args.num_words)) for _ in range(args.num)]
170+
171+
for passwd in passwords:
172+
if args.l33t:
173+
passwd = l33t(passwd)
174+
print(passwd)
175+
176+
177+
# --------------------------------------------------
178+
if __name__ == "__main__":
179+
main()

20_password/test.py

+45-44
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,34 @@
77
import string
88
from subprocess import getstatusoutput
99

10-
prg = './password.py'
11-
words = '../inputs/words.txt'
10+
PRG = "./password.py"
11+
WORDS = "../inputs/WORDS.txt"
1212

1313

1414
# --------------------------------------------------
1515
def test_exists():
1616
"""exists"""
1717

18-
assert os.path.isfile(prg)
19-
assert os.path.isfile(words)
18+
assert os.path.isfile(PRG)
19+
assert os.path.isfile(WORDS)
2020

2121

2222
# --------------------------------------------------
2323
def test_usage():
2424
"""usage"""
2525

26-
for flag in ['-h', '--help']:
27-
rv, out = getstatusoutput(f'{prg} {flag}')
26+
for flag in ["-h", "--help"]:
27+
rv, out = getstatusoutput(f"python {PRG} {flag}")
2828
assert rv == 0
29-
assert out.lower().startswith('usage')
29+
assert out.lower().startswith("usage")
3030

3131

3232
# --------------------------------------------------
3333
def test_bad_file():
3434
"""Dies on bad file"""
3535

3636
bad = random_string()
37-
rv, out = getstatusoutput(f'{prg} {bad}')
37+
rv, out = getstatusoutput(f"python {PRG} {bad}")
3838
assert rv != 0
3939
assert re.search(f"No such file or directory: '{bad}'", out)
4040

@@ -44,19 +44,19 @@ def test_bad_num():
4444
"""Dies on bad num"""
4545

4646
bad = random_string()
47-
flag = '-n' if random.choice([0, 1]) else '--num'
48-
rv, out = getstatusoutput(f'{prg} {flag} {bad} {words}')
47+
flag = "-n" if random.choice([0, 1]) else "--num"
48+
rv, out = getstatusoutput(f"python {PRG} {flag} {bad} {WORDS}")
4949
assert rv != 0
5050
assert re.search(f"invalid int value: '{bad}'", out)
5151

5252

5353
# --------------------------------------------------
54-
def test_bad_num_words():
54+
def test_bad_num_WORDS():
5555
"""Dies on bad num"""
5656

5757
bad = random_string()
58-
flag = '-w' if random.choice([0, 1]) else '--num_words'
59-
rv, out = getstatusoutput(f'{prg} {flag} {bad} {words}')
58+
flag = "-w" if random.choice([0, 1]) else "--num_WORDS"
59+
rv, out = getstatusoutput(f"python {PRG} {flag} {bad} {WORDS}")
6060
assert rv != 0
6161
assert re.search(f"invalid int value: '{bad}'", out)
6262

@@ -66,8 +66,8 @@ def test_bad_min_word_len():
6666
"""Dies on bad min_word_len"""
6767

6868
bad = random_string()
69-
flag = '-m' if random.choice([0, 1]) else '--min_word_len'
70-
rv, out = getstatusoutput(f'{prg} {flag} {bad} {words}')
69+
flag = "-m" if random.choice([0, 1]) else "--min_word_len"
70+
rv, out = getstatusoutput(f"python {PRG} {flag} {bad} {WORDS}")
7171
assert rv != 0
7272
assert re.search(f"invalid int value: '{bad}'", out)
7373

@@ -77,8 +77,8 @@ def test_bad_max_word_len():
7777
"""Dies on bad max_word_len"""
7878

7979
bad = random_string()
80-
flag = '-m' if random.choice([0, 1]) else '--max_word_len'
81-
rv, out = getstatusoutput(f'{prg} {flag} {bad} {words}')
80+
flag = "-m" if random.choice([0, 1]) else "--max_word_len"
81+
rv, out = getstatusoutput(f"python {PRG} {flag} {bad} {WORDS}")
8282
assert rv != 0
8383
assert re.search(f"invalid int value: '{bad}'", out)
8484

@@ -88,8 +88,8 @@ def test_bad_seed():
8888
"""Dies on bad seed"""
8989

9090
bad = random_string()
91-
flag = '-s' if random.choice([0, 1]) else '--seed'
92-
rv, out = getstatusoutput(f'{prg} {flag} {bad} {words}')
91+
flag = "-s" if random.choice([0, 1]) else "--seed"
92+
rv, out = getstatusoutput(f"python {PRG} {flag} {bad} {WORDS}")
9393
assert rv != 0
9494
assert re.search(f"invalid int value: '{bad}'", out)
9595

@@ -98,70 +98,71 @@ def test_bad_seed():
9898
def test_defaults():
9999
"""Test"""
100100

101-
rv, out = getstatusoutput(f'{prg} -s 1 {words}')
101+
rv, out = getstatusoutput(f"python {PRG} -s 1 {WORDS}")
102102
assert rv == 0
103-
assert out.strip() == '\n'.join([
104-
'DuniteBoonLociDefat', 'WegaTitmalUnplatSatire', 'IdeanClipsVitiArriet'
105-
])
103+
assert out.strip() == "\n".join(
104+
["DuniteBoonLociDefat", "WegaTitmalUnplatSatire", "IdeanClipsVitiArriet"]
105+
)
106106

107107

108108
# --------------------------------------------------
109109
def test_num():
110110
"""Test"""
111111

112-
rv, out = getstatusoutput(f'{prg} -s 1 -n 1 {words}')
112+
rv, out = getstatusoutput(f"python {PRG} -s 1 -n 1 {WORDS}")
113113
assert rv == 0
114-
assert out.strip() == 'DuniteBoonLociDefat'
114+
assert out.strip() == "DuniteBoonLociDefat"
115115

116116

117117
# --------------------------------------------------
118-
def test_num_words():
118+
def test_num_WORDS():
119119
"""Test"""
120120

121-
rv, out = getstatusoutput(f'{prg} -s 1 -w 2 {words}')
121+
rv, out = getstatusoutput(f"python {PRG} -s 1 -w 2 {WORDS}")
122122
assert rv == 0
123-
assert out.strip() == '\n'.join(['DuniteBoon', 'LociDefat', 'WegaTitmal'])
123+
assert out.strip() == "\n".join(["DuniteBoon", "LociDefat", "WegaTitmal"])
124124

125125

126126
# --------------------------------------------------
127127
def test_min_word_len():
128128
"""Test"""
129129

130-
rv, out = getstatusoutput(f'{prg} -s 1 -m 5 {words}')
130+
rv, out = getstatusoutput(f"python {PRG} -s 1 -m 5 {WORDS}")
131131
assert rv == 0
132-
assert out.strip() == '\n'.join([
133-
'CarneyRapperWabenoUndine', 'BabaiFarerBugleOnlepy',
134-
'UnbittMinnyNatalSkanda'
135-
])
132+
assert out.strip() == "\n".join(
133+
["CarneyRapperWabenoUndine", "BabaiFarerBugleOnlepy", "UnbittMinnyNatalSkanda"]
134+
)
136135

137136

138137
# --------------------------------------------------
139138
def test_max_word_len():
140139
"""Test"""
141140

142-
rv, out = getstatusoutput(f'{prg} -s 1 -x 10 {words}')
141+
rv, out = getstatusoutput(f"python {PRG} -s 1 -x 10 {WORDS}")
143142
assert rv == 0
144-
assert out.strip() == '\n'.join([
145-
'DicemanYardwandBoeberaKismetic', 'CubiculumTilsitSnowcapSuer',
146-
'ProhasteHaddockChristmasyTenonitis'
147-
])
143+
assert out.strip() == "\n".join(
144+
[
145+
"DicemanYardwandBoeberaKismetic",
146+
"CubiculumTilsitSnowcapSuer",
147+
"ProhasteHaddockChristmasyTenonitis",
148+
]
149+
)
148150

149151

150152
# --------------------------------------------------
151153
def test_l33t():
152154
"""Test"""
153155

154-
rv, out = getstatusoutput(f'{prg} -s 1 -l {words}')
156+
rv, out = getstatusoutput(f"python {PRG} -s 1 -l {WORDS}")
155157
assert rv == 0
156-
assert out.strip() == '\n'.join([
157-
'DUn1Teb0onloCiDef4T/', 'Weg4TiTm@LuNPl4T54+1r3_',
158-
'iD3@Ncl1P5v1+14rrie+/'
159-
])
158+
assert out.strip() == "\n".join(
159+
["DUn1Teb0onloCiDef4T/", "Weg4TiTm@LuNPl4T54+1r3_", "iD3@Ncl1P5v1+14rrie+/"]
160+
)
160161

161162

162163
# --------------------------------------------------
163164
def random_string():
164165
"""generate a random string"""
165166

166167
k = random.randint(5, 10)
167-
return ''.join(random.choices(string.ascii_letters + string.digits, k=k))
168+
return "".join(random.choices(string.ascii_letters + string.digits, k=k))

0 commit comments

Comments
 (0)