robotcode-robot 2.9.0.dev87__tar.gz → 2.9.0.dev124__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/PKG-INFO +2 -2
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/pyproject.toml +1 -1
- robotcode_robot-2.9.0.dev124/src/robotcode/robot/__version__.py +1 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/library_doc.py +11 -9
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/markdown_docs.py +54 -4
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/markdownformatter.py +112 -52
- robotcode_robot-2.9.0.dev87/src/robotcode/robot/__version__.py +0 -1
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/.gitignore +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/README.md +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/__init__.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/__init__.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/loader.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/model.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/utils.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/__init__.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/analyzer_result.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/data_cache.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/diagnostic_rules.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/diagnostics_modifier.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/document_cache_helper.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/entities.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/errors.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/import_resolver.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/imports_manager.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/keyword_finder.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/model_helper.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/namespace.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/namespace_analyzer.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/project_index.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/scope_tree.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/__init__.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/analyzer.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/enums.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/json_dump.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/model.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/nodes.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/run_keyword.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/serialization.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/semantic_analyzer/variable_tokenizer.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/variable_scope.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/diagnostics/workspace_config.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/py.typed +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/__init__.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/ast.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/match.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/robot_patching.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/robot_path.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/stubs.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/variables.py +0 -0
- {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/visitor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: robotcode-robot
|
|
3
|
-
Version: 2.9.0-dev.
|
|
3
|
+
Version: 2.9.0-dev.124
|
|
4
4
|
Summary: Support classes for RobotCode for handling Robot Framework projects.
|
|
5
5
|
Project-URL: Homepage, https://robotcode.io
|
|
6
6
|
Project-URL: Donate, https://opencollective.com/robotcode
|
|
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
|
|
|
27
27
|
Classifier: Typing :: Typed
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
29
|
Requires-Dist: platformdirs>=4.3
|
|
30
|
-
Requires-Dist: robotcode-core==2.9.0-dev.
|
|
30
|
+
Requires-Dist: robotcode-core==2.9.0-dev.124
|
|
31
31
|
Requires-Dist: robotframework>=5.0.0
|
|
32
32
|
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.9.0-dev.124"
|
|
@@ -90,6 +90,7 @@ from ..utils.markdown_docs import (
|
|
|
90
90
|
code_span,
|
|
91
91
|
code_span_variables,
|
|
92
92
|
escape_link_text,
|
|
93
|
+
escape_text,
|
|
93
94
|
extract_reference_definitions,
|
|
94
95
|
heading_anchors,
|
|
95
96
|
iter_headings,
|
|
@@ -99,6 +100,7 @@ from ..utils.markdown_docs import (
|
|
|
99
100
|
section_anchors,
|
|
100
101
|
shift_headings,
|
|
101
102
|
slugify,
|
|
103
|
+
unescape_text,
|
|
102
104
|
unique_reference_labels,
|
|
103
105
|
)
|
|
104
106
|
from ..utils.markdownformatter import MarkDownFormatter, NameLinker
|
|
@@ -241,18 +243,17 @@ def _page_heading_shift(doc_format: str, levels: int) -> Optional[Callable[[str]
|
|
|
241
243
|
|
|
242
244
|
|
|
243
245
|
# A run of `#` after a space at the end of a keyword name would close its ATX heading: `### Use C #` shows "Use C".
|
|
244
|
-
_RE_CLOSING_HASHES = re.compile(r"(\s)(#+)$")
|
|
245
|
-
_RE_ESCAPED_CLOSING_HASHES = re.compile(r"(\s)\\(#+)$")
|
|
246
246
|
|
|
247
247
|
|
|
248
248
|
def _heading_name(name: str) -> str:
|
|
249
|
-
"""A keyword name as the title of its heading:
|
|
250
|
-
|
|
249
|
+
"""A keyword name as the title of its heading: what Markdown would read as markup escaped,
|
|
250
|
+
also a closing run of `#`."""
|
|
251
|
+
return escape_text(name)
|
|
251
252
|
|
|
252
253
|
|
|
253
254
|
def _heading_title_name(title: str) -> str:
|
|
254
255
|
"""The keyword name of a heading title that `_heading_name` wrote."""
|
|
255
|
-
return
|
|
256
|
+
return unescape_text(title)
|
|
256
257
|
|
|
257
258
|
|
|
258
259
|
def _match_heading_anchors(headings: List[Tuple[str, str]], titles: List[Set[str]]) -> List[str]:
|
|
@@ -1284,7 +1285,7 @@ class KeywordDoc(SourceEntity):
|
|
|
1284
1285
|
in_code = False
|
|
1285
1286
|
for raw_line in self._format_doc_fragment(text, link_resolver, targets).splitlines():
|
|
1286
1287
|
line = raw_line.strip()
|
|
1287
|
-
if re.match(r"(`{3,}|~{3,})", line):
|
|
1288
|
+
if re.match(r"(`{3,}(?!.*`)|~{3,})", line):
|
|
1288
1289
|
in_code = not in_code
|
|
1289
1290
|
new_line = True
|
|
1290
1291
|
elif not line:
|
|
@@ -1348,7 +1349,8 @@ class KeywordDoc(SourceEntity):
|
|
|
1348
1349
|
if add_type:
|
|
1349
1350
|
result = (
|
|
1350
1351
|
f"#{'#' * header_level} "
|
|
1351
|
-
f"{(self.libtype or 'Library').capitalize() if self.is_initializer else 'Keyword'}
|
|
1352
|
+
f"{(self.libtype or 'Library').capitalize() if self.is_initializer else 'Keyword'} "
|
|
1353
|
+
f"*{_heading_name(self.name)}*\n"
|
|
1352
1354
|
)
|
|
1353
1355
|
else:
|
|
1354
1356
|
if not self.is_initializer:
|
|
@@ -1993,9 +1995,9 @@ class LibraryDoc:
|
|
|
1993
1995
|
for level, title, anchor in heading_anchors(page):
|
|
1994
1996
|
if level == header_level + 2:
|
|
1995
1997
|
in_introduction = title == "Introduction"
|
|
1996
|
-
section_anchors_by_title.setdefault(normalize_reference(title), anchor)
|
|
1998
|
+
section_anchors_by_title.setdefault(normalize_reference(_heading_title_name(title)), anchor)
|
|
1997
1999
|
elif level > header_level + 2 and in_introduction:
|
|
1998
|
-
introduction.setdefault(normalize_reference(title), anchor)
|
|
2000
|
+
introduction.setdefault(normalize_reference(_heading_title_name(title)), anchor)
|
|
1999
2001
|
section_anchors_by_title.update(introduction)
|
|
2000
2002
|
_, type_anchors = self.get_page_anchors(page, header_level)
|
|
2001
2003
|
|
|
@@ -18,9 +18,11 @@ __all__ = [
|
|
|
18
18
|
"LinkResolver",
|
|
19
19
|
"ReferenceTarget",
|
|
20
20
|
"anchor_link_resolver",
|
|
21
|
+
"code_lines_before_headings",
|
|
21
22
|
"code_span",
|
|
22
23
|
"code_span_variables",
|
|
23
24
|
"escape_link_text",
|
|
25
|
+
"escape_text",
|
|
24
26
|
"extract_reference_definitions",
|
|
25
27
|
"heading_anchors",
|
|
26
28
|
"iter_headings",
|
|
@@ -34,6 +36,7 @@ __all__ = [
|
|
|
34
36
|
"section_anchors",
|
|
35
37
|
"shift_headings",
|
|
36
38
|
"slugify",
|
|
39
|
+
"unescape_text",
|
|
37
40
|
"unique_reference_labels",
|
|
38
41
|
]
|
|
39
42
|
|
|
@@ -60,7 +63,7 @@ class ReferenceTarget(NamedTuple):
|
|
|
60
63
|
LinkResolver = Callable[[str, str], Optional[str]]
|
|
61
64
|
|
|
62
65
|
# indented more than three spaces a fence belongs to a list item
|
|
63
|
-
_RE_FENCE = re.compile(r"^\s*(`{3,}|~{3,})")
|
|
66
|
+
_RE_FENCE = re.compile(r"^\s*(`{3,}(?!.*`)|~{3,})")
|
|
64
67
|
_RE_ATX_HEADING = re.compile(r"^( {0,3})(#{1,6})(?=\s|$)(.*)$")
|
|
65
68
|
_RE_HEADING_TITLE = re.compile(r"^ {0,3}(#{1,6})\s+(.+?)(?:\s+#+)?\s*$")
|
|
66
69
|
_RE_ADMONITION = re.compile(r"^(\s*(?:>\s*)+)\[!([A-Za-z]+)\][ \t]*(.*?)\s*$")
|
|
@@ -147,6 +150,36 @@ def escape_link_text(text: str) -> str:
|
|
|
147
150
|
return _RE_LINK_TEXT_SPECIAL.sub(r"\\\1", text)
|
|
148
151
|
|
|
149
152
|
|
|
153
|
+
_RE_TEXT_SPECIAL = re.compile(
|
|
154
|
+
r"""
|
|
155
|
+
\\(?=[!-/:-@\[-`{-~]|\Z) # a backslash before ASCII punctuation or at the end
|
|
156
|
+
| [<\#`] # always
|
|
157
|
+
| (?<!\s)\* | \*(?!\s) # a star, unless it has space on both sides
|
|
158
|
+
| (?<![^\W_])_ | _(?![^\W_]) # an underscore, unless it is inside a word
|
|
159
|
+
| &(?=\#?\w+;) # what reads as an entity
|
|
160
|
+
| \](?=\() # what reads as an inline link
|
|
161
|
+
| ~(?=~) | (?<=~)~ # what reads as strikethrough
|
|
162
|
+
""",
|
|
163
|
+
re.VERBOSE,
|
|
164
|
+
)
|
|
165
|
+
_RE_ESCAPED_VARIABLE = re.compile(r"(?<!\\)((?:\\\\)+)\$")
|
|
166
|
+
_RE_MARKDOWN_ESCAPE = re.compile(r"\\([!-/:-@\[-`{-~])")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def escape_text(text: str) -> str:
|
|
170
|
+
"""Escape what Markdown would read as markup in text that is meant as text, only where
|
|
171
|
+
it would change the text: `snake_case`, `${list}[0]` and `2 * 3` stay as they are.
|
|
172
|
+
|
|
173
|
+
An odd number of backslashes before `$` is an escaped variable and stays one."""
|
|
174
|
+
text = _RE_TEXT_SPECIAL.sub(r"\\\g<0>", text)
|
|
175
|
+
return _RE_ESCAPED_VARIABLE.sub(lambda m: m.group(1) + ("\\$" if len(m.group(1)) % 4 else "$"), text)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def unescape_text(text: str) -> str:
|
|
179
|
+
"""The text that Markdown shows for `text`, without its escapes."""
|
|
180
|
+
return _RE_MARKDOWN_ESCAPE.sub(r"\1", text)
|
|
181
|
+
|
|
182
|
+
|
|
150
183
|
def anchor_link_resolver(kind: str, name: str) -> Optional[str]:
|
|
151
184
|
"""A `LinkResolver` for pages that show a whole library: keywords and
|
|
152
185
|
sections have a heading there. The heading of a type can carry a number,
|
|
@@ -200,6 +233,19 @@ def iter_headings(text: str) -> Iterator[Tuple[int, str]]:
|
|
|
200
233
|
yield len(match.group(1)), match.group(2)
|
|
201
234
|
|
|
202
235
|
|
|
236
|
+
def code_lines_before_headings(text: str) -> List[List[str]]:
|
|
237
|
+
"""For every ATX heading, the lines of code blocks since the heading before it, stripped."""
|
|
238
|
+
result: List[List[str]] = []
|
|
239
|
+
lines: List[str] = []
|
|
240
|
+
for line, in_code in _iter_lines(text):
|
|
241
|
+
if in_code:
|
|
242
|
+
lines.append(line.strip())
|
|
243
|
+
elif _RE_HEADING_TITLE.match(line):
|
|
244
|
+
result.append(lines)
|
|
245
|
+
lines = []
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
|
|
203
249
|
def heading_anchors(text: str) -> List[Tuple[int, str, str]]:
|
|
204
250
|
"""Level, title and anchor of every ATX heading, in document order.
|
|
205
251
|
|
|
@@ -522,8 +568,11 @@ def _iter_text_lines(text: str) -> Iterator[Tuple[str, bool]]:
|
|
|
522
568
|
|
|
523
569
|
|
|
524
570
|
def code_span(text: str) -> str:
|
|
525
|
-
"""`text` as inline code, with
|
|
526
|
-
|
|
571
|
+
"""`text` as inline code, with the shortest fence that no run of backticks in it has."""
|
|
572
|
+
runs = {len(run) for run in _RE_BACKTICKS.findall(text)}
|
|
573
|
+
fence = "`"
|
|
574
|
+
while len(fence) in runs:
|
|
575
|
+
fence += "`"
|
|
527
576
|
padding = " " if text.startswith("`") or text.endswith("`") else ""
|
|
528
577
|
return f"{fence}{padding}{text}{padding}{fence}"
|
|
529
578
|
|
|
@@ -538,7 +587,8 @@ def _code_span_variables_in(text: str) -> str:
|
|
|
538
587
|
# variables that follow each other directly share one code span: `${TEMPDIR}${/}`
|
|
539
588
|
while (following := search_variable(text[end:], "$", ignore_errors=True)).start == 0:
|
|
540
589
|
end += following.end
|
|
541
|
-
|
|
590
|
+
# inside code an escape would show
|
|
591
|
+
result.append(text[: match.start] + code_span(unescape_text(text[match.start : end])))
|
|
542
592
|
text = text[end:]
|
|
543
593
|
result.append(text)
|
|
544
594
|
return "".join(result)
|
|
@@ -5,7 +5,7 @@ import re
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from typing import Any, Callable, Final, Iterator, List, Optional, Tuple
|
|
7
7
|
|
|
8
|
-
from .markdown_docs import escape_link_text
|
|
8
|
+
from .markdown_docs import code_span, escape_link_text, escape_text
|
|
9
9
|
|
|
10
10
|
# Gets a name in single backticks of documentation in Robot Framework's format and
|
|
11
11
|
# returns the target of a link to it, or `None` if it stays inline code.
|
|
@@ -13,7 +13,23 @@ NameLinker = Callable[[str], Optional[str]]
|
|
|
13
13
|
|
|
14
14
|
# a name in single backticks, not code in double backticks
|
|
15
15
|
_RE_NAME: Final["re.Pattern[str]"] = re.compile(r"(?<!`)`([^`]+)`(?!`)")
|
|
16
|
-
|
|
16
|
+
_RE_CODE_SPAN: Final["re.Pattern[str]"] = re.compile(
|
|
17
|
+
r"""
|
|
18
|
+
\\. # what Markdown reads as an escaped character
|
|
19
|
+
| (?<!`) (`+) (?!`) # a run of backticks (group 1)
|
|
20
|
+
([^\ue000]+?) # anything but code that is already held (group 2)
|
|
21
|
+
(?<!`) \1 (?!`) # up to the next run of the same length
|
|
22
|
+
""",
|
|
23
|
+
re.VERBOSE,
|
|
24
|
+
)
|
|
25
|
+
# the documentation's own characters that the conversion uses
|
|
26
|
+
_RE_OWN_MARKERS: Final["re.Pattern[str]"] = re.compile("[\ue000\ue002\ue003]")
|
|
27
|
+
# what is no text: held code (group 1) and the syntax the formatter writes itself (group 2)
|
|
28
|
+
_RE_NOT_TEXT: Final["re.Pattern[str]"] = re.compile("(\ue000\\d+\ue001)|\ue002(.*?)\ue003")
|
|
29
|
+
# what starts a Markdown block: a quote, a list or a reference definition (group 1), a numbered list (groups 2 and 3)
|
|
30
|
+
_RE_BLOCK_START: Final["re.Pattern[str]"] = re.compile(r"^(?:(>|[+-](?=\s|$)|\[(?=[^\]]*\]:))|(\d{1,9})([.)])(?=\s|$))")
|
|
31
|
+
_RE_THREE_BACKTICKS: Final["re.Pattern[str]"] = re.compile("(?<!`)```(?!`)")
|
|
32
|
+
_RE_LONG_BACKTICKS: Final["re.Pattern[str]"] = re.compile("`{3,}")
|
|
17
33
|
|
|
18
34
|
|
|
19
35
|
class Formatter(ABC):
|
|
@@ -44,7 +60,7 @@ class MarkDownFormatter:
|
|
|
44
60
|
def __init__(self, name_linker: Optional[NameLinker] = None) -> None:
|
|
45
61
|
"""`name_linker` turns names in single backticks into links, as Libdoc links them.
|
|
46
62
|
|
|
47
|
-
Headings
|
|
63
|
+
Headings keep their names, in preformatted text they lose their backticks.
|
|
48
64
|
"""
|
|
49
65
|
self._results: List[str] = []
|
|
50
66
|
line_formatter = _line_formatter if name_linker is None else LineFormatter(name_linker)
|
|
@@ -112,7 +128,7 @@ class HeaderFormatter(SingleLineFormatter):
|
|
|
112
128
|
if m is not None:
|
|
113
129
|
level, text = m.groups()
|
|
114
130
|
|
|
115
|
-
return "%s %s\n" % ("#" * (len(level) + 1), text)
|
|
131
|
+
return "%s %s\n" % ("#" * (len(level) + 1), _format_heading(text))
|
|
116
132
|
return ""
|
|
117
133
|
|
|
118
134
|
|
|
@@ -144,15 +160,14 @@ class LinkFormatter:
|
|
|
144
160
|
return pre + self._get_link(url)
|
|
145
161
|
|
|
146
162
|
def _get_image(self, src: str, title: Optional[str] = None) -> str:
|
|
147
|
-
return f"})"
|
|
163
|
+
return _syntax(f"})")
|
|
148
164
|
|
|
149
165
|
def _get_link(self, href: str, content: Optional[str] = None) -> str:
|
|
150
|
-
return
|
|
166
|
+
return _syntax("[") + (content or href) + _syntax(f"]({self._target(href)})")
|
|
151
167
|
|
|
152
168
|
def _target(self, target: str) -> str:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# Markdown takes a target with spaces only in angle brackets; `<` is already escaped in the whole line
|
|
169
|
+
target = target.replace("<", "\\<")
|
|
170
|
+
# Markdown takes a target with spaces only in angle brackets
|
|
156
171
|
if " " in target:
|
|
157
172
|
return "<" + target.replace(">", "\\>") + ">"
|
|
158
173
|
return target
|
|
@@ -173,32 +188,76 @@ class LinkFormatter:
|
|
|
173
188
|
content = self._get_image(content, link)
|
|
174
189
|
elif self._is_image(link):
|
|
175
190
|
return self._get_image(link, content)
|
|
176
|
-
if link.startswith("
|
|
191
|
+
if link.startswith("#"):
|
|
177
192
|
link = link.lower()
|
|
178
193
|
return self._get_link(link, content)
|
|
179
194
|
|
|
180
195
|
def remove_link(self, text: str) -> str:
|
|
181
196
|
# 2nd, 4th, etc. token contains link, others surrounding content
|
|
182
197
|
tokens = self._link.split(text)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return "".join(f(t) for f, t in zip(formatters, tokens))
|
|
186
|
-
return text
|
|
198
|
+
formatters: Iterator[Callable[[str], str]] = itertools.cycle((str, self._remove_link))
|
|
199
|
+
return "".join(f(t) for f, t in zip(formatters, tokens))
|
|
187
200
|
|
|
188
201
|
def _remove_link(self, text: str) -> str:
|
|
189
|
-
if "|" not in text:
|
|
190
|
-
return text
|
|
191
|
-
|
|
192
202
|
link, content = (t.strip() for t in text.split("|", 1))
|
|
193
|
-
|
|
194
|
-
return self._get_image(content, link)
|
|
195
|
-
|
|
196
|
-
return content
|
|
203
|
+
return content or link
|
|
197
204
|
|
|
198
205
|
def _is_image(self, text: str) -> bool:
|
|
199
206
|
return text.startswith("data:image/") or text.lower().endswith(self._image_exts)
|
|
200
207
|
|
|
201
208
|
|
|
209
|
+
def _code_text(text: str) -> str:
|
|
210
|
+
"""The text of code as a reader copies it: as written, only links and names lose their markup,
|
|
211
|
+
which Libdoc does not show either."""
|
|
212
|
+
return _RE_NAME.sub(r"\1", LinkFormatter().remove_link(text))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _hold(held: List[str], text: str) -> str:
|
|
216
|
+
"""Puts text behind a placeholder, so that the other formatting cannot change it."""
|
|
217
|
+
held.append(text)
|
|
218
|
+
return f"\ue000{len(held) - 1}\ue001"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _hold_own_markers(line: str, held: List[str]) -> str:
|
|
222
|
+
return _RE_OWN_MARKERS.sub(lambda m: _hold(held, m.group(0)), line)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _restore(line: str, held: List[str]) -> str:
|
|
226
|
+
# backwards, the documentation's own characters can be part of held code
|
|
227
|
+
for index, text in reversed(list(enumerate(held))):
|
|
228
|
+
line = line.replace(f"\ue000{index}\ue001", text)
|
|
229
|
+
return line
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _syntax(text: str) -> str:
|
|
233
|
+
"""Marks what the formatter writes itself, the escape of the text leaves it alone."""
|
|
234
|
+
return f"\ue002{text}\ue003"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _escape_line(line: str) -> str:
|
|
238
|
+
result: List[str] = []
|
|
239
|
+
position = 0
|
|
240
|
+
for match in _RE_NOT_TEXT.finditer(line):
|
|
241
|
+
result.append(escape_text(line[position : match.start()]))
|
|
242
|
+
result.append(match.group(1) or match.group(2))
|
|
243
|
+
position = match.end()
|
|
244
|
+
result.append(escape_text(line[position:]))
|
|
245
|
+
return "".join(result)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _escape_block_start(match: re.Match[str]) -> str:
|
|
249
|
+
return f"\\{match.group(1)}" if match.group(1) else f"{match.group(2)}\\{match.group(3)}"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _format_heading(text: str) -> str:
|
|
253
|
+
"""Robot Framework formats nothing in a heading, what Markdown would read as markup is text there."""
|
|
254
|
+
held: List[str] = []
|
|
255
|
+
text = _hold_own_markers(text, held)
|
|
256
|
+
if "`" in text:
|
|
257
|
+
text = _RE_CODE_SPAN.sub(lambda m: m.group(0) if m.group(1) is None else _hold(held, m.group(0)), text)
|
|
258
|
+
return _restore(_escape_line(text), held)
|
|
259
|
+
|
|
260
|
+
|
|
202
261
|
class LineFormatter:
|
|
203
262
|
_bold: Final["re.Pattern[str]"] = re.compile(
|
|
204
263
|
r"""
|
|
@@ -242,49 +301,42 @@ _ # end of italic
|
|
|
242
301
|
|
|
243
302
|
self._name_linker = name_linker
|
|
244
303
|
self._formatters: List[Tuple[str, Callable[[str], str]]] = [
|
|
245
|
-
("<", self._quote_lower_then),
|
|
246
|
-
("#", self._quote_hash),
|
|
247
304
|
("*", self._format_bold),
|
|
248
305
|
("_", self._format_italic),
|
|
249
|
-
("``", self._format_code),
|
|
250
306
|
("", LinkFormatter().format_link),
|
|
251
307
|
]
|
|
252
308
|
|
|
253
309
|
def format(self, line: str) -> str:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
310
|
+
held: List[str] = []
|
|
311
|
+
line = _hold_own_markers(line, held)
|
|
312
|
+
if "`" in line:
|
|
313
|
+
# what Robot Framework reads as code decides first, then what else Markdown reads as code
|
|
314
|
+
line = self._code.sub(lambda m: m.group(1) + _hold(held, code_span(_code_text(m.group(3)))), line)
|
|
315
|
+
line = _RE_CODE_SPAN.sub(lambda m: self._hold_span(m, held), line)
|
|
258
316
|
for marker, formatter in self._formatters:
|
|
259
317
|
if marker in line:
|
|
260
318
|
line = formatter(line)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return line
|
|
264
|
-
|
|
265
|
-
def _link_name(self, match: re.Match[str], links: List[str]) -> str:
|
|
266
|
-
assert self._name_linker is not None
|
|
267
|
-
target = self._name_linker(match.group(1))
|
|
268
|
-
if target is None:
|
|
269
|
-
return match.group(0)
|
|
270
|
-
# the link waits behind a placeholder, so the other formatting cannot change it
|
|
271
|
-
links.append(f"[{escape_link_text(match.group(1))}]({target})")
|
|
272
|
-
return f"\ue000{len(links) - 1}\ue001"
|
|
319
|
+
# last, so that all that is left of the line is text
|
|
320
|
+
line = _RE_BLOCK_START.sub(_escape_block_start, _escape_line(line))
|
|
321
|
+
return _restore(line, held)
|
|
273
322
|
|
|
274
|
-
def
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
323
|
+
def _hold_span(self, match: re.Match[str], held: List[str]) -> str:
|
|
324
|
+
text, fence, span = match.group(0, 1, 2)
|
|
325
|
+
if fence is None:
|
|
326
|
+
return text
|
|
327
|
+
# for Robot Framework this is no code, but Markdown shows it as code
|
|
328
|
+
if len(fence) > 1:
|
|
329
|
+
return _hold(held, text)
|
|
330
|
+
target = None if self._name_linker is None else self._name_linker(span)
|
|
331
|
+
if target is not None:
|
|
332
|
+
return _hold(held, f"[{escape_link_text(span)}]({target})")
|
|
333
|
+
return _hold(held, code_span(_code_text(span)))
|
|
279
334
|
|
|
280
335
|
def _format_bold(self, line: str) -> str:
|
|
281
|
-
return self._bold.sub("\\1
|
|
336
|
+
return self._bold.sub(f"\\1{_syntax('**')}\\3{_syntax('**')}", line)
|
|
282
337
|
|
|
283
338
|
def _format_italic(self, line: str) -> str:
|
|
284
|
-
return self._italic.sub("\\1
|
|
285
|
-
|
|
286
|
-
def _format_code(self, line: str) -> str:
|
|
287
|
-
return self._code.sub("\\1`\\3`", line)
|
|
339
|
+
return self._italic.sub(f"\\1{_syntax('*')}\\3{_syntax('*')}", line)
|
|
288
340
|
|
|
289
341
|
|
|
290
342
|
_line_formatter = LineFormatter()
|
|
@@ -295,8 +347,16 @@ class PreformattedFormatter(Formatter):
|
|
|
295
347
|
return line.startswith("| ") or line == "|"
|
|
296
348
|
|
|
297
349
|
def format(self, lines: List[str]) -> str:
|
|
298
|
-
|
|
299
|
-
|
|
350
|
+
text = "\n".join(self._format_line(line[2:]) for line in lines)
|
|
351
|
+
# a line of backticks must not end the block
|
|
352
|
+
fence = "`" * max((len(run) + 1 for run in _RE_LONG_BACKTICKS.findall(text)), default=3)
|
|
353
|
+
return f"{fence}text\n{text}\n{fence}\n"
|
|
354
|
+
|
|
355
|
+
def _format_line(self, line: str) -> str:
|
|
356
|
+
# the whole block is code, so double backticks have nothing left to say
|
|
357
|
+
line = _code_text(LineFormatter._code.sub(r"\1\3", line))
|
|
358
|
+
# a backtick can't be escaped, Libdoc shows three in a row as one
|
|
359
|
+
return _RE_THREE_BACKTICKS.sub("`", line)
|
|
300
360
|
|
|
301
361
|
|
|
302
362
|
class ParagraphFormatter(Formatter):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.9.0-dev.87"
|
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/__init__.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/__init__.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/loader.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/model.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/config/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/__init__.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/ast.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/match.py
RENAMED
|
File without changes
|
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/robot_path.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/stubs.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/variables.py
RENAMED
|
File without changes
|
{robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev124}/src/robotcode/robot/utils/visitor.py
RENAMED
|
File without changes
|