18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 | class Rust(ProjectMetadataWriter):
"""Rust config file handler parsed from Cargo.toml."""
def __init__(self, path: Path):
"""Rust config file handler parsed from Cargo.toml.
See [somesy.core.writer.ProjectMetadataWriter.__init__][].
"""
self._section = ["package"]
mappings: FieldKeyMapping = {
"maintainers": IgnoreKey(),
}
super().__init__(path, create_if_not_exists=False, direct_mappings=mappings)
def _load(self) -> None:
"""Load Cargo.toml file."""
with open(self.path) as f:
self._data = load(f)
def _validate(self) -> None:
"""Validate rust config using pydantic class.
In order to preserve toml comments and structure, tomlkit library is used.
Pydantic class only used for validation.
"""
config = dict(self._get_property([]))
logger.debug(
f"Validating config using {RustConfig.__name__}: {pretty_repr(config)}"
)
RustConfig(**config)
def save(self, path: Optional[Path] = None) -> None:
"""Save the Cargo.toml file."""
path = path or self.path
with open(path, "w") as f:
dump(self._data, f)
def _get_property(
self, key: Union[str, List[str], IgnoreKey], *, remove: bool = False, **kwargs
) -> Optional[Any]:
"""Get a property from the Cargo.toml file."""
if isinstance(key, IgnoreKey):
return None
key_path = [key] if isinstance(key, str) else key
full_path = self._section + key_path
return super()._get_property(full_path, remove=remove, **kwargs)
def _set_property(self, key: Union[str, List[str], IgnoreKey], value: Any) -> None:
"""Set a property in the Cargo.toml file."""
if isinstance(key, IgnoreKey):
return
key_path = [key] if isinstance(key, str) else key
if not value: # remove value and clean up the sub-dict
self._get_property(key_path, remove=True)
return
# get the tomlkit object of the section
dat = self._get_property([])
# dig down, create missing nested objects on the fly
curr = dat
for key in key_path[:-1]:
if key not in curr:
curr.add(key, table())
curr = curr[key]
curr[key_path[-1]] = value
@staticmethod
def _from_person(person: Person):
"""Convert project metadata person object to rust string for person format "full name <email>."""
return person.to_name_email_string()
@staticmethod
def _to_person(person_obj: str) -> Optional[Person]:
"""Parse rust person string to a Person. It has format "full name <email>." but email is optional."""
try:
return Person.from_name_email_string(person_obj)
except (ValueError, AttributeError):
return None
@classmethod
def _parse_people(cls, people: Optional[List[Any]]) -> List[Person]:
"""Return a list of Persons parsed from list of format-specific people representations. to_person can return None, so filter out None values."""
return list(filter(None, map(cls._to_person, people or [])))
@property
def keywords(self) -> Optional[List[str]]:
"""Return the keywords of the project."""
return self._get_property(self._get_key("keywords"))
@keywords.setter
def keywords(self, keywords: List[str]) -> None:
"""Set the keywords of the project."""
validated_keywords = []
for keyword in keywords:
try:
check_keyword(keyword)
validated_keywords.append(keyword)
except ValueError as e:
logger.debug(f"Invalid keyword {keyword}: {e}")
# keyword count should max 5, so delete the rest
if len(validated_keywords) > 5:
validated_keywords = validated_keywords[:5]
self._set_property(self._get_key("keywords"), validated_keywords)
def sync(self, metadata: ProjectMetadata) -> None:
"""Sync the rust config with the project metadata."""
super().sync(metadata)
# if there is a license file, remove the license field
if self._get_key("license_file"):
self.license = None
|