robotcode-robot 2.7.0__py3-none-any.whl → 2.8.0.dev8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robotcode/robot/__version__.py +1 -1
- robotcode/robot/config/loader.py +18 -5
- robotcode/robot/config/model.py +360 -112
- robotcode/robot/diagnostics/analyzer_result.py +6 -1
- robotcode/robot/diagnostics/data_cache.py +12 -1
- robotcode/robot/diagnostics/diagnostic_rules.py +75 -0
- robotcode/robot/diagnostics/diagnostics_modifier.py +83 -43
- robotcode/robot/diagnostics/document_cache_helper.py +86 -8
- robotcode/robot/diagnostics/entities.py +20 -4
- robotcode/robot/diagnostics/errors.py +7 -0
- robotcode/robot/diagnostics/import_resolver.py +163 -26
- robotcode/robot/diagnostics/imports_manager.py +352 -116
- robotcode/robot/diagnostics/library_doc.py +877 -162
- robotcode/robot/diagnostics/namespace.py +65 -29
- robotcode/robot/diagnostics/namespace_analyzer.py +101 -29
- robotcode/robot/diagnostics/project_index.py +88 -13
- robotcode/robot/diagnostics/scope_tree.py +2 -2
- robotcode/robot/diagnostics/semantic_analyzer/analyzer.py +866 -214
- robotcode/robot/diagnostics/semantic_analyzer/enums.py +40 -2
- robotcode/robot/diagnostics/semantic_analyzer/json_dump.py +364 -0
- robotcode/robot/diagnostics/semantic_analyzer/model.py +113 -12
- robotcode/robot/diagnostics/semantic_analyzer/nodes.py +55 -1
- robotcode/robot/diagnostics/semantic_analyzer/serialization.py +25 -11
- robotcode/robot/diagnostics/semantic_analyzer/variable_tokenizer.py +218 -112
- robotcode/robot/diagnostics/workspace_config.py +3 -0
- robotcode/robot/utils/markdown_docs.py +733 -0
- robotcode/robot/utils/markdownformatter.py +57 -12
- robotcode/robot/utils/match.py +13 -0
- robotcode/robot/utils/robot_patching.py +4 -0
- robotcode/robot/utils/variables.py +12 -0
- {robotcode_robot-2.7.0.dist-info → robotcode_robot-2.8.0.dev8.dist-info}/METADATA +4 -3
- robotcode_robot-2.8.0.dev8.dist-info/RECORD +48 -0
- {robotcode_robot-2.7.0.dist-info → robotcode_robot-2.8.0.dev8.dist-info}/WHEEL +1 -1
- robotcode_robot-2.7.0.dist-info/RECORD +0 -46
robotcode/robot/config/model.py
CHANGED
|
@@ -297,7 +297,8 @@ class BaseOptions(ValidateMixin):
|
|
|
297
297
|
type_hints = get_type_hints(type(self))
|
|
298
298
|
base_field_names = [f.name for f in dataclasses.fields(self)]
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
# A setting replaces the value before it and its `extend-` setting adds to it, so the settings come first.
|
|
301
|
+
for f in sorted(dataclasses.fields(config), key=lambda f: f.name.startswith(EXTEND_PREFIX)):
|
|
301
302
|
if f.name.startswith(EXTEND_PREFIX):
|
|
302
303
|
if f.name not in base_field_names:
|
|
303
304
|
continue
|
|
@@ -311,7 +312,13 @@ class BaseOptions(ValidateMixin):
|
|
|
311
312
|
if new is None:
|
|
312
313
|
continue
|
|
313
314
|
|
|
314
|
-
|
|
315
|
+
# In a profile from `extend-profiles`, an `extend-` setting adds to the setting of the profile it
|
|
316
|
+
# extends; only if that profile has none does it stay an `extend-` setting of the profile.
|
|
317
|
+
old_field_name = (
|
|
318
|
+
f.name
|
|
319
|
+
if combine_extends and getattr(self, f.name[EXTEND_PREFIX_LEN:], None) is None
|
|
320
|
+
else f.name[EXTEND_PREFIX_LEN:]
|
|
321
|
+
)
|
|
315
322
|
|
|
316
323
|
old = getattr(self, old_field_name)
|
|
317
324
|
if old is None:
|
|
@@ -331,7 +338,10 @@ class BaseOptions(ValidateMixin):
|
|
|
331
338
|
elif isinstance(old, tuple):
|
|
332
339
|
setattr(self, old_field_name, (*old, *new))
|
|
333
340
|
elif isinstance(old, BaseOptions):
|
|
334
|
-
|
|
341
|
+
# The tables of `rebot`, `libdoc` and `testdoc` keep an `extend-` setting that they don't set
|
|
342
|
+
# themselves, because the command adds the table to the settings outside of it. The tables of
|
|
343
|
+
# the analysis are read as they are, so there it becomes the setting.
|
|
344
|
+
old.add_options(new, isinstance(old, (RebotProfile, LibDocProfile, TestDocProfile)))
|
|
335
345
|
else:
|
|
336
346
|
setattr(self, old_field_name, new)
|
|
337
347
|
continue
|
|
@@ -343,9 +353,6 @@ class BaseOptions(ValidateMixin):
|
|
|
343
353
|
if EXTEND_PREFIX + f.name in base_field_names and getattr(config, f.name, None) is not None:
|
|
344
354
|
setattr(self, EXTEND_PREFIX + f.name, None)
|
|
345
355
|
|
|
346
|
-
if getattr(config, f"{EXTEND_PREFIX}_{f.name}", None) is not None and not combine_extends:
|
|
347
|
-
continue
|
|
348
|
-
|
|
349
356
|
new = self._verified_value(f.name, getattr(config, f.name), type_hints[f.name], config)
|
|
350
357
|
if new is not None:
|
|
351
358
|
setattr(self, f.name, new)
|
|
@@ -577,7 +584,7 @@ class CommonOptions(RobotBaseOptions):
|
|
|
577
584
|
|
|
578
585
|
```toml
|
|
579
586
|
# tests with both "foo" and "bar*" tags
|
|
580
|
-
includes = ["
|
|
587
|
+
includes = ["foo AND bar*"]
|
|
581
588
|
```
|
|
582
589
|
|
|
583
590
|
corresponds to the `-i --include tag *` option of _robot_
|
|
@@ -828,7 +835,7 @@ class CommonOptions(RobotBaseOptions):
|
|
|
828
835
|
|
|
829
836
|
```toml
|
|
830
837
|
# match by tag pattern (same rules as --include)
|
|
831
|
-
remove-keywords = ["foo", "
|
|
838
|
+
remove-keywords = ["foo", "foo AND bar*"]
|
|
832
839
|
```
|
|
833
840
|
|
|
834
841
|
corresponds to the `--removekeywords all|passed|for|wuks|name:<pattern>|tag:<pattern> *` option of _robot_
|
|
@@ -1024,7 +1031,7 @@ class CommonOptions(RobotBaseOptions):
|
|
|
1024
1031
|
Examples:
|
|
1025
1032
|
|
|
1026
1033
|
```toml
|
|
1027
|
-
tag-stat-combine = ["requirement-*", { "
|
|
1034
|
+
tag-stat-combine = ["requirement-*", { "tag1 AND tag2" = "My_name" }]
|
|
1028
1035
|
```
|
|
1029
1036
|
|
|
1030
1037
|
corresponds to the `--tagstatcombine tags:name *` option of _robot_
|
|
@@ -1264,7 +1271,7 @@ class CommonExtendOptions(RobotBaseOptions):
|
|
|
1264
1271
|
|
|
1265
1272
|
```toml
|
|
1266
1273
|
# tests with both "foo" and "bar*" tags
|
|
1267
|
-
extend-includes = ["
|
|
1274
|
+
extend-includes = ["foo AND bar*"]
|
|
1268
1275
|
```
|
|
1269
1276
|
|
|
1270
1277
|
corresponds to the `-i --include tag *` option of _robot_
|
|
@@ -1400,7 +1407,7 @@ class CommonExtendOptions(RobotBaseOptions):
|
|
|
1400
1407
|
|
|
1401
1408
|
```toml
|
|
1402
1409
|
# match by tag pattern (same rules as --include)
|
|
1403
|
-
extend-remove-keywords = ["foo", "
|
|
1410
|
+
extend-remove-keywords = ["foo", "foo AND bar*"]
|
|
1404
1411
|
```
|
|
1405
1412
|
|
|
1406
1413
|
corresponds to the `--removekeywords all|passed|for|wuks|name:<pattern>|tag:<pattern> *` option of _robot_
|
|
@@ -1480,7 +1487,7 @@ class CommonExtendOptions(RobotBaseOptions):
|
|
|
1480
1487
|
Examples:
|
|
1481
1488
|
|
|
1482
1489
|
```toml
|
|
1483
|
-
extend-tag-stat-combine = ["requirement-*", { "
|
|
1490
|
+
extend-tag-stat-combine = ["requirement-*", { "tag1 AND tag2" = "My_name" }]
|
|
1484
1491
|
```
|
|
1485
1492
|
|
|
1486
1493
|
corresponds to the `--tagstatcombine tags:name *` option of _robot_
|
|
@@ -1588,18 +1595,22 @@ class CommonExtendOptions(RobotBaseOptions):
|
|
|
1588
1595
|
class RobotOptions(RobotBaseOptions):
|
|
1589
1596
|
"""Options for _robot_ command."""
|
|
1590
1597
|
|
|
1591
|
-
console: Optional[Literal["verbose", "dotted", "
|
|
1598
|
+
console: Optional[Union[str, Literal["verbose", "dotted", "quiet", "none"]]] = field(
|
|
1592
1599
|
description="""\
|
|
1593
1600
|
How to report execution on the console.
|
|
1601
|
+
Built-in consoles:
|
|
1594
1602
|
|
|
1595
1603
|
**verbose:** report every suite and test (default)
|
|
1596
1604
|
|
|
1597
|
-
**dotted:** only show `.` for passed test, `s`
|
|
1598
|
-
skipped
|
|
1605
|
+
**dotted:** only show `.` for passed test, `s`
|
|
1606
|
+
for skipped and `F` for failed tests
|
|
1599
1607
|
|
|
1600
|
-
**quiet:** no output except for errors
|
|
1608
|
+
**quiet:** no output except for errors/warnings
|
|
1601
1609
|
|
|
1602
1610
|
**none:** no output whatsoever
|
|
1611
|
+
Other values are interpreted as a custom
|
|
1612
|
+
console class or module. Argument format is
|
|
1613
|
+
the same as with --listener.
|
|
1603
1614
|
|
|
1604
1615
|
Examples:
|
|
1605
1616
|
|
|
@@ -1607,7 +1618,12 @@ class RobotOptions(RobotBaseOptions):
|
|
|
1607
1618
|
console = "dotted"
|
|
1608
1619
|
```
|
|
1609
1620
|
|
|
1610
|
-
|
|
1621
|
+
```toml
|
|
1622
|
+
# custom console logger class or module (Robot Framework 7.5 or newer)
|
|
1623
|
+
console = "path/to/MyConsole.py:arg"
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
corresponds to the `--console console` option of _robot_
|
|
1611
1627
|
""",
|
|
1612
1628
|
robot_name="console",
|
|
1613
1629
|
robot_priority=500,
|
|
@@ -2142,7 +2158,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2142
2158
|
extend-languages = ["German", "Finnish"]
|
|
2143
2159
|
```
|
|
2144
2160
|
|
|
2145
|
-
corresponds to the `--language lang *` option of
|
|
2161
|
+
corresponds to the `--language lang *` option of _robot_
|
|
2146
2162
|
""",
|
|
2147
2163
|
alias="extend-languages",
|
|
2148
2164
|
)
|
|
@@ -2163,7 +2179,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2163
2179
|
"path/to/Listener.py" = ["arg1", "arg2"]
|
|
2164
2180
|
```
|
|
2165
2181
|
|
|
2166
|
-
corresponds to the `--listener listener *` option of
|
|
2182
|
+
corresponds to the `--listener listener *` option of _robot_
|
|
2167
2183
|
""",
|
|
2168
2184
|
alias="extend-listeners",
|
|
2169
2185
|
)
|
|
@@ -2182,7 +2198,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2182
2198
|
"path/to/MyParser.py" = ["arg1", "arg2"]
|
|
2183
2199
|
```
|
|
2184
2200
|
|
|
2185
|
-
corresponds to the `--parser parser *` option of
|
|
2201
|
+
corresponds to the `--parser parser *` option of _robot_
|
|
2186
2202
|
""",
|
|
2187
2203
|
alias="extend-parsers",
|
|
2188
2204
|
)
|
|
@@ -2202,7 +2218,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2202
2218
|
"path/to/Modifier.py" = ["arg1"]
|
|
2203
2219
|
```
|
|
2204
2220
|
|
|
2205
|
-
corresponds to the `--prerunmodifier modifier *` option of
|
|
2221
|
+
corresponds to the `--prerunmodifier modifier *` option of _robot_
|
|
2206
2222
|
""",
|
|
2207
2223
|
alias="extend-pre-run-modifiers",
|
|
2208
2224
|
)
|
|
@@ -2219,7 +2235,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2219
2235
|
extend-skip = ["bug-*", "wip"]
|
|
2220
2236
|
```
|
|
2221
2237
|
|
|
2222
|
-
corresponds to the `--skip tag *` option of
|
|
2238
|
+
corresponds to the `--skip tag *` option of _robot_
|
|
2223
2239
|
""",
|
|
2224
2240
|
alias="extend-skip",
|
|
2225
2241
|
)
|
|
@@ -2236,7 +2252,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2236
2252
|
extend-skip-on-failure = ["unstable"]
|
|
2237
2253
|
```
|
|
2238
2254
|
|
|
2239
|
-
corresponds to the `--skiponfailure tag *` option of
|
|
2255
|
+
corresponds to the `--skiponfailure tag *` option of _robot_
|
|
2240
2256
|
""",
|
|
2241
2257
|
alias="extend-skip-on-failure",
|
|
2242
2258
|
)
|
|
@@ -2257,7 +2273,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2257
2273
|
name = "Robot"
|
|
2258
2274
|
```
|
|
2259
2275
|
|
|
2260
|
-
corresponds to the `-v --variable name:value *` option of
|
|
2276
|
+
corresponds to the `-v --variable name:value *` option of _robot_
|
|
2261
2277
|
""",
|
|
2262
2278
|
alias="extend-variables",
|
|
2263
2279
|
)
|
|
@@ -2275,7 +2291,7 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2275
2291
|
extend-variable-files = ["path/vars.yaml", "environment.py:testing"]
|
|
2276
2292
|
```
|
|
2277
2293
|
|
|
2278
|
-
corresponds to the `-V --variablefile path *` option of
|
|
2294
|
+
corresponds to the `-V --variablefile path *` option of _robot_
|
|
2279
2295
|
""",
|
|
2280
2296
|
alias="extend-variable-files",
|
|
2281
2297
|
)
|
|
@@ -2285,6 +2301,36 @@ class RobotExtendOptions(RobotBaseOptions):
|
|
|
2285
2301
|
class RebotOptions(RobotBaseOptions):
|
|
2286
2302
|
"""Options for _rebot_ command."""
|
|
2287
2303
|
|
|
2304
|
+
console: Optional[Union[str, Literal["verbose", "quiet", "none"]]] = field(
|
|
2305
|
+
description="""\
|
|
2306
|
+
How to report on the console.
|
|
2307
|
+
Built-in consoles:
|
|
2308
|
+
|
|
2309
|
+
**verbose:** show output file paths (default)
|
|
2310
|
+
|
|
2311
|
+
**quiet:** no output except for errors/warnings
|
|
2312
|
+
|
|
2313
|
+
**none:** no output whatsoever
|
|
2314
|
+
Other values are interpreted as a custom
|
|
2315
|
+
console class or module. Argument format is
|
|
2316
|
+
the same as with --listener.
|
|
2317
|
+
|
|
2318
|
+
Examples:
|
|
2319
|
+
|
|
2320
|
+
```toml
|
|
2321
|
+
console = "quiet"
|
|
2322
|
+
```
|
|
2323
|
+
|
|
2324
|
+
```toml
|
|
2325
|
+
# custom console logger class or module
|
|
2326
|
+
console = "path/to/Console.py:arg"
|
|
2327
|
+
```
|
|
2328
|
+
|
|
2329
|
+
corresponds to the `--console console` option of _rebot_
|
|
2330
|
+
""",
|
|
2331
|
+
robot_name="console",
|
|
2332
|
+
robot_priority=500,
|
|
2333
|
+
)
|
|
2288
2334
|
end_time: Optional[Union[str, StringExpression]] = field(
|
|
2289
2335
|
description="""\
|
|
2290
2336
|
Same as --starttime but for end time. If both options
|
|
@@ -2388,6 +2434,22 @@ class RebotOptions(RobotBaseOptions):
|
|
|
2388
2434
|
robot_is_flag=True,
|
|
2389
2435
|
alias="process-empty-suite",
|
|
2390
2436
|
)
|
|
2437
|
+
quiet: Union[bool, Flag, None] = field(
|
|
2438
|
+
description="""\
|
|
2439
|
+
Shortcut for `--console quiet`.
|
|
2440
|
+
|
|
2441
|
+
Examples:
|
|
2442
|
+
|
|
2443
|
+
```toml
|
|
2444
|
+
quiet = true
|
|
2445
|
+
```
|
|
2446
|
+
|
|
2447
|
+
corresponds to the `--quiet` option of _rebot_
|
|
2448
|
+
""",
|
|
2449
|
+
robot_name="quiet",
|
|
2450
|
+
robot_priority=500,
|
|
2451
|
+
robot_is_flag=True,
|
|
2452
|
+
)
|
|
2391
2453
|
start_time: Optional[Union[str, StringExpression]] = field(
|
|
2392
2454
|
description="""\
|
|
2393
2455
|
Set execution start time. Timestamp must be given in
|
|
@@ -2417,13 +2479,14 @@ class RebotOptions(RobotBaseOptions):
|
|
|
2417
2479
|
class LibDocOptions(RobotBaseOptions):
|
|
2418
2480
|
"""Options for _libdoc_ command."""
|
|
2419
2481
|
|
|
2420
|
-
doc_format: Optional[Literal["ROBOT", "HTML", "TEXT", "REST"]] = field(
|
|
2482
|
+
doc_format: Optional[Literal["ROBOT", "MARKDOWN", "HTML", "TEXT", "REST"]] = field(
|
|
2421
2483
|
description="""\
|
|
2422
2484
|
Specifies the source documentation format. Possible
|
|
2423
2485
|
values are Robot Framework's documentation format,
|
|
2424
|
-
HTML, plain text
|
|
2425
|
-
value can be specified in library source
|
|
2426
|
-
the initial default value is ROBOT.
|
|
2486
|
+
Markdown, HTML, plain text and reStructuredText.
|
|
2487
|
+
The default value can be specified in library source
|
|
2488
|
+
code and the initial default value is ROBOT. Markdown
|
|
2489
|
+
support is new in RF 7.5.
|
|
2427
2490
|
|
|
2428
2491
|
Examples:
|
|
2429
2492
|
|
|
@@ -2431,20 +2494,21 @@ class LibDocOptions(RobotBaseOptions):
|
|
|
2431
2494
|
doc-format = "REST"
|
|
2432
2495
|
```
|
|
2433
2496
|
|
|
2434
|
-
corresponds to the `-F --docformat ROBOT|HTML|TEXT|REST` option of _libdoc_
|
|
2497
|
+
corresponds to the `-F --docformat ROBOT|MARKDOWN|HTML|TEXT|REST` option of _libdoc_
|
|
2435
2498
|
""",
|
|
2436
2499
|
robot_name="docformat",
|
|
2437
2500
|
robot_priority=500,
|
|
2438
2501
|
robot_short_name="F",
|
|
2439
2502
|
alias="doc-format",
|
|
2440
2503
|
)
|
|
2441
|
-
format: Optional[Literal["HTML", "XML", "JSON", "LIBSPEC"]] = field(
|
|
2504
|
+
format: Optional[Literal["HTML", "XML", "JSON", "LIBSPEC", "MARKDOWN"]] = field(
|
|
2442
2505
|
description="""\
|
|
2443
2506
|
Specifies whether to generate an HTML output for
|
|
2444
|
-
humans
|
|
2445
|
-
format. The LIBSPEC format means
|
|
2446
|
-
documentations converted to HTML. The
|
|
2447
|
-
is got from the output file
|
|
2507
|
+
humans, a machine readable spec file in XML or JSON
|
|
2508
|
+
format, or a Markdown file. The LIBSPEC format means
|
|
2509
|
+
XML spec with documentations converted to HTML. The
|
|
2510
|
+
default format is got from the output file
|
|
2511
|
+
extension. MARKDOWN is new in RF 7.5.
|
|
2448
2512
|
|
|
2449
2513
|
Examples:
|
|
2450
2514
|
|
|
@@ -2452,7 +2516,7 @@ class LibDocOptions(RobotBaseOptions):
|
|
|
2452
2516
|
format = "HTML"
|
|
2453
2517
|
```
|
|
2454
2518
|
|
|
2455
|
-
corresponds to the `-f --format HTML|XML|JSON|LIBSPEC` option of _libdoc_
|
|
2519
|
+
corresponds to the `-f --format HTML|XML|JSON|LIBSPEC|MARKDOWN` option of _libdoc_
|
|
2456
2520
|
""",
|
|
2457
2521
|
robot_name="format",
|
|
2458
2522
|
robot_priority=500,
|
|
@@ -2516,7 +2580,8 @@ class LibDocOptions(RobotBaseOptions):
|
|
|
2516
2580
|
documentation format and HTML means converting
|
|
2517
2581
|
documentation to HTML. The default is RAW with XML
|
|
2518
2582
|
spec files and HTML with JSON specs and when using
|
|
2519
|
-
the special LIBSPEC format.
|
|
2583
|
+
the special LIBSPEC format. Not applicable with HTML
|
|
2584
|
+
or MARKDOWN outputs.
|
|
2520
2585
|
|
|
2521
2586
|
Examples:
|
|
2522
2587
|
|
|
@@ -2627,7 +2692,7 @@ class TestDocOptions(RobotBaseOptions):
|
|
|
2627
2692
|
|
|
2628
2693
|
```toml
|
|
2629
2694
|
# tests with both "foo" and "bar*" tags
|
|
2630
|
-
includes = ["
|
|
2695
|
+
includes = ["foo AND bar*"]
|
|
2631
2696
|
```
|
|
2632
2697
|
|
|
2633
2698
|
corresponds to the `-i --include tag *` option of _testdoc_
|
|
@@ -2776,7 +2841,7 @@ class TestDocExtendOptions(RobotBaseOptions):
|
|
|
2776
2841
|
|
|
2777
2842
|
```toml
|
|
2778
2843
|
# tests with both "foo" and "bar*" tags
|
|
2779
|
-
extend-includes = ["
|
|
2844
|
+
extend-includes = ["foo AND bar*"]
|
|
2780
2845
|
```
|
|
2781
2846
|
|
|
2782
2847
|
corresponds to the `-i --include tag *` option of _testdoc_
|
|
@@ -2931,20 +2996,29 @@ class RobotBaseProfile(CommonOptions, CommonExtendOptions, RobotOptions, RobotEx
|
|
|
2931
2996
|
|
|
2932
2997
|
rebot: Optional[RebotProfile] = field(
|
|
2933
2998
|
description="""\
|
|
2934
|
-
Options to be passed to _rebot_.
|
|
2999
|
+
Options to be passed to _rebot_. They win over the settings outside
|
|
3000
|
+
of this table. Of those, _rebot_ gets the ones that Robot Framework
|
|
3001
|
+
itself passes from _robot_ to _rebot_ after a run, so not
|
|
3002
|
+
`includes`, `excludes`, `tests`, `suites`, `parse-include`, `name`,
|
|
3003
|
+
`doc`, `metadata`, `set-tag`, `output`, `log-level`,
|
|
3004
|
+
`timestamp-outputs`, `console` and `quiet`; `run-empty-suite`
|
|
3005
|
+
becomes `process-empty-suite`.
|
|
2935
3006
|
"""
|
|
2936
3007
|
)
|
|
2937
3008
|
|
|
2938
3009
|
libdoc: Optional[LibDocProfile] = field(
|
|
2939
3010
|
description="""\
|
|
2940
|
-
Options to be passed to _libdoc_.
|
|
2941
|
-
|
|
3011
|
+
Options to be passed to _libdoc_. Of the settings outside of this
|
|
3012
|
+
table, _libdoc_ gets only `python-path`, and this table wins over it.
|
|
3013
|
+
"""
|
|
2942
3014
|
)
|
|
2943
3015
|
|
|
2944
3016
|
testdoc: Optional[TestDocProfile] = field(
|
|
2945
3017
|
description="""\
|
|
2946
|
-
Options to be passed to _testdoc_.
|
|
2947
|
-
|
|
3018
|
+
Options to be passed to _testdoc_. They win over the settings outside
|
|
3019
|
+
of this table that _testdoc_ shares with _robot_, such as `name`,
|
|
3020
|
+
`metadata` or `includes`.
|
|
3021
|
+
"""
|
|
2948
3022
|
)
|
|
2949
3023
|
|
|
2950
3024
|
def save(self, path: "os.PathLike[str]") -> None:
|
|
@@ -3008,6 +3082,51 @@ class RobotExtendBaseProfile(RobotBaseProfile):
|
|
|
3008
3082
|
alias="extend-wrapper",
|
|
3009
3083
|
)
|
|
3010
3084
|
|
|
3085
|
+
extend_rebot: Optional[RebotProfile] = field(
|
|
3086
|
+
description="""\
|
|
3087
|
+
Changes the `[rebot]` options that come before it, from an earlier
|
|
3088
|
+
configuration file or before a profile: a setting given here
|
|
3089
|
+
replaces the one of `[rebot]`, and an `extend-` setting adds to it.
|
|
3090
|
+
|
|
3091
|
+
Examples:
|
|
3092
|
+
```toml
|
|
3093
|
+
[extend-rebot]
|
|
3094
|
+
report-title = "My run"
|
|
3095
|
+
```
|
|
3096
|
+
""",
|
|
3097
|
+
alias="extend-rebot",
|
|
3098
|
+
)
|
|
3099
|
+
|
|
3100
|
+
extend_libdoc: Optional[LibDocProfile] = field(
|
|
3101
|
+
description="""\
|
|
3102
|
+
Changes the `[libdoc]` options that come before it, from an earlier
|
|
3103
|
+
configuration file or before a profile: a setting given here
|
|
3104
|
+
replaces the one of `[libdoc]`, and an `extend-` setting adds to it.
|
|
3105
|
+
|
|
3106
|
+
Examples:
|
|
3107
|
+
```toml
|
|
3108
|
+
[extend-libdoc]
|
|
3109
|
+
theme = "DARK"
|
|
3110
|
+
```
|
|
3111
|
+
""",
|
|
3112
|
+
alias="extend-libdoc",
|
|
3113
|
+
)
|
|
3114
|
+
|
|
3115
|
+
extend_testdoc: Optional[TestDocProfile] = field(
|
|
3116
|
+
description="""\
|
|
3117
|
+
Changes the `[testdoc]` options that come before it, from an earlier
|
|
3118
|
+
configuration file or before a profile: a setting given here
|
|
3119
|
+
replaces the one of `[testdoc]`, and an `extend-` setting adds to it.
|
|
3120
|
+
|
|
3121
|
+
Examples:
|
|
3122
|
+
```toml
|
|
3123
|
+
[extend-testdoc]
|
|
3124
|
+
title = "My tests"
|
|
3125
|
+
```
|
|
3126
|
+
""",
|
|
3127
|
+
alias="extend-testdoc",
|
|
3128
|
+
)
|
|
3129
|
+
|
|
3011
3130
|
extend_paths: Union[str, List[str], None] = field(
|
|
3012
3131
|
description="""\
|
|
3013
3132
|
Append extra entries to the paths argument.
|
|
@@ -3032,15 +3151,23 @@ class RobotProfile(RobotExtendBaseProfile):
|
|
|
3032
3151
|
|
|
3033
3152
|
detached: Optional[bool] = field(
|
|
3034
3153
|
description="""\
|
|
3035
|
-
|
|
3036
|
-
|
|
3154
|
+
Starts the profile from scratch: the settings outside of profiles,
|
|
3155
|
+
from all configuration files, and the profiles applied before it
|
|
3156
|
+
are not used.
|
|
3157
|
+
|
|
3158
|
+
Examples:
|
|
3159
|
+
```toml
|
|
3160
|
+
[profiles.clean]
|
|
3161
|
+
detached = true
|
|
3162
|
+
paths = ["tests"]
|
|
3163
|
+
```
|
|
3037
3164
|
"""
|
|
3038
3165
|
)
|
|
3039
3166
|
|
|
3040
3167
|
enabled: Union[bool, Condition, None] = field(
|
|
3041
3168
|
description="""\
|
|
3042
|
-
|
|
3043
|
-
|
|
3169
|
+
Whether the profile is used when it is selected; a disabled profile
|
|
3170
|
+
is skipped. An `if` condition computes the value.
|
|
3044
3171
|
|
|
3045
3172
|
Examples:
|
|
3046
3173
|
```toml
|
|
@@ -3049,7 +3176,7 @@ class RobotProfile(RobotExtendBaseProfile):
|
|
|
3049
3176
|
```
|
|
3050
3177
|
|
|
3051
3178
|
```toml
|
|
3052
|
-
# enabled if
|
|
3179
|
+
# enabled if the environment variable CI is "true"
|
|
3053
3180
|
enabled = { if = 'environ.get("CI") == "true"' }
|
|
3054
3181
|
```
|
|
3055
3182
|
"""
|
|
@@ -3060,8 +3187,9 @@ class RobotProfile(RobotExtendBaseProfile):
|
|
|
3060
3187
|
|
|
3061
3188
|
hidden: Union[bool, Condition, None] = field(
|
|
3062
3189
|
description="""\
|
|
3063
|
-
|
|
3064
|
-
|
|
3190
|
+
Hides the profile from `robotcode profiles list` and from the
|
|
3191
|
+
profile lists of the editors; it can still be selected by name.
|
|
3192
|
+
Profiles whose names start with `_` are hidden too.
|
|
3065
3193
|
|
|
3066
3194
|
Examples:
|
|
3067
3195
|
```toml
|
|
@@ -3076,13 +3204,15 @@ class RobotProfile(RobotExtendBaseProfile):
|
|
|
3076
3204
|
|
|
3077
3205
|
precedence: Optional[int] = field(
|
|
3078
3206
|
description="""\
|
|
3079
|
-
Precedence of the profile. Lower values are executed first.
|
|
3207
|
+
Precedence of the profile. Lower values are executed first. A profile without a precedence counts as 0;
|
|
3208
|
+
profiles with the same precedence are applied in the order they are selected, for example on the command line.
|
|
3080
3209
|
"""
|
|
3081
3210
|
)
|
|
3082
3211
|
|
|
3083
3212
|
inherits: Union[str, StringExpression, List[Union[str, StringExpression]], None] = field(
|
|
3084
3213
|
description="""\
|
|
3085
|
-
Profiles to inherit from.
|
|
3214
|
+
Profiles to inherit from. Selecting this profile selects them too;
|
|
3215
|
+
they are applied before it unless they have a higher `precedence`.
|
|
3086
3216
|
|
|
3087
3217
|
Examples:
|
|
3088
3218
|
```toml
|
|
@@ -3092,13 +3222,50 @@ class RobotProfile(RobotExtendBaseProfile):
|
|
|
3092
3222
|
)
|
|
3093
3223
|
|
|
3094
3224
|
|
|
3225
|
+
@dataclass(frozen=True)
|
|
3226
|
+
class UnknownProfile:
|
|
3227
|
+
"""A profile name or pattern that selects no profile, and where it comes from."""
|
|
3228
|
+
|
|
3229
|
+
name: str
|
|
3230
|
+
# the profile whose `inherits` names it; None for a name of the selection or of `default-profiles`
|
|
3231
|
+
inherited_by: Optional[str] = None
|
|
3232
|
+
# whether it comes from `default-profiles`
|
|
3233
|
+
default: bool = False
|
|
3234
|
+
|
|
3235
|
+
@property
|
|
3236
|
+
def selected(self) -> bool:
|
|
3237
|
+
"""Whether the name comes from the selection: `-p`, `ROBOTCODE_PROFILES` or an editor."""
|
|
3238
|
+
return self.inherited_by is None and not self.default
|
|
3239
|
+
|
|
3240
|
+
@property
|
|
3241
|
+
def message(self) -> str:
|
|
3242
|
+
return f"No profile matches '{self.name}'."
|
|
3243
|
+
|
|
3244
|
+
|
|
3245
|
+
class ProfileNotFoundError(ValueError):
|
|
3246
|
+
"""A profile name or pattern that selects no profile."""
|
|
3247
|
+
|
|
3248
|
+
def __init__(self, unknown: UnknownProfile) -> None:
|
|
3249
|
+
super().__init__(unknown.message)
|
|
3250
|
+
self.unknown = unknown
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
class ProfileInheritanceError(ValueError):
|
|
3254
|
+
"""A profile that inherits itself, directly or through other profiles."""
|
|
3255
|
+
|
|
3256
|
+
def __init__(self, path: Tuple[str, ...]) -> None:
|
|
3257
|
+
super().__init__(f"The profile '{path[0]}' inherits itself: {' -> '.join(path)}.")
|
|
3258
|
+
self.path = path
|
|
3259
|
+
|
|
3260
|
+
|
|
3095
3261
|
@dataclass
|
|
3096
3262
|
class RobotConfig(RobotExtendBaseProfile):
|
|
3097
3263
|
"""Robot Framework configuration."""
|
|
3098
3264
|
|
|
3099
3265
|
default_profiles: Union[str, List[str], None] = field(
|
|
3100
3266
|
description="""\
|
|
3101
|
-
|
|
3267
|
+
The profiles to use when no profile is selected, for example with
|
|
3268
|
+
`-p` on the command line or in the editor.
|
|
3102
3269
|
|
|
3103
3270
|
Examples:
|
|
3104
3271
|
```toml
|
|
@@ -3111,24 +3278,59 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3111
3278
|
""",
|
|
3112
3279
|
alias="default-profiles",
|
|
3113
3280
|
)
|
|
3114
|
-
profiles: Optional[Dict[str, RobotProfile]] = field(
|
|
3281
|
+
profiles: Optional[Dict[str, RobotProfile]] = field(
|
|
3282
|
+
description="""\
|
|
3283
|
+
Execution profiles: named sets of settings that are applied on top
|
|
3284
|
+
of the other settings when the profile is selected, for example
|
|
3285
|
+
with `robotcode -p ci`. In a later configuration file, such as
|
|
3286
|
+
`.robot.toml`, `profiles` replaces all profiles of the earlier
|
|
3287
|
+
files; `extend-profiles` adds to them.
|
|
3288
|
+
|
|
3289
|
+
Examples:
|
|
3290
|
+
```toml
|
|
3291
|
+
[profiles.ci]
|
|
3292
|
+
description = "CI server"
|
|
3293
|
+
output-dir = "results/ci"
|
|
3294
|
+
```
|
|
3295
|
+
"""
|
|
3296
|
+
)
|
|
3115
3297
|
|
|
3116
3298
|
extend_profiles: Optional[Dict[str, RobotProfile]] = field(
|
|
3117
|
-
description="
|
|
3299
|
+
description="""\
|
|
3300
|
+
Adds profiles to those of the earlier configuration files, for
|
|
3301
|
+
example in `.robot.toml` to those of `robot.toml`. For a profile
|
|
3302
|
+
that exists already, a setting given here replaces the one of the
|
|
3303
|
+
profile, and an `extend-` setting adds to it.
|
|
3304
|
+
|
|
3305
|
+
Examples:
|
|
3306
|
+
```toml
|
|
3307
|
+
[extend-profiles.local]
|
|
3308
|
+
description = "My local settings"
|
|
3309
|
+
extend-variables = { BROWSER = "firefox" }
|
|
3310
|
+
```
|
|
3311
|
+
""",
|
|
3312
|
+
alias="extend-profiles",
|
|
3118
3313
|
)
|
|
3119
3314
|
|
|
3120
|
-
tool: Optional[Dict[str, Any]] = field(
|
|
3315
|
+
tool: Optional[Dict[str, Any]] = field(
|
|
3316
|
+
description="""\
|
|
3317
|
+
Settings of other tools, for example `[tool.robotcode-analyze]`
|
|
3318
|
+
for RobotCode's analysis.
|
|
3319
|
+
"""
|
|
3320
|
+
)
|
|
3121
3321
|
|
|
3122
3322
|
def _select_profiles(
|
|
3123
3323
|
self,
|
|
3124
3324
|
*names: str,
|
|
3125
3325
|
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
|
3126
3326
|
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
|
3327
|
+
unknown_profile_callback: Optional[Callable[[UnknownProfile], None]] = None,
|
|
3127
3328
|
) -> Dict[str, Tuple[RobotProfile, Optional[Set[Tuple[str, RobotProfile]]]]]:
|
|
3128
3329
|
result: Dict[str, Tuple[RobotProfile, Optional[Set[Tuple[str, RobotProfile]]]]] = {}
|
|
3129
3330
|
|
|
3130
3331
|
profiles = self.profiles or {}
|
|
3131
3332
|
|
|
3333
|
+
from_default_profiles = not names
|
|
3132
3334
|
if not names:
|
|
3133
3335
|
if verbose_callback:
|
|
3134
3336
|
verbose_callback("No profiles given, try to check if there are default profiles specified.")
|
|
@@ -3142,40 +3344,65 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3142
3344
|
|
|
3143
3345
|
names = (*(default_profile or ()),)
|
|
3144
3346
|
|
|
3145
|
-
|
|
3347
|
+
# each name that selects no profile is reported once for each place it comes from
|
|
3348
|
+
reported: Set[UnknownProfile] = set()
|
|
3349
|
+
|
|
3350
|
+
def report(unknown: UnknownProfile) -> None:
|
|
3351
|
+
if unknown in reported:
|
|
3352
|
+
return
|
|
3353
|
+
reported.add(unknown)
|
|
3354
|
+
|
|
3355
|
+
# a name that selects no profile is an error of the configuration; only callers that must go on, such as
|
|
3356
|
+
# the language server, take it and go on without it
|
|
3357
|
+
if unknown_profile_callback is not None:
|
|
3358
|
+
unknown_profile_callback(unknown)
|
|
3359
|
+
# a name of `default-profiles` is only reported: the `default-profiles` of the user configuration can name
|
|
3360
|
+
# a profile of that file, which the `[profiles]` of a project replace
|
|
3361
|
+
elif unknown.default and error_callback is not None:
|
|
3362
|
+
error_callback(unknown.message)
|
|
3363
|
+
else:
|
|
3364
|
+
raise ProfileNotFoundError(unknown)
|
|
3365
|
+
|
|
3366
|
+
# `path` holds the profiles whose `inherits` lead to the name; the last one names it
|
|
3367
|
+
def select(name: str, path: Tuple[str, ...] = ()) -> None:
|
|
3146
3368
|
if not name:
|
|
3147
3369
|
return
|
|
3148
3370
|
|
|
3149
3371
|
nonlocal result
|
|
3150
3372
|
|
|
3373
|
+
parent = path[-1] if path else None
|
|
3374
|
+
|
|
3151
3375
|
if verbose_callback:
|
|
3152
|
-
if
|
|
3153
|
-
verbose_callback(
|
|
3154
|
-
f"Selecting profiles matching '{name}'"
|
|
3155
|
-
f" for parent profile '{next(f for f in parent_profiles)[0]}'."
|
|
3156
|
-
)
|
|
3376
|
+
if parent is not None:
|
|
3377
|
+
verbose_callback(f"Selecting profiles matching '{name}' for parent profile '{parent}'.")
|
|
3157
3378
|
else:
|
|
3158
3379
|
verbose_callback(f"Selecting profiles matching '{name}'.")
|
|
3159
3380
|
|
|
3160
|
-
|
|
3381
|
+
if name in profiles:
|
|
3382
|
+
# an exact name selects its profile, also when it contains `*`, `?` or `[`
|
|
3383
|
+
profile_names = [name]
|
|
3384
|
+
else:
|
|
3385
|
+
# a pattern in `inherits` does not match the profile that has it
|
|
3386
|
+
profile_names = [p for p in profiles.keys() if p != parent and fnmatch.fnmatchcase(p, name)]
|
|
3161
3387
|
|
|
3162
3388
|
if not profile_names:
|
|
3163
|
-
|
|
3164
|
-
if error_callback is None:
|
|
3165
|
-
raise ValueError(message)
|
|
3166
|
-
|
|
3167
|
-
error_callback(message)
|
|
3389
|
+
report(UnknownProfile(name, inherited_by=parent, default=from_default_profiles and parent is None))
|
|
3168
3390
|
return
|
|
3169
3391
|
|
|
3392
|
+
parent_profiles = {(parent, profiles[parent])} if parent is not None else None
|
|
3393
|
+
|
|
3170
3394
|
for v in profile_names:
|
|
3395
|
+
if v in path:
|
|
3396
|
+
raise ProfileInheritanceError((*path[path.index(v) :], v))
|
|
3397
|
+
|
|
3171
3398
|
p = profiles[v]
|
|
3172
3399
|
|
|
3173
3400
|
if p.inherits:
|
|
3174
3401
|
if isinstance(p.inherits, list):
|
|
3175
3402
|
for i in p.inherits:
|
|
3176
|
-
select(str(i),
|
|
3403
|
+
select(str(i), (*path, v))
|
|
3177
3404
|
else:
|
|
3178
|
-
select(str(p.inherits),
|
|
3405
|
+
select(str(p.inherits), (*path, v))
|
|
3179
3406
|
|
|
3180
3407
|
if v in result:
|
|
3181
3408
|
if parent_profiles is None:
|
|
@@ -3198,14 +3425,21 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3198
3425
|
*names: str,
|
|
3199
3426
|
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
|
3200
3427
|
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
|
3428
|
+
unknown_profile_callback: Optional[Callable[[UnknownProfile], None]] = None,
|
|
3201
3429
|
) -> RobotBaseProfile:
|
|
3202
|
-
return self.combine_profiles_ex(
|
|
3430
|
+
return self.combine_profiles_ex(
|
|
3431
|
+
*names,
|
|
3432
|
+
verbose_callback=verbose_callback,
|
|
3433
|
+
error_callback=error_callback,
|
|
3434
|
+
unknown_profile_callback=unknown_profile_callback,
|
|
3435
|
+
)[0]
|
|
3203
3436
|
|
|
3204
3437
|
def combine_profiles_ex(
|
|
3205
3438
|
self,
|
|
3206
3439
|
*names: str,
|
|
3207
3440
|
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
|
3208
3441
|
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
|
|
3442
|
+
unknown_profile_callback: Optional[Callable[[UnknownProfile], None]] = None,
|
|
3209
3443
|
) -> Tuple[RobotBaseProfile, Dict[str, RobotProfile], List[str]]:
|
|
3210
3444
|
enabled_profiles = []
|
|
3211
3445
|
type_hints = get_type_hints(RobotBaseProfile)
|
|
@@ -3226,7 +3460,10 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3226
3460
|
)
|
|
3227
3461
|
|
|
3228
3462
|
selected_profiles = self._select_profiles(
|
|
3229
|
-
*names,
|
|
3463
|
+
*names,
|
|
3464
|
+
verbose_callback=verbose_callback,
|
|
3465
|
+
error_callback=error_callback,
|
|
3466
|
+
unknown_profile_callback=unknown_profile_callback,
|
|
3230
3467
|
)
|
|
3231
3468
|
if verbose_callback:
|
|
3232
3469
|
if selected_profiles:
|
|
@@ -3234,47 +3471,54 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3234
3471
|
else:
|
|
3235
3472
|
verbose_callback("No profiles selected.")
|
|
3236
3473
|
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
):
|
|
3240
|
-
try:
|
|
3241
|
-
if not profile.is_enabled():
|
|
3242
|
-
if verbose_callback:
|
|
3243
|
-
verbose_callback(f'Skipping profile "{profile_name}" because it\'s disabled.')
|
|
3244
|
-
continue
|
|
3245
|
-
except EvaluationError as e:
|
|
3246
|
-
message = f"Error evaluating 'enabled' condition for profile '{profile_name}': {e}"
|
|
3474
|
+
# whether a selected profile is enabled; None when its condition has an error, which is reported only once
|
|
3475
|
+
enabled_states: Dict[str, Optional[bool]] = {}
|
|
3247
3476
|
|
|
3248
|
-
|
|
3249
|
-
|
|
3477
|
+
def is_enabled(name: str) -> Optional[bool]:
|
|
3478
|
+
if name not in enabled_states:
|
|
3479
|
+
try:
|
|
3480
|
+
enabled_states[name] = selected_profiles[name][0].is_enabled()
|
|
3481
|
+
except EvaluationError as e:
|
|
3482
|
+
message = f"Error evaluating 'enabled' condition for profile '{name}': {e}"
|
|
3250
3483
|
|
|
3251
|
-
|
|
3252
|
-
|
|
3484
|
+
if error_callback is None:
|
|
3485
|
+
raise ValueError(message) from e
|
|
3253
3486
|
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
for parent_profile in parent_profiles:
|
|
3258
|
-
try:
|
|
3259
|
-
if not parent_profile[1].is_enabled():
|
|
3260
|
-
disabled_profiles.append(parent_profile[0])
|
|
3261
|
-
except EvaluationError as e:
|
|
3262
|
-
message = f"Error evaluating 'enabled' condition for profile '{parent_profile[0]}': {e}"
|
|
3487
|
+
error_callback(message)
|
|
3488
|
+
enabled_states[name] = None
|
|
3489
|
+
return enabled_states[name]
|
|
3263
3490
|
|
|
3264
|
-
|
|
3265
|
-
|
|
3491
|
+
# a profile is used when it is enabled and selected itself, or when one of the profiles that inherit it is
|
|
3492
|
+
# used, so that a disabled profile brings none of the profiles it inherits from along, at any depth
|
|
3493
|
+
used_states: Dict[str, bool] = {}
|
|
3266
3494
|
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3495
|
+
def is_used(name: str) -> bool:
|
|
3496
|
+
if name not in used_states:
|
|
3497
|
+
parents = selected_profiles[name][1]
|
|
3498
|
+
used_states[name] = bool(is_enabled(name)) and (
|
|
3499
|
+
parents is None or any(is_used(parent_name) for parent_name, _ in parents)
|
|
3500
|
+
)
|
|
3501
|
+
return used_states[name]
|
|
3270
3502
|
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3503
|
+
for profile_name, (profile, parent_profiles) in sorted(
|
|
3504
|
+
selected_profiles.items(), key=lambda x: x[1][0].precedence or 0
|
|
3505
|
+
):
|
|
3506
|
+
profile_enabled = is_enabled(profile_name)
|
|
3507
|
+
if profile_enabled is None:
|
|
3508
|
+
continue
|
|
3509
|
+
|
|
3510
|
+
if not profile_enabled:
|
|
3511
|
+
if verbose_callback:
|
|
3512
|
+
verbose_callback(f'Skipping profile "{profile_name}" because it\'s disabled.')
|
|
3513
|
+
continue
|
|
3514
|
+
|
|
3515
|
+
if not is_used(profile_name):
|
|
3516
|
+
if verbose_callback:
|
|
3517
|
+
verbose_callback(
|
|
3518
|
+
f"Skipping inherited profile '{profile_name}' because none of the profiles that inherit it"
|
|
3519
|
+
f" is used: {', '.join(sorted(name for name, _ in parent_profiles or ()))}."
|
|
3520
|
+
)
|
|
3521
|
+
continue
|
|
3278
3522
|
|
|
3279
3523
|
if verbose_callback:
|
|
3280
3524
|
verbose_callback(f'Using profile "{profile_name}".')
|
|
@@ -3290,7 +3534,8 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3290
3534
|
if profile.detached:
|
|
3291
3535
|
result = RobotBaseProfile()
|
|
3292
3536
|
|
|
3293
|
-
|
|
3537
|
+
# A setting replaces the value before it and its `extend-` setting adds to it, so the settings come first.
|
|
3538
|
+
for f in sorted(dataclasses.fields(profile), key=lambda f: f.name.startswith(EXTEND_PREFIX)):
|
|
3294
3539
|
if f.name.startswith(EXTEND_PREFIX):
|
|
3295
3540
|
new = self._verified_value(
|
|
3296
3541
|
f.name,
|
|
@@ -3315,6 +3560,12 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3315
3560
|
setattr(result, f.name[EXTEND_PREFIX_LEN:], [*old, *new])
|
|
3316
3561
|
elif isinstance(old, tuple):
|
|
3317
3562
|
setattr(result, f.name[EXTEND_PREFIX_LEN:], (*old, *new))
|
|
3563
|
+
elif isinstance(old, BaseOptions):
|
|
3564
|
+
# a table such as `[rebot]`, changed on a copy because `old` may be the configuration's own;
|
|
3565
|
+
# it keeps the `extend-` settings it doesn't set itself, see `add_options`
|
|
3566
|
+
changed = dataclasses.replace(old)
|
|
3567
|
+
changed.add_options(new, True)
|
|
3568
|
+
setattr(result, f.name[EXTEND_PREFIX_LEN:], changed)
|
|
3318
3569
|
else:
|
|
3319
3570
|
setattr(result, f.name[EXTEND_PREFIX_LEN:], new)
|
|
3320
3571
|
continue
|
|
@@ -3322,9 +3573,6 @@ class RobotConfig(RobotExtendBaseProfile):
|
|
|
3322
3573
|
if f.name not in base_field_names:
|
|
3323
3574
|
continue
|
|
3324
3575
|
|
|
3325
|
-
if getattr(profile, f"{EXTEND_PREFIX}_{f.name}", None) is not None:
|
|
3326
|
-
continue
|
|
3327
|
-
|
|
3328
3576
|
new = self._verified_value(
|
|
3329
3577
|
f.name,
|
|
3330
3578
|
getattr(profile, f.name),
|