@@ -32,10 +32,13 @@ def full_section_name(node) -> list[str]:
3232
3333class TomlWriter (configio .ConfigurationWriter ):
3434 @classmethod
35- def dump_section (cls , node ) -> list :
35+ def dump_section (cls , node ) -> list [str ]:
36+ """Dump a spec.Section node and return a list of output lines."""
3637 if " " in node ._name :
3738 raise ValueError (node ._name )
3839
40+ # "base" is all the junk/lines at the beginning of our section.
41+
3942 if node ._parent is not None :
4043 base = [f"\n [{ '.' .join (full_section_name (node )[1 :])} ]" ]
4144 else :
@@ -45,108 +48,139 @@ def dump_section(cls, node) -> list:
4548 for line in node .__doc__ .split ("\n " ):
4649 base .append (f"# { line } " )
4750
48- sorted_values : dict [int , dict [str , Any ]] = {}
51+ sorted_values : dict [int , dict [spec . ConfigurationField , Any ]] = {}
4952 for name , value in node ._value .items ():
5053 field = node ._ALL_FIELDS [name ]
5154 if field ._sorting_order not in sorted_values .keys ():
52- sorted_values [field ._sorting_order ] = {name : value }
53- continue
54- sorted_values [field ._sorting_order ][name ] = value
55+ sorted_values [field ._sorting_order ] = {}
56+ sorted_values [field ._sorting_order ][field ] = value
5557
5658 return [
57- * base ,
58- * (
59+ * base , # dump all base lines
60+ * ( # append all of these dumped fields as lines
5961 (
6062 "\n " .join (cls .dump_section (value ))
6163 if isinstance (value , spec .Section )
62- else cls .dump_field (node , name , node . _FIELD_VAR_MAP [ name ] , value )
64+ else cls .dump_field (field , value )
6365 )
6466 for sub_dict in sorted_values .values ()
65- for name , value in sub_dict .items ()
67+ for field , value in sub_dict .items ()
6668 ),
6769 ]
6870
6971 @classmethod
70- def format_value (cls , value ) -> str :
72+ def format_value (cls , field , value ) -> str :
73+ """Format individual values into properly represented strings of valid toml values."""
7174 match value :
7275 case int () | float ():
7376 return str (value )
7477 case str ():
7578 return f'"{ escape (value )} "'
7679 case list ():
77- return f"[{ ", " .join ([str (cls .format_value (inner_val )) for inner_val in value ])} ]"
80+ return f"[{ ", " .join ([str (cls .format_value (field , inner_val )) for inner_val in value ])} ]"
7881 case dict ():
79- return f"{{ { ", " .join ([f"{ key } = { cls .format_value (inner_val )} " for key , inner_val in value .items ()])} }}"
82+ return f"{{ { ", " .join ([f"{ key } = { cls .format_value (field , inner_val )} " for key , inner_val in value .items ()])} }}"
8083 case enum .Enum ():
81- return f"{ cls .format_value (value .value )} "
84+ return f"{ cls .format_value (field , value .value )} "
8285 case _:
86+ # magic method to make writing new field types possible
87+ if hasattr (value , "__write_toml_value__" ):
88+ return str (value .__write_toml_value__ (field , value ))
89+ # No known way exists to write this field:
8390 raise ValueError (value )
8491
8592 @classmethod
86- def dump_field (
87- cls , node : spec .AnyConfigField , original_name : str , field_name : str , value
88- ) -> str :
89- if isinstance (node , spec .Section ):
90- field = node .get_field (original_name )
93+ def dump_table (cls , table_node : spec .Table , value ) -> str :
94+ for name , val in value .items ():
95+ if not isinstance (val , spec .Section ):
96+ continue
97+ val ._name = name
98+ val ._parent = table_node
99+
100+ section_name = "." .join (full_section_name (table_node )[1 :])
101+
102+ return f"\n [{ section_name } ]\n { "\n " .join (cls .dumps (val ) for key , val in value .items ())} "
103+
104+ @classmethod
105+ def create_basic_field_doc (cls , field : spec .ConfigurationField ) -> str :
106+ """generates our basic field_doc"""
107+ if field ._inline_doc and field .doc :
108+ doc_comment = " "
109+ elif field .doc :
110+ doc_comment = "\n "
91111 else :
92- field = node
93- match field :
94- case spec .Table (spec .Text (), type () | spec .ConfigUnion ()) as table_node :
95- for name , val in value .items ():
96- if not isinstance (val , spec .Section ):
97- continue
98- val ._name = name
99- val ._parent = table_node
112+ return ""
113+
114+ return doc_comment + f"# { "\n # " .join (field .doc .split ("\n " ))} "
115+
116+ @classmethod
117+ def dump_enum (cls , field : spec .ConfigEnum , value ):
118+ if isinstance (value , spec .Section ):
119+ return "\n " .join (cls .dump_section (value ))
120+
121+ by_name = field ._by_name
122+ field_doc = " " if field ._inline_doc and field .doc else "\n "
123+
124+ if field .doc :
125+ field_doc += f"# { "\n # " .join (field .doc .split ("\n " ))} "
100126
101- section_name = "." .join (full_section_name (table_node )[1 :])
127+ if field ._enum .__doc__ :
128+ delimeter = "\n ## - "
129+ doc_comment = f"# { "\n # " .join (field ._enum .__doc__ .split ("\n " ))} \n #"
130+ else :
131+ delimeter = "\n # - "
132+ doc_comment = ""
133+
134+ doc_comment += f"# Available Options for { field ._name } :{ delimeter } "
135+ if by_name :
136+ doc_comment += delimeter .join (
137+ member for member in field ._enum .__members__ .keys ()
138+ )
139+ return f"{ field ._name } = { cls .format_value (field , value .name )} { field_doc } \n { doc_comment } "
140+ doc_comment += delimeter .join (
141+ str (member .value ) for member in field ._enum .__members__ .values ()
142+ )
143+ return f"{ field ._name } = { cls .format_value (field , value .value )} { field_doc } \n { doc_comment } "
102144
103- return f"\n [{ section_name } ]\n { "\n " .join (cls .dumps (val ) if isinstance (val , spec .Section ) else cls .dump_field (val , key , key , val ) for key , val in value .items ())} "
145+ @classmethod
146+ def dump_field (cls , field : spec .AnyConfigField , value ) -> str :
147+ """dump a field object given its value"""
148+ match field :
149+ case spec .Table (spec .Text (), type () | spec .ConfigUnion ()) as table_node :
150+ return cls .dump_table (table_node , value )
104151 case spec .Section ():
105- return "\n " .join (cls .dump_section (node ))
152+ return "\n " .join (cls .dump_section (field ))
106153 case spec .ConfigEnum (_, by_name ):
107- if isinstance (value , spec .Section ):
108- return "\n " .join (cls .dump_section (value ))
154+ return cls .dump_enum (field , value )
155+ case spec .ConfigurationField ():
156+ # magic method to make writing new field types possible
157+ if hasattr (field , "__write_toml_full__" ):
158+ return str (value .__write_toml_full__ (field , value ))
109159
110- field_doc = " " if field ._inline_doc and field .doc else "\n "
111-
112- if field .doc :
113- field_doc += f"# { "\n # " .join (field .doc .split ("\n " ))} "
114-
115- if field ._enum .__doc__ :
116- delimeter = "\n ## - "
117- doc_comment = f"# { "\n # " .join (field ._enum .__doc__ .split ("\n " ))} \n #"
118- else :
119- delimeter = "\n # - "
120- doc_comment = ""
121-
122- doc_comment += f"# Available Options for { field_name } :{ delimeter } "
123- if by_name :
124- doc_comment += delimeter .join (
125- member for member in field ._enum .__members__ .keys ()
126- )
127- return f"{ field_name } = { cls .format_value (value .name )} { field_doc } \n { doc_comment } "
128- doc_comment += delimeter .join (
129- str (member .value ) for member in field ._enum .__members__ .values ()
130- )
131- return f"{ field_name } = { cls .format_value (value .value )} { field_doc } \n { doc_comment } "
132- case _:
133160 if isinstance (value , spec .Section ):
134161 return "\n " .join (cls .dump_section (value ))
135- real_field = node ._ALL_FIELDS [original_name ]
136- doc_comment = (
137- " " if real_field ._inline_doc and real_field .doc else "\n "
138- )
139162
140- if real_field .doc :
141- doc_comment += f"# { "\n # " .join (real_field .doc .split ("\n " ))} "
142- return f"{ field_name } = { cls .format_value (value )} { doc_comment } "
163+ return f"{ field ._name } = { cls .format_value (field , value )} { cls .create_basic_field_doc (field )} "
164+ case _:
165+ # magic method to make writing new field types possible
166+ if hasattr (field , "__write_toml_full__" ):
167+ return str (value .__write_toml_full__ (field , value ))
168+ # No known way exists to write this field:
169+ raise ValueError (field )
143170
144171 @classmethod
145172 def dumps (cls , node ) -> str :
146173 match node :
147174 case spec .Section ():
148175 return "\n " .join (cls .dump_section (node ))
149-
176+ case spec .ConfigurationField ():
177+ # Dump passed in node to the best of our ability. This typically looks like dumping its default value
178+ if not node ._has_default :
179+ raise ValueError ("Node does not have a default value" )
180+ return cls .dump_field (
181+ node ,
182+ node ._default_value ,
183+ )
150184 case _:
151185 raise ValueError (node )
152186
@@ -161,5 +195,6 @@ def load(cls, file):
161195 return tomllib .load (f )
162196 return tomllib .load (file )
163197
164- # just alias the name
165- loads = tomllib .loads
198+ @classmethod
199+ def loads (cls , data : str ) -> dict [str , Any ]:
200+ return tomllib .loads (data )
0 commit comments