File: utils.py

package info (click to toggle)
python-pyproject-parser 0.13.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,432 kB
  • sloc: python: 3,086; makefile: 7
file content (189 lines) | stat: -rw-r--r-- 5,431 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env python3
#
#  utils.py
"""
Utility functions.
"""
#
#  Copyright © 2021-2023 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
#  OR OTHER DEALINGS IN THE SOFTWARE.
#

# stdlib
import functools
import io
import os
import sys
from typing import TYPE_CHECKING, Any, Dict, Optional

# 3rd party
import dom_toml
from dom_toml.parser import BadConfigError
from domdf_python_tools.paths import PathPlus
from domdf_python_tools.typing import PathLike

if TYPE_CHECKING:
	# this package
	from pyproject_parser.type_hints import ContentTypes

__all__ = ["render_markdown", "render_rst", "content_type_from_filename", "PyProjectDeprecationWarning"]


def render_markdown(content: str) -> None:
	"""
	Attempt to render the given content as :wikipedia:`Markdown`.

	.. extras-require:: readme
		:pyproject:
		:scope: function

	:param content:
	"""

	try:
		# 3rd party
		import cmarkgfm  # type: ignore[import]  # noqa: F401
		import readme_renderer.markdown
	except ImportError:  # pragma: no cover
		return

	rendering_result = readme_renderer.markdown.render(content, stream=sys.stderr)

	if rendering_result is None:  # pragma: no cover
		raise BadConfigError("Error rendering README.")


def render_rst(content: str, filename: PathLike = "<string>") -> None:
	"""
	Attempt to render the given content as :wikipedia:`ReStructuredText`.

	.. extras-require:: readme
		:pyproject:
		:scope: function

	:param content:
	:param filename: The original filename.

	.. versionchanged:: 0.8.0  Added the ``filename`` argument.
	"""

	try:
		# 3rd party
		import docutils.core
		import readme_renderer.rst
		from docutils.utils import SystemMessage
		from docutils.writers.html4css1 import Writer

	except ImportError:  # pragma: no cover
		return

	# Adapted from https://github.com/pypa/readme_renderer/blob/main/readme_renderer/rst.py#L106
	settings = readme_renderer.rst.SETTINGS.copy()
	settings["warning_stream"] = io.StringIO()

	writer = Writer()
	writer.translator_class = readme_renderer.rst.ReadMeHTMLTranslator  # type: ignore[assignment]

	try:
		parts = docutils.core.publish_parts(content, str(filename), writer=writer, settings_overrides=settings)
		if parts.get("docinfo", '') + parts.get("fragment", ''):
			# Success!
			return
	except SystemMessage:
		pass

	warning_stream: io.StringIO = settings["warning_stream"]  # type: ignore[assignment]
	if not warning_stream.tell():
		raise BadConfigError("Error rendering README: No content rendered from RST source.")
	else:
		sys.stderr.write(warning_stream.getvalue())
		raise BadConfigError("Error rendering README.")


@functools.lru_cache()
def content_type_from_filename(filename: PathLike) -> "ContentTypes":
	"""
	Return the inferred content type for the given (readme) filename.

	:param filename:
	"""

	filename = PathPlus(filename)

	if filename.suffix.lower() == ".md":
		return "text/markdown"
	elif filename.suffix.lower() == ".rst":
		return "text/x-rst"
	elif filename.suffix.lower() == ".txt":
		return "text/plain"

	raise ValueError(f"Unsupported extension for {filename.as_posix()!r}")


def render_readme(
		readme_file: PathLike,
		content_type: Optional["ContentTypes"] = None,
		encoding: str = "UTF-8",
		) -> None:
	"""
	Attempts to render the given readme file.

	:param readme_file:
	:param content_type: The content-type of the readme.
		If :py:obj:`None` the type will be inferred from the file extension.
	:param encoding: The encoding to read the file with.
	"""

	readme_file = PathPlus(readme_file)

	if content_type is None:
		content_type = content_type_from_filename(filename=readme_file)

	content = readme_file.read_text(encoding=encoding)

	if int(os.environ.get("CHECK_README", 1)):
		if content_type == "text/markdown":
			render_markdown(content)
		elif content_type == "text/x-rst":
			render_rst(content, readme_file)


class PyProjectDeprecationWarning(Warning):
	"""
	Warning for the use of deprecated features in `pyproject.toml`.

	This is a user-facing warning which will be shown by default.
	For developer-facing warnings intended for direct consumers of this library,
	use a standard :class:`DeprecationWarning`.

	.. versionadded:: 0.5.0
	"""


def _load_toml(filename: PathLike, ) -> Dict[str, Any]:
	r"""
	Parse TOML from the given file.

	:param filename: The filename to read from to.

	:returns: A mapping containing the ``TOML`` data.
	"""

	return dom_toml.load(filename)