1 |
Author: grobian |
2 |
Date: 2008-10-03 16:50:31 +0000 (Fri, 03 Oct 2008) |
3 |
New Revision: 11614 |
4 |
|
5 |
Modified: |
6 |
main/branches/prefix/bin/glsa-check |
7 |
main/branches/prefix/doc/config/sets.docbook |
8 |
main/branches/prefix/pym/_emerge/__init__.py |
9 |
main/branches/prefix/pym/portage/glsa.py |
10 |
main/branches/prefix/pym/portage/sets/__init__.py |
11 |
Log: |
12 |
Merged from trunk -r11580:11593 |
13 |
|
14 |
| 11581 | implement set arguments to reconfigure and create package | |
15 |
| genone | sets on the commandline | |
16 |
|
17 |
| 11582 | disable redefintion errors when updating the set definitions | |
18 |
| genone | | |
19 |
|
20 |
| 11583 | Properly process set arguments inside set expressions | |
21 |
| genone | | |
22 |
|
23 |
| 11584 | Add operator logic to sets.conf | |
24 |
| genone | | |
25 |
|
26 |
| 11585 | use ignorelist to avoid potential infite loop | |
27 |
| genone | | |
28 |
|
29 |
| 11586 | update set config documentation to include extend, remove and | |
30 |
| genone | intersect options | |
31 |
|
32 |
| 11587 | document AgeSet handler class | |
33 |
| genone | | |
34 |
|
35 |
| 11588 | account for DTD changes wrt 'revised' element (patch by | |
36 |
| genone | Robert Buchholz <rbu@g.o>) | |
37 |
|
38 |
| 11589 | also accept glsa-2.dtd as valid for GLSAs (patch by Robert | |
39 |
| genone | Buchholz <rbu@g.o>) | |
40 |
|
41 |
| 11590 | print dates in a consistent format (patch by Robert Buchholz | |
42 |
| genone | <rbu@g.o>) | |
43 |
|
44 |
| 11591 | sort summarylist output (patch by Robert Buchholz | |
45 |
| genone | <rbu@g.o>) | |
46 |
|
47 |
| 11592 | use summarylist for output of test mode when --verbose is | |
48 |
| genone | given (patch by Robert Buchholz <rbu@g.o>) | |
49 |
|
50 |
| 11593 | add support for slot dependencies (original patch by Robert | |
51 |
| genone | Buchholz <rbu@g.o>) | |
52 |
|
53 |
|
54 |
Modified: main/branches/prefix/bin/glsa-check |
55 |
=================================================================== |
56 |
--- main/branches/prefix/bin/glsa-check 2008-10-02 16:57:45 UTC (rev 11613) |
57 |
+++ main/branches/prefix/bin/glsa-check 2008-10-03 16:50:31 UTC (rev 11614) |
58 |
@@ -169,6 +169,7 @@ |
59 |
fd2.write(green("[U]")+" means the system is not affected and\n") |
60 |
fd2.write(red("[N]")+" indicates that the system might be affected.\n\n") |
61 |
|
62 |
+ myglsalist.sort() |
63 |
for myid in myglsalist: |
64 |
try: |
65 |
myglsa = Glsa(myid, portage.settings, vardb, portdb) |
66 |
@@ -275,13 +276,13 @@ |
67 |
sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) |
68 |
continue |
69 |
if myglsa.isVulnerable(): |
70 |
- if verbose: |
71 |
- outputlist.append(str(myglsa.nr)+" ( "+myglsa.title+" ) ") |
72 |
- else: |
73 |
- outputlist.append(str(myglsa.nr)) |
74 |
+ outputlist.append(str(myglsa.nr)) |
75 |
if len(outputlist) > 0: |
76 |
sys.stderr.write("This system is affected by the following GLSAs:\n") |
77 |
- sys.stdout.write("\n".join(outputlist)+"\n") |
78 |
+ if verbose: |
79 |
+ summarylist(outputlist) |
80 |
+ else: |
81 |
+ sys.stdout.write("\n".join(outputlist)+"\n") |
82 |
else: |
83 |
sys.stderr.write("This system is not affected by any of the listed GLSAs\n") |
84 |
sys.exit(0) |
85 |
|
86 |
Modified: main/branches/prefix/doc/config/sets.docbook |
87 |
=================================================================== |
88 |
--- main/branches/prefix/doc/config/sets.docbook 2008-10-02 16:57:45 UTC (rev 11613) |
89 |
+++ main/branches/prefix/doc/config/sets.docbook 2008-10-03 16:50:31 UTC (rev 11614) |
90 |
@@ -49,12 +49,21 @@ |
91 |
isn't stricly required, but it should always be used as the default |
92 |
handler might be changed in future versions.</para></footnote>. |
93 |
That option defines which handler class should be used to |
94 |
- create the set. Other universal options available for single sets are |
95 |
- <varname>name</varname> (which is usually not needed as the name |
96 |
+ create the set. Other universal options available for single sets are: |
97 |
+ <itemizedlist> |
98 |
+ <listitem><varname>name</varname> (which is usually not needed as the name |
99 |
of the set is generated from the section name if <varname>name</varname> |
100 |
- is missing) and <varname>world-candidate</varname>, which determines if |
101 |
- given package should be added to the <varname>world</varname> set. Some |
102 |
- handler classes might require additional options for their configuration, |
103 |
+ is missing)</listitem> |
104 |
+ <listitem><varname>world-candidate</varname>, which determines if |
105 |
+ given package should be added to the <varname>world</varname> set</listitem> |
106 |
+ <listitem><varname>extend</varname> to include the contents of other package sets |
107 |
+ </listitem> |
108 |
+ <listitem><varname>remove</varname> to remove the contents of other package sets |
109 |
+ </listitem> |
110 |
+ <listitem><varname>intersect</varname> to only include packages that are also |
111 |
+ included in one or more other package sets</listitem> |
112 |
+ </itemizedlist> |
113 |
+ Some handler classes might require additional options for their configuration, |
114 |
these will be covered later in this chapter. |
115 |
</para> |
116 |
<para> |
117 |
@@ -83,8 +92,10 @@ |
118 |
sets each section still requires the <varname>class</varname> option, |
119 |
but to indicate that the section should generate multiple sets it's |
120 |
also necessary to set the <varname>multiset</varname> option to |
121 |
- <parameter>true</parameter>. The <varname>world-candidate</varname> |
122 |
- option is also supported like with single sets. |
123 |
+ <parameter>true</parameter>. The <varname>world-candidate</varname>, |
124 |
+ <varname>extend</varname>, <varname>remove</varname> and |
125 |
+ <varname>intersect</varname> options are also supported like with |
126 |
+ single sets (they'll apply to all sets generated by the section). |
127 |
</para> |
128 |
<para> |
129 |
As it doesn't make much sense to specify a single name for multiple sets |
130 |
@@ -386,6 +397,32 @@ |
131 |
</sect3> |
132 |
</sect2> |
133 |
|
134 |
+ <sect2 id='config-set-classes-AgeSet'> |
135 |
+ <title>portage.sets.dbapi.AgeSet</title> |
136 |
+ <para> |
137 |
+ Package sets created by this class will include installed packages that |
138 |
+ have been installed before / after a given date. |
139 |
+ </para> |
140 |
+ |
141 |
+ <sect3> |
142 |
+ <title>Single Set Configuration</title> |
143 |
+ <para> |
144 |
+ In single set configurations this class supports the following options: |
145 |
+ <itemizedlist> |
146 |
+ <listitem><varname>age</varname>: Optional, defaults to 7. Specifies |
147 |
+ the number of days passed since installation to use as cut-off point. |
148 |
+ </listitem> |
149 |
+ <listitem><varname>mode</varname>: Optional, defaults to "older". Must |
150 |
+ be either "older" or "newer" to select packages installed either |
151 |
+ before resp. after the cut-off-date given by <varname>age</varname>. |
152 |
+ E.g. the defaults will select all installed packages that have been |
153 |
+ installed more than one week ago. |
154 |
+ </listitem> |
155 |
+ </itemizedlist> |
156 |
+ </para> |
157 |
+ </sect3> |
158 |
+ </sect2> |
159 |
+ |
160 |
<sect2 id='config-set-classes-CategorySet'> |
161 |
<title>portage.sets.dbapi.CategorySet</title> |
162 |
<para> |
163 |
|
164 |
Modified: main/branches/prefix/pym/_emerge/__init__.py |
165 |
=================================================================== |
166 |
--- main/branches/prefix/pym/_emerge/__init__.py 2008-10-02 16:57:45 UTC (rev 11613) |
167 |
+++ main/branches/prefix/pym/_emerge/__init__.py 2008-10-03 16:50:31 UTC (rev 11614) |
168 |
@@ -13452,25 +13452,8 @@ |
169 |
retval = os.EX_OK |
170 |
setconfig = root_config.setconfig |
171 |
|
172 |
- # display errors that occured while loading the SetConfig instance |
173 |
- for e in setconfig.errors: |
174 |
- print colorize("BAD", "Error during set creation: %s" % e) |
175 |
- |
176 |
sets = setconfig.getSets() |
177 |
|
178 |
- # emerge relies on the existance of sets with names "world" and "system" |
179 |
- required_sets = ("world", "system") |
180 |
- |
181 |
- for s in required_sets: |
182 |
- if s not in sets: |
183 |
- msg = ["emerge: incomplete set configuration, " + \ |
184 |
- "no \"%s\" set defined" % s] |
185 |
- msg.append(" sets defined: %s" % ", ".join(sets)) |
186 |
- for line in msg: |
187 |
- sys.stderr.write(line + "\n") |
188 |
- retval = 1 |
189 |
- unmerge_actions = ("unmerge", "prune", "clean", "depclean") |
190 |
- |
191 |
# In order to know exactly which atoms/sets should be added to the |
192 |
# world file, the depgraph performs set expansion later. It will get |
193 |
# confused about where the atoms came from if it's not allowed to |
194 |
@@ -13485,12 +13468,65 @@ |
195 |
myfiles = newargs |
196 |
del newargs |
197 |
newargs = [] |
198 |
- |
199 |
+ |
200 |
+ # separators for set arguments |
201 |
+ ARG_START = "{" |
202 |
+ ARG_END = "}" |
203 |
+ |
204 |
# WARNING: all operators must be of equal length |
205 |
IS_OPERATOR = "/@" |
206 |
DIFF_OPERATOR = "-@" |
207 |
UNION_OPERATOR = "+@" |
208 |
|
209 |
+ for i in range(0, len(myfiles)): |
210 |
+ if myfiles[i].startswith(SETPREFIX): |
211 |
+ start = 0 |
212 |
+ end = 0 |
213 |
+ x = myfiles[i][len(SETPREFIX):] |
214 |
+ newset = "" |
215 |
+ while x: |
216 |
+ start = x.find(ARG_START) |
217 |
+ end = x.find(ARG_END) |
218 |
+ if start > 0 and start < end: |
219 |
+ namepart = x[:start] |
220 |
+ argpart = x[start+1:end] |
221 |
+ |
222 |
+ # TODO: implement proper quoting |
223 |
+ args = argpart.split(",") |
224 |
+ options = {} |
225 |
+ for a in args: |
226 |
+ if "=" in a: |
227 |
+ k, v = a.split("=", 1) |
228 |
+ options[k] = v |
229 |
+ else: |
230 |
+ options[a] = "True" |
231 |
+ setconfig.update(namepart, options) |
232 |
+ newset += (x[:start-len(namepart)]+namepart) |
233 |
+ x = x[end+len(ARG_END):] |
234 |
+ else: |
235 |
+ newset += x |
236 |
+ x = "" |
237 |
+ myfiles[i] = SETPREFIX+newset |
238 |
+ |
239 |
+ sets = setconfig.getSets() |
240 |
+ |
241 |
+ # display errors that occured while loading the SetConfig instance |
242 |
+ for e in setconfig.errors: |
243 |
+ print colorize("BAD", "Error during set creation: %s" % e) |
244 |
+ |
245 |
+ # emerge relies on the existance of sets with names "world" and "system" |
246 |
+ required_sets = ("world", "system") |
247 |
+ |
248 |
+ for s in required_sets: |
249 |
+ if s not in sets: |
250 |
+ msg = ["emerge: incomplete set configuration, " + \ |
251 |
+ "no \"%s\" set defined" % s] |
252 |
+ msg.append(" sets defined: %s" % ", ".join(sets)) |
253 |
+ for line in msg: |
254 |
+ sys.stderr.write(line + "\n") |
255 |
+ retval = 1 |
256 |
+ unmerge_actions = ("unmerge", "prune", "clean", "depclean") |
257 |
+ |
258 |
for a in myfiles: |
259 |
if a.startswith(SETPREFIX): |
260 |
# support simple set operations (intersection, difference and union) |
261 |
|
262 |
Modified: main/branches/prefix/pym/portage/glsa.py |
263 |
=================================================================== |
264 |
--- main/branches/prefix/pym/portage/glsa.py 2008-10-02 16:57:45 UTC (rev 11613) |
265 |
+++ main/branches/prefix/pym/portage/glsa.py 2008-10-03 16:50:31 UTC (rev 11614) |
266 |
@@ -226,6 +226,8 @@ |
267 |
rValue = opMapping[versionNode.getAttribute("range")] \ |
268 |
+ pkgname \ |
269 |
+ "-" + getText(versionNode, format="strip") |
270 |
+ if "slot" in versionNode.attributes and versionNode.getAttribute("slot") != "*": |
271 |
+ rValue += ":"+versionNode.getAttribute("slot") |
272 |
return str(rValue) |
273 |
|
274 |
def makeVersion(versionNode): |
275 |
@@ -239,8 +241,11 @@ |
276 |
@rtype: String |
277 |
@return: the version string |
278 |
""" |
279 |
- return opMapping[versionNode.getAttribute("range")] \ |
280 |
+ rValue = opMapping[versionNode.getAttribute("range")] \ |
281 |
+ getText(versionNode, format="strip") |
282 |
+ if "slot" in versionNode.attributes and versionNode.getAttribute("slot") != "*": |
283 |
+ rValue += ":"+versionNode.getAttribute("slot") |
284 |
+ return rValue |
285 |
|
286 |
def match(atom, dbapi, match_type="default"): |
287 |
""" |
288 |
@@ -283,9 +288,9 @@ |
289 |
@return: a list with the matching versions |
290 |
""" |
291 |
if match_type == "default" or not hasattr(dbapi, "xmatch"): |
292 |
- mylist = dbapi.match(re.sub("-r[0-9]+$", "", revisionAtom[2:])) |
293 |
+ mylist = dbapi.match(re.sub(r'-r[0-9]+(:[^ ]+)?$', r'\1', revisionAtom[2:])) |
294 |
else: |
295 |
- mylist = dbapi.xmatch(match_type, re.sub("-r[0-9]+$", "", revisionAtom[2:])) |
296 |
+ mylist = dbapi.xmatch(match_type, re.sub(r'-r[0-9]+(:[^ ]+)?$', r'\1', revisionAtom[2:])) |
297 |
rValue = [] |
298 |
for v in mylist: |
299 |
r1 = pkgsplit(v)[-1][1:] |
300 |
@@ -353,6 +358,32 @@ |
301 |
rValue += "-"+c_pv[3] |
302 |
return rValue |
303 |
|
304 |
+def format_date(datestr): |
305 |
+ """ |
306 |
+ Takes a date (announced, revised) date from a GLSA and formats |
307 |
+ it as readable text (i.e. "January 1, 2008"). |
308 |
+ |
309 |
+ @type date: String |
310 |
+ @param date: the date string to reformat |
311 |
+ @rtype: String |
312 |
+ @return: a reformatted string, or the original string |
313 |
+ if it cannot be reformatted. |
314 |
+ """ |
315 |
+ splitdate = datestr.split("-", 2) |
316 |
+ if len(splitdate) != 3: |
317 |
+ return datestr |
318 |
+ |
319 |
+ # This cannot raise an error as we use () instead of [] |
320 |
+ splitdate = (int(x) for x in splitdate) |
321 |
+ |
322 |
+ from datetime import date |
323 |
+ try: |
324 |
+ d = date(*splitdate) |
325 |
+ except ValueError: |
326 |
+ return datestr |
327 |
+ |
328 |
+ # TODO We could format to local date format '%x' here? |
329 |
+ return d.strftime("%B %d, %Y") |
330 |
|
331 |
# simple Exception classes to catch specific errors |
332 |
class GlsaTypeException(Exception): |
333 |
@@ -432,7 +463,11 @@ |
334 |
self.DOM = xml.dom.minidom.parse(myfile) |
335 |
if not self.DOM.doctype: |
336 |
raise GlsaTypeException(None) |
337 |
- elif self.DOM.doctype.systemId != "http://www.gentoo.org/dtd/glsa.dtd": |
338 |
+ elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa.dtd": |
339 |
+ self.dtdversion = 0 |
340 |
+ elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa-2.dtd": |
341 |
+ self.dtdversion = 2 |
342 |
+ else: |
343 |
raise GlsaTypeException(self.DOM.doctype.systemId) |
344 |
myroot = self.DOM.getElementsByTagName("glsa")[0] |
345 |
if self.type == "id" and myroot.getAttribute("id") != self.nr: |
346 |
@@ -441,9 +476,27 @@ |
347 |
# the simple (single, required, top-level, #PCDATA) tags first |
348 |
self.title = getText(myroot.getElementsByTagName("title")[0], format="strip") |
349 |
self.synopsis = getText(myroot.getElementsByTagName("synopsis")[0], format="strip") |
350 |
- self.announced = getText(myroot.getElementsByTagName("announced")[0], format="strip") |
351 |
- self.revised = getText(myroot.getElementsByTagName("revised")[0], format="strip") |
352 |
+ self.announced = format_date(getText(myroot.getElementsByTagName("announced")[0], format="strip")) |
353 |
|
354 |
+ count = 1 |
355 |
+ # Support both formats of revised: |
356 |
+ # <revised>December 30, 2007: 02</revised> |
357 |
+ # <revised count="2">2007-12-30</revised> |
358 |
+ revisedEl = myroot.getElementsByTagName("revised")[0] |
359 |
+ self.revised = getText(revisedEl, format="strip") |
360 |
+ if (revisedEl.attributes.has_key("count")): |
361 |
+ count = revisedEl.getAttribute("count") |
362 |
+ elif (self.revised.find(":") >= 0): |
363 |
+ (self.revised, count) = self.revised.split(":") |
364 |
+ |
365 |
+ self.revised = format_date(self.revised) |
366 |
+ |
367 |
+ try: |
368 |
+ self.count = int(count) |
369 |
+ except ValueError: |
370 |
+ # TODO should this rais a GlsaFormatException? |
371 |
+ self.count = 1 |
372 |
+ |
373 |
# now the optional and 0-n toplevel, #PCDATA tags and references |
374 |
try: |
375 |
self.access = getText(myroot.getElementsByTagName("access")[0], format="strip") |
376 |
@@ -499,7 +552,7 @@ |
377 |
outstream.write((width*"=")+"\n") |
378 |
outstream.write(wrap(self.synopsis, width, caption="Synopsis: ")+"\n") |
379 |
outstream.write("Announced on: %s\n" % self.announced) |
380 |
- outstream.write("Last revised on: %s\n\n" % self.revised) |
381 |
+ outstream.write("Last revised on: %s : %02d\n\n" % (self.revised, self.count)) |
382 |
if self.glsatype == "ebuild": |
383 |
for k in self.packages.keys(): |
384 |
pkg = self.packages[k] |
385 |
@@ -565,7 +618,7 @@ |
386 |
@rtype: Boolean |
387 |
@returns: True if the GLSA was applied, False if not |
388 |
""" |
389 |
- return (self.nr in get_applied_glsas()) |
390 |
+ return (self.nr in get_applied_glsas(self.config)) |
391 |
|
392 |
def inject(self): |
393 |
""" |
394 |
|
395 |
Modified: main/branches/prefix/pym/portage/sets/__init__.py |
396 |
=================================================================== |
397 |
--- main/branches/prefix/pym/portage/sets/__init__.py 2008-10-02 16:57:45 UTC (rev 11613) |
398 |
+++ main/branches/prefix/pym/portage/sets/__init__.py 2008-10-03 16:50:31 UTC (rev 11614) |
399 |
@@ -33,8 +33,30 @@ |
400 |
self._parsed = False |
401 |
self.active = [] |
402 |
|
403 |
- def _parse(self): |
404 |
- if self._parsed: |
405 |
+ def update(self, setname, options): |
406 |
+ self.errors = [] |
407 |
+ if not setname in self.psets: |
408 |
+ options["name"] = setname |
409 |
+ |
410 |
+ # for the unlikely case that there is already a section with the requested setname |
411 |
+ import random |
412 |
+ while setname in self.sections(): |
413 |
+ setname = "%08d" % random.randint(0, 10**10) |
414 |
+ |
415 |
+ self.add_section(setname) |
416 |
+ for k, v in options.items(): |
417 |
+ self.set(setname, k, v) |
418 |
+ else: |
419 |
+ section = self.psets[setname].creator |
420 |
+ if self.has_option(section, "multiset") and self.getboolean(section, "multiset"): |
421 |
+ self.errors.append("Invalid request to reconfigure set '%s' generated by multiset section '%s'" % (setname, section)) |
422 |
+ return |
423 |
+ for k, v in options.items(): |
424 |
+ self.set(section, k, v) |
425 |
+ self._parse(update=True) |
426 |
+ |
427 |
+ def _parse(self, update=False): |
428 |
+ if self._parsed and not update: |
429 |
return |
430 |
for sname in self.sections(): |
431 |
# find classname for current section, default to file based sets |
432 |
@@ -64,7 +86,7 @@ |
433 |
self.errors.append("Configuration error in section '%s': %s" % (sname, str(e))) |
434 |
continue |
435 |
for x in newsets: |
436 |
- if x in self.psets: |
437 |
+ if x in self.psets and not update: |
438 |
self.errors.append("Redefinition of set '%s' (sections: '%s', '%s')" % (x, self.psets[x].creator, sname)) |
439 |
newsets[x].creator = sname |
440 |
if self.has_option(sname, "world-candidate") and not self.getboolean(sname, "world-candidate"): |
441 |
@@ -78,7 +100,7 @@ |
442 |
setname = self.get(sname, "name") |
443 |
except NoOptionError: |
444 |
setname = sname |
445 |
- if setname in self.psets: |
446 |
+ if setname in self.psets and not update: |
447 |
self.errors.append("Redefinition of set '%s' (sections: '%s', '%s')" % (setname, self.psets[setname].creator, sname)) |
448 |
if hasattr(setclass, "singleBuilder"): |
449 |
try: |
450 |
@@ -101,14 +123,38 @@ |
451 |
def getSetAtoms(self, setname, ignorelist=None): |
452 |
myset = self.getSets()[setname] |
453 |
myatoms = myset.getAtoms() |
454 |
+ |
455 |
+ extend = set() |
456 |
+ remove = set() |
457 |
+ intersect = set() |
458 |
+ |
459 |
if ignorelist is None: |
460 |
ignorelist = set() |
461 |
+ if not setname in ignorelist: |
462 |
+ if self.has_option(myset.creator, "extend"): |
463 |
+ extend.update(self.get(myset.creator, "extend").split()) |
464 |
+ if self.has_option(myset.creator, "remove"): |
465 |
+ remove.update(self.get(myset.creator, "remove").split()) |
466 |
+ if self.has_option(myset.creator, "intersect"): |
467 |
+ intersect.update(self.get(myset.creator, "intersect").split()) |
468 |
+ |
469 |
ignorelist.add(setname) |
470 |
for n in myset.getNonAtoms(): |
471 |
- if n[0] == SETPREFIX and n[1:] in self.psets: |
472 |
- if n[1:] not in ignorelist: |
473 |
- myatoms.update(self.getSetAtoms(n[1:], |
474 |
- ignorelist=ignorelist)) |
475 |
+ if n.startswith(SETPREFIX) and n[len(SETPREFIX):] in self.psets: |
476 |
+ extend.add(n[len(SETPREFIX):]) |
477 |
+ |
478 |
+ for s in ignorelist: |
479 |
+ extend.discard(s) |
480 |
+ remove.discard(s) |
481 |
+ intersect.discard(s) |
482 |
+ |
483 |
+ for s in extend: |
484 |
+ myatoms.update(self.getSetAtoms(s, ignorelist=ignorelist)) |
485 |
+ for s in remove: |
486 |
+ myatoms.difference_update(self.getSetAtoms(s, ignorelist=ignorelist)) |
487 |
+ for s in intersect: |
488 |
+ myatoms.intersection_update(self.getSetAtoms(s, ignorelist=ignorelist)) |
489 |
+ |
490 |
return myatoms |
491 |
|
492 |
def load_default_config(settings, trees): |