# -*- coding: utf-8 -*-"""Random password generator and its Alfred Script Filter entry point.Charset design:- ``lowercase + uppercase + digits + symbols`` (``!%@#&^*``)- minus visually-confusing characters: ``0``, ``1``, ``l``, ``I``, ``o``, ``O``Policy: every accepted password must contain at least one character from eachof the four classes (lower, upper, digit, symbol) and must start with a letter— hand-typeable, mixed-class, and unambiguous when read aloud."""importrandomimportafwf.apiasafwffrom.constantsimportcharset_alphafrom.constantsimportcharset_digitsfrom.constantsimportcharset_lowerfrom.constantsimportcharset_listfrom.constantsimportcharset_symbolsfrom.constantsimportcharset_upperfrom.constantsimportdefault_lengthfrom.constantsimportmax_lengthfrom.constantsimportmin_lengthfrom.constantsimportmsg_autocompletefrom.constantsimportmsg_enter_passwordfrom.constantsimportmsg_invalid_length_valuefrom.constantsimportn_password
[docs]defis_valid_password(password:str)->bool:"""Check that ``password`` satisfies the four-class + starts-with-letter policy."""has_lower=len(set(password).intersection(charset_lower))>0has_upper=len(set(password).intersection(charset_upper))>0has_digits=len(set(password).intersection(charset_digits))>0has_symbol=len(set(password).intersection(charset_symbols))>0startswith_alpha=password[0]incharset_alphareturnhas_lowerandhas_upperandhas_digitsandhas_symbolandstartswith_alpha
[docs]defrandom_password(length:int)->str:"""Generate one random password of ``length`` chars; retries until valid."""password="".join([random.choice(charset_list)for_inrange(length)])ifnotis_valid_password(password):returnrandom_password(length)returnpassword
[docs]defgen_passwords(length:int)->afwf.ScriptFilter:"""Return a ``ScriptFilter`` of ``n_password`` fresh passwords of ``length``."""sf=afwf.ScriptFilter()for_inrange(n_password):password=random_password(length)item=afwf.Item(title=password,subtitle="Hit 'Command + C' to copy",arg=password,valid=True,)sf.items.append(item)returnsf
[docs]defmain(query:str)->afwf.ScriptFilter:"""Alfred entry point. ``query`` parses as int in ``[min_length, max_length]``, otherwise an error item is shown."""query=query.strip()ifnotquery:item=afwf.Item(title=msg_enter_password,subtitle=msg_autocomplete,autocomplete=str(default_length),valid=True,)returnafwf.ScriptFilter(items=[item])try:length=int(query)exceptValueError:return_invalid_length_sf(f"`{query}` is NOT a valid length!")ifmin_length<=length<=max_length:returngen_passwords(length)return_invalid_length_sf(msg_invalid_length_value)