robotcode-robot 0.64.1__tar.gz → 0.66.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/PKG-INFO +2 -2
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/pyproject.toml +1 -1
- robotcode_robot-0.66.0/src/robotcode/robot/__version__.py +1 -0
- robotcode_robot-0.66.0/src/robotcode/robot/utils/markdownformatter.py +358 -0
- robotcode_robot-0.64.1/src/robotcode/robot/__version__.py +0 -1
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/.gitignore +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/LICENSE.txt +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/README.md +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/__init__.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/config/__init__.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/config/loader.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/config/model.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/config/utils.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/py.typed +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/utils/__init__.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/utils/ast.py +0 -0
- {robotcode_robot-0.64.1 → robotcode_robot-0.66.0}/src/robotcode/robot/utils/visitors.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: robotcode-robot
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.66.0
|
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://github.com/sponsors/d-biehl
|
@@ -26,7 +26,7 @@ Classifier: Topic :: Utilities
|
|
26
26
|
Classifier: Typing :: Typed
|
27
27
|
Requires-Python: >=3.8
|
28
28
|
Requires-Dist: platformdirs<3.12.0,>=3.2.0
|
29
|
-
Requires-Dist: robotcode-core==0.
|
29
|
+
Requires-Dist: robotcode-core==0.66.0
|
30
30
|
Requires-Dist: robotframework>=4.1.0
|
31
31
|
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
|
32
32
|
Description-Content-Type: text/markdown
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.66.0"
|
@@ -0,0 +1,358 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import functools
|
4
|
+
import itertools
|
5
|
+
import re
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
from typing import Any, Callable, Iterator, List, Optional, Tuple
|
8
|
+
|
9
|
+
|
10
|
+
class Formatter(ABC):
|
11
|
+
_strip_lines = True
|
12
|
+
|
13
|
+
def __init__(self) -> None:
|
14
|
+
self._lines: List[str] = []
|
15
|
+
|
16
|
+
def handles(self, line: str) -> bool:
|
17
|
+
return self._handles(line.strip() if self._strip_lines else line)
|
18
|
+
|
19
|
+
@abstractmethod
|
20
|
+
def _handles(self, line: str) -> bool:
|
21
|
+
...
|
22
|
+
|
23
|
+
def add(self, line: str) -> None:
|
24
|
+
self._lines.append(line.strip() if self._strip_lines else line)
|
25
|
+
|
26
|
+
def end(self) -> str:
|
27
|
+
result = self.format(self._lines)
|
28
|
+
self._lines = []
|
29
|
+
return result
|
30
|
+
|
31
|
+
@abstractmethod
|
32
|
+
def format(self, lines: List[str]) -> str:
|
33
|
+
...
|
34
|
+
|
35
|
+
|
36
|
+
class MarkDownFormatter:
|
37
|
+
def __init__(self) -> None:
|
38
|
+
self._results: List[str] = []
|
39
|
+
self._formatters: List[Formatter] = [
|
40
|
+
TableFormatter(),
|
41
|
+
PreformattedFormatter(),
|
42
|
+
ListFormatter(),
|
43
|
+
HeaderFormatter(),
|
44
|
+
RulerFormatter(),
|
45
|
+
]
|
46
|
+
self._formatters.append(ParagraphFormatter(self._formatters[:]))
|
47
|
+
self._current: Optional[Formatter] = None
|
48
|
+
|
49
|
+
def format(self, text: str) -> str:
|
50
|
+
for line in text.splitlines():
|
51
|
+
self._process_line(line)
|
52
|
+
self._end_current()
|
53
|
+
return "\n".join(self._results)
|
54
|
+
|
55
|
+
def _process_line(self, line: str) -> None:
|
56
|
+
if not line.strip():
|
57
|
+
self._end_current()
|
58
|
+
elif self._current and self._current.handles(line):
|
59
|
+
self._current.add(line)
|
60
|
+
else:
|
61
|
+
self._end_current()
|
62
|
+
self._current = self._find_formatter(line)
|
63
|
+
if self._current is not None:
|
64
|
+
self._current.add(line)
|
65
|
+
|
66
|
+
def _end_current(self) -> None:
|
67
|
+
if self._current:
|
68
|
+
self._results.append(self._current.end())
|
69
|
+
self._current = None
|
70
|
+
|
71
|
+
def _find_formatter(self, line: str) -> Optional[Formatter]:
|
72
|
+
for formatter in self._formatters:
|
73
|
+
if formatter.handles(line):
|
74
|
+
return formatter
|
75
|
+
return None
|
76
|
+
|
77
|
+
|
78
|
+
class SingleLineFormatter(Formatter):
|
79
|
+
def _handles(self, line: str) -> bool:
|
80
|
+
return bool(not self._lines and self.match(line))
|
81
|
+
|
82
|
+
@abstractmethod
|
83
|
+
def match(self, line: str) -> Optional[re.Match[str]]:
|
84
|
+
...
|
85
|
+
|
86
|
+
def format(self, lines: List[str]) -> str:
|
87
|
+
return self.format_line(lines[0])
|
88
|
+
|
89
|
+
@abstractmethod
|
90
|
+
def format_line(self, line: str) -> str:
|
91
|
+
...
|
92
|
+
|
93
|
+
|
94
|
+
class HeaderFormatter(SingleLineFormatter):
|
95
|
+
_regex = re.compile(r"^(={1,5})\s+(\S.*?)\s+\1$")
|
96
|
+
|
97
|
+
def match(self, line: str) -> Optional[re.Match[str]]:
|
98
|
+
return self._regex.match(line)
|
99
|
+
|
100
|
+
def format_line(self, line: str) -> str:
|
101
|
+
m = self.match(line)
|
102
|
+
if m is not None:
|
103
|
+
level, text = m.groups()
|
104
|
+
|
105
|
+
return "%s %s\n" % ("#" * (len(level) + 1), text)
|
106
|
+
return ""
|
107
|
+
|
108
|
+
|
109
|
+
class LinkFormatter:
|
110
|
+
_image_exts = (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg")
|
111
|
+
_link = re.compile(r"\[(.+?\|.*?)\]")
|
112
|
+
_url = re.compile(
|
113
|
+
r"""
|
114
|
+
((^|\ ) ["'(\[{]*) # begin of line or space and opt. any char "'([{
|
115
|
+
([a-z][\w+-.]*://[^\s|]+?) # url
|
116
|
+
(?=[)\]}"'.,!?:;|]* ($|\ )) # opt. any char )]}"'.,!?:;| and eol or space
|
117
|
+
""",
|
118
|
+
re.VERBOSE | re.MULTILINE | re.IGNORECASE,
|
119
|
+
)
|
120
|
+
|
121
|
+
def format_url(self, text: str) -> str:
|
122
|
+
return self._format_url(text, format_as_image=False)
|
123
|
+
|
124
|
+
def _format_url(self, text: str, format_as_image: bool = True) -> str:
|
125
|
+
if "://" not in text:
|
126
|
+
return text
|
127
|
+
return self._url.sub(functools.partial(self._replace_url, format_as_image), text)
|
128
|
+
|
129
|
+
def _replace_url(self, format_as_image: bool, match: re.Match[str]) -> str:
|
130
|
+
pre = match.group(1)
|
131
|
+
url = match.group(3)
|
132
|
+
if format_as_image and self._is_image(url):
|
133
|
+
return pre + self._get_image(url)
|
134
|
+
return pre + self._get_link(url)
|
135
|
+
|
136
|
+
def _get_image(self, src: str, title: Optional[str] = None) -> str:
|
137
|
+
return f"![{title or src}]({src})"
|
138
|
+
|
139
|
+
def _get_link(self, href: str, content: Optional[str] = None) -> str:
|
140
|
+
return f"[{content or href}]({href})"
|
141
|
+
|
142
|
+
def _quot(self, attr: str) -> str:
|
143
|
+
return attr if '"' not in attr else attr.replace('"', """)
|
144
|
+
|
145
|
+
def format_link(self, text: str) -> str:
|
146
|
+
# 2nd, 4th, etc. token contains link, others surrounding content
|
147
|
+
tokens = self._link.split(text)
|
148
|
+
|
149
|
+
formatters: Iterator[Callable[[str], Any]] = itertools.cycle((self._format_url, self._format_link))
|
150
|
+
return "".join(f(t) for f, t in zip(formatters, tokens))
|
151
|
+
|
152
|
+
def _format_link(self, text: str) -> str:
|
153
|
+
link, content = [t.strip() for t in text.split("|", 1)]
|
154
|
+
if self._is_image(content):
|
155
|
+
content = self._get_image(content, link)
|
156
|
+
elif self._is_image(link):
|
157
|
+
return self._get_image(link, content)
|
158
|
+
if link.startswith("\\#"):
|
159
|
+
link = link.lower()
|
160
|
+
return self._get_link(link, content)
|
161
|
+
|
162
|
+
def remove_link(self, text: str) -> str:
|
163
|
+
# 2nd, 4th, etc. token contains link, others surrounding content
|
164
|
+
tokens = self._link.split(text)
|
165
|
+
if len(tokens) > 1:
|
166
|
+
formatters: Iterator[Callable[[str], Any]] = itertools.cycle([self._remove_link])
|
167
|
+
return "".join(f(t) for f, t in zip(formatters, tokens))
|
168
|
+
return text
|
169
|
+
|
170
|
+
def _remove_link(self, text: str) -> str:
|
171
|
+
if "|" not in text:
|
172
|
+
return text
|
173
|
+
|
174
|
+
link, content = [t.strip() for t in text.split("|", 1)]
|
175
|
+
if self._is_image(content):
|
176
|
+
return self._get_image(content, link)
|
177
|
+
|
178
|
+
return content
|
179
|
+
|
180
|
+
def _is_image(self, text: str) -> bool:
|
181
|
+
return text.startswith("data:image/") or text.lower().endswith(self._image_exts)
|
182
|
+
|
183
|
+
|
184
|
+
class LineFormatter:
|
185
|
+
_bold = re.compile(
|
186
|
+
r"""
|
187
|
+
( # prefix (group 1)
|
188
|
+
(^|\ ) # begin of line or space
|
189
|
+
["'(]* _? # optionally any char "'( and optional begin of italic
|
190
|
+
) #
|
191
|
+
\* # start of bold
|
192
|
+
([^\ ].*?) # no space and then anything (group 3)
|
193
|
+
\* # end of bold
|
194
|
+
(?= # start of postfix (non-capturing group)
|
195
|
+
_? ["').,!?:;]* # optional end of italic and any char "').,!?:;
|
196
|
+
($|\ ) # end of line or space
|
197
|
+
)
|
198
|
+
""",
|
199
|
+
re.VERBOSE,
|
200
|
+
)
|
201
|
+
_italic = re.compile(
|
202
|
+
r"""
|
203
|
+
( (^|\ ) ["'(]* ) # begin of line or space and opt. any char "'(
|
204
|
+
_ # start of italic
|
205
|
+
([^\ _].*?) # no space or underline and then anything
|
206
|
+
_ # end of italic
|
207
|
+
(?= ["').,!?:;]* ($|\ ) ) # opt. any char "').,!?:; and end of line or space
|
208
|
+
""",
|
209
|
+
re.VERBOSE,
|
210
|
+
)
|
211
|
+
_code = re.compile(
|
212
|
+
r"""
|
213
|
+
( (^|\ ) ["'(]* ) # same as above with _ changed to ``
|
214
|
+
``
|
215
|
+
([^\ `].*?)
|
216
|
+
``
|
217
|
+
(?= ["').,!?:;]* ($|\ ) )
|
218
|
+
""",
|
219
|
+
re.VERBOSE,
|
220
|
+
)
|
221
|
+
|
222
|
+
def __init__(self) -> None:
|
223
|
+
super().__init__()
|
224
|
+
|
225
|
+
self._formatters: List[Tuple[str, Callable[[str], str]]] = [
|
226
|
+
("<", self._quote_lower_then),
|
227
|
+
("#", self._quote_hash),
|
228
|
+
("*", self._format_bold),
|
229
|
+
("_", self._format_italic),
|
230
|
+
("``", self._format_code),
|
231
|
+
("", functools.partial(LinkFormatter().format_link)),
|
232
|
+
]
|
233
|
+
|
234
|
+
def format(self, line: str) -> str:
|
235
|
+
for marker, formatter in self._formatters:
|
236
|
+
if marker in line:
|
237
|
+
line = formatter(line)
|
238
|
+
return line
|
239
|
+
|
240
|
+
def _quote_lower_then(self, line: str) -> str:
|
241
|
+
return line.replace("<", "\\<")
|
242
|
+
|
243
|
+
def _quote_hash(self, line: str) -> str:
|
244
|
+
return line.replace("#", "\\#")
|
245
|
+
|
246
|
+
def _format_bold(self, line: str) -> str:
|
247
|
+
return self._bold.sub("\\1**\\3**", line)
|
248
|
+
|
249
|
+
def _format_italic(self, line: str) -> str:
|
250
|
+
return self._italic.sub("\\1*\\3*", line)
|
251
|
+
|
252
|
+
def _format_code(self, line: str) -> str:
|
253
|
+
return self._code.sub("\\1`\\3`", line)
|
254
|
+
|
255
|
+
|
256
|
+
class PreformattedFormatter(Formatter):
|
257
|
+
_format_line = functools.partial(LineFormatter().format)
|
258
|
+
|
259
|
+
def _handles(self, line: str) -> bool:
|
260
|
+
return line.startswith("| ") or line == "|"
|
261
|
+
|
262
|
+
def format(self, lines: List[str]) -> str:
|
263
|
+
lines = [LinkFormatter().remove_link(line[2:]) for line in lines]
|
264
|
+
return "```text\n" + "\n".join(lines) + "\n```\n"
|
265
|
+
|
266
|
+
|
267
|
+
class ParagraphFormatter(Formatter):
|
268
|
+
_format_line = functools.partial(LineFormatter().format)
|
269
|
+
|
270
|
+
def __init__(self, other_formatters: List[Formatter]) -> None:
|
271
|
+
super().__init__()
|
272
|
+
self._other_formatters = other_formatters
|
273
|
+
|
274
|
+
def _handles(self, line: str) -> bool:
|
275
|
+
return not any(other.handles(line) for other in self._other_formatters)
|
276
|
+
|
277
|
+
def format(self, lines: List[str]) -> str:
|
278
|
+
return self._format_line(" ".join(lines)) + "\n\n"
|
279
|
+
|
280
|
+
|
281
|
+
class ListFormatter(Formatter):
|
282
|
+
_strip_lines = False
|
283
|
+
_format_item = functools.partial(LineFormatter().format)
|
284
|
+
|
285
|
+
def _handles(self, line: str) -> bool:
|
286
|
+
return bool(line.strip().startswith("- ") or line.startswith(" ") and self._lines)
|
287
|
+
|
288
|
+
def format(self, lines: List[str]) -> str:
|
289
|
+
items = ["- %s" % self._format_item(line) for line in self._combine_lines(lines)]
|
290
|
+
return "\n".join(items) + "\n\n"
|
291
|
+
|
292
|
+
def _combine_lines(self, lines: List[str]) -> Iterator[str]:
|
293
|
+
current = []
|
294
|
+
for line in lines:
|
295
|
+
line = line.strip()
|
296
|
+
if not line.startswith("- "):
|
297
|
+
current.append(line)
|
298
|
+
continue
|
299
|
+
if current:
|
300
|
+
yield " ".join(current)
|
301
|
+
current = [line[2:].strip()]
|
302
|
+
yield " ".join(current)
|
303
|
+
|
304
|
+
|
305
|
+
class RulerFormatter(SingleLineFormatter):
|
306
|
+
regex = re.compile("^-{3,}$")
|
307
|
+
|
308
|
+
def match(self, line: str) -> Optional[re.Match[str]]:
|
309
|
+
return self.regex.match(line)
|
310
|
+
|
311
|
+
def format_line(self, line: str) -> str:
|
312
|
+
return "---"
|
313
|
+
|
314
|
+
|
315
|
+
class TableFormatter(Formatter):
|
316
|
+
_table_line = re.compile(r"^\| (.* |)\|$")
|
317
|
+
_line_splitter = re.compile(r" \|(?= )")
|
318
|
+
_format_cell_content = functools.partial(LineFormatter().format)
|
319
|
+
|
320
|
+
def _handles(self, line: str) -> bool:
|
321
|
+
return self._table_line.match(line) is not None
|
322
|
+
|
323
|
+
def format(self, lines: List[str]) -> str:
|
324
|
+
return self._format_table([self._split_to_cells(line) for line in lines])
|
325
|
+
|
326
|
+
def _split_to_cells(self, line: str) -> List[str]:
|
327
|
+
return [cell.strip() for cell in self._line_splitter.split(line[1:-1])]
|
328
|
+
|
329
|
+
def _format_table(self, rows: List[List[str]]) -> str:
|
330
|
+
table = []
|
331
|
+
|
332
|
+
max_columns = max(len(row) for row in rows)
|
333
|
+
|
334
|
+
try:
|
335
|
+
header_rows = [list(next(row for row in rows if any(cell for cell in row if cell.startswith("="))))]
|
336
|
+
except StopIteration:
|
337
|
+
header_rows = [[]]
|
338
|
+
|
339
|
+
body_rows = [row for row in rows if row not in header_rows]
|
340
|
+
|
341
|
+
for row in header_rows or [[]]:
|
342
|
+
row += [""] * (max_columns - len(row))
|
343
|
+
table.append(f'|{"|".join(self._format_cell(cell) for cell in row)}|')
|
344
|
+
|
345
|
+
row_ = [" :--- "] * max_columns
|
346
|
+
table.append(f'|{"|".join(row_)}|')
|
347
|
+
|
348
|
+
for row in body_rows:
|
349
|
+
row += [""] * (max_columns - len(row))
|
350
|
+
table.append(f'|{"|".join(self._format_cell(cell) for cell in row)}|')
|
351
|
+
|
352
|
+
return "\n".join(table) + "\n\n"
|
353
|
+
|
354
|
+
def _format_cell(self, content: str) -> str:
|
355
|
+
if content.startswith("=") and content.endswith("="):
|
356
|
+
content = content[1:-1]
|
357
|
+
|
358
|
+
return f" {self._format_cell_content(content).strip()} "
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "0.64.1"
|
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
|