77import sublime
88
99
10+ def deep_merge (base : dict [str , Any ], update : dict [str , Any ]) -> None :
11+ """Recursively merge update dict with base dict."""
12+ for key , value in update .items ():
13+ if isinstance (value , dict ) and key in base and isinstance (base [key ], dict ):
14+ deep_merge (base [key ], value )
15+ else :
16+ base [key ] = deepcopy (value )
17+
18+
1019class DottedDict :
1120
1221 __slots__ = ('_d' ,)
@@ -15,6 +24,9 @@ def __init__(self, d: dict[str, Any] | None = None) -> None:
1524 """
1625 Construct a DottedDict, optionally from an existing dictionary.
1726
27+ The dots within the first-level keys (only) of the passed dict will be interpreted as nesting triggers and the
28+ resulting dict will have those transformed into nested keys.
29+
1830 :param d: An existing dictionary.
1931 """
2032 self ._d : dict [str , Any ] = {}
@@ -138,13 +150,13 @@ def update(self, d: dict[str, Any]) -> None:
138150 """
139151 Overwrite and/or add new key-value pairs to the collection.
140152
153+ The dots within the first-level keys (only) of the passed dict will be interpreted as nesting triggers and the
154+ resulting dict will have those transformed into nested keys.
155+
141156 :param d: The overriding dictionary. Can contain nested dictionaries.
142157 """
143158 for key , value in d .items ():
144- if isinstance (value , dict ):
145- self ._update_recursive (value , key )
146- else :
147- self .set (key , value )
159+ self ._merge (key , value )
148160
149161 def get_resolved (self , variables : dict [str , str ]) -> dict [str , Any ]:
150162 """
@@ -156,15 +168,29 @@ def get_resolved(self, variables: dict[str, str]) -> dict[str, Any]:
156168 """
157169 return sublime .expand_variables (self ._d , variables )
158170
159- def _update_recursive (self , current : dict [str , Any ], prefix : str ) -> None :
160- if not current or any (filter (lambda key : isinstance (key , str ) and (":" in key or "/" in key ), current .keys ())):
161- return self .set (prefix , current )
162- for key , value in current .items ():
163- path = f"{ prefix } .{ key } "
164- if isinstance (value , dict ):
165- self ._update_recursive (value , path )
166- else :
167- self .set (path , value )
171+ def _merge (self , path : str , value : Any ) -> None :
172+ """
173+ Update a value in the dictionary (merge if value is a dict).
174+
175+ :param path: The path, e.g. foo.bar.baz
176+ :param value: The value
177+ """
178+ current = self ._d
179+ keys = path .split ('.' )
180+ for i in range (0 , len (keys ) - 1 ):
181+ key = keys [i ]
182+ next_current = current .get (key )
183+ if not isinstance (next_current , dict ):
184+ next_current = {}
185+ current [key ] = next_current
186+ current = next_current
187+ last_key = keys [- 1 ]
188+ if isinstance (value , dict ):
189+ if not isinstance (current .get (last_key ), dict ):
190+ current [last_key ] = {}
191+ deep_merge (current [last_key ], value )
192+ else :
193+ current [last_key ] = value
168194
169195 def __repr__ (self ) -> str :
170196 return f"{ self .__class__ .__name__ } ({ repr (self ._d )} )"
0 commit comments