~bennabiy/+junk/python-xlib

« back to all changes in this revision

Viewing changes to .pc/python3.patch/Xlib/rdb.py

  • Committer: Package Import Robot
  • Author(s): Andrew Shadura, Ramkumar Ramachandra, Andrew Shadura
  • Date: 2015-08-13 08:14:19 UTC
  • mfrom: (6.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20150813081419-hdefinnghp2iydkx
Tags: 0.14+20091101-3
[ Ramkumar Ramachandra ]
* Remove useless debugging output (Closes: #565996)

[ Andrew Shadura ]
* Switch to 3.0 (quilt) format.
* Rename patches.
* Use debhelper 9 in its short form.
* Use pybuild.
* Bump Standards-Version.
* Don't build or install PostScript documentation and info files.
* Use system-provided texi2html instead of a shipped version
  (Closes: #795057).
* Update debian/copyright (Closes: #795057).
* Don't install Makefile or texi2html with the documentation.
* Set executable bit for examples.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Xlib.rdb -- X resource database implementation
 
2
#
 
3
#    Copyright (C) 2000 Peter Liljenberg <petli@ctrl-c.liu.se>
 
4
#
 
5
#    This program is free software; you can redistribute it and/or modify
 
6
#    it under the terms of the GNU General Public License as published by
 
7
#    the Free Software Foundation; either version 2 of the License, or
 
8
#    (at your option) any later version.
 
9
#
 
10
#    This program is distributed in the hope that it will be useful,
 
11
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
#    GNU General Public License for more details.
 
14
#
 
15
#    You should have received a copy of the GNU General Public License
 
16
#    along with this program; if not, write to the Free Software
 
17
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 
 
19
 
 
20
# See end of file for an explanation of the algorithm and
 
21
# data structures used.
 
22
 
 
23
 
 
24
# Standard modules
 
25
import string
 
26
import types
 
27
import re
 
28
import sys
 
29
 
 
30
# Xlib modules
 
31
from support import lock
 
32
 
 
33
# Set up a few regexpes for parsing string representation of resources
 
34
 
 
35
comment_re = re.compile(r'^\s*!')
 
36
resource_spec_re = re.compile(r'^\s*([-_a-zA-Z0-9?.*]+)\s*:\s*(.*)$')
 
37
value_escape_re = re.compile('\\\\([ \tn\\\\]|[0-7]{3,3})')
 
38
resource_parts_re = re.compile(r'([.*]+)')
 
39
 
 
40
# Constants used for determining which match is best
 
41
 
 
42
NAME_MATCH = 0
 
43
CLASS_MATCH = 2
 
44
WILD_MATCH = 4
 
45
MATCH_SKIP = 6
 
46
 
 
47
# Option error class
 
48
class OptionError(Exception):
 
49
    pass
 
50
 
 
51
 
 
52
class ResourceDB:
 
53
    def __init__(self, file = None, string = None, resources = None):
 
54
        self.db = {}
 
55
        self.lock = lock.allocate_lock()
 
56
 
 
57
        if file is not None:
 
58
            self.insert_file(file)
 
59
        if string is not None:
 
60
            self.insert_string(string)
 
61
        if resources is not None:
 
62
            self.insert_resources(resources)
 
63
 
 
64
    def insert_file(self, file):
 
65
        """insert_file(file)
 
66
 
 
67
        Load resources entries from FILE, and insert them into the
 
68
        database.  FILE can be a filename (a string)or a file object.
 
69
 
 
70
        """
 
71
 
 
72
        if type(file) is types.StringType:
 
73
            file = open(file, 'r')
 
74
 
 
75
        self.insert_string(file.read())
 
76
 
 
77
 
 
78
    def insert_string(self, data):
 
79
        """insert_string(data)
 
80
 
 
81
        Insert the resources entries in the string DATA into the
 
82
        database.
 
83
 
 
84
        """
 
85
 
 
86
        # First split string into lines
 
87
        lines = string.split(data, '\n')
 
88
 
 
89
        while lines:
 
90
            line = lines[0]
 
91
            del lines[0]
 
92
 
 
93
            # Skip empty line
 
94
            if not line:
 
95
                continue
 
96
 
 
97
            # Skip comments
 
98
            if comment_re.match(line):
 
99
                continue
 
100
 
 
101
            # Handle continued lines
 
102
            while line[-1] == '\\':
 
103
                if lines:
 
104
                    line = line[:-1] + lines[0]
 
105
                    del lines[0]
 
106
                else:
 
107
                    line = line[:-1]
 
108
                    break
 
109
 
 
110
            # Split line into resource and value
 
111
            m = resource_spec_re.match(line)
 
112
 
 
113
            # Bad line, just ignore it silently
 
114
            if not m:
 
115
                continue
 
116
 
 
117
            res, value = m.group(1, 2)
 
118
 
 
119
            # Convert all escape sequences in value
 
120
            splits = value_escape_re.split(value)
 
121
 
 
122
            for i in range(1, len(splits), 2):
 
123
                s = splits[i]
 
124
                if len(s) == 3:
 
125
                    splits[i] = chr(string.atoi(s, 8))
 
126
                elif s == 'n':
 
127
                    splits[i] = '\n'
 
128
 
 
129
            # strip the last value part to get rid of any
 
130
            # unescaped blanks
 
131
            splits[-1] = string.rstrip(splits[-1])
 
132
 
 
133
            value = string.join(splits, '')
 
134
 
 
135
            self.insert(res, value)
 
136
 
 
137
 
 
138
    def insert_resources(self, resources):
 
139
        """insert_resources(resources)
 
140
 
 
141
        Insert all resources entries in the list RESOURCES into the
 
142
        database.  Each element in RESOURCES should be a tuple:
 
143
 
 
144
          (resource, value)
 
145
 
 
146
        Where RESOURCE is a string and VALUE can be any Python value.
 
147
 
 
148
        """
 
149
 
 
150
        for res, value in resources:
 
151
            self.insert(res, value)
 
152
 
 
153
    def insert(self, resource, value):
 
154
        """insert(resource, value)
 
155
 
 
156
        Insert a resource entry into the database.  RESOURCE is a
 
157
        string and VALUE can be any Python value.
 
158
 
 
159
        """
 
160
 
 
161
        # Split res into components and bindings
 
162
        parts = resource_parts_re.split(resource)
 
163
 
 
164
        # If the last part is empty, this is an invalid resource
 
165
        # which we simply ignore
 
166
        if parts[-1] == '':
 
167
            return
 
168
 
 
169
        self.lock.acquire()
 
170
 
 
171
        db = self.db
 
172
        for i in range(1, len(parts), 2):
 
173
 
 
174
            # Create a new mapping/value group
 
175
            if not db.has_key(parts[i - 1]):
 
176
                db[parts[i - 1]] = ({}, {})
 
177
 
 
178
            # Use second mapping if a loose binding, first otherwise
 
179
            if '*' in parts[i]:
 
180
                db = db[parts[i - 1]][1]
 
181
            else:
 
182
                db = db[parts[i - 1]][0]
 
183
 
 
184
        # Insert value into the derived db
 
185
        if db.has_key(parts[-1]):
 
186
            db[parts[-1]] = db[parts[-1]][:2] + (value, )
 
187
        else:
 
188
            db[parts[-1]] = ({}, {}, value)
 
189
 
 
190
        self.lock.release()
 
191
 
 
192
    def __getitem__(self, (name, cls)):
 
193
        """db[name, class]
 
194
 
 
195
        Return the value matching the resource identified by NAME and
 
196
        CLASS.  If no match is found, KeyError is raised.
 
197
        """
 
198
 
 
199
        # Split name and class into their parts
 
200
 
 
201
        namep = string.split(name, '.')
 
202
        clsp = string.split(cls, '.')
 
203
 
 
204
        # It is an error for name and class to have different number
 
205
        # of parts
 
206
 
 
207
        if len(namep) != len(clsp):
 
208
            raise ValueError('Different number of parts in resource name/class: %s/%s' % (name, cls))
 
209
 
 
210
        complen = len(namep)
 
211
        matches = []
 
212
 
 
213
        # Lock database and wrap the lookup code in a try-finally
 
214
        # block to make sure that it is unlocked.
 
215
 
 
216
        self.lock.acquire()
 
217
        try:
 
218
 
 
219
            # Precedence order: name -> class -> ?
 
220
 
 
221
            if self.db.has_key(namep[0]):
 
222
                bin_insert(matches, _Match((NAME_MATCH, ), self.db[namep[0]]))
 
223
 
 
224
            if self.db.has_key(clsp[0]):
 
225
                bin_insert(matches, _Match((CLASS_MATCH, ), self.db[clsp[0]]))
 
226
 
 
227
            if self.db.has_key('?'):
 
228
                bin_insert(matches, _Match((WILD_MATCH, ), self.db['?']))
 
229
 
 
230
 
 
231
            # Special case for the unlikely event that the resource
 
232
            # only has one component
 
233
            if complen == 1 and matches:
 
234
                x = matches[0]
 
235
                if x.final(complen):
 
236
                    return x.value()
 
237
                else:
 
238
                    raise KeyError((name, cls))
 
239
 
 
240
 
 
241
            # Special case for resources which begins with a loose
 
242
            # binding, e.g. '*foo.bar'
 
243
            if self.db.has_key(''):
 
244
                bin_insert(matches, _Match((), self.db[''][1]))
 
245
 
 
246
 
 
247
            # Now iterate over all components until we find the best match.
 
248
 
 
249
            # For each component, we choose the best partial match among
 
250
            # the mappings by applying these rules in order:
 
251
 
 
252
            # Rule 1: If the current group contains a match for the
 
253
            # name, class or '?', we drop all previously found loose
 
254
            # binding mappings.
 
255
 
 
256
            # Rule 2: A matching name has precedence over a matching
 
257
            # class, which in turn has precedence over '?'.
 
258
 
 
259
            # Rule 3: Tight bindings have precedence over loose
 
260
            # bindings.
 
261
 
 
262
            while matches:
 
263
 
 
264
                # Work on the first element == the best current match
 
265
 
 
266
                x = matches[0]
 
267
                del matches[0]
 
268
 
 
269
                # print 'path:  ', x.path
 
270
                # if x.skip:
 
271
                #         print 'skip:  ', x.db
 
272
                # else:
 
273
                #         print 'group: ', x.group
 
274
                # print
 
275
 
 
276
                i = x.match_length()
 
277
 
 
278
                for part, score in ((namep[i], NAME_MATCH),
 
279
                                    (clsp[i], CLASS_MATCH),
 
280
                                    ('?', WILD_MATCH)):
 
281
 
 
282
                    # Attempt to find a match in x
 
283
                    match = x.match(part, score)
 
284
                    if match:
 
285
                        # Hey, we actually found a value!
 
286
                        if match.final(complen):
 
287
                            return match.value()
 
288
 
 
289
                        # Else just insert the new match
 
290
                        else:
 
291
                            bin_insert(matches, match)
 
292
 
 
293
                    # Generate a new loose match
 
294
                    match = x.skip_match(complen)
 
295
                    if match:
 
296
                        bin_insert(matches, match)
 
297
 
 
298
            # Oh well, nothing matched
 
299
            raise KeyError((name, cls))
 
300
 
 
301
        finally:
 
302
            self.lock.release()
 
303
 
 
304
    def get(self, res, cls, default = None):
 
305
        """get(name, class [, default])
 
306
 
 
307
        Return the value matching the resource identified by NAME and
 
308
        CLASS.  If no match is found, DEFAULT is returned, or None if
 
309
        DEFAULT isn't specified.
 
310
 
 
311
        """
 
312
 
 
313
        try:
 
314
            return self[(res, cls)]
 
315
        except KeyError:
 
316
            return default
 
317
 
 
318
    def update(self, db):
 
319
        """update(db)
 
320
 
 
321
        Update this database with all resources entries in the resource
 
322
        database DB.
 
323
 
 
324
        """
 
325
 
 
326
        self.lock.acquire()
 
327
        update_db(self.db, db.db)
 
328
        self.lock.release()
 
329
 
 
330
    def output(self):
 
331
        """output()
 
332
 
 
333
        Return the resource database in text representation.
 
334
        """
 
335
 
 
336
        self.lock.acquire()
 
337
        text = output_db('', self.db)
 
338
        self.lock.release()
 
339
        return text
 
340
 
 
341
    def getopt(self, name, argv, opts):
 
342
        """getopt(name, argv, opts)
 
343
 
 
344
        Parse X command line options, inserting the recognised options
 
345
        into the resource database.
 
346
 
 
347
        NAME is the application name, and will be prepended to all
 
348
        specifiers.  ARGV is the list of command line arguments,
 
349
        typically sys.argv[1:].
 
350
 
 
351
        OPTS is a mapping of options to resource specifiers.  The key is
 
352
        the option flag (with leading -), and the value is an instance of
 
353
        some Option subclass:
 
354
 
 
355
        NoArg(specifier, value): set resource to value.
 
356
        IsArg(specifier):        set resource to option itself
 
357
        SepArg(specifier):       value is next argument
 
358
        ResArg:                  resource and value in next argument
 
359
        SkipArg:                 ignore this option and next argument
 
360
        SkipLine:                ignore rest of arguments
 
361
        SkipNArgs(count):        ignore this option and count arguments
 
362
 
 
363
        The remaining, non-option, oparguments is returned.
 
364
 
 
365
        rdb.OptionError is raised if there is an error in the argument list.
 
366
        """
 
367
 
 
368
        while argv and argv[0] and argv[0][0] == '-':
 
369
            try:
 
370
                argv = opts[argv[0]].parse(name, self, argv)
 
371
            except KeyError:
 
372
                raise OptionError('unknown option: %s' % argv[0])
 
373
            except IndexError:
 
374
                raise OptionError('missing argument to option: %s' % argv[0])
 
375
 
 
376
        return argv
 
377
 
 
378
 
 
379
class _Match:
 
380
    def __init__(self, path, dbs):
 
381
        self.path = path
 
382
 
 
383
        if type(dbs) is types.TupleType:
 
384
            self.skip = 0
 
385
            self.group = dbs
 
386
 
 
387
        else:
 
388
            self.skip = 1
 
389
            self.db = dbs
 
390
 
 
391
    def __cmp__(self, other):
 
392
        return cmp(self.path, other.path)
 
393
 
 
394
    def match_length(self):
 
395
        return len(self.path)
 
396
 
 
397
    def match(self, part, score):
 
398
        if self.skip:
 
399
            if self.db.has_key(part):
 
400
                return _Match(self.path + (score, ), self.db[part])
 
401
            else:
 
402
                return None
 
403
        else:
 
404
            if self.group[0].has_key(part):
 
405
                return _Match(self.path + (score, ), self.group[0][part])
 
406
            elif self.group[1].has_key(part):
 
407
                return _Match(self.path + (score + 1, ), self.group[1][part])
 
408
            else:
 
409
                return None
 
410
 
 
411
    def skip_match(self, complen):
 
412
        # Can't make another skip if we have run out of components
 
413
        if len(self.path) + 1 >= complen:
 
414
            return None
 
415
 
 
416
        # If this already is a skip match, clone a new one
 
417
        if self.skip:
 
418
            if self.db:
 
419
                return _Match(self.path + (MATCH_SKIP, ), self.db)
 
420
            else:
 
421
                return None
 
422
 
 
423
        # Only generate a skip match if the loose binding mapping
 
424
        # is non-empty
 
425
        elif self.group[1]:
 
426
            return _Match(self.path + (MATCH_SKIP, ), self.group[1])
 
427
 
 
428
        # This is a dead end match
 
429
        else:
 
430
            return None
 
431
 
 
432
    def final(self, complen):
 
433
        if not self.skip and len(self.path) == complen and len(self.group) > 2:
 
434
            return 1
 
435
        else:
 
436
            return 0
 
437
 
 
438
    def value(self):
 
439
        return self.group[2]
 
440
 
 
441
 
 
442
#
 
443
# Helper function for ResourceDB.__getitem__()
 
444
#
 
445
 
 
446
def bin_insert(list, element):
 
447
    """bin_insert(list, element)
 
448
 
 
449
    Insert ELEMENT into LIST.  LIST must be sorted, and ELEMENT will
 
450
    be inserted to that LIST remains sorted.  If LIST already contains
 
451
    ELEMENT, it will not be duplicated.
 
452
 
 
453
    """
 
454
 
 
455
    if not list:
 
456
        list.append(element)
 
457
        return
 
458
 
 
459
    lower = 0
 
460
    upper = len(list) - 1
 
461
 
 
462
    while lower <= upper:
 
463
        center = (lower + upper) / 2
 
464
        if element < list[center]:
 
465
            upper = center - 1
 
466
        elif element > list[center]:
 
467
            lower = center + 1
 
468
        elif element == list[center]:
 
469
            return
 
470
 
 
471
    if element < list[upper]:
 
472
        list.insert(upper, element)
 
473
    elif element > list[upper]:
 
474
        list.insert(upper + 1, element)
 
475
 
 
476
 
 
477
#
 
478
# Helper functions for ResourceDB.update()
 
479
#
 
480
 
 
481
def update_db(dest, src):
 
482
    for comp, group in src.items():
 
483
 
 
484
        # DEST already contains this component, update it
 
485
        if dest.has_key(comp):
 
486
 
 
487
            # Update tight and loose binding databases
 
488
            update_db(dest[comp][0], group[0])
 
489
            update_db(dest[comp][1], group[1])
 
490
 
 
491
            # If a value has been set in SRC, update
 
492
            # value in DEST
 
493
 
 
494
            if len(group) > 2:
 
495
                dest[comp] = dest[comp][:2] + group[2:]
 
496
 
 
497
        # COMP not in src, make a deep copy
 
498
        else:
 
499
            dest[comp] = copy_group(group)
 
500
 
 
501
def copy_group(group):
 
502
    return (copy_db(group[0]), copy_db(group[1])) + group[2:]
 
503
 
 
504
def copy_db(db):
 
505
    newdb = {}
 
506
    for comp, group in db.items():
 
507
        newdb[comp] = copy_group(group)
 
508
 
 
509
    return newdb
 
510
 
 
511
 
 
512
#
 
513
# Helper functions for output
 
514
#
 
515
 
 
516
def output_db(prefix, db):
 
517
    res = ''
 
518
    for comp, group in db.items():
 
519
 
 
520
        # There's a value for this component
 
521
        if len(group) > 2:
 
522
            res = res + '%s%s: %s\n' % (prefix, comp, output_escape(group[2]))
 
523
 
 
524
        # Output tight and loose bindings
 
525
        res = res + output_db(prefix + comp + '.', group[0])
 
526
        res = res + output_db(prefix + comp + '*', group[1])
 
527
 
 
528
    return res
 
529
 
 
530
def output_escape(value):
 
531
    value = str(value)
 
532
    if not value:
 
533
        return value
 
534
 
 
535
    for char, esc in (('\\', '\\\\'),
 
536
                      ('\000', '\\000'),
 
537
                      ('\n', '\\n')):
 
538
 
 
539
        value = string.replace(value, char, esc)
 
540
 
 
541
    # If first or last character is space or tab, escape them.
 
542
    if value[0] in ' \t':
 
543
        value = '\\' + value
 
544
    if value[-1] in ' \t' and value[-2:-1] != '\\':
 
545
        value = value[:-1] + '\\' + value[-1]
 
546
 
 
547
    return value
 
548
 
 
549
 
 
550
#
 
551
# Option type definitions
 
552
#
 
553
 
 
554
class Option:
 
555
    def __init__(self):
 
556
        pass
 
557
 
 
558
    def parse(self, name, db, args):
 
559
        pass
 
560
 
 
561
class NoArg(Option):
 
562
    """Value is provided to constructor."""
 
563
    def __init__(self, specifier, value):
 
564
        self.specifier = specifier
 
565
        self.value = value
 
566
 
 
567
    def parse(self, name, db, args):
 
568
        db.insert(name + self.specifier, self.value)
 
569
        return args[1:]
 
570
 
 
571
class IsArg(Option):
 
572
    """Value is the option string itself."""
 
573
    def __init__(self, specifier):
 
574
        self.specifier = specifier
 
575
 
 
576
    def parse(self, name, db, args):
 
577
        db.insert(name + self.specifier, args[0])
 
578
        return args[1:]
 
579
 
 
580
class SepArg(Option):
 
581
    """Value is the next argument."""
 
582
    def __init__(self, specifier):
 
583
        self.specifier = specifier
 
584
 
 
585
    def parse(self, name, db, args):
 
586
        db.insert(name + self.specifier, args[1])
 
587
        return args[2:]
 
588
 
 
589
class ResArgClass(Option):
 
590
    """Resource and value in the next argument."""
 
591
    def parse(self, name, db, args):
 
592
        db.insert_string(args[1])
 
593
        return args[2:]
 
594
 
 
595
ResArg = ResArgClass()
 
596
 
 
597
class SkipArgClass(Option):
 
598
    """Ignore this option and next argument."""
 
599
    def parse(self, name, db, args):
 
600
        return args[2:]
 
601
 
 
602
SkipArg = SkipArgClass()
 
603
 
 
604
class SkipLineClass(Option):
 
605
    """Ignore rest of the arguments."""
 
606
    def parse(self, name, db, args):
 
607
        return []
 
608
 
 
609
SkipLine = SkipLineClass()
 
610
 
 
611
class SkipNArgs(Option):
 
612
    """Ignore this option and the next COUNT arguments."""
 
613
    def __init__(self, count):
 
614
        self.count = count
 
615
 
 
616
    def parse(self, name, db, args):
 
617
        return args[1 + self.count:]
 
618
 
 
619
 
 
620
 
 
621
def get_display_opts(options, argv = sys.argv):
 
622
    """display, name, db, args = get_display_opts(options, [argv])
 
623
 
 
624
    Parse X OPTIONS from ARGV (or sys.argv if not provided).
 
625
 
 
626
    Connect to the display specified by a *.display resource if one is
 
627
    set, or to the default X display otherwise.  Extract the
 
628
    RESOURCE_MANAGER property and insert all resources from ARGV.
 
629
 
 
630
    The four return values are:
 
631
      DISPLAY -- the display object
 
632
      NAME    -- the application name (the filname of ARGV[0])
 
633
      DB      -- the created resource database
 
634
      ARGS    -- any remaining arguments
 
635
    """
 
636
 
 
637
    from Xlib import display, Xatom
 
638
    import os
 
639
 
 
640
    name = os.path.splitext(os.path.basename(argv[0]))[0]
 
641
 
 
642
    optdb = ResourceDB()
 
643
    leftargv = optdb.getopt(name, argv[1:], options)
 
644
 
 
645
    dname = optdb.get(name + '.display', name + '.Display', None)
 
646
    d = display.Display(dname)
 
647
 
 
648
    rdbstring = d.screen(0).root.get_full_property(Xatom.RESOURCE_MANAGER,
 
649
                                                   Xatom.STRING)
 
650
    if rdbstring:
 
651
        data = rdbstring.value
 
652
    else:
 
653
        data = None
 
654
 
 
655
    db = ResourceDB(string = data)
 
656
    db.update(optdb)
 
657
 
 
658
    return d, name, db, leftargv
 
659
 
 
660
 
 
661
# Common X options
 
662
stdopts = {'-bg': SepArg('*background'),
 
663
           '-background': SepArg('*background'),
 
664
           '-fg': SepArg('*foreground'),
 
665
           '-foreground': SepArg('*foreground'),
 
666
           '-fn': SepArg('*font'),
 
667
           '-font': SepArg('*font'),
 
668
           '-name': SepArg('.name'),
 
669
           '-title': SepArg('.title'),
 
670
           '-synchronous': NoArg('*synchronous', 'on'),
 
671
           '-xrm': ResArg,
 
672
           '-display': SepArg('.display'),
 
673
           '-d': SepArg('.display'),
 
674
           }
 
675
 
 
676
 
 
677
# Notes on the implementation:
 
678
 
 
679
# Resource names are split into their components, and each component
 
680
# is stored in a mapping.  The value for a component is a tuple of two
 
681
# or three elements:
 
682
 
 
683
#   (tightmapping, loosemapping [, value])
 
684
 
 
685
# tightmapping contains the next components which are connected with a
 
686
# tight binding (.).  loosemapping contains the ones connected with
 
687
# loose binding (*).  If value is present, then this component is the
 
688
# last component for some resource which that value.
 
689
 
 
690
# The top level components are stored in the mapping r.db, where r is
 
691
# the resource object.
 
692
 
 
693
# Example:  Inserting "foo.bar*gazonk: yep" into an otherwise empty
 
694
# resource database would give the folliwing structure:
 
695
 
 
696
# { 'foo': ( { 'bar': ( { },
 
697
#                       { 'gazonk': ( { },
 
698
#                                     { },
 
699
#                                     'yep')
 
700
#                         }
 
701
#                       )
 
702
#              },
 
703
#            {})
 
704
#   }