Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/betterproto/casing.py: 64%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

42 statements  

1import keyword 

2import re 

3 

4 

5# Word delimiters and symbols that will not be preserved when re-casing. 

6# language=PythonRegExp 

7SYMBOLS = "[^a-zA-Z0-9]*" 

8 

9# Optionally capitalized word. 

10# language=PythonRegExp 

11WORD = "[A-Z]*[a-z]*[0-9]*" 

12 

13# Uppercase word, not followed by lowercase letters. 

14# language=PythonRegExp 

15WORD_UPPER = "[A-Z]+(?![a-z])[0-9]*" 

16 

17 

18def safe_snake_case(value: str) -> str: 

19 """Snake case a value taking into account Python keywords.""" 

20 value = snake_case(value) 

21 value = sanitize_name(value) 

22 return value 

23 

24 

25def snake_case(value: str, strict: bool = True) -> str: 

26 """ 

27 Join words with an underscore into lowercase and remove symbols. 

28 

29 Parameters 

30 ----------- 

31 value: :class:`str` 

32 The value to convert. 

33 strict: :class:`bool` 

34 Whether or not to force single underscores. 

35 

36 Returns 

37 -------- 

38 :class:`str` 

39 The value in snake_case. 

40 """ 

41 

42 def substitute_word(symbols: str, word: str, is_start: bool) -> str: 

43 if not word: 

44 return "" 

45 if strict: 

46 delimiter_count = 0 if is_start else 1 # Single underscore if strict. 

47 elif is_start: 

48 delimiter_count = len(symbols) 

49 elif word.isupper() or word.islower(): 

50 delimiter_count = max( 

51 1, len(symbols) 

52 ) # Preserve all delimiters if not strict. 

53 else: 

54 delimiter_count = len(symbols) + 1 # Extra underscore for leading capital. 

55 

56 return ("_" * delimiter_count) + word.lower() 

57 

58 snake = re.sub( 

59 f"(^)?({SYMBOLS})({WORD_UPPER}|{WORD})", 

60 lambda groups: substitute_word(groups[2], groups[3], groups[1] is not None), 

61 value, 

62 ) 

63 return snake 

64 

65 

66def pascal_case(value: str, strict: bool = True) -> str: 

67 """ 

68 Capitalize each word and remove symbols. 

69 

70 Parameters 

71 ----------- 

72 value: :class:`str` 

73 The value to convert. 

74 strict: :class:`bool` 

75 Whether or not to output only alphanumeric characters. 

76 

77 Returns 

78 -------- 

79 :class:`str` 

80 The value in PascalCase. 

81 """ 

82 

83 def substitute_word(symbols, word): 

84 if strict: 

85 return word.capitalize() # Remove all delimiters 

86 

87 if word.islower(): 

88 delimiter_length = len(symbols[:-1]) # Lose one delimiter 

89 else: 

90 delimiter_length = len(symbols) # Preserve all delimiters 

91 

92 return ("_" * delimiter_length) + word.capitalize() 

93 

94 return re.sub( 

95 f"({SYMBOLS})({WORD_UPPER}|{WORD})", 

96 lambda groups: substitute_word(groups[1], groups[2]), 

97 value, 

98 ) 

99 

100 

101def camel_case(value: str, strict: bool = True) -> str: 

102 """ 

103 Capitalize all words except first and remove symbols. 

104 

105 Parameters 

106 ----------- 

107 value: :class:`str` 

108 The value to convert. 

109 strict: :class:`bool` 

110 Whether or not to output only alphanumeric characters. 

111 

112 Returns 

113 -------- 

114 :class:`str` 

115 The value in camelCase. 

116 """ 

117 return lowercase_first(pascal_case(value, strict=strict)) 

118 

119 

120def lowercase_first(value: str) -> str: 

121 """ 

122 Lower cases the first character of the value. 

123 

124 Parameters 

125 ---------- 

126 value: :class:`str` 

127 The value to lower case. 

128 

129 Returns 

130 ------- 

131 :class:`str` 

132 The lower cased string. 

133 """ 

134 return value[0:1].lower() + value[1:] 

135 

136 

137def sanitize_name(value: str) -> str: 

138 # https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles 

139 if keyword.iskeyword(value): 

140 return f"{value}_" 

141 if not value.isidentifier(): 

142 return f"_{value}" 

143 return value