"""Internal library for manipulating, creating and dealing with names, or more generallyidentifiers."""importkeywordimportrefromtypingimportCallable,Iterable,List,TypeVarfrom.exceptionsimportInvalidIdentifierT=TypeVar("T")
[docs]defis_valid_identifier(string:str)->bool:"""Check if string is a valid package name Args: string: package name Returns: True if string is valid package name else False """ifnotre.match("[_A-Za-z][_a-zA-Z0-9]*$",string):returnFalseifkeyword.iskeyword(string):returnFalsereturnTrue
[docs]defmake_valid_identifier(string:str)->str:"""Try to make a valid package name identifier from a string Args: string: invalid package name Returns: Valid package name as string or :obj:`RuntimeError` Raises: :obj:`InvalidIdentifier`: raised if identifier can not be converted """string=str(string).strip()string=string.replace("-","_")string=string.replace(" ","_")string=re.sub("[^_a-zA-Z0-9]","",string)string=string.lower()ifis_valid_identifier(string):returnstringraiseInvalidIdentifier("String cannot be converted to a valid identifier.")
# from https://en.wikibooks.org/, Creative Commons Attribution-ShareAlike 3.0
[docs]deflevenshtein(s1:str,s2:str)->int:"""Calculate the Levenshtein distance between two strings Args: s1: first string s2: second string Returns: Distance between s1 and s2 """iflen(s1)<len(s2):returnlevenshtein(s2,s1)# len(s1) >= len(s2)iflen(s2)==0:returnlen(s1)previous_row=list(range(len(s2)+1))fori,c1inenumerate(s1):current_row=[i+1]forj,c2inenumerate(s2):insertions=previous_row[j+1]+1deletions=current_row[j]+1substitutions=previous_row[j]+(c1!=c2)current_row.append(min(insertions,deletions,substitutions))previous_row=current_rowreturnprevious_row[-1]
[docs]defdasherize(word:str)->str:"""Replace underscores with dashes in the string. Example:: >>> dasherize("foo_bar") "foo-bar" Args: word (str): input word Returns: input word with underscores replaced by dashes """returnword.replace("_","-")
[docs]defunderscore(word:str)->str:"""Convert CamelCasedStrings or dasherized-strings into underscore_strings. Example:: >>> underscore("FooBar-foo") "foo_bar_foo" """return"_".join(wforwinCAMEL_CASE_SPLITTER.split(word)ifw).lower()
[docs]defdeterministic_name(obj):"""Private API that returns an string that can be used to deterministically deduplicate and sort sequences of objects. """mod_name=getattr(obj,"__module__","..")qual_name=getattr(obj,"__qualname__",obj.__class__.__qualname__)returnf"{mod_name}.{qual_name}"
[docs]defdeterministic_sort(sequence:Iterable[T])->List[T]:"""Private API that order a sequence of objects lexicographically (by :obj:`deterministic_name`), removing duplicates, which is needed for determinism. The main purpose of this function is to deterministically sort a sequence of PyScaffold extensions (it will also sort internal extensions before external: "pyscaffold.*" < "pyscaffoldext.*"). """deduplicated={deterministic_name(x):xforxinsequence}# ^ duplicated keys will overwrite each other, so just one of them is leftreturn[vfor(_k,v)insorted(deduplicated.items())]
[docs]defget_id(function:Callable)->str:"""Given a function, calculate its identifier. A identifier is a string in the format ``<module name>:<function name>``, similarly to the convention used for setuptools entry points. Note: This function does not return a Python 3 ``__qualname__`` equivalent. If the function is nested inside another function or class, the parent name is ignored. Args: function (callable): function object """returnf"{function.__module__}:{function.__name__}"