self.assertEqual("".join(map(lambda x: x[0], result.split())), word)
def test_commandlineCount(self):
- count = 5
+ count = 6
result = subprocess.check_output([
sys.executable, "xkcdpass/xkcd_password.py",
"-w", WORDFILE,
"--separator", ""])
self.assertEqual(result.find(b"\n"), -1)
+ def test_set_case(self):
+ words = "this is only a test".lower().split()
+ words_before = set(words)
+
+ results = {}
+
+ results["lower"] = xkcd_password.set_case(words, method="lower")
+ results["upper"] = xkcd_password.set_case(words, method="upper")
+ results["alternating"] = xkcd_password.set_case(words, method="alternating")
+ results["random"] = xkcd_password.set_case(words, method="random", testing=True)
+
+ words_after = set([word.lower() for group in list(results.values()) for word in group])
+
+ # Test that no words have been fundamentally mutated by any of the methods
+ self.assertTrue(words_before == words_after)
+
+ # Test that the words have been uppered or lowered respectively.
+ self.assertTrue(all([word.islower() for word in results["lower"]]))
+ self.assertTrue(all([word.isupper() for word in results["upper"]]))
+
+ # Test that the words have been correctly uppered randomly.
+ expected_random_result_1 = ['THIS', 'IS', 'ONLY', 'a', 'test']
+ expected_random_result_2 = ['THIS', 'IS', 'a', 'test', 'ALSO']
+
+ words_extra = "this is a test also".lower().split()
+ observed_random_result_1 = results["random"]
+ observed_random_result_2 = xkcd_password.set_case(
+ words_extra,
+ method="random",
+ testing=True
+ )
+
+ self.assertTrue(expected_random_result_1 == observed_random_result_1)
+ self.assertTrue(expected_random_result_2 == observed_random_result_2)
+
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(XkcdPasswordTests)
return validate(answer)
+def alternating_case(words):
+ """
+ Set EVERY OTHER word to UPPER case.
+ """
+ return [word.upper()
+ if i % 2 == 0
+ else word
+ for i, word in enumerate(lower_case(words))]
+
+
+def upper_case(words):
+ """
+ Set ALL words to UPPER case.
+ """
+ return [w.upper() for w in words]
+
+
+def lower_case(words):
+ """
+ Set ALL words to LOWER case.
+ """
+ return [w.lower() for w in words]
+
+
+def random_case(words, testing=False):
+ """
+ Set RANDOM words to UPPER case.
+ """
+ def make_upper(word):
+ """Return 'word.upper()' on a random basis."""
+ if testing:
+ random.seed(word)
+
+ if random.choice([True, False]):
+ return word.upper()
+ else:
+ return word
+
+ return [make_upper(word) for word in lower_case(words)]
+
+
+CASE_METHODS = {"alternating": alternating_case,
+ "upper": upper_case,
+ "lower": lower_case,
+ "random": random_case}
+
+
+def set_case(words, method="lower", testing=False):
+ """
+ Perform capitalization on some or all of the strings in `words`.
+
+ Default method is "lower".
+
+ Args:
+ words (list): word list generated by `choose_words()` or `find_acrostic()`.
+ method (str): one of {"alternating", "upper", "lower", "random"}.
+ testing (bool): only affects method="random".
+ If True: the random seed will be set to each word prior
+ to choosing True or False before setting the case to upper.
+ This way we can test that random is working by giving different
+ word lists.
+ """
+ if (method == "random") and (testing):
+ return random_case(words, testing=True)
+ else:
+ return CASE_METHODS[method](words)
+
+
def generate_xkcdpassword(wordlist,
numwords=6,
interactive=False,
acrostic=False,
- delimiter=" "):
+ delimiter=" ",
+ case="lower"):
"""
Generate an XKCD-style password from the words in wordlist.
"""
words = choose_words(wordlist, numwords)
else:
words = find_acrostic(acrostic, worddict)
-
- return delimiter.join(words)
+
+ return delimiter.join(set_case(words, method=case))
# useful if driving the logic from other code
if not interactive:
interactive=options.interactive,
numwords=options.numwords,
acrostic=options.acrostic,
- delimiter=options.delimiter
+ delimiter=options.delimiter,
+ case=options.case,
),
end=options.separator)
count -= 1
"-s", "--separator",
dest="separator", default="\n", metavar="SEP",
help="Separate generated passphrases with SEP.")
+ self.add_argument(
+ "-C", "--case",
+ dest="case", type=str, metavar="CASE",
+ choices=list(CASE_METHODS.keys()), default="lower",
+ help="Choose the method for setting the case of each word in the passphrase. "
+ "Choices: {cap_meths} (default: 'lower').".format(cap_meths=list(CASE_METHODS.keys())))
self.add_argument(
"--allow-weak-rng",
action="store_true", dest="allow_weak_rng", default=False,