robotcode-robot 2.9.0.dev87__tar.gz → 2.9.0.dev115__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.
Files changed (50) hide show
  1. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/PKG-INFO +2 -2
  2. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/pyproject.toml +1 -1
  3. robotcode_robot-2.9.0.dev115/src/robotcode/robot/__version__.py +1 -0
  4. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/library_doc.py +1 -1
  5. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/markdown_docs.py +20 -3
  6. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/markdownformatter.py +62 -34
  7. robotcode_robot-2.9.0.dev87/src/robotcode/robot/__version__.py +0 -1
  8. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/.gitignore +0 -0
  9. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/README.md +0 -0
  10. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/__init__.py +0 -0
  11. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/config/__init__.py +0 -0
  12. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/config/loader.py +0 -0
  13. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/config/model.py +0 -0
  14. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/config/utils.py +0 -0
  15. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/__init__.py +0 -0
  16. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/analyzer_result.py +0 -0
  17. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/data_cache.py +0 -0
  18. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/diagnostic_rules.py +0 -0
  19. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/diagnostics_modifier.py +0 -0
  20. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/document_cache_helper.py +0 -0
  21. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/entities.py +0 -0
  22. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/errors.py +0 -0
  23. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/import_resolver.py +0 -0
  24. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/imports_manager.py +0 -0
  25. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/keyword_finder.py +0 -0
  26. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/model_helper.py +0 -0
  27. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/namespace.py +0 -0
  28. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/namespace_analyzer.py +0 -0
  29. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/project_index.py +0 -0
  30. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/scope_tree.py +0 -0
  31. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/__init__.py +0 -0
  32. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/analyzer.py +0 -0
  33. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/enums.py +0 -0
  34. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/json_dump.py +0 -0
  35. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/model.py +0 -0
  36. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/nodes.py +0 -0
  37. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/run_keyword.py +0 -0
  38. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/serialization.py +0 -0
  39. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/semantic_analyzer/variable_tokenizer.py +0 -0
  40. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/variable_scope.py +0 -0
  41. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/diagnostics/workspace_config.py +0 -0
  42. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/py.typed +0 -0
  43. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/__init__.py +0 -0
  44. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/ast.py +0 -0
  45. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/match.py +0 -0
  46. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/robot_patching.py +0 -0
  47. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/robot_path.py +0 -0
  48. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/stubs.py +0 -0
  49. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/src/robotcode/robot/utils/variables.py +0 -0
  50. {robotcode_robot-2.9.0.dev87 → robotcode_robot-2.9.0.dev115}/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.87
3
+ Version: 2.9.0-dev.115
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.87
30
+ Requires-Dist: robotcode-core==2.9.0-dev.115
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
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "robotframework>=5.0.0",
33
33
  "tomli>=1.1.0; python_version < '3.11'",
34
34
  "platformdirs>=4.3",
35
- "robotcode-core==2.9.0-dev.87",
35
+ "robotcode-core==2.9.0-dev.115",
36
36
  ]
37
37
  dynamic = ["version"]
38
38
 
@@ -0,0 +1 @@
1
+ __version__ = "2.9.0-dev.115"
@@ -1284,7 +1284,7 @@ class KeywordDoc(SourceEntity):
1284
1284
  in_code = False
1285
1285
  for raw_line in self._format_doc_fragment(text, link_resolver, targets).splitlines():
1286
1286
  line = raw_line.strip()
1287
- if re.match(r"(`{3,}|~{3,})", line):
1287
+ if re.match(r"(`{3,}(?!.*`)|~{3,})", line):
1288
1288
  in_code = not in_code
1289
1289
  new_line = True
1290
1290
  elif not line:
@@ -18,6 +18,7 @@ __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",
@@ -60,7 +61,7 @@ class ReferenceTarget(NamedTuple):
60
61
  LinkResolver = Callable[[str, str], Optional[str]]
61
62
 
62
63
  # indented more than three spaces a fence belongs to a list item
63
- _RE_FENCE = re.compile(r"^\s*(`{3,}|~{3,})")
64
+ _RE_FENCE = re.compile(r"^\s*(`{3,}(?!.*`)|~{3,})")
64
65
  _RE_ATX_HEADING = re.compile(r"^( {0,3})(#{1,6})(?=\s|$)(.*)$")
65
66
  _RE_HEADING_TITLE = re.compile(r"^ {0,3}(#{1,6})\s+(.+?)(?:\s+#+)?\s*$")
66
67
  _RE_ADMONITION = re.compile(r"^(\s*(?:>\s*)+)\[!([A-Za-z]+)\][ \t]*(.*?)\s*$")
@@ -200,6 +201,19 @@ def iter_headings(text: str) -> Iterator[Tuple[int, str]]:
200
201
  yield len(match.group(1)), match.group(2)
201
202
 
202
203
 
204
+ def code_lines_before_headings(text: str) -> List[List[str]]:
205
+ """For every ATX heading, the lines of code blocks since the heading before it, stripped."""
206
+ result: List[List[str]] = []
207
+ lines: List[str] = []
208
+ for line, in_code in _iter_lines(text):
209
+ if in_code:
210
+ lines.append(line.strip())
211
+ elif _RE_HEADING_TITLE.match(line):
212
+ result.append(lines)
213
+ lines = []
214
+ return result
215
+
216
+
203
217
  def heading_anchors(text: str) -> List[Tuple[int, str, str]]:
204
218
  """Level, title and anchor of every ATX heading, in document order.
205
219
 
@@ -522,8 +536,11 @@ def _iter_text_lines(text: str) -> Iterator[Tuple[str, bool]]:
522
536
 
523
537
 
524
538
  def code_span(text: str) -> str:
525
- """`text` as inline code, with a fence longer than any run of backticks in it."""
526
- fence = "`" * (max((len(run) for run in _RE_BACKTICKS.findall(text)), default=0) + 1)
539
+ """`text` as inline code, with the shortest fence that no run of backticks in it has."""
540
+ runs = {len(run) for run in _RE_BACKTICKS.findall(text)}
541
+ fence = "`"
542
+ while len(fence) in runs:
543
+ fence += "`"
527
544
  padding = " " if text.startswith("`") or text.endswith("`") else ""
528
545
  return f"{fence}{padding}{text}{padding}{fence}"
529
546
 
@@ -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
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,18 @@ 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
- _RE_NAME_PLACEHOLDER: Final["re.Pattern[str]"] = re.compile("\ue000(\\d+)\ue001")
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
+ | `+ # a run of backticks without a partner
23
+ """,
24
+ re.VERBOSE,
25
+ )
26
+ _RE_THREE_BACKTICKS: Final["re.Pattern[str]"] = re.compile("(?<!`)```(?!`)")
27
+ _RE_LONG_BACKTICKS: Final["re.Pattern[str]"] = re.compile("`{3,}")
17
28
 
18
29
 
19
30
  class Formatter(ABC):
@@ -44,7 +55,7 @@ class MarkDownFormatter:
44
55
  def __init__(self, name_linker: Optional[NameLinker] = None) -> None:
45
56
  """`name_linker` turns names in single backticks into links, as Libdoc links them.
46
57
 
47
- Headings and preformatted text keep their names.
58
+ Headings keep their names, in preformatted text they lose their backticks.
48
59
  """
49
60
  self._results: List[str] = []
50
61
  line_formatter = _line_formatter if name_linker is None else LineFormatter(name_linker)
@@ -180,25 +191,29 @@ class LinkFormatter:
180
191
  def remove_link(self, text: str) -> str:
181
192
  # 2nd, 4th, etc. token contains link, others surrounding content
182
193
  tokens = self._link.split(text)
183
- if len(tokens) > 1:
184
- formatters: Iterator[Callable[[str], Any]] = itertools.cycle([self._remove_link])
185
- return "".join(f(t) for f, t in zip(formatters, tokens))
186
- return text
194
+ formatters: Iterator[Callable[[str], str]] = itertools.cycle((str, self._remove_link))
195
+ return "".join(f(t) for f, t in zip(formatters, tokens))
187
196
 
188
197
  def _remove_link(self, text: str) -> str:
189
- if "|" not in text:
190
- return text
191
-
192
198
  link, content = (t.strip() for t in text.split("|", 1))
193
- if self._is_image(content):
194
- return self._get_image(content, link)
195
-
196
- return content
199
+ return content or link
197
200
 
198
201
  def _is_image(self, text: str) -> bool:
199
202
  return text.startswith("data:image/") or text.lower().endswith(self._image_exts)
200
203
 
201
204
 
205
+ def _code_text(text: str) -> str:
206
+ """The text of code as a reader copies it: as written, only links and names lose their markup,
207
+ which Libdoc does not show either."""
208
+ return _RE_NAME.sub(r"\1", LinkFormatter().remove_link(text))
209
+
210
+
211
+ def _hold(held: List[str], text: str) -> str:
212
+ """Puts text behind a placeholder, so that the other formatting cannot change it."""
213
+ held.append(text)
214
+ return f"\ue000{len(held) - 1}\ue001"
215
+
216
+
202
217
  class LineFormatter:
203
218
  _bold: Final["re.Pattern[str]"] = re.compile(
204
219
  r"""
@@ -246,30 +261,38 @@ _ # end of italic
246
261
  ("#", self._quote_hash),
247
262
  ("*", self._format_bold),
248
263
  ("_", self._format_italic),
249
- ("``", self._format_code),
250
264
  ("", LinkFormatter().format_link),
251
265
  ]
252
266
 
253
267
  def format(self, line: str) -> str:
254
- links: List[str] = []
255
- if self._name_linker is not None and "`" in line:
256
- # before the other formatting, which makes ``code`` the same code span as a `name`
257
- line = _RE_NAME.sub(lambda m: self._link_name(m, links), line)
268
+ held: List[str] = []
269
+ if "\ue000" in line:
270
+ # the documentation's own character must not read as a placeholder
271
+ line = line.replace("\ue000", _hold(held, "\ue000"))
272
+ if "`" in line:
273
+ # what Robot Framework reads as code decides first, then what else Markdown reads as code
274
+ line = self._code.sub(lambda m: m.group(1) + _hold(held, code_span(_code_text(m.group(3)))), line)
275
+ line = _RE_CODE_SPAN.sub(lambda m: self._hold_span(m, held), line)
258
276
  for marker, formatter in self._formatters:
259
277
  if marker in line:
260
278
  line = formatter(line)
261
- if links:
262
- line = _RE_NAME_PLACEHOLDER.sub(lambda m: links[int(m.group(1))], line)
279
+ # backwards, the documentation's own character can be part of held code
280
+ for index, text in reversed(list(enumerate(held))):
281
+ line = line.replace(f"\ue000{index}\ue001", text)
263
282
  return line
264
283
 
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"
284
+ def _hold_span(self, match: re.Match[str], held: List[str]) -> str:
285
+ text, fence, span = match.group(0, 1, 2)
286
+ if fence is None:
287
+ # Markdown must not pair a backtick left over with the code written here
288
+ return text if text.startswith("\\") else text.replace("`", "\\`")
289
+ # for Robot Framework this is no code, but Markdown shows it as code
290
+ if len(fence) > 1:
291
+ return _hold(held, text)
292
+ target = None if self._name_linker is None else self._name_linker(span)
293
+ if target is not None:
294
+ return _hold(held, f"[{escape_link_text(span)}]({target})")
295
+ return _hold(held, code_span(_code_text(span)))
273
296
 
274
297
  def _quote_lower_then(self, line: str) -> str:
275
298
  return line.replace("<", "\\<")
@@ -283,9 +306,6 @@ _ # end of italic
283
306
  def _format_italic(self, line: str) -> str:
284
307
  return self._italic.sub("\\1*\\3*", line)
285
308
 
286
- def _format_code(self, line: str) -> str:
287
- return self._code.sub("\\1`\\3`", line)
288
-
289
309
 
290
310
  _line_formatter = LineFormatter()
291
311
 
@@ -295,8 +315,16 @@ class PreformattedFormatter(Formatter):
295
315
  return line.startswith("| ") or line == "|"
296
316
 
297
317
  def format(self, lines: List[str]) -> str:
298
- lines = [LinkFormatter().remove_link(line[2:]) for line in lines]
299
- return "```text\n" + "\n".join(lines) + "\n```\n"
318
+ text = "\n".join(self._format_line(line[2:]) for line in lines)
319
+ # a line of backticks must not end the block
320
+ fence = "`" * max((len(run) + 1 for run in _RE_LONG_BACKTICKS.findall(text)), default=3)
321
+ return f"{fence}text\n{text}\n{fence}\n"
322
+
323
+ def _format_line(self, line: str) -> str:
324
+ # the whole block is code, so double backticks have nothing left to say
325
+ line = _code_text(LineFormatter._code.sub(r"\1\3", line))
326
+ # a backtick can't be escaped, Libdoc shows three in a row as one
327
+ return _RE_THREE_BACKTICKS.sub("`", line)
300
328
 
301
329
 
302
330
  class ParagraphFormatter(Formatter):
@@ -1 +0,0 @@
1
- __version__ = "2.9.0-dev.87"