Skip to content

rust

Rust module.

Rust

Bases: ProjectMetadataWriter

Rust config file handler parsed from Cargo.toml.

Source code in src/somesy/rust/writer.py
 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
class Rust(ProjectMetadataWriter):
    """Rust config file handler parsed from Cargo.toml."""

    def __init__(
        self,
        path: Path,
        pass_validation: Optional[bool] = False,
    ):
        """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,
            pass_validation=pass_validation,
        )

    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.
        """
        if self.pass_validation:
            return
        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

        if "description" in self._data["package"]:
            if "\n" in self._data["package"]["description"]:
                self._data["package"]["description"] = string(
                    self._data["package"]["description"], multiline=True
                )

        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]

        # Handle arrays with proper formatting
        if isinstance(value, list):
            arr = array()
            arr.extend(value)
            arr.multiline(True)
            # Ensure whitespace after commas in inline tables
            for item in arr:
                if isinstance(item, items.InlineTable):
                    # Rebuild the inline table with desired formatting
                    formatted_item = inline_table()
                    for k, v in item.value.items():
                        formatted_item[k] = v
                    formatted_item.trivia.trail = " "  # Add space after each comma
                    arr[arr.index(item)] = formatted_item
            curr[key_path[-1]] = arr
        else:
            curr[key_path[-1]] = value

    @staticmethod
    def _from_person(person: Union[Person, Entity]):
        """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: str) -> Optional[Union[Person, Entity]]:
        """Parse rust person string to a Person. It has format "full name <email>." but email is optional.

        Since there is no way to know whether this entry is a person or an entity, we will directly convert to Person.
        """
        try:
            return Person.from_name_email_string(person)
        except (ValueError, AttributeError):
            logger.info(f"Cannot convert {person} to Person object, trying Entity.")

        try:
            return Entity.from_name_email_string(person)
        except (ValueError, AttributeError):
            logger.warning(f"Cannot convert {person} to Entity.")
            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

keywords property writable

keywords: Optional[List[str]]

Return the keywords of the project.

__init__

__init__(
    path: Path, pass_validation: Optional[bool] = False
)

Rust config file handler parsed from Cargo.toml.

See somesy.core.writer.ProjectMetadataWriter.init.

Source code in src/somesy/rust/writer.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def __init__(
    self,
    path: Path,
    pass_validation: Optional[bool] = False,
):
    """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,
        pass_validation=pass_validation,
    )

save

save(path: Optional[Path] = None) -> None

Save the Cargo.toml file.

Source code in src/somesy/rust/writer.py
60
61
62
63
64
65
66
67
68
69
70
71
def save(self, path: Optional[Path] = None) -> None:
    """Save the Cargo.toml file."""
    path = path or self.path

    if "description" in self._data["package"]:
        if "\n" in self._data["package"]["description"]:
            self._data["package"]["description"] = string(
                self._data["package"]["description"], multiline=True
            )

    with open(path, "w") as f:
        dump(self._data, f)

sync

sync(metadata: ProjectMetadata) -> None

Sync the rust config with the project metadata.

Source code in src/somesy/rust/writer.py
169
170
171
172
173
174
175
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