Coverage for muutils/misc/string.py: 85%

34 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-04-04 03:33 -0600

1from __future__ import annotations 

2 

3 

4from muutils.misc.hashing import stable_hash 

5 

6 

7def sanitize_name( 

8 name: str | None, 

9 additional_allowed_chars: str = "", 

10 replace_invalid: str = "", 

11 when_none: str | None = "_None_", 

12 leading_digit_prefix: str = "", 

13) -> str: 

14 """sanitize a string, leaving only alphanumerics and `additional_allowed_chars` 

15 

16 # Parameters: 

17 - `name : str | None` 

18 input string 

19 - `additional_allowed_chars : str` 

20 additional characters to allow, none by default 

21 (defaults to `""`) 

22 - `replace_invalid : str` 

23 character to replace invalid characters with 

24 (defaults to `""`) 

25 - `when_none : str | None` 

26 string to return if `name` is `None`. if `None`, raises an exception 

27 (defaults to `"_None_"`) 

28 - `leading_digit_prefix : str` 

29 character to prefix the string with if it starts with a digit 

30 (defaults to `""`) 

31 

32 # Returns: 

33 - `str` 

34 sanitized string 

35 """ 

36 

37 if name is None: 

38 if when_none is None: 

39 raise ValueError("name is None") 

40 else: 

41 return when_none 

42 

43 sanitized: str = "" 

44 for char in name: 

45 if char.isalnum(): 

46 sanitized += char 

47 elif char in additional_allowed_chars: 

48 sanitized += char 

49 else: 

50 sanitized += replace_invalid 

51 

52 if sanitized[0].isdigit(): 

53 sanitized = leading_digit_prefix + sanitized 

54 

55 return sanitized 

56 

57 

58def sanitize_fname(fname: str | None, **kwargs) -> str: 

59 """sanitize a filename to posix standards 

60 

61 - leave only alphanumerics, `_` (underscore), '-' (dash) and `.` (period) 

62 """ 

63 return sanitize_name(fname, additional_allowed_chars="._-", **kwargs) 

64 

65 

66def sanitize_identifier(fname: str | None, **kwargs) -> str: 

67 """sanitize an identifier (variable or function name) 

68 

69 - leave only alphanumerics and `_` (underscore) 

70 - prefix with `_` if it starts with a digit 

71 """ 

72 return sanitize_name( 

73 fname, additional_allowed_chars="_", leading_digit_prefix="_", **kwargs 

74 ) 

75 

76 

77def dict_to_filename( 

78 data: dict, 

79 format_str: str = "{key}_{val}", 

80 separator: str = ".", 

81 max_length: int = 255, 

82): 

83 # Convert the dictionary items to a list of strings using the format string 

84 formatted_items: list[str] = [ 

85 format_str.format(key=k, val=v) for k, v in data.items() 

86 ] 

87 

88 # Join the formatted items using the separator 

89 joined_str: str = separator.join(formatted_items) 

90 

91 # Remove special characters and spaces 

92 sanitized_str: str = sanitize_fname(joined_str) 

93 

94 # Check if the length is within limits 

95 if len(sanitized_str) <= max_length: 

96 return sanitized_str 

97 

98 # If the string is too long, generate a hash 

99 return f"h_{stable_hash(sanitized_str)}" 

100 

101 

102def dynamic_docstring(**doc_params): 

103 def decorator(func): 

104 if func.__doc__: 

105 func.__doc__ = func.__doc__.format(**doc_params) 

106 return func 

107 

108 return decorator