behave-model 1.0.0__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.
- behave_model/__init__.py +164 -0
- behave_model/exceptions.py +28 -0
- behave_model/model/__init__.py +34 -0
- behave_model/model/background.py +39 -0
- behave_model/model/comment.py +23 -0
- behave_model/model/docstring.py +29 -0
- behave_model/model/examples.py +44 -0
- behave_model/model/feature.py +113 -0
- behave_model/model/location.py +30 -0
- behave_model/model/metadata.py +23 -0
- behave_model/model/project.py +208 -0
- behave_model/model/rule.py +83 -0
- behave_model/model/scenario.py +56 -0
- behave_model/model/scenario_outline.py +69 -0
- behave_model/model/step.py +57 -0
- behave_model/model/table.py +93 -0
- behave_model/model/tag.py +31 -0
- behave_model/parser/__init__.py +13 -0
- behave_model/parser/adapter.py +240 -0
- behave_model/parser/loader.py +82 -0
- behave_model/parser/parser.py +46 -0
- behave_model/queries/__init__.py +25 -0
- behave_model/queries/query.py +155 -0
- behave_model/serializers/__init__.py +11 -0
- behave_model/serializers/dict_serializer.py +165 -0
- behave_model/serializers/json_serializer.py +41 -0
- behave_model/serializers/pretty_printer.py +191 -0
- behave_model/transformations/__init__.py +23 -0
- behave_model/transformations/transform.py +192 -0
- behave_model/utils/__init__.py +15 -0
- behave_model/utils/utils.py +29 -0
- behave_model/validation/__init__.py +23 -0
- behave_model/validation/validator.py +228 -0
- behave_model/visitors/__init__.py +13 -0
- behave_model/visitors/visitor.py +142 -0
- behave_model-1.0.0.dist-info/METADATA +273 -0
- behave_model-1.0.0.dist-info/RECORD +39 -0
- behave_model-1.0.0.dist-info/WHEEL +4 -0
- behave_model-1.0.0.dist-info/licenses/LICENSE +21 -0
behave_model/__init__.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""behave-model — Canonical object model for Behave projects.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
|
|
5
|
+
from behave_model import (
|
|
6
|
+
load_project,
|
|
7
|
+
load_feature,
|
|
8
|
+
Project,
|
|
9
|
+
Feature,
|
|
10
|
+
Scenario,
|
|
11
|
+
ScenarioOutline,
|
|
12
|
+
Step,
|
|
13
|
+
Table,
|
|
14
|
+
Tag,
|
|
15
|
+
Background,
|
|
16
|
+
Examples,
|
|
17
|
+
DocString,
|
|
18
|
+
Comment,
|
|
19
|
+
Location,
|
|
20
|
+
Metadata,
|
|
21
|
+
Visitor,
|
|
22
|
+
Validator,
|
|
23
|
+
DictSerializer,
|
|
24
|
+
JsonSerializer,
|
|
25
|
+
PrettyPrinter,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
project = load_project("features/")
|
|
29
|
+
print(project.statistics())
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from behave_model.exceptions import (
|
|
33
|
+
BehaveModelError,
|
|
34
|
+
ParseError,
|
|
35
|
+
SerializationError,
|
|
36
|
+
TransformationError,
|
|
37
|
+
ValidationError,
|
|
38
|
+
)
|
|
39
|
+
from behave_model.model import (
|
|
40
|
+
Background,
|
|
41
|
+
Comment,
|
|
42
|
+
DocString,
|
|
43
|
+
Examples,
|
|
44
|
+
Feature,
|
|
45
|
+
Location,
|
|
46
|
+
Metadata,
|
|
47
|
+
Project,
|
|
48
|
+
Rule,
|
|
49
|
+
Scenario,
|
|
50
|
+
ScenarioOutline,
|
|
51
|
+
Step,
|
|
52
|
+
Table,
|
|
53
|
+
TableRow,
|
|
54
|
+
Tag,
|
|
55
|
+
)
|
|
56
|
+
from behave_model.parser import (
|
|
57
|
+
BehaveParserAdapter,
|
|
58
|
+
load_feature,
|
|
59
|
+
load_project,
|
|
60
|
+
parse_feature,
|
|
61
|
+
parse_project,
|
|
62
|
+
)
|
|
63
|
+
from behave_model.queries import (
|
|
64
|
+
find_feature,
|
|
65
|
+
find_features_with_tag,
|
|
66
|
+
find_outlines,
|
|
67
|
+
find_plain_scenarios,
|
|
68
|
+
find_scenarios,
|
|
69
|
+
find_scenarios_with_tag,
|
|
70
|
+
find_steps,
|
|
71
|
+
find_tag,
|
|
72
|
+
)
|
|
73
|
+
from behave_model.serializers import DictSerializer, JsonSerializer, PrettyPrinter
|
|
74
|
+
from behave_model.transformations import (
|
|
75
|
+
add_tag_to_feature,
|
|
76
|
+
normalize_whitespace,
|
|
77
|
+
remove_tag,
|
|
78
|
+
rename_scenario,
|
|
79
|
+
rename_tag,
|
|
80
|
+
sort_features,
|
|
81
|
+
sort_scenarios,
|
|
82
|
+
sort_tags,
|
|
83
|
+
)
|
|
84
|
+
from behave_model.validation import (
|
|
85
|
+
DuplicateFeatureNamesRule,
|
|
86
|
+
DuplicateScenarioNamesRule,
|
|
87
|
+
EmptyFeatureRule,
|
|
88
|
+
EmptyScenarioRule,
|
|
89
|
+
InvalidTableRule,
|
|
90
|
+
ValidationIssue,
|
|
91
|
+
ValidationRule,
|
|
92
|
+
Validator,
|
|
93
|
+
)
|
|
94
|
+
from behave_model.visitors import CollectingVisitor, CountingVisitor, Visitor
|
|
95
|
+
|
|
96
|
+
__version__ = "0.1.0"
|
|
97
|
+
|
|
98
|
+
__all__ = [
|
|
99
|
+
# model
|
|
100
|
+
"Background",
|
|
101
|
+
"Comment",
|
|
102
|
+
"DocString",
|
|
103
|
+
"Examples",
|
|
104
|
+
"Feature",
|
|
105
|
+
"Location",
|
|
106
|
+
"Metadata",
|
|
107
|
+
"Project",
|
|
108
|
+
"Rule",
|
|
109
|
+
"Scenario",
|
|
110
|
+
"ScenarioOutline",
|
|
111
|
+
"Step",
|
|
112
|
+
"Table",
|
|
113
|
+
"TableRow",
|
|
114
|
+
"Tag",
|
|
115
|
+
# parser
|
|
116
|
+
"BehaveParserAdapter",
|
|
117
|
+
"load_feature",
|
|
118
|
+
"load_project",
|
|
119
|
+
"parse_feature",
|
|
120
|
+
"parse_project",
|
|
121
|
+
# queries
|
|
122
|
+
"find_feature",
|
|
123
|
+
"find_features_with_tag",
|
|
124
|
+
"find_outlines",
|
|
125
|
+
"find_plain_scenarios",
|
|
126
|
+
"find_scenarios",
|
|
127
|
+
"find_scenarios_with_tag",
|
|
128
|
+
"find_steps",
|
|
129
|
+
"find_tag",
|
|
130
|
+
# visitors
|
|
131
|
+
"CollectingVisitor",
|
|
132
|
+
"CountingVisitor",
|
|
133
|
+
"Visitor",
|
|
134
|
+
# serializers
|
|
135
|
+
"DictSerializer",
|
|
136
|
+
"JsonSerializer",
|
|
137
|
+
"PrettyPrinter",
|
|
138
|
+
# transformations
|
|
139
|
+
"add_tag_to_feature",
|
|
140
|
+
"normalize_whitespace",
|
|
141
|
+
"remove_tag",
|
|
142
|
+
"rename_scenario",
|
|
143
|
+
"rename_tag",
|
|
144
|
+
"sort_features",
|
|
145
|
+
"sort_scenarios",
|
|
146
|
+
"sort_tags",
|
|
147
|
+
# validation
|
|
148
|
+
"DuplicateFeatureNamesRule",
|
|
149
|
+
"DuplicateScenarioNamesRule",
|
|
150
|
+
"EmptyFeatureRule",
|
|
151
|
+
"EmptyScenarioRule",
|
|
152
|
+
"InvalidTableRule",
|
|
153
|
+
"ValidationIssue",
|
|
154
|
+
"ValidationRule",
|
|
155
|
+
"Validator",
|
|
156
|
+
# exceptions
|
|
157
|
+
"BehaveModelError",
|
|
158
|
+
"ParseError",
|
|
159
|
+
"SerializationError",
|
|
160
|
+
"TransformationError",
|
|
161
|
+
"ValidationError",
|
|
162
|
+
# version
|
|
163
|
+
"__version__",
|
|
164
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Exception hierarchy for behave-model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BehaveModelError(Exception):
|
|
7
|
+
"""Base exception for all behave-model errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ParseError(BehaveModelError):
|
|
11
|
+
"""Raised when a feature file cannot be parsed."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ValidationError(BehaveModelError):
|
|
15
|
+
"""Raised when a validation rule fails."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, *, rule: str = "", location: str = "") -> None:
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.rule = rule
|
|
20
|
+
self.location = location
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TransformationError(BehaveModelError):
|
|
24
|
+
"""Raised when a transformation cannot be applied."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SerializationError(BehaveModelError):
|
|
28
|
+
"""Raised when serialization fails."""
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Domain model package — re-exports all model classes."""
|
|
2
|
+
|
|
3
|
+
from behave_model.model.background import Background
|
|
4
|
+
from behave_model.model.comment import Comment
|
|
5
|
+
from behave_model.model.docstring import DocString
|
|
6
|
+
from behave_model.model.examples import Examples
|
|
7
|
+
from behave_model.model.feature import Feature
|
|
8
|
+
from behave_model.model.location import Location
|
|
9
|
+
from behave_model.model.metadata import Metadata
|
|
10
|
+
from behave_model.model.project import Project
|
|
11
|
+
from behave_model.model.rule import Rule
|
|
12
|
+
from behave_model.model.scenario import Scenario
|
|
13
|
+
from behave_model.model.scenario_outline import ScenarioOutline
|
|
14
|
+
from behave_model.model.step import Step
|
|
15
|
+
from behave_model.model.table import Table, TableRow
|
|
16
|
+
from behave_model.model.tag import Tag
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Background",
|
|
20
|
+
"Comment",
|
|
21
|
+
"DocString",
|
|
22
|
+
"Examples",
|
|
23
|
+
"Feature",
|
|
24
|
+
"Location",
|
|
25
|
+
"Metadata",
|
|
26
|
+
"Project",
|
|
27
|
+
"Rule",
|
|
28
|
+
"Scenario",
|
|
29
|
+
"ScenarioOutline",
|
|
30
|
+
"Step",
|
|
31
|
+
"Table",
|
|
32
|
+
"TableRow",
|
|
33
|
+
"Tag",
|
|
34
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Background model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from behave_model.model.location import Location
|
|
9
|
+
from behave_model.model.step import Step
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from behave_model.visitors.visitor import Visitor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Background:
|
|
17
|
+
"""A Background block that runs before every scenario in a feature.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
name: Optional background name.
|
|
21
|
+
steps: Steps that execute before each scenario.
|
|
22
|
+
location: Source location.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
name: str = ""
|
|
26
|
+
steps: list[Step] = field(default_factory=list)
|
|
27
|
+
location: Location = field(default_factory=Location)
|
|
28
|
+
|
|
29
|
+
def __iter__(self):
|
|
30
|
+
return iter(self.steps)
|
|
31
|
+
|
|
32
|
+
def __len__(self) -> int:
|
|
33
|
+
return len(self.steps)
|
|
34
|
+
|
|
35
|
+
def accept(self, visitor: "Visitor") -> None:
|
|
36
|
+
"""Accept a visitor."""
|
|
37
|
+
visitor.visit_background(self)
|
|
38
|
+
for step in self.steps:
|
|
39
|
+
step.accept(visitor)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Comment model preserving source-file comments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from behave_model.model.location import Location
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Comment:
|
|
12
|
+
"""A comment extracted from a feature file.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
text: The raw comment text including the ``#`` prefix.
|
|
16
|
+
location: Source location of the comment.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
text: str
|
|
20
|
+
location: Location = Location()
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
return self.text
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""DocString (multi-line text) model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
from behave_model.model.location import Location
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class DocString:
|
|
12
|
+
"""A docstring attached to a step.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
content: The raw text content.
|
|
16
|
+
content_type: Optional content-type hint (e.g. ``json``, ``xml``).
|
|
17
|
+
delimiter: The delimiter used (``\"\"\"`` or `````).
|
|
18
|
+
location: Source location.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
content: str = ""
|
|
22
|
+
content_type: str = ""
|
|
23
|
+
delimiter: str = '"""'
|
|
24
|
+
location: Location = field(default_factory=Location)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def lines(self) -> list[str]:
|
|
28
|
+
"""Return the content split into lines."""
|
|
29
|
+
return self.content.splitlines()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Examples model for scenario outlines."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from behave_model.model.location import Location
|
|
9
|
+
from behave_model.model.table import Table
|
|
10
|
+
from behave_model.model.tag import Tag
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from behave_model.visitors.visitor import Visitor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Examples:
|
|
18
|
+
"""An Examples table attached to a ScenarioOutline.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
name: Optional name for the examples block.
|
|
22
|
+
tags: Tags on the examples block.
|
|
23
|
+
table: The data table containing headers and rows.
|
|
24
|
+
location: Source location.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name: str = ""
|
|
28
|
+
tags: list[Tag] = field(default_factory=list)
|
|
29
|
+
table: Table = field(default_factory=Table)
|
|
30
|
+
location: Location = field(default_factory=Location)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def headers(self) -> list[str]:
|
|
34
|
+
"""Column headers from the underlying table."""
|
|
35
|
+
return self.table.headers
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def rows(self) -> list:
|
|
39
|
+
"""Data rows from the underlying table."""
|
|
40
|
+
return self.table.rows
|
|
41
|
+
|
|
42
|
+
def accept(self, visitor: "Visitor") -> None:
|
|
43
|
+
"""Accept a visitor."""
|
|
44
|
+
visitor.visit_examples(self)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Feature model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Iterator, Union
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from behave_model.model.background import Background
|
|
10
|
+
from behave_model.model.comment import Comment
|
|
11
|
+
from behave_model.model.location import Location
|
|
12
|
+
from behave_model.model.rule import Rule
|
|
13
|
+
from behave_model.model.scenario import Scenario
|
|
14
|
+
from behave_model.model.scenario_outline import ScenarioOutline
|
|
15
|
+
from behave_model.model.tag import Tag
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from behave_model.visitors.visitor import Visitor
|
|
19
|
+
|
|
20
|
+
# Union type for any scenario-like element
|
|
21
|
+
ScenarioLike = Union[Scenario, ScenarioOutline]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Feature:
|
|
26
|
+
"""A feature parsed from a ``.feature`` file.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
name: Feature name.
|
|
30
|
+
description: Optional description text (multi-line).
|
|
31
|
+
tags: Tags applied at the feature level.
|
|
32
|
+
background: Optional Background block (feature-level).
|
|
33
|
+
scenarios: List of Scenario and ScenarioOutline objects directly
|
|
34
|
+
under the feature (not inside a Rule).
|
|
35
|
+
rules: List of Rule blocks (Gherkin v6).
|
|
36
|
+
comments: Comments associated with the feature.
|
|
37
|
+
location: Source location.
|
|
38
|
+
language: Feature file language (default ``en``).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str = ""
|
|
42
|
+
description: str = ""
|
|
43
|
+
tags: list[Tag] = field(default_factory=list)
|
|
44
|
+
background: Background | None = None
|
|
45
|
+
scenarios: list[ScenarioLike] = field(default_factory=list)
|
|
46
|
+
rules: list[Rule] = field(default_factory=list)
|
|
47
|
+
comments: list[Comment] = field(default_factory=list)
|
|
48
|
+
location: Location = field(default_factory=Location)
|
|
49
|
+
language: str = "en"
|
|
50
|
+
|
|
51
|
+
def __iter__(self) -> Iterator[ScenarioLike]:
|
|
52
|
+
return iter(self.scenarios)
|
|
53
|
+
|
|
54
|
+
def __len__(self) -> int:
|
|
55
|
+
return len(self.scenarios)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def tag_names(self) -> list[str]:
|
|
59
|
+
"""Return tag names as strings."""
|
|
60
|
+
return [t.name for t in self.tags]
|
|
61
|
+
|
|
62
|
+
def has_tag(self, name: str) -> bool:
|
|
63
|
+
"""Check whether the feature has a given tag."""
|
|
64
|
+
return any(t.name == name for t in self.tags)
|
|
65
|
+
|
|
66
|
+
def outlines(self) -> list[ScenarioOutline]:
|
|
67
|
+
"""Return only scenario outlines."""
|
|
68
|
+
return [s for s in self.scenarios if isinstance(s, ScenarioOutline)]
|
|
69
|
+
|
|
70
|
+
def plain_scenarios(self) -> list[Scenario]:
|
|
71
|
+
"""Return only plain (non-outline) scenarios."""
|
|
72
|
+
return [s for s in self.scenarios if isinstance(s, Scenario)]
|
|
73
|
+
|
|
74
|
+
def all_scenarios(self) -> list[ScenarioLike]:
|
|
75
|
+
"""Return all scenario-like elements (including those inside rules)."""
|
|
76
|
+
result = list(self.scenarios)
|
|
77
|
+
for rule in self.rules:
|
|
78
|
+
result.extend(rule.scenarios)
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
def all_tags(self) -> list[Tag]:
|
|
82
|
+
"""Return all tags (feature + rule + scenario tags)."""
|
|
83
|
+
tags = list(self.tags)
|
|
84
|
+
for s in self.scenarios:
|
|
85
|
+
tags.extend(s.tags)
|
|
86
|
+
for rule in self.rules:
|
|
87
|
+
tags.extend(rule.tags)
|
|
88
|
+
for s in rule.scenarios:
|
|
89
|
+
tags.extend(s.tags)
|
|
90
|
+
return tags
|
|
91
|
+
|
|
92
|
+
def all_steps(self):
|
|
93
|
+
"""Return every step in the feature (background + scenarios + rules)."""
|
|
94
|
+
from behave_model.model.step import Step
|
|
95
|
+
|
|
96
|
+
steps: list[Step] = []
|
|
97
|
+
if self.background:
|
|
98
|
+
steps.extend(self.background.steps)
|
|
99
|
+
for s in self.scenarios:
|
|
100
|
+
steps.extend(s.steps)
|
|
101
|
+
for rule in self.rules:
|
|
102
|
+
steps.extend(rule.all_steps())
|
|
103
|
+
return steps
|
|
104
|
+
|
|
105
|
+
def accept(self, visitor: "Visitor") -> None:
|
|
106
|
+
"""Accept a visitor."""
|
|
107
|
+
visitor.visit_feature(self)
|
|
108
|
+
if self.background:
|
|
109
|
+
self.background.accept(visitor)
|
|
110
|
+
for s in self.scenarios:
|
|
111
|
+
s.accept(visitor)
|
|
112
|
+
for rule in self.rules:
|
|
113
|
+
rule.accept(visitor)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Location model representing a position in a source file."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Location:
|
|
10
|
+
"""Represents a position in a feature file.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
filename: Path to the source file.
|
|
14
|
+
line: 1-based line number.
|
|
15
|
+
column: 1-based column number (0 if unknown).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
filename: str = ""
|
|
19
|
+
line: int = 0
|
|
20
|
+
column: int = 0
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
parts: list[str] = []
|
|
24
|
+
if self.filename:
|
|
25
|
+
parts.append(self.filename)
|
|
26
|
+
if self.line:
|
|
27
|
+
parts.append(f"line {self.line}")
|
|
28
|
+
if self.column:
|
|
29
|
+
parts.append(f"column {self.column}")
|
|
30
|
+
return ", ".join(parts) if parts else "<unknown>"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Metadata model for project-level information."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Metadata:
|
|
11
|
+
"""Project-level metadata.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
created_at: When the project model was created.
|
|
15
|
+
source_path: Root path from which the project was loaded.
|
|
16
|
+
tool_version: Version of behave-model that produced this metadata.
|
|
17
|
+
extra: Free-form key-value pairs for extensions.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
21
|
+
source_path: str = ""
|
|
22
|
+
tool_version: str = "0.1.0"
|
|
23
|
+
extra: dict[str, str] = field(default_factory=dict)
|