robotcode-robot 0.68.0__py3-none-any.whl → 0.68.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2799 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import hashlib
5
+ import importlib
6
+ import importlib.util
7
+ import io
8
+ import os
9
+ import pkgutil
10
+ import re
11
+ import sys
12
+ import tempfile
13
+ import traceback
14
+ from contextlib import contextmanager
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum
17
+ from pathlib import Path
18
+ from typing import (
19
+ AbstractSet,
20
+ Any,
21
+ Callable,
22
+ Dict,
23
+ Iterable,
24
+ Iterator,
25
+ List,
26
+ NamedTuple,
27
+ Optional,
28
+ Sequence,
29
+ Set,
30
+ Tuple,
31
+ TypeVar,
32
+ Union,
33
+ cast,
34
+ )
35
+
36
+ from robot.parsing.lexer.tokens import Token
37
+ from robotcode.core.lsp.types import Position, Range
38
+ from robotcode.robot.diagnostics.entities import (
39
+ ArgumentDefinition,
40
+ ImportedVariableDefinition,
41
+ LibraryArgumentDefinition,
42
+ NativeValue,
43
+ SourceEntity,
44
+ single_call,
45
+ )
46
+ from robotcode.robot.utils import get_robot_version
47
+ from robotcode.robot.utils.ast import (
48
+ get_variable_token,
49
+ range_from_token,
50
+ strip_variable_token,
51
+ )
52
+ from robotcode.robot.utils.markdownformatter import MarkDownFormatter
53
+ from robotcode.robot.utils.match import normalize, normalize_namespace
54
+ from robotcode.robot.utils.stubs import HasError, HasErrors
55
+
56
+ RUN_KEYWORD_NAMES = [
57
+ "Run Keyword",
58
+ "Run Keyword And Continue On Failure",
59
+ "Run Keyword And Ignore Error",
60
+ "Run Keyword And Return",
61
+ "Run Keyword And Return Status",
62
+ "Run Keyword If All Critical Tests Passed",
63
+ "Run Keyword If All Tests Passed",
64
+ "Run Keyword If Any Critical Tests Failed",
65
+ "Run Keyword If Any Tests Failed",
66
+ "Run Keyword If Test Failed",
67
+ "Run Keyword If Test Passed",
68
+ "Run Keyword If Timeout Occurred",
69
+ "Run Keyword And Warn On Failure",
70
+ ]
71
+
72
+ RUN_KEYWORD_WITH_CONDITION_NAMES: Dict[str, int] = {
73
+ "Run Keyword And Expect Error": 1,
74
+ "Run Keyword And Return If": 1,
75
+ "Run Keyword Unless": 1,
76
+ "Repeat Keyword": 1,
77
+ "Wait Until Keyword Succeeds": 2,
78
+ }
79
+
80
+ RUN_KEYWORD_IF_NAME = "Run Keyword If"
81
+
82
+ RUN_KEYWORDS_NAME = "Run Keywords"
83
+
84
+ ALL_RUN_KEYWORDS = [
85
+ *RUN_KEYWORD_NAMES,
86
+ *RUN_KEYWORD_WITH_CONDITION_NAMES.keys(),
87
+ RUN_KEYWORDS_NAME,
88
+ RUN_KEYWORD_IF_NAME,
89
+ ]
90
+
91
+ BUILTIN_LIBRARY_NAME = "BuiltIn"
92
+ RESERVED_LIBRARY_NAME = "Reserved"
93
+
94
+ if get_robot_version() < (7, 0):
95
+ DEFAULT_LIBRARIES = {BUILTIN_LIBRARY_NAME, RESERVED_LIBRARY_NAME, "Easter"}
96
+ else:
97
+ DEFAULT_LIBRARIES = {BUILTIN_LIBRARY_NAME, "Easter"}
98
+
99
+ ROBOT_LIBRARY_PACKAGE = "robot.libraries"
100
+
101
+ ALLOWED_LIBRARY_FILE_EXTENSIONS = [".py"]
102
+
103
+ ROBOT_FILE_EXTENSION = ".robot"
104
+ RESOURCE_FILE_EXTENSION = ".resource"
105
+ REST_EXTENSIONS = {".rst", ".rest"}
106
+ JSON_EXTENSIONS = {".json", ".rsrc"}
107
+
108
+ ALLOWED_RESOURCE_FILE_EXTENSIONS = (
109
+ {
110
+ ROBOT_FILE_EXTENSION,
111
+ RESOURCE_FILE_EXTENSION,
112
+ *REST_EXTENSIONS,
113
+ *JSON_EXTENSIONS,
114
+ }
115
+ if get_robot_version() >= (6, 1)
116
+ else {ROBOT_FILE_EXTENSION, RESOURCE_FILE_EXTENSION, *REST_EXTENSIONS}
117
+ )
118
+
119
+ ALLOWED_VARIABLES_FILE_EXTENSIONS = (
120
+ {".py", ".yml", ".yaml", ".json"} if get_robot_version() >= (6, 1) else {".py", ".yml", ".yaml"}
121
+ )
122
+ ROBOT_DOC_FORMAT = "ROBOT"
123
+ REST_DOC_FORMAT = "REST"
124
+
125
+ _F = TypeVar("_F", bound=Callable[..., Any])
126
+
127
+
128
+ def convert_from_rest(text: str) -> str:
129
+ try:
130
+ from docutils.core import publish_parts
131
+
132
+ parts = publish_parts(
133
+ text,
134
+ writer_name="html5",
135
+ settings_overrides={"syntax_highlight": "none"},
136
+ )
137
+
138
+ return str(parts["html_body"])
139
+
140
+ except ImportError:
141
+ pass
142
+
143
+ return text
144
+
145
+
146
+ def is_embedded_keyword(name: str) -> bool:
147
+ from robot.errors import DataError, VariableError
148
+ from robot.running.arguments.embedded import EmbeddedArguments
149
+
150
+ try:
151
+ if get_robot_version() >= (6, 0):
152
+ if EmbeddedArguments.from_name(name):
153
+ return True
154
+ else:
155
+ if EmbeddedArguments(name):
156
+ return True
157
+ except (VariableError, DataError):
158
+ return True
159
+
160
+ return False
161
+
162
+
163
+ class KeywordMatcher:
164
+ def __init__(
165
+ self,
166
+ name: str,
167
+ can_have_embedded: bool = True,
168
+ is_namespace: bool = False,
169
+ ) -> None:
170
+ self.name = name
171
+ self._can_have_embedded = can_have_embedded and not is_namespace
172
+ self._is_namespace = is_namespace
173
+ self._normalized_name: Optional[str] = None
174
+ self._embedded_arguments: Any = None
175
+
176
+ @property
177
+ def normalized_name(self) -> str:
178
+ if self._normalized_name is None:
179
+ self._normalized_name = str(normalize_namespace(self.name) if self._is_namespace else normalize(self.name))
180
+
181
+ return self._normalized_name
182
+
183
+ @property
184
+ def embedded_arguments(self) -> Any:
185
+ from robot.errors import DataError, VariableError
186
+ from robot.running.arguments.embedded import EmbeddedArguments
187
+
188
+ if self._embedded_arguments is None:
189
+ if self._can_have_embedded:
190
+ try:
191
+ if get_robot_version() >= (6, 0):
192
+ self._embedded_arguments = EmbeddedArguments.from_name(self.name)
193
+ else:
194
+ self._embedded_arguments = EmbeddedArguments(self.name)
195
+ except (VariableError, DataError):
196
+ self._embedded_arguments = ()
197
+ else:
198
+ self._embedded_arguments = ()
199
+
200
+ return self._embedded_arguments
201
+
202
+ def __eq__(self, o: object) -> bool:
203
+ if isinstance(o, KeywordMatcher):
204
+ if self._is_namespace != o._is_namespace:
205
+ return False
206
+
207
+ if not self.embedded_arguments:
208
+ return self.normalized_name == o.normalized_name
209
+
210
+ o = o.name
211
+
212
+ if not isinstance(o, str):
213
+ return False
214
+
215
+ if self.embedded_arguments:
216
+ if get_robot_version() >= (6, 0):
217
+ return self.embedded_arguments.match(o) is not None
218
+
219
+ return self.embedded_arguments.name.match(o) is not None
220
+
221
+ return self.normalized_name == str(normalize_namespace(o) if self._is_namespace else normalize(o))
222
+
223
+ @single_call
224
+ def __hash__(self) -> int:
225
+ return hash(
226
+ (self.embedded_arguments.name, tuple(self.embedded_arguments.args))
227
+ if self.embedded_arguments
228
+ else (self.normalized_name, self._is_namespace)
229
+ )
230
+
231
+ def __str__(self) -> str:
232
+ return self.name
233
+
234
+ def __repr__(self) -> str:
235
+ return f"{type(self).__name__}(name={self.name!r})"
236
+
237
+
238
+ RUN_KEYWORD_WITH_CONDITION_MATCHERS = [KeywordMatcher(e) for e in RUN_KEYWORD_WITH_CONDITION_NAMES]
239
+
240
+ RUN_KEYWORD_IF_MATCHER = KeywordMatcher(RUN_KEYWORD_IF_NAME)
241
+
242
+ RUN_KEYWORDS_MATCHER = KeywordMatcher(RUN_KEYWORDS_NAME)
243
+
244
+ ALL_RUN_KEYWORDS_MATCHERS = [KeywordMatcher(e) for e in ALL_RUN_KEYWORDS]
245
+
246
+
247
+ class TypeDocType(Enum):
248
+ ENUM = "Enum"
249
+ TYPED_DICT = "TypedDict"
250
+ CUSTOM = "Custom"
251
+ STANDARD = "Standard"
252
+
253
+
254
+ @dataclass
255
+ class TypedDictItem:
256
+ key: str
257
+ type: str
258
+ required: Optional[bool] = None
259
+
260
+
261
+ @dataclass
262
+ class EnumMember:
263
+ name: str
264
+ value: str
265
+
266
+
267
+ @dataclass
268
+ class TypeDoc:
269
+ type: str
270
+ name: str
271
+ doc: Optional[str] = None
272
+ accepts: List[str] = field(default_factory=list)
273
+ usages: List[str] = field(default_factory=list)
274
+ members: Optional[List[EnumMember]] = None
275
+ items: Optional[List[TypedDictItem]] = None
276
+ libname: Optional[str] = None
277
+ libtype: Optional[str] = None
278
+
279
+ doc_format: str = ROBOT_DOC_FORMAT
280
+
281
+ @single_call
282
+ def __hash__(self) -> int:
283
+ return hash(
284
+ (
285
+ self.type,
286
+ self.name,
287
+ self.libname,
288
+ self.libtype,
289
+ tuple(self.accepts),
290
+ tuple(self.usages),
291
+ )
292
+ )
293
+
294
+ def to_markdown(self, header_level: int = 2) -> str:
295
+ result = ""
296
+
297
+ result += f"##{'#' * header_level} {self.name} ({self.type})\n\n"
298
+
299
+ if self.doc:
300
+ result += f"###{'#' * header_level} Documentation:\n\n"
301
+ result += "\n\n"
302
+
303
+ if self.doc_format == ROBOT_DOC_FORMAT:
304
+ result += MarkDownFormatter().format(self.doc)
305
+ elif self.doc_format == REST_DOC_FORMAT:
306
+ result += convert_from_rest(self.doc)
307
+ else:
308
+ result += self.doc
309
+
310
+ if self.members:
311
+ result += f"\n\n###{'#' * header_level} Allowed Values:\n\n"
312
+ result += "- " + "\n- ".join(f"`{m.name}`" for m in self.members)
313
+
314
+ if self.items:
315
+ result += f"\n\n###{'#' * header_level} Dictionary Structure:\n\n"
316
+ result += "```\n{"
317
+ result += "\n ".join(f"'{m.key}': <{m.type}> {'# optional' if not m.required else ''}" for m in self.items)
318
+ result += "\n}\n```"
319
+
320
+ if self.accepts:
321
+ result += f"\n\n###{'#' * header_level} Converted Types:\n\n"
322
+ result += "- " + "\n- ".join(self.accepts)
323
+
324
+ return result
325
+
326
+
327
+ @dataclass
328
+ class SourceAndLineInfo:
329
+ source: str
330
+ line_no: int
331
+
332
+
333
+ @dataclass
334
+ class Error:
335
+ message: str
336
+ type_name: str
337
+ source: Optional[str] = None
338
+ line_no: Optional[int] = None
339
+ message_traceback: Optional[List[SourceAndLineInfo]] = None
340
+ traceback: Optional[List[SourceAndLineInfo]] = None
341
+
342
+
343
+ class KeywordArgumentKind(Enum):
344
+ POSITIONAL_ONLY = "POSITIONAL_ONLY"
345
+ POSITIONAL_ONLY_MARKER = "POSITIONAL_ONLY_MARKER"
346
+ POSITIONAL_OR_NAMED = "POSITIONAL_OR_NAMED"
347
+ VAR_POSITIONAL = "VAR_POSITIONAL"
348
+ NAMED_ONLY_MARKER = "NAMED_ONLY_MARKER"
349
+ NAMED_ONLY = "NAMED_ONLY"
350
+ VAR_NAMED = "VAR_NAMED"
351
+
352
+
353
+ def robot_arg_repr(arg: Any) -> Optional[str]:
354
+ from robot.running.arguments.argumentspec import ArgInfo
355
+
356
+ if get_robot_version() >= (7, 0):
357
+ from robot.utils import NOT_SET as robot_notset # noqa: N811
358
+ else:
359
+ robot_notset = ArgInfo.NOTSET
360
+
361
+ robot_arg = cast(ArgInfo, arg)
362
+
363
+ if robot_arg.default is robot_notset:
364
+ return None
365
+
366
+ if robot_arg.default is None:
367
+ return "${None}"
368
+
369
+ if isinstance(robot_arg.default, (int, float, bool)):
370
+ return f"${{{robot_arg.default!r}}}"
371
+
372
+ if isinstance(robot_arg.default, str) and robot_arg.default == "":
373
+ return "${EMPTY}"
374
+
375
+ if isinstance(robot_arg.default, str) and robot_arg.default == "\\ \\":
376
+ return "${SPACE}"
377
+
378
+ return str(robot_arg.default_repr)
379
+
380
+
381
+ @dataclass
382
+ class ArgumentInfo:
383
+ name: str
384
+ str_repr: str
385
+ kind: KeywordArgumentKind
386
+ required: bool
387
+ default_value: Optional[Any] = None
388
+ types: Optional[List[str]] = None
389
+
390
+ @staticmethod
391
+ def from_robot(arg: Any) -> ArgumentInfo:
392
+ from robot.running.arguments.argumentspec import ArgInfo
393
+
394
+ robot_arg = cast(ArgInfo, arg)
395
+
396
+ return ArgumentInfo(
397
+ name=robot_arg.name,
398
+ default_value=robot_arg_repr(robot_arg),
399
+ str_repr=str(arg),
400
+ types=robot_arg.types_reprs
401
+ if get_robot_version() < (7, 0)
402
+ else ([str(robot_arg.type)] if not robot_arg.type.is_union else [str(t) for t in robot_arg.type.nested])
403
+ if robot_arg.type
404
+ else None,
405
+ kind=KeywordArgumentKind[robot_arg.kind],
406
+ required=robot_arg.required,
407
+ )
408
+
409
+ def __str__(self) -> str:
410
+ return self.signature()
411
+
412
+ def signature(self, add_types: bool = True) -> str:
413
+ prefix = ""
414
+ if self.kind == KeywordArgumentKind.POSITIONAL_ONLY_MARKER:
415
+ prefix = "*\u200d"
416
+ elif self.kind == KeywordArgumentKind.NAMED_ONLY_MARKER:
417
+ prefix = "/\u200d"
418
+ elif self.kind == KeywordArgumentKind.VAR_NAMED:
419
+ prefix = "*\u200d*\u200d"
420
+ elif self.kind == KeywordArgumentKind.VAR_POSITIONAL:
421
+ prefix = "*\u200d"
422
+ elif self.kind == KeywordArgumentKind.NAMED_ONLY:
423
+ prefix = "🏷\u200d"
424
+ elif self.kind == KeywordArgumentKind.POSITIONAL_ONLY:
425
+ prefix = "⟶\u200d"
426
+ result = f"{prefix}{self.name!s}"
427
+ if add_types:
428
+ result += (
429
+ f"{(': ' + (' | '.join(f'{s}' for s in self.types))) if self.types else ''}"
430
+ f"{' =' if self.default_value is not None else ''}"
431
+ f"{f' {self.default_value!s}' if self.default_value else ''}"
432
+ if add_types
433
+ else ""
434
+ )
435
+ return result
436
+
437
+ def __hash__(self) -> int:
438
+ return id(self)
439
+
440
+
441
+ DEPRECATED_PATTERN = re.compile(r"^\*DEPRECATED(?P<message>.*)\*(?P<doc>.*)")
442
+
443
+
444
+ @dataclass
445
+ class ArgumentSpec:
446
+ name: Optional[str]
447
+ type: str
448
+ positional_only: List[str]
449
+ positional_or_named: List[str]
450
+ var_positional: Any
451
+ named_only: Any
452
+ var_named: Any
453
+ embedded: Any
454
+ defaults: Any
455
+ types: Optional[Dict[str, str]] = None
456
+ return_type: Optional[str] = None
457
+
458
+ @staticmethod
459
+ def from_robot_argument_spec(spec: Any) -> ArgumentSpec:
460
+ return ArgumentSpec(
461
+ name=spec.name,
462
+ type=str(spec.type),
463
+ positional_only=spec.positional_only,
464
+ positional_or_named=spec.positional_or_named,
465
+ var_positional=spec.var_positional,
466
+ named_only=spec.named_only,
467
+ var_named=spec.var_named,
468
+ embedded=spec.embedded if get_robot_version() >= (7, 0) else None,
469
+ defaults={k: str(v) for k, v in spec.defaults.items()} if spec.defaults else {},
470
+ types={k: str(v) for k, v in spec.types.items()} if get_robot_version() > (7, 0) and spec.types else None,
471
+ return_type=str(spec.return_type) if get_robot_version() > (7, 0) and spec.return_type else None,
472
+ )
473
+
474
+ def resolve(
475
+ self,
476
+ arguments: Any,
477
+ variables: Any,
478
+ resolve_named: bool = True,
479
+ resolve_variables_until: Any = None,
480
+ dict_to_kwargs: bool = False,
481
+ validate: bool = True,
482
+ ) -> Tuple[List[Any], List[Tuple[str, Any]]]:
483
+ from robot.running.arguments.argumentresolver import (
484
+ ArgumentResolver,
485
+ DictToKwargs,
486
+ NamedArgumentResolver,
487
+ VariableReplacer,
488
+ )
489
+ from robot.running.arguments.argumentspec import (
490
+ ArgumentSpec as RobotArgumentSpec,
491
+ )
492
+
493
+ if not hasattr(self, "__robot_arguments"):
494
+ if get_robot_version() < (7, 0):
495
+ self.__robot_arguments = RobotArgumentSpec(
496
+ self.name,
497
+ self.type,
498
+ self.positional_only,
499
+ self.positional_or_named,
500
+ self.var_positional,
501
+ self.named_only,
502
+ self.var_named,
503
+ self.defaults,
504
+ None,
505
+ )
506
+ else:
507
+ self.__robot_arguments = RobotArgumentSpec(
508
+ self.name,
509
+ self.type,
510
+ self.positional_only,
511
+ self.positional_or_named,
512
+ self.var_positional,
513
+ self.named_only,
514
+ self.var_named,
515
+ self.defaults,
516
+ self.embedded,
517
+ None,
518
+ )
519
+ self.__robot_arguments.name = self.name
520
+ if validate:
521
+ if get_robot_version() < (7, 0):
522
+ resolver = ArgumentResolver(
523
+ self.__robot_arguments,
524
+ resolve_named=resolve_named,
525
+ resolve_variables_until=resolve_variables_until,
526
+ dict_to_kwargs=dict_to_kwargs,
527
+ )
528
+ else:
529
+ resolver = ArgumentResolver(
530
+ self.__robot_arguments,
531
+ resolve_named=resolve_named,
532
+ resolve_args_until=resolve_variables_until,
533
+ dict_to_kwargs=dict_to_kwargs,
534
+ )
535
+ return cast(
536
+ Tuple[List[Any], List[Tuple[str, Any]]],
537
+ resolver.resolve(arguments, variables),
538
+ )
539
+
540
+ class MyNamedArgumentResolver(NamedArgumentResolver):
541
+ def _raise_positional_after_named(self) -> None:
542
+ pass
543
+
544
+ positional, named = MyNamedArgumentResolver(self.__robot_arguments).resolve(arguments, variables)
545
+ if get_robot_version() < (7, 0):
546
+ positional, named = VariableReplacer(resolve_variables_until).replace(positional, named, variables)
547
+ else:
548
+ positional, named = VariableReplacer(self.__robot_arguments, resolve_variables_until).replace(
549
+ positional, named, variables
550
+ )
551
+ positional, named = DictToKwargs(self.__robot_arguments, dict_to_kwargs).handle(positional, named)
552
+ return positional, named
553
+
554
+
555
+ @dataclass
556
+ class KeywordDoc(SourceEntity):
557
+ name: str = ""
558
+ name_token: Optional[Token] = field(default=None, compare=False)
559
+ arguments: List[ArgumentInfo] = field(default_factory=list, compare=False)
560
+ arguments_spec: Optional[ArgumentSpec] = field(default=None, compare=False)
561
+ argument_definitions: Optional[List[ArgumentDefinition]] = field(
562
+ default=None, compare=False, metadata={"nosave": True}
563
+ )
564
+ doc: str = field(default="", compare=False)
565
+ tags: List[str] = field(default_factory=list)
566
+ type: str = "keyword"
567
+ libname: Optional[str] = None
568
+ libtype: Optional[str] = None
569
+ longname: Optional[str] = None
570
+ is_embedded: bool = False
571
+ errors: Optional[List[Error]] = field(default=None, compare=False)
572
+ doc_format: str = ROBOT_DOC_FORMAT
573
+ is_error_handler: bool = False
574
+ error_handler_message: Optional[str] = field(default=None, compare=False)
575
+ is_initializer: bool = False
576
+ is_registered_run_keyword: bool = field(default=False, compare=False)
577
+ args_to_process: Optional[int] = field(default=None, compare=False)
578
+ deprecated: bool = field(default=False, compare=False)
579
+ return_type: Optional[str] = field(default=None, compare=False)
580
+
581
+ parent_digest: Optional[str] = field(default=None, init=False, metadata={"nosave": True})
582
+ parent: Optional[LibraryDoc] = field(default=None, init=False, metadata={"nosave": True})
583
+
584
+ def _get_argument_definitions(self) -> Optional[List[ArgumentDefinition]]:
585
+ return (
586
+ [
587
+ LibraryArgumentDefinition(
588
+ self.line_no if self.line_no is not None else -1,
589
+ col_offset=-1,
590
+ end_line_no=-1,
591
+ end_col_offset=-1,
592
+ source=self.source,
593
+ name="${" + a.name + "}",
594
+ name_token=None,
595
+ keyword_doc=self,
596
+ )
597
+ for a in self.arguments
598
+ ]
599
+ if self.arguments_spec is not None and not self.is_error_handler and self.arguments
600
+ else []
601
+ )
602
+
603
+ digest: Optional[str] = field(init=False)
604
+
605
+ def __post_init__(self) -> None:
606
+ s = (
607
+ f"{self.name}|{self.source}|{self.line_no}|"
608
+ f"{self.end_line_no}|{self.col_offset}|{self.end_col_offset}|"
609
+ f"{self.type}|{self.libname}|{self.libtype}"
610
+ )
611
+ self.digest = hashlib.sha224(s.encode("utf-8")).hexdigest()
612
+
613
+ if self.argument_definitions is None:
614
+ self.argument_definitions = self._get_argument_definitions()
615
+
616
+ def __str__(self) -> str:
617
+ return f"{self.name}({', '.join(str(arg) for arg in self.arguments)})"
618
+
619
+ @property
620
+ def matcher(self) -> KeywordMatcher:
621
+ if not hasattr(self, "__matcher"):
622
+ self.__matcher = KeywordMatcher(self.name)
623
+ return self.__matcher
624
+
625
+ @property
626
+ def is_deprecated(self) -> bool:
627
+ return self.deprecated or DEPRECATED_PATTERN.match(self.doc) is not None
628
+
629
+ @property
630
+ def is_resource_keyword(self) -> bool:
631
+ return self.libtype == "RESOURCE"
632
+
633
+ @property
634
+ def is_library_keyword(self) -> bool:
635
+ return self.libtype == "LIBRARY"
636
+
637
+ @property
638
+ def deprecated_message(self) -> str:
639
+ if (m := DEPRECATED_PATTERN.match(self.doc)) is not None:
640
+ return m.group("message").strip()
641
+ return ""
642
+
643
+ @property
644
+ def name_range(self) -> Range:
645
+ if self.name_token is not None:
646
+ return range_from_token(self.name_token)
647
+
648
+ return Range.invalid()
649
+
650
+ @single_call
651
+ def normalized_tags(self) -> List[str]:
652
+ return [normalize(tag) for tag in self.tags]
653
+
654
+ @single_call
655
+ def is_private(self) -> bool:
656
+ if get_robot_version() < (6, 0):
657
+ return False
658
+
659
+ return "robot:private" in self.normalized_tags()
660
+
661
+ @property
662
+ def range(self) -> Range:
663
+ if self.name_token is not None:
664
+ return range_from_token(self.name_token)
665
+
666
+ return Range(
667
+ start=Position(
668
+ line=self.line_no - 1 if self.line_no > 0 else 0,
669
+ character=max(self.col_offset, 0),
670
+ ),
671
+ end=Position(
672
+ line=self.end_line_no - 1 if self.end_line_no >= 0 else max(0, self.line_no),
673
+ character=self.end_col_offset if self.end_col_offset >= 0 else max(self.col_offset, 0),
674
+ ),
675
+ )
676
+
677
+ def to_markdown(
678
+ self,
679
+ add_signature: bool = True,
680
+ header_level: int = 2,
681
+ add_type: bool = True,
682
+ ) -> str:
683
+ result = ""
684
+
685
+ if add_signature:
686
+ result += self._get_signature(header_level, add_type)
687
+
688
+ if self.doc:
689
+ if result:
690
+ result += "\n\n"
691
+
692
+ result += f"##{'#' * header_level} Documentation:\n"
693
+
694
+ if self.doc_format == ROBOT_DOC_FORMAT:
695
+ result += MarkDownFormatter().format(self.doc)
696
+ elif self.doc_format == REST_DOC_FORMAT:
697
+ result += convert_from_rest(self.doc)
698
+ else:
699
+ result += self.doc
700
+
701
+ return result
702
+
703
+ def _get_signature(self, header_level: int, add_type: bool = True) -> str:
704
+ if add_type:
705
+ result = (
706
+ f"#{'#' * header_level} "
707
+ f"{(self.libtype or 'Library').capitalize() if self.is_initializer else 'Keyword'} *{self.name}*\n"
708
+ )
709
+ else:
710
+ if not self.is_initializer:
711
+ result = f"\n\n#{'#' * header_level} {self.name}\n"
712
+ else:
713
+ result = ""
714
+
715
+ if self.arguments:
716
+ result += f"\n##{'#' * header_level} Arguments: \n"
717
+
718
+ result += "\n| | | | |"
719
+ result += "\n|:--|:--|:--|:--|"
720
+
721
+ escaped_pipe = " \\| "
722
+
723
+ for a in self.arguments:
724
+ prefix = ""
725
+ if a.kind in [
726
+ KeywordArgumentKind.NAMED_ONLY_MARKER,
727
+ KeywordArgumentKind.POSITIONAL_ONLY_MARKER,
728
+ ]:
729
+ continue
730
+
731
+ if a.kind == KeywordArgumentKind.VAR_POSITIONAL:
732
+ prefix = "*"
733
+ elif a.kind == KeywordArgumentKind.VAR_NAMED:
734
+ prefix = "**"
735
+ elif a.kind == KeywordArgumentKind.NAMED_ONLY:
736
+ prefix = "🏷"
737
+ elif a.kind == KeywordArgumentKind.POSITIONAL_ONLY:
738
+ prefix = "⟶"
739
+
740
+ def escape_pipe(s: str) -> str:
741
+ return s.replace("|", "\\|")
742
+
743
+ result += (
744
+ f"\n| `{prefix}{a.name!s}`"
745
+ f'| {": " if a.types else " "}'
746
+ f"{escaped_pipe.join(f'`{escape_pipe(s)}`' for s in a.types) if a.types else ''} "
747
+ f"| {'=' if a.default_value is not None else ''} "
748
+ f"| {f'`{a.default_value!s}`' if a.default_value else ''} |"
749
+ )
750
+ if self.return_type:
751
+ if result:
752
+ result += "\n\n"
753
+
754
+ result += f"**Return Type**: `{self.return_type}`\n"
755
+
756
+ if self.tags:
757
+ if result:
758
+ result += "\n\n"
759
+
760
+ result += "**Tags**: \n- "
761
+ result += "\n- ".join(self.tags)
762
+
763
+ return result
764
+
765
+ @property
766
+ def signature(self) -> str:
767
+ return (
768
+ f'({self.type}) "{self.name}": ('
769
+ + ", ".join(
770
+ str(a)
771
+ for a in self.arguments
772
+ if a.kind
773
+ not in [
774
+ KeywordArgumentKind.POSITIONAL_ONLY_MARKER,
775
+ KeywordArgumentKind.NAMED_ONLY_MARKER,
776
+ ]
777
+ )
778
+ + ")"
779
+ )
780
+
781
+ def parameter_signature(self, full_signatures: Optional[Sequence[int]] = None) -> str:
782
+ return (
783
+ "("
784
+ + ", ".join(
785
+ a.signature(full_signatures is None or i in full_signatures)
786
+ for i, a in enumerate(self.arguments)
787
+ if a.kind
788
+ not in [
789
+ KeywordArgumentKind.POSITIONAL_ONLY_MARKER,
790
+ KeywordArgumentKind.NAMED_ONLY_MARKER,
791
+ ]
792
+ )
793
+ + ")"
794
+ )
795
+
796
+ def is_reserved(self) -> bool:
797
+ if get_robot_version() < (7, 0):
798
+ return self.libname == RESERVED_LIBRARY_NAME
799
+
800
+ return False
801
+
802
+ def is_any_run_keyword(self) -> bool:
803
+ return self.libname == BUILTIN_LIBRARY_NAME and self.name in ALL_RUN_KEYWORDS
804
+
805
+ def is_run_keyword(self) -> bool:
806
+ return self.libname == BUILTIN_LIBRARY_NAME and self.name in RUN_KEYWORD_NAMES
807
+
808
+ def is_run_keyword_with_condition(self) -> bool:
809
+ return self.libname == BUILTIN_LIBRARY_NAME and self.name in RUN_KEYWORD_WITH_CONDITION_NAMES
810
+
811
+ def run_keyword_condition_count(self) -> int:
812
+ return (
813
+ RUN_KEYWORD_WITH_CONDITION_NAMES[self.name]
814
+ if self.libname == BUILTIN_LIBRARY_NAME and self.name in RUN_KEYWORD_WITH_CONDITION_NAMES
815
+ else 0
816
+ )
817
+
818
+ def is_run_keyword_if(self) -> bool:
819
+ return self.libname == BUILTIN_LIBRARY_NAME and self.name == RUN_KEYWORD_IF_NAME
820
+
821
+ def is_run_keywords(self) -> bool:
822
+ return self.libname == BUILTIN_LIBRARY_NAME and self.name == RUN_KEYWORDS_NAME
823
+
824
+ @single_call
825
+ def __hash__(self) -> int:
826
+ return hash(
827
+ (
828
+ self.name,
829
+ self.longname,
830
+ self.source,
831
+ self.line_no,
832
+ self.col_offset,
833
+ self.end_line_no,
834
+ self.end_col_offset,
835
+ self.type,
836
+ self.libname,
837
+ self.libtype,
838
+ self.is_embedded,
839
+ self.is_initializer,
840
+ self.is_error_handler,
841
+ self.doc_format,
842
+ tuple(self.tags),
843
+ )
844
+ )
845
+
846
+
847
+ class KeywordError(Exception):
848
+ def __init__(
849
+ self,
850
+ *args: Any,
851
+ multiple_keywords: Optional[List[KeywordDoc]] = None,
852
+ **kwargs: Any,
853
+ ) -> None:
854
+ super().__init__(*args, **kwargs)
855
+ self.multiple_keywords = multiple_keywords
856
+
857
+
858
+ @dataclass
859
+ class KeywordStore:
860
+ source: Optional[str] = None
861
+ source_type: Optional[str] = None
862
+ keywords: List[KeywordDoc] = field(default_factory=list)
863
+
864
+ @property
865
+ def _matchers(self) -> Dict[KeywordMatcher, KeywordDoc]:
866
+ if not hasattr(self, "__matchers"):
867
+ self.__matchers = {v.matcher: v for v in self.keywords}
868
+ return self.__matchers
869
+
870
+ def __getitem__(self, key: str) -> KeywordDoc:
871
+ items = [(k, v) for k, v in self._matchers.items() if k == key]
872
+
873
+ if not items:
874
+ raise KeyError
875
+ if len(items) == 1:
876
+ return items[0][1]
877
+
878
+ if self.source and self.source_type:
879
+ file_info = ""
880
+ if self.source_type == "RESOURCE":
881
+ file_info += f"Resource file '{self.source}'"
882
+ elif self.source_type == "LIBRARY":
883
+ file_info += f"Test library '{self.source}'"
884
+ elif self.source_type == "TESTCASE":
885
+ file_info += "Test case file"
886
+ else:
887
+ file_info += f"File '{self.source}'"
888
+ else:
889
+ file_info = "File"
890
+ error = [f"{file_info} contains multiple keywords matching name '{key}':"]
891
+ names = sorted(k.name for k, v in items)
892
+ raise KeywordError(
893
+ "\n ".join(error + names),
894
+ multiple_keywords=[v for _, v in items],
895
+ )
896
+
897
+ def __contains__(self, __x: object) -> bool:
898
+ return any(k == __x for k in self._matchers.keys())
899
+
900
+ def __len__(self) -> int:
901
+ return len(self.keywords)
902
+
903
+ def __bool__(self) -> bool:
904
+ return len(self) > 0
905
+
906
+ def items(self) -> AbstractSet[Tuple[str, KeywordDoc]]:
907
+ return {(v.name, v) for v in self.keywords}
908
+
909
+ def keys(self) -> AbstractSet[str]:
910
+ return {v.name for v in self.keywords}
911
+
912
+ def values(self) -> AbstractSet[KeywordDoc]:
913
+ return set(self.keywords)
914
+
915
+ def __iter__(self) -> Iterator[KeywordDoc]:
916
+ return self.keywords.__iter__()
917
+
918
+ def get(self, key: str, default: Optional[KeywordDoc] = None) -> Optional[KeywordDoc]:
919
+ try:
920
+ return self.__getitem__(key)
921
+ except KeyError:
922
+ return default
923
+
924
+ def get_all(self, key: str) -> List[KeywordDoc]:
925
+ return [v for k, v in self._matchers.items() if k == key]
926
+
927
+
928
+ @dataclass
929
+ class ModuleSpec:
930
+ name: str
931
+ origin: Optional[str]
932
+ submodule_search_locations: Optional[List[str]]
933
+ member_name: Optional[str]
934
+
935
+
936
+ class LibraryType(Enum):
937
+ CLASS = "CLASS"
938
+ MODULE = "MODULE"
939
+ HYBRID = "HYBRID"
940
+ DYNAMIC = "DYNAMIC"
941
+
942
+
943
+ ROBOT_DEFAULT_SCOPE = "TEST"
944
+
945
+ RE_INLINE_LINK = re.compile(r"([\`])((?:\1|.)+?)\1", re.VERBOSE)
946
+ RE_HEADERS = re.compile(r"^(#{2,9})\s+(\S.*)$", re.MULTILINE)
947
+
948
+
949
+ @dataclass
950
+ class LibraryDoc:
951
+ name: str = ""
952
+ doc: str = field(default="", compare=False)
953
+ version: str = ""
954
+ type: str = "LIBRARY"
955
+ scope: str = ROBOT_DEFAULT_SCOPE
956
+ named_args: bool = True
957
+ doc_format: str = ROBOT_DOC_FORMAT
958
+ source: Optional[str] = None
959
+ line_no: int = -1
960
+ end_line_no: int = -1
961
+ _inits: KeywordStore = field(default_factory=KeywordStore, compare=False)
962
+ _keywords: KeywordStore = field(default_factory=KeywordStore, compare=False)
963
+ types: List[TypeDoc] = field(default_factory=list)
964
+ module_spec: Optional[ModuleSpec] = None
965
+ member_name: Optional[str] = None
966
+ errors: Optional[List[Error]] = field(default=None, compare=False)
967
+ python_path: Optional[List[str]] = None
968
+ stdout: Optional[str] = field(default=None, compare=False)
969
+ has_listener: Optional[bool] = None
970
+ library_type: Optional[LibraryType] = None
971
+
972
+ digest: Optional[str] = field(init=False)
973
+
974
+ @property
975
+ def inits(self) -> KeywordStore:
976
+ return self._inits
977
+
978
+ @inits.setter
979
+ def inits(self, value: KeywordStore) -> None:
980
+ self._inits = value
981
+ self._update_keywords(self._inits)
982
+
983
+ @property
984
+ def keywords(self) -> KeywordStore:
985
+ return self._keywords
986
+
987
+ @keywords.setter
988
+ def keywords(self, value: KeywordStore) -> None:
989
+ self._keywords = value
990
+ self._update_keywords(self._keywords)
991
+
992
+ def _update_keywords(self, keywords: Optional[Iterable[KeywordDoc]]) -> None:
993
+ if not keywords:
994
+ return
995
+
996
+ for k in keywords:
997
+ k.parent = self
998
+ k.parent_digest = self.digest
999
+
1000
+ def __post_init__(self) -> None:
1001
+ s = (
1002
+ f"{self.name}|{self.source}|{self.line_no}|"
1003
+ f"{self.end_line_no}|{self.version}|"
1004
+ f"{self.type}|{self.scope}|{self.doc_format}"
1005
+ )
1006
+ self.digest = hashlib.sha224(s.encode("utf-8")).hexdigest()
1007
+
1008
+ self._update_keywords(self._inits)
1009
+ self._update_keywords(self._keywords)
1010
+
1011
+ @single_call
1012
+ def __hash__(self) -> int:
1013
+ return hash(
1014
+ (
1015
+ self.name,
1016
+ self.source,
1017
+ self.line_no,
1018
+ self.end_line_no,
1019
+ self.version,
1020
+ self.type,
1021
+ self.scope,
1022
+ self.doc_format,
1023
+ )
1024
+ )
1025
+
1026
+ def get_types(self, type_names: Optional[List[str]]) -> List[TypeDoc]:
1027
+ def alias(s: str) -> str:
1028
+ if s == "boolean":
1029
+ return "bool"
1030
+ return s
1031
+
1032
+ if not type_names:
1033
+ return []
1034
+
1035
+ return [t for t in self.types if alias(t.name) in type_names]
1036
+
1037
+ @property
1038
+ def is_deprecated(self) -> bool:
1039
+ return DEPRECATED_PATTERN.match(self.doc) is not None
1040
+
1041
+ @property
1042
+ def deprecated_message(self) -> str:
1043
+ if (m := DEPRECATED_PATTERN.match(self.doc)) is not None:
1044
+ return m.group("message").strip()
1045
+ return ""
1046
+
1047
+ @property
1048
+ def range(self) -> Range:
1049
+ return Range(
1050
+ start=Position(line=self.line_no - 1 if self.line_no >= 0 else 0, character=0),
1051
+ end=Position(
1052
+ line=self.end_line_no - 1 if self.end_line_no >= 0 else max(self.line_no, 0),
1053
+ character=0,
1054
+ ),
1055
+ )
1056
+
1057
+ def to_markdown(
1058
+ self,
1059
+ add_signature: bool = True,
1060
+ only_doc: bool = True,
1061
+ header_level: int = 2,
1062
+ ) -> str:
1063
+ with io.StringIO(newline="\n") as result:
1064
+
1065
+ def write_lines(*args: str) -> None:
1066
+ result.writelines(i + "\n" for i in args)
1067
+
1068
+ if add_signature and any(v for v in self.inits.values() if v.arguments):
1069
+ for i in self.inits.values():
1070
+ write_lines(i.to_markdown(header_level=header_level), "", "---")
1071
+
1072
+ write_lines(
1073
+ f"#{'#' * header_level} {(self.type.capitalize()) if self.type else 'Unknown'} *{self.name}*",
1074
+ "",
1075
+ )
1076
+
1077
+ if self.version or self.scope:
1078
+ write_lines("| | |", "| :--- | :--- |")
1079
+
1080
+ if self.version:
1081
+ write_lines(f"| **Library Version:** | {self.version} |")
1082
+ if self.scope:
1083
+ write_lines(f"| **Library Scope:** | {self.scope} |")
1084
+
1085
+ write_lines("", "")
1086
+
1087
+ if self.doc:
1088
+ write_lines(f"##{'#' * header_level} Introduction", "")
1089
+
1090
+ if self.doc_format == ROBOT_DOC_FORMAT:
1091
+ doc = MarkDownFormatter().format(self.doc)
1092
+
1093
+ if "%TOC%" in doc:
1094
+ doc = self._add_toc(doc, only_doc)
1095
+
1096
+ result.write(doc)
1097
+
1098
+ elif self.doc_format == REST_DOC_FORMAT:
1099
+ result.write(convert_from_rest(self.doc))
1100
+ else:
1101
+ result.write(self.doc)
1102
+
1103
+ if not only_doc:
1104
+ result.write(self._get_doc_for_keywords(header_level=header_level))
1105
+
1106
+ return self._link_inline_links(result.getvalue())
1107
+
1108
+ @property
1109
+ def source_or_origin(self) -> Optional[str]:
1110
+ if self.source is not None:
1111
+ return self.source
1112
+ if self.module_spec is not None:
1113
+ if self.module_spec.origin is not None:
1114
+ return self.module_spec.origin
1115
+
1116
+ if self.module_spec.submodule_search_locations:
1117
+ for e in self.module_spec.submodule_search_locations:
1118
+ p = Path(e, "__init__.py")
1119
+ if p.exists():
1120
+ return str(p)
1121
+
1122
+ return None
1123
+
1124
+ def _link_inline_links(self, text: str) -> str:
1125
+ headers = [v.group(2) for v in RE_HEADERS.finditer(text)]
1126
+
1127
+ def repl(m: re.Match) -> str: # type: ignore
1128
+ if m.group(2) in headers:
1129
+ return f"[{m.group(2)!s}](\\#{str(m.group(2)).lower().replace(' ', '-')})"
1130
+ return str(m.group(0))
1131
+
1132
+ return str(RE_INLINE_LINK.sub(repl, text))
1133
+
1134
+ def _get_doc_for_keywords(self, header_level: int = 2) -> str:
1135
+ result = ""
1136
+ if any(v for v in self.inits.values() if v.arguments):
1137
+ result += "\n---\n\n"
1138
+ result += f"\n##{'#' * header_level} Importing\n\n"
1139
+
1140
+ first = True
1141
+
1142
+ for kw in self.inits.values():
1143
+ if not first:
1144
+ result += "\n---\n"
1145
+ first = False
1146
+
1147
+ result += "\n" + kw.to_markdown(add_type=False)
1148
+
1149
+ if self.keywords:
1150
+ result += "\n---\n\n"
1151
+ result += f"\n##{'#' * header_level} Keywords\n\n"
1152
+
1153
+ first = True
1154
+
1155
+ for kw in self.keywords.values():
1156
+ if not first:
1157
+ result += "\n---\n"
1158
+ first = False
1159
+
1160
+ result += "\n" + kw.to_markdown(header_level=header_level, add_type=False)
1161
+ return result
1162
+
1163
+ def _add_toc(self, doc: str, only_doc: bool = True) -> str:
1164
+ toc = self._create_toc(doc, only_doc)
1165
+ return "\n".join(line if line.strip() != "%TOC%" else toc for line in doc.splitlines())
1166
+
1167
+ def _create_toc(self, doc: str, only_doc: bool = True) -> str:
1168
+ entries = re.findall(r"^##\s+(.+)", doc, flags=re.MULTILINE)
1169
+
1170
+ if not only_doc:
1171
+ if any(v for v in self.inits.values() if v.arguments):
1172
+ entries.append("Importing")
1173
+ if self.keywords:
1174
+ entries.append("Keywords")
1175
+ # TODO if self.data_types:
1176
+ # entries.append("Data types")
1177
+
1178
+ return "\n".join(f"- [{entry}](#{entry.lower().replace(' ', '-')})" for entry in entries)
1179
+
1180
+
1181
+ def var_repr(value: Any) -> str:
1182
+ if value is None:
1183
+ return ""
1184
+
1185
+ if isinstance(value, NativeValue):
1186
+ value = value.value
1187
+
1188
+ if value is None:
1189
+ return "${None}"
1190
+
1191
+ if isinstance(value, (int, float, bool)):
1192
+ return f"${{{value!r}}}"
1193
+
1194
+ if isinstance(value, str) and value == "":
1195
+ return "${EMPTY}"
1196
+
1197
+ if isinstance(value, str) and value == " ":
1198
+ return "${SPACE}"
1199
+
1200
+ if isinstance(value, str):
1201
+ return value
1202
+
1203
+ return "${{ " + repr(value) + " }}"
1204
+
1205
+
1206
+ @dataclass
1207
+ class VariablesDoc(LibraryDoc):
1208
+ type: str = "VARIABLES"
1209
+ scope: str = "GLOBAL"
1210
+
1211
+ variables: List[ImportedVariableDefinition] = field(default_factory=list)
1212
+
1213
+ def to_markdown(
1214
+ self,
1215
+ add_signature: bool = True,
1216
+ only_doc: bool = True,
1217
+ header_level: int = 2,
1218
+ ) -> str:
1219
+ result = super().to_markdown(add_signature, only_doc, header_level)
1220
+
1221
+ if self.variables:
1222
+ result += "\n---\n\n"
1223
+
1224
+ result += "\n```robotframework"
1225
+ result += "\n*** Variables ***"
1226
+
1227
+ for var in self.variables:
1228
+ result += "\n" + var.name
1229
+ if var.has_value:
1230
+ result += " " + var_repr(var.value)
1231
+ else:
1232
+ result += " ..."
1233
+ result += "\n```"
1234
+
1235
+ return result
1236
+
1237
+
1238
+ def is_library_by_path(path: str) -> bool:
1239
+ return path.lower().endswith((".py", "/", os.sep))
1240
+
1241
+
1242
+ def is_variables_by_path(path: str) -> bool:
1243
+ if get_robot_version() >= (6, 1):
1244
+ return path.lower().endswith((".py", ".yml", ".yaml", ".json", "/", os.sep))
1245
+ return path.lower().endswith((".py", ".yml", ".yaml", "/", os.sep))
1246
+
1247
+
1248
+ def _update_env(working_dir: str = ".") -> None:
1249
+ os.chdir(Path(working_dir))
1250
+
1251
+
1252
+ def get_module_spec(module_name: str) -> Optional[ModuleSpec]:
1253
+ import importlib.util
1254
+
1255
+ result = None
1256
+ member_name: Optional[str] = None
1257
+ while result is None:
1258
+ try:
1259
+ result = importlib.util.find_spec(module_name)
1260
+ except (SystemExit, KeyboardInterrupt):
1261
+ raise
1262
+ except BaseException:
1263
+ pass
1264
+ if result is None:
1265
+ splitted = module_name.rsplit(".", 1)
1266
+ if len(splitted) <= 1:
1267
+ break
1268
+ module_name, m = splitted
1269
+ if m:
1270
+ member_name = m + "." + member_name if m and member_name is not None else m
1271
+
1272
+ if result is not None:
1273
+ return ModuleSpec( # type: ignore
1274
+ name=result.name,
1275
+ origin=result.origin,
1276
+ submodule_search_locations=list(result.submodule_search_locations)
1277
+ if result.submodule_search_locations
1278
+ else None,
1279
+ member_name=member_name,
1280
+ )
1281
+ return None
1282
+
1283
+
1284
+ class KeywordWrapper:
1285
+ def __init__(self, kw: Any, source: str) -> None:
1286
+ self.kw = kw
1287
+ self.lib_source = source
1288
+
1289
+ @property
1290
+ def name(self) -> Any:
1291
+ return self.kw.name
1292
+
1293
+ @property
1294
+ def arguments(self) -> Any:
1295
+ return self.kw.arguments
1296
+
1297
+ @property
1298
+ def doc(self) -> Any:
1299
+ try:
1300
+ return self.kw.doc
1301
+ except (SystemExit, KeyboardInterrupt):
1302
+ raise
1303
+ except BaseException:
1304
+ return ""
1305
+
1306
+ @property
1307
+ def tags(self) -> Any:
1308
+ try:
1309
+ return self.kw.tags
1310
+ except (SystemExit, KeyboardInterrupt):
1311
+ raise
1312
+ except BaseException:
1313
+ return []
1314
+
1315
+ @property
1316
+ def source(self) -> Any:
1317
+ try:
1318
+ return str(self.kw.source) if self.kw.source is not None else self.source
1319
+ except (SystemExit, KeyboardInterrupt):
1320
+ raise
1321
+ except BaseException:
1322
+ return str(self.lib_source) if self.lib_source is not None else self.lib_source
1323
+
1324
+ @property
1325
+ def lineno(self) -> Any:
1326
+ try:
1327
+ return self.kw.lineno
1328
+ except (SystemExit, KeyboardInterrupt):
1329
+ raise
1330
+ except BaseException:
1331
+ return 0
1332
+
1333
+ @property
1334
+ def libname(self) -> Any:
1335
+ try:
1336
+ return self.kw.libname
1337
+ except (SystemExit, KeyboardInterrupt):
1338
+ raise
1339
+ except BaseException:
1340
+ return ""
1341
+
1342
+ @property
1343
+ def longname(self) -> Any:
1344
+ try:
1345
+ return self.kw.longname
1346
+ except (SystemExit, KeyboardInterrupt):
1347
+ raise
1348
+ except BaseException:
1349
+ return ""
1350
+
1351
+ @property
1352
+ def is_error_handler(self) -> bool:
1353
+ if get_robot_version() < (7, 0):
1354
+ from robot.running.usererrorhandler import UserErrorHandler
1355
+
1356
+ return isinstance(self.kw, UserErrorHandler)
1357
+
1358
+ from robot.running.invalidkeyword import InvalidKeyword
1359
+
1360
+ return isinstance(self.kw, InvalidKeyword)
1361
+
1362
+ @property
1363
+ def error_handler_message(self) -> Optional[str]:
1364
+ if self.is_error_handler:
1365
+ return str(self.kw.error)
1366
+
1367
+ return None
1368
+
1369
+ @property
1370
+ def error(self) -> Any:
1371
+ return self.kw.error
1372
+
1373
+ @property
1374
+ def args(self) -> Any:
1375
+ try:
1376
+ return self.kw.args
1377
+ except (SystemExit, KeyboardInterrupt):
1378
+ raise
1379
+ except BaseException:
1380
+ return None
1381
+
1382
+
1383
+ class MessageAndTraceback(NamedTuple):
1384
+ message: str
1385
+ traceback: List[SourceAndLineInfo]
1386
+
1387
+
1388
+ __RE_MESSAGE = re.compile(r"^Traceback.*$", re.MULTILINE)
1389
+ __RE_TRACEBACK = re.compile(r'^ +File +"(.*)", +line +(\d+).*$', re.MULTILINE)
1390
+
1391
+
1392
+ def get_message_and_traceback_from_exception_text(
1393
+ text: str,
1394
+ ) -> MessageAndTraceback:
1395
+ splitted = __RE_MESSAGE.split(text, 1)
1396
+
1397
+ return MessageAndTraceback(
1398
+ message=splitted[0].strip(),
1399
+ traceback=[SourceAndLineInfo(t.group(1), int(t.group(2))) for t in __RE_TRACEBACK.finditer(splitted[1])]
1400
+ if len(splitted) > 1
1401
+ else [],
1402
+ )
1403
+
1404
+
1405
+ def error_from_exception(
1406
+ ex: BaseException,
1407
+ default_source: Optional[str],
1408
+ default_line_no: Optional[int],
1409
+ ) -> Error:
1410
+ message_and_traceback = get_message_and_traceback_from_exception_text(str(ex))
1411
+ if message_and_traceback.traceback:
1412
+ tr = message_and_traceback.traceback[-1]
1413
+ return Error(
1414
+ message=str(ex),
1415
+ type_name=type(ex).__qualname__,
1416
+ source=tr.source,
1417
+ line_no=tr.line_no,
1418
+ message_traceback=message_and_traceback.traceback,
1419
+ )
1420
+
1421
+ exc_type, exc_value, tb_type = sys.exc_info()
1422
+ txt = "".join(traceback.format_exception(exc_type, exc_value, tb_type))
1423
+ message_and_traceback = get_message_and_traceback_from_exception_text(txt)
1424
+
1425
+ return Error(
1426
+ message=str(ex),
1427
+ type_name=type(ex).__qualname__,
1428
+ source=default_source,
1429
+ line_no=default_line_no,
1430
+ traceback=message_and_traceback.traceback if message_and_traceback else None,
1431
+ )
1432
+
1433
+
1434
+ @dataclass
1435
+ class _Variable:
1436
+ name: str
1437
+ value: Iterable[str]
1438
+ source: Optional[str] = None
1439
+ lineno: Optional[int] = None
1440
+ error: Optional[str] = None
1441
+ separator: Optional[str] = None
1442
+
1443
+ def report_invalid_syntax(self, message: str, level: str = "ERROR") -> None:
1444
+ pass
1445
+
1446
+
1447
+ __default_variables: Any = None
1448
+
1449
+
1450
+ def _get_default_variables() -> Any:
1451
+ from robot.variables import Variables
1452
+
1453
+ global __default_variables
1454
+ if __default_variables is None:
1455
+ __default_variables = Variables()
1456
+ for k, v in {
1457
+ "${TEMPDIR}": str(Path(tempfile.gettempdir()).resolve()),
1458
+ "${/}": os.sep,
1459
+ "${:}": os.pathsep,
1460
+ "${\\n}": os.linesep,
1461
+ "${SPACE}": " ",
1462
+ "${True}": True,
1463
+ "${False}": False,
1464
+ "${None}": None,
1465
+ "${null}": None,
1466
+ "${TEST NAME}": "",
1467
+ "@{TEST TAGS}": [],
1468
+ "${TEST DOCUMENTATION}": "",
1469
+ "${TEST STATUS}": "",
1470
+ "${TEST MESSAGE}": "",
1471
+ "${PREV TEST NAME}": "",
1472
+ "${PREV TEST STATUS}": "",
1473
+ "${PREV TEST MESSAGE}": "",
1474
+ "${SUITE NAME}": "",
1475
+ "${SUITE SOURCE}": "",
1476
+ "${SUITE DOCUMENTATION}": "",
1477
+ "&{SUITE METADATA}": {},
1478
+ "${SUITE STATUS}": "",
1479
+ "${SUITE MESSAGE}": "",
1480
+ "${KEYWORD STATUS}": "",
1481
+ "${KEYWORD MESSAGE}": "",
1482
+ "${LOG LEVEL}": "",
1483
+ "${OUTPUT FILE}": "",
1484
+ "${LOG FILE}": "",
1485
+ "${REPORT FILE}": "",
1486
+ "${DEBUG FILE}": "",
1487
+ "${OUTPUT DIR}": "",
1488
+ "${OPTIONS}": "",
1489
+ }.items():
1490
+ __default_variables[k] = v
1491
+
1492
+ return __default_variables
1493
+
1494
+
1495
+ def resolve_robot_variables(
1496
+ working_dir: str = ".",
1497
+ base_dir: str = ".",
1498
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
1499
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1500
+ ) -> Any:
1501
+ from robot.variables import Variables
1502
+
1503
+ result: Variables = _get_default_variables().copy()
1504
+
1505
+ for k, v in {
1506
+ "${CURDIR}": str(Path(base_dir).absolute()),
1507
+ "${EXECDIR}": str(Path(working_dir).absolute()),
1508
+ }.items():
1509
+ result[k] = v
1510
+
1511
+ if command_line_variables:
1512
+ for k1, v1 in command_line_variables.items():
1513
+ result[k1] = v1
1514
+
1515
+ if variables is not None:
1516
+ vars = [_Variable(k, v) for k, v in variables.items() if v is not None and not isinstance(v, NativeValue)]
1517
+ if get_robot_version() < (7, 0):
1518
+ result.set_from_variable_table(vars)
1519
+ else:
1520
+ result.set_from_variable_section(vars)
1521
+
1522
+ for k2, v2 in variables.items():
1523
+ if isinstance(v2, NativeValue):
1524
+ result[k2] = v2.value
1525
+
1526
+ result.resolve_delayed()
1527
+
1528
+ return result
1529
+
1530
+
1531
+ def resolve_variable(
1532
+ name: str,
1533
+ working_dir: str = ".",
1534
+ base_dir: str = ".",
1535
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
1536
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1537
+ ) -> Any:
1538
+ from robot.variables.finders import VariableFinder
1539
+
1540
+ _update_env(working_dir)
1541
+
1542
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
1543
+ if get_robot_version() >= (6, 1):
1544
+ return VariableFinder(robot_variables).find(name.replace("\\", "\\\\"))
1545
+
1546
+ return VariableFinder(robot_variables.store).find(name.replace("\\", "\\\\"))
1547
+
1548
+
1549
+ @contextmanager
1550
+ def _std_capture() -> Iterator[io.StringIO]:
1551
+ old__stdout__ = sys.__stdout__
1552
+ old__stderr__ = sys.__stderr__
1553
+ old_stdout = sys.stdout
1554
+ old_stderr = sys.stderr
1555
+
1556
+ capturer = sys.stdout = sys.__stdout__ = sys.stderr = sys.__stderr__ = io.StringIO() # type: ignore
1557
+
1558
+ try:
1559
+ yield capturer
1560
+ finally:
1561
+ sys.stderr = old_stderr
1562
+ sys.stdout = old_stdout
1563
+ sys.__stderr__ = old__stderr__ # type: ignore
1564
+ sys.__stdout__ = old__stdout__ # type: ignore
1565
+
1566
+
1567
+ class IgnoreEasterEggLibraryWarning(Exception): # noqa: N818
1568
+ pass
1569
+
1570
+
1571
+ def _find_library_internal(
1572
+ name: str,
1573
+ working_dir: str = ".",
1574
+ base_dir: str = ".",
1575
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
1576
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1577
+ ) -> Tuple[str, Any]:
1578
+ from robot.errors import DataError
1579
+ from robot.libraries import STDLIBS
1580
+ from robot.utils.robotpath import find_file as robot_find_file
1581
+
1582
+ _update_env(working_dir)
1583
+
1584
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
1585
+
1586
+ try:
1587
+ name = robot_variables.replace_string(name, ignore_errors=False)
1588
+ except DataError as error:
1589
+ raise DataError(f"Replacing variables from setting 'Library' failed: {error}")
1590
+
1591
+ if name in STDLIBS:
1592
+ result = ROBOT_LIBRARY_PACKAGE + "." + name
1593
+ else:
1594
+ result = name
1595
+
1596
+ if is_library_by_path(result):
1597
+ result = robot_find_file(result, base_dir or ".", "Library")
1598
+
1599
+ return (result, robot_variables)
1600
+
1601
+
1602
+ def find_library(
1603
+ name: str,
1604
+ working_dir: str = ".",
1605
+ base_dir: str = ".",
1606
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
1607
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1608
+ ) -> str:
1609
+ return _find_library_internal(name, working_dir, base_dir, command_line_variables, variables)[0]
1610
+
1611
+
1612
+ def get_robot_library_html_doc_str(
1613
+ name: str,
1614
+ args: Optional[str],
1615
+ working_dir: str = ".",
1616
+ base_dir: str = ".",
1617
+ theme: Optional[str] = None,
1618
+ ) -> str:
1619
+ from robot.libdocpkg import LibraryDocumentation
1620
+ from robot.libdocpkg.htmlwriter import LibdocHtmlWriter
1621
+
1622
+ _update_env(working_dir)
1623
+
1624
+ if Path(name).suffix.lower() in ALLOWED_RESOURCE_FILE_EXTENSIONS:
1625
+ name = find_file(name, working_dir, base_dir)
1626
+ elif Path(name).suffix.lower() in ALLOWED_LIBRARY_FILE_EXTENSIONS:
1627
+ name = find_file(name, working_dir, base_dir, file_type="Library")
1628
+
1629
+ robot_libdoc = LibraryDocumentation(name + ("::" + args if args else ""))
1630
+ robot_libdoc.convert_docs_to_html()
1631
+ with io.StringIO() as output:
1632
+ if get_robot_version() > (6, 0):
1633
+ writer = LibdocHtmlWriter(theme=theme)
1634
+ else:
1635
+ writer = LibdocHtmlWriter()
1636
+ writer.write(robot_libdoc, output)
1637
+
1638
+ return output.getvalue()
1639
+
1640
+
1641
+ def get_library_doc(
1642
+ name: str,
1643
+ args: Optional[Tuple[Any, ...]] = None,
1644
+ working_dir: str = ".",
1645
+ base_dir: str = ".",
1646
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
1647
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1648
+ ) -> LibraryDoc:
1649
+ import robot.running.testlibraries
1650
+ from robot.libdocpkg.robotbuilder import KeywordDocBuilder
1651
+ from robot.libraries import STDLIBS
1652
+ from robot.output import LOGGER
1653
+ from robot.output.loggerhelper import AbstractLogger
1654
+ from robot.running.outputcapture import OutputCapturer
1655
+ from robot.running.runkwregister import RUN_KW_REGISTER
1656
+ from robot.utils import Importer
1657
+
1658
+ class Logger(AbstractLogger):
1659
+ def __init__(self) -> None:
1660
+ super().__init__()
1661
+ self.messages: List[Tuple[str, str, bool]] = []
1662
+
1663
+ def write(self, message: str, level: str, html: bool = False) -> None:
1664
+ self.messages.append((message, level, html))
1665
+
1666
+ def import_test_library(name: str) -> Union[Any, Tuple[Any, str]]:
1667
+ with OutputCapturer(library_import=True):
1668
+ importer = Importer("test library", LOGGER)
1669
+ return importer.import_class_or_module(name, return_source=True)
1670
+
1671
+ def get_test_library(
1672
+ libcode: Any,
1673
+ source: str,
1674
+ name: str,
1675
+ args: Optional[Tuple[Any, ...]] = None,
1676
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1677
+ create_handlers: bool = True,
1678
+ logger: Any = LOGGER,
1679
+ ) -> Any:
1680
+ if get_robot_version() < (7, 0):
1681
+ libclass = robot.running.testlibraries._get_lib_class(libcode)
1682
+ lib = libclass(libcode, name, args or [], source, logger, variables)
1683
+ if create_handlers:
1684
+ lib.create_handlers()
1685
+ else:
1686
+ lib = robot.running.testlibraries.TestLibrary.from_code(
1687
+ libcode,
1688
+ name,
1689
+ source=Path(source),
1690
+ args=args or [],
1691
+ variables=variables,
1692
+ create_keywords=create_handlers,
1693
+ logger=logger,
1694
+ )
1695
+
1696
+ return lib
1697
+
1698
+ with _std_capture() as std_capturer:
1699
+ import_name, robot_variables = _find_library_internal(
1700
+ name,
1701
+ working_dir=working_dir,
1702
+ base_dir=base_dir,
1703
+ command_line_variables=command_line_variables,
1704
+ variables=variables,
1705
+ )
1706
+
1707
+ module_spec: Optional[ModuleSpec] = None
1708
+ if not is_library_by_path(import_name):
1709
+ module_spec = get_module_spec(import_name)
1710
+
1711
+ # skip antigravity easter egg
1712
+ # see https://python-history.blogspot.com/2010/06/import-antigravity.html
1713
+ if import_name.lower() == "antigravity" or import_name.lower().endswith("antigravity.py"):
1714
+ raise IgnoreEasterEggLibraryWarning(f"Ignoring import for python easter egg '{import_name}'.")
1715
+
1716
+ errors: List[Error] = []
1717
+
1718
+ source = None
1719
+ try:
1720
+ libcode, source = import_test_library(import_name)
1721
+ except (SystemExit, KeyboardInterrupt):
1722
+ raise
1723
+ except BaseException as e:
1724
+ return LibraryDoc(
1725
+ name=name,
1726
+ source=source or module_spec.origin
1727
+ if module_spec is not None and module_spec.origin
1728
+ else import_name
1729
+ if is_library_by_path(import_name)
1730
+ else None,
1731
+ module_spec=module_spec,
1732
+ errors=[
1733
+ error_from_exception(
1734
+ e,
1735
+ source or module_spec.origin
1736
+ if module_spec is not None and module_spec.origin
1737
+ else import_name
1738
+ if is_library_by_path(import_name)
1739
+ else None,
1740
+ 1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
1741
+ )
1742
+ ],
1743
+ python_path=sys.path,
1744
+ )
1745
+
1746
+ if name in STDLIBS and import_name.startswith(ROBOT_LIBRARY_PACKAGE + "."):
1747
+ library_name = name
1748
+ else:
1749
+ if import_name.startswith(ROBOT_LIBRARY_PACKAGE + "."):
1750
+ library_name = import_name[len(ROBOT_LIBRARY_PACKAGE + ".") :]
1751
+ else:
1752
+ library_name = import_name
1753
+
1754
+ library_name_path = Path(import_name)
1755
+ if library_name_path.exists():
1756
+ library_name = library_name_path.stem
1757
+
1758
+ lib = None
1759
+ try:
1760
+ lib = get_test_library(
1761
+ libcode,
1762
+ source,
1763
+ library_name,
1764
+ args,
1765
+ create_handlers=False,
1766
+ variables=robot_variables,
1767
+ )
1768
+ if get_robot_version() < (7, 0):
1769
+ _ = lib.get_instance()
1770
+ else:
1771
+ _ = lib.instance
1772
+ except (SystemExit, KeyboardInterrupt):
1773
+ raise
1774
+ except BaseException as e:
1775
+ lib = None
1776
+ errors.append(
1777
+ error_from_exception(
1778
+ e,
1779
+ source or module_spec.origin if module_spec is not None else None,
1780
+ 1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
1781
+ )
1782
+ )
1783
+
1784
+ if args:
1785
+ try:
1786
+ lib = get_test_library(libcode, source, library_name, (), create_handlers=False)
1787
+ if get_robot_version() < (7, 0):
1788
+ _ = lib.get_instance()
1789
+ else:
1790
+ _ = lib.instance
1791
+ except (SystemExit, KeyboardInterrupt):
1792
+ raise
1793
+ except BaseException:
1794
+ lib = None
1795
+
1796
+ real_source = str(lib.source) if lib is not None else source
1797
+
1798
+ libdoc = LibraryDoc(
1799
+ name=library_name,
1800
+ source=real_source,
1801
+ module_spec=module_spec
1802
+ if module_spec is not None
1803
+ and module_spec.origin != real_source
1804
+ and module_spec.submodule_search_locations is None
1805
+ else None,
1806
+ python_path=sys.path,
1807
+ line_no=lib.lineno if lib is not None else -1,
1808
+ doc=str(lib.doc) if lib is not None else "",
1809
+ version=str(lib.version) if lib is not None else "",
1810
+ scope=str(lib.scope) if lib is not None else ROBOT_DEFAULT_SCOPE,
1811
+ doc_format=(str(lib.doc_format) or ROBOT_DOC_FORMAT) if lib is not None else ROBOT_DOC_FORMAT,
1812
+ member_name=module_spec.member_name if module_spec is not None else None,
1813
+ )
1814
+
1815
+ if lib is not None:
1816
+ try:
1817
+ if get_robot_version() < (7, 0):
1818
+ libdoc.has_listener = lib.has_listener
1819
+
1820
+ if isinstance(lib, robot.running.testlibraries._ModuleLibrary):
1821
+ libdoc.library_type = LibraryType.MODULE
1822
+ elif isinstance(lib, robot.running.testlibraries._ClassLibrary):
1823
+ libdoc.library_type = LibraryType.CLASS
1824
+ elif isinstance(lib, robot.running.testlibraries._DynamicLibrary):
1825
+ libdoc.library_type = LibraryType.DYNAMIC
1826
+ elif isinstance(lib, robot.running.testlibraries._HybridLibrary):
1827
+ libdoc.library_type = LibraryType.HYBRID
1828
+ else:
1829
+ libdoc.has_listener = bool(lib.listeners)
1830
+
1831
+ if isinstance(lib, robot.running.testlibraries.ModuleLibrary):
1832
+ libdoc.library_type = LibraryType.MODULE
1833
+ elif isinstance(lib, robot.running.testlibraries.ClassLibrary):
1834
+ libdoc.library_type = LibraryType.CLASS
1835
+ elif isinstance(lib, robot.running.testlibraries.DynamicLibrary):
1836
+ libdoc.library_type = LibraryType.DYNAMIC
1837
+ elif isinstance(lib, robot.running.testlibraries.HybridLibrary):
1838
+ libdoc.library_type = LibraryType.HYBRID
1839
+
1840
+ init_wrappers = [KeywordWrapper(lib.init, source)]
1841
+ init_keywords = [(KeywordDocBuilder().build_keyword(k), k) for k in init_wrappers]
1842
+ libdoc.inits = KeywordStore(
1843
+ keywords=[
1844
+ KeywordDoc(
1845
+ name=libdoc.name,
1846
+ arguments=[ArgumentInfo.from_robot(a) for a in kw[0].args],
1847
+ doc=kw[0].doc,
1848
+ tags=list(kw[0].tags),
1849
+ source=kw[0].source,
1850
+ line_no=kw[0].lineno if kw[0].lineno is not None else -1,
1851
+ col_offset=-1,
1852
+ end_col_offset=-1,
1853
+ end_line_no=-1,
1854
+ type="library",
1855
+ libname=libdoc.name,
1856
+ libtype=libdoc.type,
1857
+ longname=f"{libdoc.name}.{kw[0].name}",
1858
+ doc_format=str(lib.doc_format) or ROBOT_DOC_FORMAT,
1859
+ is_initializer=True,
1860
+ arguments_spec=ArgumentSpec.from_robot_argument_spec(
1861
+ kw[1].arguments if get_robot_version() < (7, 0) else kw[1].args
1862
+ ),
1863
+ )
1864
+ for kw in init_keywords
1865
+ ]
1866
+ )
1867
+
1868
+ logger = Logger()
1869
+ lib.logger = logger
1870
+
1871
+ if get_robot_version() < (7, 0):
1872
+ lib.create_handlers()
1873
+ else:
1874
+ lib.create_keywords()
1875
+
1876
+ for m in logger.messages:
1877
+ if m[1] == "ERROR":
1878
+ errors.append(
1879
+ Error(
1880
+ message=m[0],
1881
+ type_name=m[1],
1882
+ source=libdoc.source,
1883
+ line_no=libdoc.line_no,
1884
+ )
1885
+ )
1886
+
1887
+ if get_robot_version() < (7, 0):
1888
+ keyword_wrappers = [KeywordWrapper(k, source) for k in lib.handlers]
1889
+ else:
1890
+ keyword_wrappers = [KeywordWrapper(k, source) for k in lib.keywords]
1891
+
1892
+ def get_args_to_process(libdoc_name: str, kw_name: str) -> Any:
1893
+ result = RUN_KW_REGISTER.get_args_to_process(libdoc_name, kw_name)
1894
+ if result == -1:
1895
+ return None
1896
+
1897
+ return result
1898
+
1899
+ keyword_docs = [(KeywordDocBuilder().build_keyword(k), k) for k in keyword_wrappers]
1900
+ libdoc.keywords = KeywordStore(
1901
+ source=libdoc.name,
1902
+ source_type=libdoc.type,
1903
+ keywords=[
1904
+ KeywordDoc(
1905
+ name=kw[0].name,
1906
+ arguments=[ArgumentInfo.from_robot(a) for a in kw[0].args],
1907
+ doc=kw[0].doc,
1908
+ tags=list(kw[0].tags),
1909
+ source=kw[0].source,
1910
+ line_no=kw[0].lineno if kw[0].lineno is not None else -1,
1911
+ col_offset=-1,
1912
+ end_col_offset=-1,
1913
+ end_line_no=-1,
1914
+ libname=libdoc.name,
1915
+ libtype=libdoc.type,
1916
+ longname=f"{libdoc.name}.{kw[0].name}",
1917
+ is_embedded=is_embedded_keyword(kw[0].name),
1918
+ doc_format=str(lib.doc_format) or ROBOT_DOC_FORMAT,
1919
+ is_error_handler=kw[1].is_error_handler,
1920
+ error_handler_message=kw[1].error_handler_message,
1921
+ is_registered_run_keyword=RUN_KW_REGISTER.is_run_keyword(libdoc.name, kw[0].name),
1922
+ args_to_process=get_args_to_process(libdoc.name, kw[0].name),
1923
+ deprecated=kw[0].deprecated,
1924
+ arguments_spec=ArgumentSpec.from_robot_argument_spec(
1925
+ kw[1].arguments if get_robot_version() < (7, 0) else kw[1].args
1926
+ )
1927
+ if not kw[1].is_error_handler
1928
+ else None,
1929
+ return_type=str(kw[1].args.return_type) if get_robot_version() >= (7, 0) else None,
1930
+ )
1931
+ for kw in keyword_docs
1932
+ ],
1933
+ )
1934
+
1935
+ if get_robot_version() >= (6, 1):
1936
+ from robot.libdocpkg.datatypes import (
1937
+ TypeDoc as RobotTypeDoc,
1938
+ )
1939
+ from robot.running.arguments.argumentspec import TypeInfo
1940
+
1941
+ def _yield_type_info(info: TypeInfo) -> Iterable[TypeInfo]:
1942
+ if not info.is_union:
1943
+ yield info
1944
+ for nested in info.nested:
1945
+ yield from _yield_type_info(nested)
1946
+
1947
+ def _get_type_docs(keywords: List[Any], custom_converters: List[Any]) -> Set[RobotTypeDoc]:
1948
+ type_docs: Dict[RobotTypeDoc, Set[str]] = {}
1949
+ for kw in keywords:
1950
+ for arg in kw.args:
1951
+ kw.type_docs[arg.name] = {}
1952
+ for type_info in _yield_type_info(arg.type):
1953
+ if type_info.type is not None:
1954
+ if get_robot_version() < (7, 0):
1955
+ type_doc = RobotTypeDoc.for_type(
1956
+ type_info.type,
1957
+ custom_converters,
1958
+ )
1959
+ else:
1960
+ type_doc = RobotTypeDoc.for_type(type_info, custom_converters)
1961
+ if type_doc:
1962
+ kw.type_docs[arg.name][type_info.name] = type_doc.name
1963
+ type_docs.setdefault(type_doc, set()).add(kw.name)
1964
+ for type_doc, usages in type_docs.items():
1965
+ type_doc.usages = sorted(usages, key=str.lower)
1966
+ return set(type_docs)
1967
+
1968
+ libdoc.types = [
1969
+ TypeDoc(
1970
+ td.type,
1971
+ td.name,
1972
+ td.doc,
1973
+ td.accepts,
1974
+ td.usages,
1975
+ [EnumMember(m.name, m.value) for m in td.members] if td.members else None,
1976
+ [TypedDictItem(i.key, i.type, i.required) for i in td.items] if td.items else None,
1977
+ libname=libdoc.name,
1978
+ libtype=libdoc.type,
1979
+ doc_format=libdoc.doc_format,
1980
+ )
1981
+ for td in _get_type_docs(
1982
+ [kw[0] for kw in keyword_docs + init_keywords],
1983
+ lib.converters,
1984
+ )
1985
+ ]
1986
+
1987
+ except (SystemExit, KeyboardInterrupt):
1988
+ raise
1989
+ except BaseException as e:
1990
+ errors.append(
1991
+ error_from_exception(
1992
+ e,
1993
+ source or module_spec.origin if module_spec is not None else None,
1994
+ 1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
1995
+ )
1996
+ )
1997
+
1998
+ if errors:
1999
+ libdoc.errors = errors
2000
+
2001
+ libdoc.stdout = std_capturer.getvalue()
2002
+
2003
+ return libdoc
2004
+
2005
+
2006
+ def _find_variables_internal(
2007
+ name: str,
2008
+ working_dir: str = ".",
2009
+ base_dir: str = ".",
2010
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2011
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2012
+ ) -> Tuple[str, Any]:
2013
+ from robot.errors import DataError
2014
+ from robot.utils.robotpath import find_file as robot_find_file
2015
+
2016
+ _update_env(working_dir)
2017
+
2018
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
2019
+
2020
+ try:
2021
+ name = robot_variables.replace_string(name, ignore_errors=False)
2022
+ except DataError as error:
2023
+ raise DataError(f"Replacing variables from setting 'Variables' failed: {error}")
2024
+
2025
+ result = name
2026
+
2027
+ if is_variables_by_path(result):
2028
+ result = robot_find_file(result, base_dir or ".", "Variables")
2029
+
2030
+ return (result, robot_variables)
2031
+
2032
+
2033
+ def resolve_args(
2034
+ args: Tuple[Any, ...],
2035
+ working_dir: str = ".",
2036
+ base_dir: str = ".",
2037
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2038
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2039
+ ) -> Tuple[Any, ...]:
2040
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
2041
+
2042
+ result = []
2043
+ for arg in args:
2044
+ if isinstance(arg, str):
2045
+ result.append(robot_variables.replace_string(arg, ignore_errors=True))
2046
+ else:
2047
+ result.append(arg)
2048
+
2049
+ return tuple(result)
2050
+
2051
+
2052
+ def find_variables(
2053
+ name: str,
2054
+ working_dir: str = ".",
2055
+ base_dir: str = ".",
2056
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2057
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2058
+ ) -> str:
2059
+ if get_robot_version() >= (5, 0):
2060
+ return _find_variables_internal(name, working_dir, base_dir, command_line_variables, variables)[0]
2061
+
2062
+ return find_file(
2063
+ name,
2064
+ working_dir,
2065
+ base_dir,
2066
+ command_line_variables,
2067
+ variables,
2068
+ file_type="Variables",
2069
+ )
2070
+
2071
+
2072
+ def get_variables_doc(
2073
+ name: str,
2074
+ args: Optional[Tuple[Any, ...]] = None,
2075
+ working_dir: str = ".",
2076
+ base_dir: str = ".",
2077
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2078
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2079
+ ) -> VariablesDoc:
2080
+ from robot.libdocpkg.robotbuilder import KeywordDocBuilder
2081
+ from robot.output import LOGGER
2082
+ from robot.utils.importer import Importer
2083
+ from robot.variables.filesetter import PythonImporter, YamlImporter
2084
+
2085
+ if get_robot_version() >= (6, 1):
2086
+ from robot.variables.filesetter import JsonImporter
2087
+
2088
+ import_name: str = name
2089
+ stem = Path(name).stem
2090
+ module_spec: Optional[ModuleSpec] = None
2091
+ source: Optional[str] = None
2092
+ try:
2093
+ python_import = False
2094
+
2095
+ with _std_capture() as std_capturer:
2096
+ import_name = find_variables(name, working_dir, base_dir, command_line_variables, variables)
2097
+ get_variables = None
2098
+
2099
+ if import_name.lower().endswith((".yaml", ".yml")):
2100
+ source = import_name
2101
+ importer = YamlImporter()
2102
+ elif get_robot_version() >= (6, 1) and import_name.lower().endswith(".json"):
2103
+ source = import_name
2104
+ importer = JsonImporter()
2105
+ else:
2106
+ python_import = True
2107
+
2108
+ if not is_variables_by_path(import_name):
2109
+ module_spec = get_module_spec(import_name)
2110
+
2111
+ # skip antigravity easter egg
2112
+ # see https://python-history.blogspot.com/2010/06/import-antigravity.html
2113
+ if import_name.lower() == "antigravity" or import_name.lower().endswith("antigravity.py"):
2114
+ raise IgnoreEasterEggLibraryWarning(f"Ignoring import for python easter egg '{import_name}'.")
2115
+
2116
+ class MyPythonImporter(PythonImporter):
2117
+ def __init__(self, var_file: Any) -> None:
2118
+ self.var_file = var_file
2119
+
2120
+ def import_variables(self, path: str, args: Optional[Tuple[Any, ...]] = None) -> Any:
2121
+ return self._get_variables(self.var_file, args)
2122
+
2123
+ def is_dynamic(self) -> bool:
2124
+ return hasattr(self.var_file, "get_variables") or hasattr(self.var_file, "getVariables")
2125
+
2126
+ module_importer = Importer("variable file", LOGGER)
2127
+
2128
+ if get_robot_version() >= (5, 0):
2129
+ libcode, source = module_importer.import_class_or_module(
2130
+ import_name,
2131
+ instantiate_with_args=(),
2132
+ return_source=True,
2133
+ )
2134
+ else:
2135
+ source = import_name
2136
+ libcode = module_importer.import_class_or_module_by_path(import_name, instantiate_with_args=())
2137
+
2138
+ importer = MyPythonImporter(libcode)
2139
+
2140
+ if importer.is_dynamic():
2141
+ get_variables = getattr(libcode, "get_variables", None) or getattr(libcode, "getVariables", None)
2142
+
2143
+ libdoc = VariablesDoc(
2144
+ name=stem,
2145
+ source=source or module_spec.origin if module_spec is not None else import_name,
2146
+ module_spec=module_spec,
2147
+ stdout=std_capturer.getvalue(),
2148
+ python_path=sys.path,
2149
+ )
2150
+
2151
+ if python_import:
2152
+ if get_variables is not None:
2153
+ if get_robot_version() >= (7, 0):
2154
+ # TODO: variables initializer for RF7
2155
+ # from robot.running.librarykeyword import StaticKeywordCreator, LibraryInitCreator
2156
+
2157
+ # class MyStaticKeywordCreator(StaticKeywordCreator):
2158
+ # def __init__(self, name: str, library: TestLibrary):
2159
+ # super().__init__(name, library)
2160
+ pass
2161
+ else:
2162
+ from robot.running.handlers import _PythonHandler
2163
+
2164
+ class VarHandler(_PythonHandler):
2165
+ def _get_name(self, handler_name: Any, handler_method: Any) -> Any:
2166
+ return get_variables.__name__ if get_variables is not None else ""
2167
+
2168
+ def _get_initial_handler(self, library: Any, name: Any, method: Any) -> Any:
2169
+ return None
2170
+
2171
+ vars_initializer = VarHandler(libdoc, get_variables.__name__, get_variables)
2172
+
2173
+ libdoc.inits = KeywordStore(
2174
+ keywords=[
2175
+ KeywordDoc(
2176
+ name=libdoc.name,
2177
+ arguments=[ArgumentInfo.from_robot(a) for a in kw[0].args],
2178
+ doc=kw[0].doc,
2179
+ source=kw[0].source,
2180
+ line_no=kw[0].lineno if kw[0].lineno is not None else -1,
2181
+ col_offset=-1,
2182
+ end_col_offset=-1,
2183
+ end_line_no=-1,
2184
+ type="variables",
2185
+ libname=libdoc.name,
2186
+ libtype=libdoc.type,
2187
+ longname=f"{libdoc.name}.{kw[0].name}",
2188
+ is_initializer=True,
2189
+ arguments_spec=ArgumentSpec.from_robot_argument_spec(kw[1].arguments),
2190
+ )
2191
+ for kw in [
2192
+ (KeywordDocBuilder().build_keyword(k), k)
2193
+ for k in [
2194
+ KeywordWrapper(
2195
+ vars_initializer,
2196
+ libdoc.source or "",
2197
+ )
2198
+ ]
2199
+ ]
2200
+ ]
2201
+ )
2202
+ else:
2203
+ if get_robot_version() >= (7, 0):
2204
+ # TODO: variables initializer for RF7
2205
+
2206
+ pass
2207
+ else:
2208
+ from robot.running.handlers import _PythonInitHandler
2209
+
2210
+ get_variables = getattr(libcode, "__init__", None) or getattr(libcode, "__init__", None)
2211
+
2212
+ class InitVarHandler(_PythonInitHandler):
2213
+ def _get_name(self, handler_name: Any, handler_method: Any) -> Any:
2214
+ return get_variables.__name__ if get_variables is not None else ""
2215
+
2216
+ def _get_initial_handler(self, library: Any, name: Any, method: Any) -> Any:
2217
+ return None
2218
+
2219
+ if get_variables is not None:
2220
+ vars_initializer = InitVarHandler(
2221
+ libdoc,
2222
+ get_variables.__name__,
2223
+ get_variables,
2224
+ None,
2225
+ )
2226
+
2227
+ libdoc.inits = KeywordStore(
2228
+ keywords=[
2229
+ KeywordDoc(
2230
+ name=libdoc.name,
2231
+ arguments=[],
2232
+ doc=kw[0].doc,
2233
+ source=kw[0].source,
2234
+ line_no=kw[0].lineno if kw[0].lineno is not None else -1,
2235
+ col_offset=-1,
2236
+ end_col_offset=-1,
2237
+ end_line_no=-1,
2238
+ type="variables",
2239
+ libname=libdoc.name,
2240
+ libtype=libdoc.type,
2241
+ longname=f"{libdoc.name}.{kw[0].name}",
2242
+ is_initializer=True,
2243
+ )
2244
+ for kw in [
2245
+ (
2246
+ KeywordDocBuilder().build_keyword(k),
2247
+ k,
2248
+ )
2249
+ for k in [
2250
+ KeywordWrapper(
2251
+ vars_initializer,
2252
+ libdoc.source or "",
2253
+ )
2254
+ ]
2255
+ ]
2256
+ ]
2257
+ )
2258
+ try:
2259
+ # TODO: add type information of the value including dict key names and member names
2260
+ libdoc.variables = [
2261
+ ImportedVariableDefinition(
2262
+ line_no=1,
2263
+ col_offset=0,
2264
+ end_line_no=1,
2265
+ end_col_offset=0,
2266
+ source=source or (module_spec.origin if module_spec is not None else None) or "",
2267
+ name=name if get_robot_version() < (7, 0) else f"${{{name}}}",
2268
+ name_token=None,
2269
+ value=NativeValue(value)
2270
+ if value is None or isinstance(value, (int, float, bool, str))
2271
+ else None,
2272
+ has_value=value is None or isinstance(value, (int, float, bool, str)),
2273
+ value_is_native=value is None or isinstance(value, (int, float, bool, str)),
2274
+ )
2275
+ for name, value in importer.import_variables(import_name, args)
2276
+ ]
2277
+ except (SystemExit, KeyboardInterrupt):
2278
+ raise
2279
+ except BaseException as e:
2280
+ libdoc.errors = [
2281
+ error_from_exception(
2282
+ e,
2283
+ source or module_spec.origin
2284
+ if module_spec is not None and module_spec.origin
2285
+ else import_name
2286
+ if is_variables_by_path(import_name)
2287
+ else None,
2288
+ 1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
2289
+ )
2290
+ ]
2291
+
2292
+ return libdoc
2293
+ except (SystemExit, KeyboardInterrupt, IgnoreEasterEggLibraryWarning):
2294
+ raise
2295
+ except BaseException as e:
2296
+ return VariablesDoc(
2297
+ name=stem,
2298
+ source=source or module_spec.origin if module_spec is not None else import_name,
2299
+ module_spec=module_spec,
2300
+ errors=[
2301
+ error_from_exception(
2302
+ e,
2303
+ source or module_spec.origin
2304
+ if module_spec is not None and module_spec.origin
2305
+ else import_name
2306
+ if is_variables_by_path(import_name)
2307
+ else None,
2308
+ 1 if source is not None or module_spec is not None and module_spec.origin is not None else None,
2309
+ )
2310
+ ],
2311
+ python_path=sys.path,
2312
+ )
2313
+
2314
+
2315
+ def find_file(
2316
+ name: str,
2317
+ working_dir: str = ".",
2318
+ base_dir: str = ".",
2319
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2320
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2321
+ file_type: str = "Resource",
2322
+ ) -> str:
2323
+ from robot.errors import DataError
2324
+ from robot.utils.robotpath import find_file as robot_find_file
2325
+
2326
+ _update_env(working_dir)
2327
+
2328
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
2329
+ try:
2330
+ name = robot_variables.replace_string(name, ignore_errors=False)
2331
+ except DataError as error:
2332
+ raise DataError(f"Replacing variables from setting '{file_type}' failed: {error}")
2333
+
2334
+ return cast(str, robot_find_file(name, base_dir or ".", file_type))
2335
+
2336
+
2337
+ class CompleteResultKind(Enum):
2338
+ MODULE_INTERNAL = "Module (Internal)"
2339
+ MODULE = "Module"
2340
+ FILE = "File"
2341
+ RESOURCE = "Resource"
2342
+ VARIABLES = "Variables"
2343
+ VARIABLES_MODULE = "Variables Module"
2344
+ FOLDER = "Directory"
2345
+ KEYWORD = "Keyword"
2346
+
2347
+
2348
+ class CompleteResult(NamedTuple):
2349
+ label: str
2350
+ kind: CompleteResultKind
2351
+
2352
+
2353
+ def is_file_like(name: Optional[str]) -> bool:
2354
+ if name is None:
2355
+ return False
2356
+
2357
+ base, filename = os.path.split(name)
2358
+ return name.startswith(".") or bool(base) and filename != name
2359
+
2360
+
2361
+ def iter_module_names(name: Optional[str] = None) -> Iterator[str]:
2362
+ if name is not None:
2363
+ spec = importlib.util.find_spec(name)
2364
+ if spec is None:
2365
+ return
2366
+ else:
2367
+ spec = None
2368
+
2369
+ if spec is None:
2370
+ for e in pkgutil.iter_modules():
2371
+ if not e.name.startswith(("_", ".")):
2372
+ yield e.name
2373
+ return
2374
+
2375
+ if spec.submodule_search_locations is None:
2376
+ return
2377
+
2378
+ for e in pkgutil.iter_modules(spec.submodule_search_locations):
2379
+ if not e.name.startswith(("_", ".")):
2380
+ yield e.name
2381
+
2382
+
2383
+ NOT_WANTED_DIR_EXTENSIONS = [".dist-info"]
2384
+
2385
+
2386
+ def iter_modules_from_python_path(
2387
+ path: Optional[str] = None,
2388
+ ) -> Iterator[CompleteResult]:
2389
+ path = path.replace(".", os.sep) if path is not None and not path.startswith((".", "/", os.sep)) else path
2390
+
2391
+ if path is None:
2392
+ paths = sys.path
2393
+ else:
2394
+ paths = [str(Path(s, path)) for s in sys.path]
2395
+
2396
+ for e in [Path(p) for p in set(paths)]:
2397
+ if e.is_dir():
2398
+ for f in e.iterdir():
2399
+ if not f.name.startswith(("_", ".")) and (
2400
+ f.is_file()
2401
+ and f.suffix in ALLOWED_LIBRARY_FILE_EXTENSIONS
2402
+ or f.is_dir()
2403
+ and f.suffix not in NOT_WANTED_DIR_EXTENSIONS
2404
+ ):
2405
+ if f.is_dir():
2406
+ yield CompleteResult(f.name, CompleteResultKind.MODULE)
2407
+
2408
+ if f.is_file():
2409
+ yield CompleteResult(f.stem, CompleteResultKind.MODULE)
2410
+
2411
+
2412
+ def complete_library_import(
2413
+ name: Optional[str],
2414
+ working_dir: str = ".",
2415
+ base_dir: str = ".",
2416
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2417
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2418
+ ) -> List[CompleteResult]:
2419
+ _update_env(working_dir)
2420
+
2421
+ result: List[CompleteResult] = []
2422
+
2423
+ if name is None:
2424
+ result += [
2425
+ CompleteResult(e, CompleteResultKind.MODULE_INTERNAL)
2426
+ for e in iter_module_names(ROBOT_LIBRARY_PACKAGE)
2427
+ if e not in DEFAULT_LIBRARIES
2428
+ ]
2429
+
2430
+ if name is not None:
2431
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
2432
+
2433
+ name = robot_variables.replace_string(name, ignore_errors=True)
2434
+
2435
+ file_like = is_file_like(name)
2436
+
2437
+ if name is None or not file_like:
2438
+ result += list(iter_modules_from_python_path(name))
2439
+
2440
+ if name is None or file_like:
2441
+ name_path = Path(name if name else base_dir)
2442
+ if name and name_path.is_absolute():
2443
+ paths = [name_path]
2444
+ else:
2445
+ paths = [
2446
+ Path(base_dir, name) if name else Path(base_dir),
2447
+ *((Path(s) for s in sys.path) if not name else []),
2448
+ *((Path(s, name) for s in sys.path) if name and not name.startswith(".") else []),
2449
+ ]
2450
+
2451
+ for p in paths:
2452
+ path = p.resolve()
2453
+
2454
+ if path.exists() and path.is_dir():
2455
+ result += [
2456
+ CompleteResult(
2457
+ str(f.name),
2458
+ CompleteResultKind.FILE if f.is_file() else CompleteResultKind.FOLDER,
2459
+ )
2460
+ for f in path.iterdir()
2461
+ if not f.name.startswith(("_", "."))
2462
+ and (
2463
+ (f.is_file() and f.suffix in ALLOWED_LIBRARY_FILE_EXTENSIONS)
2464
+ or f.is_dir()
2465
+ and f.suffix not in NOT_WANTED_DIR_EXTENSIONS
2466
+ )
2467
+ ]
2468
+
2469
+ return list(set(result))
2470
+
2471
+
2472
+ def iter_resources_from_python_path(
2473
+ path: Optional[str] = None,
2474
+ ) -> Iterator[CompleteResult]:
2475
+ if path is None:
2476
+ paths = sys.path
2477
+ else:
2478
+ paths = [str(Path(s, path)) for s in sys.path]
2479
+
2480
+ for e in [Path(p) for p in set(paths)]:
2481
+ if e.is_dir():
2482
+ for f in e.iterdir():
2483
+ if not f.name.startswith(("_", ".")) and (
2484
+ f.is_file()
2485
+ and f.suffix in ALLOWED_RESOURCE_FILE_EXTENSIONS
2486
+ or f.is_dir()
2487
+ and f.suffix not in NOT_WANTED_DIR_EXTENSIONS
2488
+ ):
2489
+ yield CompleteResult(
2490
+ f.name,
2491
+ CompleteResultKind.RESOURCE if f.is_file() else CompleteResultKind.FOLDER,
2492
+ )
2493
+
2494
+
2495
+ def complete_resource_import(
2496
+ name: Optional[str],
2497
+ working_dir: str = ".",
2498
+ base_dir: str = ".",
2499
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2500
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2501
+ ) -> Optional[List[CompleteResult]]:
2502
+ _update_env(working_dir)
2503
+
2504
+ result: List[CompleteResult] = []
2505
+
2506
+ if name is not None:
2507
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
2508
+
2509
+ name = robot_variables.replace_string(name, ignore_errors=True)
2510
+
2511
+ if name is None or not name.startswith(".") and not name.startswith("/") and not name.startswith(os.sep):
2512
+ result += list(iter_resources_from_python_path(name))
2513
+
2514
+ if name is None or name.startswith((".", "/", os.sep)):
2515
+ name_path = Path(name if name else base_dir)
2516
+ if name_path.is_absolute():
2517
+ path = name_path.resolve()
2518
+ else:
2519
+ path = Path(base_dir, name if name else base_dir).resolve()
2520
+
2521
+ if path.exists() and (path.is_dir()):
2522
+ result += [
2523
+ CompleteResult(
2524
+ str(f.name),
2525
+ CompleteResultKind.RESOURCE if f.is_file() else CompleteResultKind.FOLDER,
2526
+ )
2527
+ for f in path.iterdir()
2528
+ if not f.name.startswith(("_", "."))
2529
+ and (f.is_dir() or (f.is_file() and f.suffix in ALLOWED_RESOURCE_FILE_EXTENSIONS))
2530
+ ]
2531
+
2532
+ return list(set(result))
2533
+
2534
+
2535
+ def complete_variables_import(
2536
+ name: Optional[str],
2537
+ working_dir: str = ".",
2538
+ base_dir: str = ".",
2539
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
2540
+ variables: Optional[Dict[str, Optional[Any]]] = None,
2541
+ ) -> Optional[List[CompleteResult]]:
2542
+ _update_env(working_dir)
2543
+
2544
+ result: List[CompleteResult] = []
2545
+
2546
+ if name is not None:
2547
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
2548
+
2549
+ name = robot_variables.replace_string(name, ignore_errors=True)
2550
+
2551
+ file_like = get_robot_version() < (5, 0) or is_file_like(name)
2552
+
2553
+ if get_robot_version() >= (5, 0) and (name is None or not file_like):
2554
+ result += list(iter_modules_from_python_path(name))
2555
+
2556
+ if name is None or file_like:
2557
+ name_path = Path(name if name else base_dir)
2558
+ if name and name_path.is_absolute():
2559
+ paths = [name_path]
2560
+ else:
2561
+ paths = [
2562
+ Path(base_dir, name) if name else Path(base_dir),
2563
+ *((Path(s) for s in sys.path) if not name else []),
2564
+ *((Path(s, name) for s in sys.path) if name and not name.startswith(".") else []),
2565
+ ]
2566
+
2567
+ for p in paths:
2568
+ path = p.resolve()
2569
+
2570
+ if path.exists() and path.is_dir():
2571
+ result += [
2572
+ CompleteResult(
2573
+ str(f.name),
2574
+ CompleteResultKind.FILE if f.is_file() else CompleteResultKind.FOLDER,
2575
+ )
2576
+ for f in path.iterdir()
2577
+ if not f.name.startswith(("_", "."))
2578
+ and (
2579
+ (f.is_file() and f.suffix in ALLOWED_VARIABLES_FILE_EXTENSIONS)
2580
+ or f.is_dir()
2581
+ and f.suffix not in NOT_WANTED_DIR_EXTENSIONS
2582
+ )
2583
+ ]
2584
+
2585
+ return list(set(result))
2586
+
2587
+
2588
+ def get_model_doc(
2589
+ model: ast.AST,
2590
+ source: str,
2591
+ model_type: str = "RESOURCE",
2592
+ scope: str = "GLOBAL",
2593
+ append_model_errors: bool = True,
2594
+ ) -> LibraryDoc:
2595
+ from robot.errors import DataError, VariableError
2596
+ from robot.libdocpkg.robotbuilder import KeywordDocBuilder
2597
+ from robot.output.logger import LOGGER
2598
+ from robot.parsing.lexer.tokens import Token as RobotToken
2599
+ from robot.parsing.model.blocks import Keyword
2600
+ from robot.parsing.model.statements import Arguments, KeywordName
2601
+ from robot.running.builder.transformers import ResourceBuilder
2602
+
2603
+ if get_robot_version() < (7, 0):
2604
+ from robot.running.model import ResourceFile
2605
+ from robot.running.usererrorhandler import UserErrorHandler
2606
+ from robot.running.userkeyword import UserLibrary
2607
+ else:
2608
+ from robot.running.invalidkeyword import (
2609
+ InvalidKeyword as UserErrorHandler,
2610
+ )
2611
+ from robot.running.resourcemodel import ResourceFile
2612
+
2613
+ errors: List[Error] = []
2614
+ keyword_name_nodes: List[KeywordName] = []
2615
+ keywords_nodes: List[Keyword] = []
2616
+ for node in ast.walk(model):
2617
+ if isinstance(node, Keyword):
2618
+ keywords_nodes.append(node)
2619
+ if isinstance(node, KeywordName):
2620
+ keyword_name_nodes.append(node)
2621
+
2622
+ error = node.error if isinstance(node, HasError) else None
2623
+ if error is not None:
2624
+ errors.append(
2625
+ Error(
2626
+ message=error,
2627
+ type_name="ModelError",
2628
+ source=source,
2629
+ line_no=node.lineno,
2630
+ )
2631
+ )
2632
+ if append_model_errors:
2633
+ node_errors = node.errors if isinstance(node, HasErrors) else None
2634
+ if node_errors is not None:
2635
+ for e in node_errors:
2636
+ errors.append(
2637
+ Error(
2638
+ message=e,
2639
+ type_name="ModelError",
2640
+ source=source,
2641
+ line_no=node.lineno,
2642
+ )
2643
+ )
2644
+
2645
+ def get_keyword_name_token_from_line(line: int) -> Optional[Token]:
2646
+ for keyword_name in keyword_name_nodes:
2647
+ if keyword_name.lineno == line:
2648
+ return cast(Token, keyword_name.get_token(RobotToken.KEYWORD_NAME))
2649
+
2650
+ return None
2651
+
2652
+ def get_argument_definitions_from_line(
2653
+ line: int,
2654
+ ) -> List[ArgumentDefinition]:
2655
+ keyword_node = next((k for k in keywords_nodes if k.lineno == line), None)
2656
+ if keyword_node is None:
2657
+ return []
2658
+
2659
+ arguments_node = next(
2660
+ (n for n in ast.walk(keyword_node) if isinstance(n, Arguments)),
2661
+ None,
2662
+ )
2663
+ if arguments_node is None:
2664
+ return []
2665
+
2666
+ args: List[str] = []
2667
+ arguments = arguments_node.get_tokens(RobotToken.ARGUMENT)
2668
+ argument_definitions = []
2669
+
2670
+ for argument_token in (cast(RobotToken, e) for e in arguments):
2671
+ try:
2672
+ argument = get_variable_token(argument_token)
2673
+
2674
+ if argument is not None and argument.value != "@{}":
2675
+ if argument.value not in args:
2676
+ args.append(argument.value)
2677
+ arg_def = ArgumentDefinition(
2678
+ name=argument.value,
2679
+ name_token=strip_variable_token(argument),
2680
+ line_no=argument.lineno,
2681
+ col_offset=argument.col_offset,
2682
+ end_line_no=argument.lineno,
2683
+ end_col_offset=argument.end_col_offset,
2684
+ source=source,
2685
+ )
2686
+ argument_definitions.append(arg_def)
2687
+
2688
+ except VariableError:
2689
+ pass
2690
+
2691
+ return argument_definitions
2692
+
2693
+ res = ResourceFile(source=source)
2694
+
2695
+ with LOGGER.cache_only:
2696
+ ResourceBuilder(res).visit(model)
2697
+
2698
+ if get_robot_version() < (7, 0):
2699
+
2700
+ class MyUserLibrary(UserLibrary):
2701
+ current_kw: Any = None
2702
+
2703
+ def _log_creating_failed(self, handler: UserErrorHandler, error: BaseException) -> None:
2704
+ err = Error(
2705
+ message=f"Creating keyword '{handler.name}' failed: {error!s}",
2706
+ type_name=type(error).__qualname__,
2707
+ source=self.current_kw.source if self.current_kw is not None else None,
2708
+ line_no=self.current_kw.lineno if self.current_kw is not None else None,
2709
+ )
2710
+ errors.append(err)
2711
+
2712
+ def _create_handler(self, kw: Any) -> Any:
2713
+ self.current_kw = kw
2714
+ try:
2715
+ handler = super()._create_handler(kw)
2716
+ handler.errors = None
2717
+ except DataError as e:
2718
+ err = Error(
2719
+ message=str(e),
2720
+ type_name=type(e).__qualname__,
2721
+ source=kw.source,
2722
+ line_no=kw.lineno,
2723
+ )
2724
+ errors.append(err)
2725
+
2726
+ handler = UserErrorHandler(e, kw.name, self.name)
2727
+ handler.source = kw.source
2728
+ handler.lineno = kw.lineno
2729
+
2730
+ handler.errors = [err]
2731
+
2732
+ return handler
2733
+
2734
+ lib = MyUserLibrary(res)
2735
+ else:
2736
+ lib = res
2737
+
2738
+ libdoc = LibraryDoc(
2739
+ name=lib.name or "",
2740
+ doc=lib.doc,
2741
+ type=model_type,
2742
+ scope=scope,
2743
+ source=source,
2744
+ line_no=1,
2745
+ errors=errors,
2746
+ )
2747
+
2748
+ def get_kw_errors(kw: Any) -> Any:
2749
+ r = kw.errors if hasattr(kw, "errors") else None
2750
+ if get_robot_version() >= (7, 0) and kw.error:
2751
+ if not r:
2752
+ r = []
2753
+ r.append(
2754
+ Error(
2755
+ message=str(kw.error),
2756
+ type_name="KeywordError",
2757
+ source=kw.source,
2758
+ line_no=kw.lineno,
2759
+ )
2760
+ )
2761
+ return r
2762
+
2763
+ libdoc.keywords = KeywordStore(
2764
+ source=libdoc.name,
2765
+ source_type=libdoc.type,
2766
+ keywords=[
2767
+ KeywordDoc(
2768
+ name=kw[0].name,
2769
+ arguments=[ArgumentInfo.from_robot(a) for a in kw[0].args],
2770
+ doc=kw[0].doc,
2771
+ tags=list(kw[0].tags),
2772
+ source=str(kw[0].source),
2773
+ name_token=get_keyword_name_token_from_line(kw[0].lineno),
2774
+ line_no=kw[0].lineno if kw[0].lineno is not None else -1,
2775
+ col_offset=-1,
2776
+ end_col_offset=-1,
2777
+ end_line_no=-1,
2778
+ libname=libdoc.name,
2779
+ libtype=libdoc.type,
2780
+ longname=f"{libdoc.name}.{kw[0].name}",
2781
+ is_embedded=is_embedded_keyword(kw[0].name),
2782
+ errors=get_kw_errors(kw[1]),
2783
+ is_error_handler=isinstance(kw[1], UserErrorHandler),
2784
+ error_handler_message=str(cast(UserErrorHandler, kw[1]).error)
2785
+ if isinstance(kw[1], UserErrorHandler)
2786
+ else None,
2787
+ arguments_spec=ArgumentSpec.from_robot_argument_spec(
2788
+ kw[1].arguments if get_robot_version() < (7, 0) else kw[1].args
2789
+ ),
2790
+ argument_definitions=get_argument_definitions_from_line(kw[0].lineno),
2791
+ )
2792
+ for kw in [
2793
+ (KeywordDocBuilder(resource=True).build_keyword(lw), lw)
2794
+ for lw in (lib.handlers if get_robot_version() < (7, 0) else lib.keywords)
2795
+ ]
2796
+ ],
2797
+ )
2798
+
2799
+ return libdoc