Coverage for src/somesy/julia/writer.py: 76%

59 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-04 11:35 +0000

1"""Julia writer.""" 

2 

3import logging 

4from pathlib import Path 

5 

6import tomlkit 

7from rich.pretty import pretty_repr 

8from tomlkit.items import InlineTable 

9 

10from somesy.core.models import Entity, Person, ProjectMetadata 

11from somesy.core.writer import ProjectMetadataWriter 

12 

13from .models import JuliaConfig 

14 

15logger = logging.getLogger("somesy") 

16 

17 

18class Julia(ProjectMetadataWriter): 

19 """Julia config file handler parsed from Project.toml.""" 

20 

21 def __init__( 

22 self, 

23 path: Path, 

24 pass_validation: bool | None = False, 

25 ): 

26 """Julia config file handler parsed from Project.toml. 

27 

28 See [somesy.core.writer.ProjectMetadataWriter.__init__][]. 

29 """ 

30 super().__init__( 

31 path, 

32 create_if_not_exists=False, 

33 pass_validation=pass_validation, 

34 ) 

35 

36 def _load(self) -> None: 

37 """Load Project.toml file.""" 

38 with open(self.path) as f: 

39 self._data = tomlkit.load(f) 

40 

41 def _validate(self) -> None: 

42 """Validate poetry config using pydantic class. 

43 

44 In order to preserve toml comments and structure, tomlkit library is used. 

45 Pydantic class only used for validation. 

46 """ 

47 if self.pass_validation: 

48 return 

49 config = dict(self._get_property([])) 

50 logger.debug( 

51 f"Validating config using {JuliaConfig.__name__}: {pretty_repr(config)}" 

52 ) 

53 JuliaConfig(**config) 

54 

55 def save(self, path: Path | None = None) -> None: 

56 """Save the julia file.""" 

57 path = path or self.path 

58 if "description" in self._data and "\n" in self._data["description"]: 

59 self._data["description"] = tomlkit.string( 

60 self._data["description"], multiline=True 

61 ) 

62 

63 # Handle arrays with proper formatting 

64 for key, value in self._data.items(): 

65 if isinstance(value, list): 

66 array = tomlkit.array() 

67 array.extend(value) 

68 array.multiline(True) 

69 # Ensure whitespace after commas in inline tables 

70 for item in array: 

71 if isinstance(item, InlineTable): 

72 # Rebuild the inline table with desired formatting 

73 formatted_item = tomlkit.inline_table() 

74 for k, v in item.value.items(): 

75 formatted_item[k] = v 

76 formatted_item.trivia.trail = " " # Add space after each comma 

77 array[array.index(item)] = formatted_item 

78 self._data[key] = array 

79 else: 

80 self._data[key] = value 

81 

82 with open(path, "w") as f: 

83 tomlkit.dump(self._data, f) 

84 

85 @staticmethod 

86 def _from_person(person: Person | Entity): 

87 """Convert project metadata person object to a name+email string.""" 

88 return person.to_name_email_string() 

89 

90 @staticmethod 

91 def _to_person(person_obj: str) -> Person | Entity | None: 

92 """Convert from free string to person or entity object.""" 

93 try: 

94 return Person.from_name_email_string(person_obj) 

95 except (ValueError, AttributeError): 

96 logger.info(f"Cannot convert {person_obj} to Person object, trying Entity.") 

97 

98 try: 

99 return Entity.from_name_email_string(person_obj) 

100 except (ValueError, AttributeError): 

101 logger.warning(f"Cannot convert {person_obj} to Entity.") 

102 return None 

103 

104 def sync(self, metadata: ProjectMetadata) -> None: 

105 """Sync output file with other metadata files.""" 

106 # overridden to not sync fields that are not present in the Project.toml file 

107 self.name = metadata.name 

108 self.version = metadata.version 

109 

110 self._sync_authors(metadata)