Coverage for pymend\docstring_parser\common.py: 100%

1"""Common methods for parsing."""

2

3 import enum

4 from dataclasses import dataclass

5 from typing import Optional, Union

6

7 from typing_extensions import TypeAlias

8

9 PARAM_KEYWORDS = {

10 "param",

11 "parameter",

12 "arg",

13 "argument",

14 "attribute",

15 "key",

16 "keyword",

17}

18# These could be made into frozen sets

19# Then one could create a dictionary

20# {KEYWORD: FUNCTION_THAT_HANDLES_THAT_KEYWORD_SECTION} # noqa: ERA001

21 RAISES_KEYWORDS = {"raises", "raise", "except", "exception"}

22 DEPRECATION_KEYWORDS = {"deprecation", "deprecated"}

23 RETURNS_KEYWORDS = {"return", "returns"}

24 YIELDS_KEYWORDS = {"yield", "yields"}

25 EXAMPLES_KEYWORDS = {"example", "examples"}

26

27

28 def clean_str(string: str) -> Optional[str]:

29 """Strip a string and return None if it is now empty.

30

31 Parameters

32 ----------

33 string : str

34 String to clean

35

36 Returns

37 -------

38 Optional[str]

39 None of the stripped string is empty. Otherwise the stripped string.

40 """

41 string = string.strip()

42 return string if string != "" else None

43

44

45 class ParseError(RuntimeError):

46 """Base class for all parsing related errors."""

47

48

49 class DocstringStyle(enum.Enum):

50 """Docstring style."""

51

52 REST = 1

53 GOOGLE = 2

54 NUMPYDOC = 3

55 EPYDOC = 4

56 AUTO = 255

57

58

59 class RenderingStyle(enum.Enum):

60 """Rendering style when unparsing parsed docstrings."""

61

62 COMPACT = 1

63 CLEAN = 2

64 EXPANDED = 3

65

66

67@dataclass

68 class DocstringMeta:

69 """Docstring meta information.

70

71 Symbolizes lines in form of

72

73 Parameters

74 ----------

75 args : List[str]

76 list of arguments. The exact content of this variable is

77 dependent on the kind of docstring; it's used to distinguish

78 between custom docstring meta information items.

79 description : Optional[str]

80 associated docstring description.

81 """

82

83 args: list[str]

84 description: Optional[str]

85

86

87@dataclass

88 class DocstringParam(DocstringMeta):

89 """DocstringMeta symbolizing :param metadata."""

90

91 arg_name: str

92 type_name: Optional[str]

93 is_optional: Optional[bool]

94 default: Optional[str]

95

96

97@dataclass

98 class DocstringReturns(DocstringMeta):

99 """DocstringMeta symbolizing :returns metadata."""

100

101 type_name: Optional[str]

102 is_generator: bool

103 return_name: Optional[str] = None

104

105

106@dataclass

107 class DocstringYields(DocstringMeta):

108 """DocstringMeta symbolizing :yields metadata."""

109

110 type_name: Optional[str]

111 is_generator: bool

112 yield_name: Optional[str] = None

113

114

115@dataclass

116 class DocstringRaises(DocstringMeta):

117 """DocstringMeta symbolizing :raises metadata."""

118

119 type_name: Optional[str]

120

121

122 MainSections: TypeAlias = Union[

123 DocstringParam, DocstringRaises, DocstringReturns, DocstringYields

124]

125

126

127@dataclass

128 class DocstringDeprecated(DocstringMeta):

129 """DocstringMeta symbolizing deprecation metadata."""

130

131 version: Optional[str]

132

133

134@dataclass

135 class DocstringExample(DocstringMeta):

136 """DocstringMeta symbolizing example metadata."""

137

138 snippet: Optional[str]

139

140

141 class Docstring:

142 """Docstring object representation."""

143

144 def __init__(

145 self,

146 style: Optional[DocstringStyle] = None,

147 ) -> None:

148 """Initialize self.

149

150 Parameters

151 ----------

152 style : Optional[DocstringStyle]

153 Style that this docstring was formatted in. (Default value = None)

154 """

155 self.short_description: Optional[str] = None

156 self.long_description: Optional[str] = None

157 self.blank_after_short_description: bool = False

158 self.blank_after_long_description: bool = False

159 self.meta: list[DocstringMeta] = []

160 self.style: Optional[DocstringStyle] = style

161

162 @property

163 def params(self) -> list[DocstringParam]:

164 """Return a list of information on function params.

165

166 Returns

167 -------

168 list[DocstringParam]

169 list of information on function params

170 """

171 return [item for item in self.meta if isinstance(item, DocstringParam)]

172

173 @property

174 def raises(self) -> list[DocstringRaises]:

175 """Return a list of the exceptions that the function may raise.

176

177 Returns

178 -------

179 list[DocstringRaises]

180 list of the exceptions that the function may raise.

181 """

182 return [item for item in self.meta if isinstance(item, DocstringRaises)]

183

184 @property

185 def returns(self) -> Optional[DocstringReturns]:

186 """Return a single information on function return.

187

188 Takes the first return information.

189

190 Returns

191 -------

192 Optional[DocstringReturns]

193 Single information on function return.

194 """

195 return next(

196 (item for item in self.meta if isinstance(item, DocstringReturns)),

197 None,

198 )

199

200 @property

201 def many_returns(self) -> list[DocstringReturns]:

202 """Return a list of information on function return.

203

204 Returns

205 -------

206 list[DocstringReturns]

207 list of information on function return.

208 """

209 return [item for item in self.meta if isinstance(item, DocstringReturns)]

210

211 @property

212 def yields(self) -> Optional[DocstringYields]:

213 """Return information on function yield.

214

215 Takes the first generator information.

216

217 Returns

218 -------

219 Optional[DocstringYields]

220 Single information on function yield.

221 """

222 return next(

223 (

224 item

225 for item in self.meta

226 if isinstance(item, DocstringYields) and item.is_generator

227 ),

228 None,

229 )

230

231 @property

232 def many_yields(self) -> list[DocstringYields]:

233 """Return a list of information on function yields.

234

235 Returns

236 -------

237 list[DocstringYields]

238 list of information on function yields.

239 """

240 return [item for item in self.meta if isinstance(item, DocstringYields)]

241

242 @property

243 def deprecation(self) -> Optional[DocstringDeprecated]:

244 """Return a single information on function deprecation notes.

245

246 Returns

247 -------

248 Optional[DocstringDeprecated]

249 single information on function deprecation notes.

250 """

251 return next(

252 (item for item in self.meta if isinstance(item, DocstringDeprecated)),

253 None,

254 )

255

256 @property

257 def examples(self) -> list[DocstringExample]:

258 """Return a list of information on function examples.

259

260 Returns

261 -------

262 list[DocstringExample]

263 list of information on function examples.

264 """

265 return [item for item in self.meta if isinstance(item, DocstringExample)]

266

267

268 def split_description(docstring: Docstring, desc_chunk: str) -> None:

269 """Break description into short and long parts.

270

271 Parameters

272 ----------

273 docstring : Docstring

274 Docstring to fill with description information.

275 desc_chunk : str

276 Chunk of the raw docstring representing the description.

277 """

278 parts = desc_chunk.split("\n", 1)

279 docstring.short_description = parts[0] or None

280 if len(parts) > 1:

281 long_desc_chunk = parts[1] or ""

282 docstring.blank_after_short_description = long_desc_chunk.startswith("\n")

283 docstring.blank_after_long_description = long_desc_chunk.endswith("\n\n")

284 docstring.long_description = long_desc_chunk.strip() or None

285

286

287 def append_description(docstring: Docstring, parts: list[str]) -> None:

288 """Append the docstrings description to the output stream.

289

290 Parameters

291 ----------

292 docstring : Docstring

293 Docstring whose information should be added.

294 parts : list[str]

295 List of strings representing the output of compose().

296 Descriptions should be added to this.

297 """

298 if docstring.short_description:

299 parts.append(docstring.short_description)

300 if docstring.blank_after_short_description:

301 parts.append("")

302 if docstring.long_description:

303 parts.append(docstring.long_description)

304 if docstring.blank_after_long_description:

305 parts.append("")