/
C_snode.py
763 lines (673 loc) · 29.1 KB
/
C_snode.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
#!/usr/bin/env python
"""
NAME
C_snode, C_snodeBranch, C_stree
DESCRIPTION
These three classes are used to construct tree-structures
composed of 'C_snode' instances. Branches contain dictionaries
of C_snodes, while leaves contain dictionaries of a specific
external data class.
HISTORY
o 26 March 2008
Initial design and coding
o 25 February 2014
Resurrection as part of the fnndsc-netmap app.
o 27 February 2014
Cleanup, refactoring, re-indenting.
Fix class/instance variable mixups.
"""
# System modules
import os
import sys
import re
from string import *
from C_stringCore import *
import itertools
# from IPython.core.debugger import Tracer;
class C_meta:
'''
A "base" class containing 'meta' data pertinent to a node.
'''
def __init__(self, al_mustInclude = [],
al_mustNotInclude = []):
self._hitCount = 0;
self.l_canInclude = []
self.l_mustInclude = al_mustInclude
self.l_mustNotInclude = al_mustNotInclude
self.b_printPre = False
self.sCore = C_stringCore()
self._depth = 0
self.str_pre = ' '
#
## Getters/setters
def pre(self, *args):
'''
Get / set the str_pre
'''
if len(args):
self.str_pre = args[0]
else:
return self.str_pre
def mustInclude(self, *args):
'''
Get / set the [mustInclude].
'''
if len(args):
self.l_mustInclude = args[0]
else:
return l_mustInclude
def mustNotInclude(self, *args):
'''
Get / set the [mustInclude].
'''
if len(args):
self.l_mustNotInclude = args[0]
else:
return l_mustNotInclude
def canInclude(self, *args):
'''
Get / set the [mustInclude].
'''
if len(args):
self.l_canInclude = args[0]
else:
return l_canInclude
def depth(self, *args):
'''
Get/set the depth
'''
if len(args):
self._depth = args[0]
else:
return self._depth
#
## core overloads
def __str__(self):
self.sCore.write('%s +--depth............ %d\n' % (self.str_pre, self._depth))
self.sCore.write('%s +--hitCount......... %d\n' % (self.str_pre, self._hitCount))
self.sCore.write('%s +--mustInclude...... %s\n' % (self.str_pre, self.l_mustInclude))
self.sCore.write('%s +--mustNotInclude... %s\n' % (self.str_pre, self.l_mustNotInclude))
return self.sCore.strget()
class C_snode:
"""
A "container" node class. This container is the
basic building block for larger tree-like database
structures.
The C_snode defines a single 'node' in this tree. It contains
two lists, 'l_mustInclude' and 'l_mustNotInclude' that define
the features described in the 'd_nodes' dictionary. This
dictionary can in turn contain other C_snodes.
"""
#
# Methods
#
def __init__(self, astr_nodeName = "",
al_mustInclude = [],
al_mustNotInclude = []
):
# - Core variables
self.str_obj = 'C_snode' # name of object class
self.str_name = 'void' # name of object variable
self._id = -1; # id of agent
self._iter = 0; # current iteration in an
# arbitrary processing
# scheme
self._verbosity = 0; # debug related value for
# object
self._warnings = 0; # show warnings
self.sCore = C_stringCore()
# The d_nodes is the basic building block of the C_snode container
#+ class. It is simply a dictionary that contains 'nodes' that
#+ satisfy a given feature set described by 'mustInclude' and
#+ 'mustNotInclude'.
#+
#+ In general:
#+ 'snode_parent' : the parent node of this node -- useful
#+ for tree structuring.
#+ '_hitCount' : count of hits for all items branching
#+ at this level. At the leaf level, this
#+ contains the length of 'contents'.
#+ 'l_mustInclude' : descriptive trait for specific feature
#+ level
#+ 'l_mustNotInclude' : exclusion trait for specific feature
#+ level
#+ 'd_nodes' : dictionary of child nodes branching
#+ from this node
#+ 'd_data' : a dictionary of data for *this* node
#+
#+ The pattern of 'mustInclude' and 'mustNotInclude' uniquely
#+ characterizes a particular level. "Deeper" features (i.e. features
#+ further along the dictionary tree) must satisfy the combined set
#+ described by all the 'mustInclude' and 'mustNotInclude' traits of
#+ each higher level.
self.meta = C_meta()
self.snode_parent = None
self.d_nodes = {}
self.d_data = {}
self.b_printMetaData = True
self.b_printContents = True
self.b_printPre = False
self.str_nodeName = astr_nodeName
self.b_printPre = False
#
# Getters and setters
def metaData_print(self, *args):
if len(args):
self.b_printMetaData = args[0]
return True
else:
return self.b_printMetaData
def depth(self, *args):
'''
Get/set the depth of this node.
'''
if len(args):
self.meta.depth(args[0])
else:
return self.meta.depth()
def printPre(self, *args):
'''
get/set the str_pre string.
'''
if len(args):
self.b_printPre = args[0]
else:
return self.b_printPre
@staticmethod
def str_blockIndent(astr_buf, a_tabs=1, a_tabLength=4, **kwargs):
"""
For the input string <astr_buf>, replace each '\n'
with '\n<tab>' where the number of tabs is indicated
by <a_tabs> and the length of the tab by <a_tabLength>
Trailing '\n' are *not* replaced.
"""
str_tabBoundary = " "
for key, value in kwargs.iteritems():
if key == 'tabBoundary': str_tabBoundary = value
b_trailN = False
length = len(astr_buf)
ch_trailN = astr_buf[length - 1]
if ch_trailN == '\n':
b_trailN = True
astr_buf = astr_buf[0:length - 1]
str_ret = astr_buf
str_tab = ''
str_Indent = ''
for i in range(a_tabLength):
str_tab = '%s ' % str_tab
str_tab = "%s%s" % (str_tab, str_tabBoundary)
for i in range(a_tabs):
str_Indent = '%s%s' % (str_Indent, str_tab)
str_ret = re.sub('\n', '\n%s' % str_Indent, astr_buf)
str_ret = '%s%s' % (str_Indent, str_ret)
if b_trailN: str_ret = str_ret + '\n'
return str_ret
def __str__(self):
self.sCore.reset()
str_pre = ""
if not self.depth():
str_pre = "o"
else:
str_pre = "+"
self.sCore.write('%s---%s\n' % (str_pre, self.str_nodeName))
if self.b_printPre:
str_pre = "|"
else:
str_pre = " "
self.meta.pre(str_pre)
if self.b_printMetaData: self.sCore.write('%s' % self.meta)
for key, value in self.d_data.iteritems():
self.sCore.write('%s +--%-17s %s\n' % (str_pre, key, value))
nodeCount = len(self.d_nodes)
if nodeCount and self.b_printContents:
self.sCore.write('%s +---+\n' % str_pre )
elCount = 0
lastKey = self.d_nodes.keys()[-1]
for node in self.d_nodes.keys():
self.d_nodes[node].printPre(True)
if node == lastKey:
self.d_nodes[node].printPre(False)
str_contents = C_snode.str_blockIndent('%s' %
self.d_nodes[node], 1, 8, tabBoundary = "")
# str_contents = re.sub(r' ', 'xxxxxxxx|xxxxxxx', str_contents)
if self.d_nodes[node].printPre():
str_contents = re.sub(r' ', ' | ', str_contents)
self.sCore.write(str_contents)
elCount = elCount + 1
return self.sCore.strget()
#
# Simple error handling
def error_exit(self, astr_action, astr_error, astr_code):
print("%s: FATAL error occurred" % self.str_obj)
print("While %s," % astr_action)
print("%s" % astr_error)
print("\nReturning to system with code %s\n" % astr_code)
sys.exit(astr_code)
def node_branch(self, al_keys, al_values):
"""
For each node in <al_values>, add to internal contents
dictionary using key from <al_keys>.
"""
if len(al_keys) != len(al_values):
self.error_exit("adding branch nodes", "#keys != #values", 1)
ldict = dict(zip(al_keys, al_values))
self.node_dictBranch(ldict)
def node_dictBranch(self, adict):
"""
Expands the internal md_nodes with <adict>
"""
self.d_nodes.update(adict)
class C_snodeBranch:
"""
The C_snodeBranch class is basically a dictionary collection
of C_snodes. Conceptually, a C_snodeBranch is a single "layer"
of C_snodes all branching from a common ancestor node.
"""
#
# Member variables
#
#
# Methods
#
def __str__(self):
self.sCore.reset()
for node in self.dict_branch.keys():
self.sCore.write('%s' % self.dict_branch[node])
return self.sCore.strget()
def __init__(self, al_branchNodes):
'''
Constructor.
If instantiated with a list of nodes, will create/populate
internal dictionary with appropriate C_snodes.
'''
self.str_obj = 'C_snodeBranch'; # name of object class
self.str_name = 'void'; # name of object variable
self._id = -1; # id of agent
self._iter = 0; # current iteration in an
# arbitrary processing
# scheme
self._verbosity = 0; # debug related value for
# object
self._warnings = 0; # show warnings
self.dict_branch = {}
self.sCore = C_stringCore()
element = al_branchNodes[0]
if isinstance(element, C_snode):
for node in al_branchNodes:
self.dict_branch[node] = node
else:
for node in al_branchNodes:
self.dict_branch[node] = C_snode(node)
#
# Simple error handling
def error_exit(self, astr_action, astr_error, astr_code):
print("%s: FATAL error occurred" % self.str_obj)
print("While %s," % astr_action)
print("%s" % astr_error)
print("\nReturning to system with code %s\n" % astr_code)
sys.exit(astr_code)
def node_branch(self, astr_node, abranch):
"""
Adds a branch to a node, i.e. depth addition. The given
node's md_nodes is set to the abranch's mdict_branch.
"""
self.dict_branch[astr_node].node_dictBranch(abranch.dict_branch)
class C_stree:
"""
The C_stree class provides methods for creating / navigating
a tree composed of C_snodes.
A C_stree is an ordered (and nested) collection of C_snodeBranch
instances, with additional logic to match nodes with their parent
node.
The metaphor designed into the tree structure is that of a UNIX
directory tree, with equivalent functions for 'cdnode', 'mknode'
'lsnode'.
"""
#
# Methods
#
def metaData_print(self, *args):
if len(args):
self.b_printMetaData = args[0]
return True
else:
return self.b_printMetaData
def __init__(self, al_rootBranch=[]):
"""
Creates a tree structure and populates the "root"
branch.
"""
#
# Member variables
#
# - Core variables
self.str_obj = 'C_stree'; # name of object class
self.str_name = 'void'; # name of object variable
self._id = -1; # id of agent
self._iter = 0; # current iteration in an
# arbitrary processing
# scheme
self._verbosity = 0; # debug related value for
# object
self._warnings = 0; # show warnings
self.b_printMetaData = False
self.l_allPaths = [] # Each time a new C_snode is
#+ added to the tree, its path
#+ list is appended to this
#+ list variable.
if not len(al_rootBranch):
al_rootBranch = ['/']
if len(al_rootBranch):
if not isinstance(al_rootBranch, list):
al_rootBranch = ['/']
self.sCore = C_stringCore()
str_treeRoot = '/'
self.l_cwd = [str_treeRoot]
self.sbranch_root = C_snodeBranch([str_treeRoot])
self.snode_current = None
self.snode_root = self.sbranch_root.dict_branch[str_treeRoot]
self.snode_root.depth(0)
self.snode_root.snode_parent = self.snode_root
self.root()
self.l_allPaths = self.l_cwd[:]
if len(al_rootBranch) and al_rootBranch != ['/']:
self.mknode(al_rootBranch)
def __str__(self):
self.sCore.reset()
self.sCore.write('%s' % self.snode_root)
return self.sCore.strget()
def root(self):
"""
Reset all nodes and branches to 'root'.
"""
str_treeRoot = '/'
self.l_cwd = [str_treeRoot]
self.snode_current = self.snode_root
self.sbranch_current = self.sbranch_root
def cwd(self):
'''
Return a UNIX FS type string of the current working 'directory'.
'''
l_cwd = self.l_cwd[:]
str_cwd = '/'.join(l_cwd)
if len(str_cwd)>1: str_cwd = str_cwd[1:]
return str_cwd
def pwd(self):
'''
Prints the cwd
'''
return self.cwd()
def ptree(self):
'''
Return all the paths in the tree.
'''
return self.l_allPaths
def node_mustNotInclude(self, al_mustNotInclude, ab_reset=False):
"""
Either appends or resets the <mustNotInclude> list of snode_current
depending on <ab_reset>.
"""
if ab_reset:
self.snode_current.l_mustNotInclude = al_mustNotInclude[:]
else:
l_current = self.snode_current.l_mustNotInclude[:]
l_total = l_current + al_mustNotInclude
self.snode_current.l_mustNotInclude = l_total[:]
def node_mustInclude(self, al_mustInclude, ab_reset=False):
"""
Either appends or resets the <mustInclude> list of snode_current
depending on <ab_reset>.
"""
if ab_reset:
self.snode_current.l_mustInclude = al_mustInclude[:]
else:
l_current = self.snode_current.l_mustInclude[:]
l_total = l_current + al_mustInclude
self.snode_current.l_mustInclude = l_total[:]
def paths_update(self, al_branchNodes):
"""
Add each node in <al_branchNodes> to the self.ml_cwd and
append the combined list to ml_allPaths. This method is
typically not called by a user, but by other methods in
this module.
"""
for node in al_branchNodes:
#print "appending %s" % node
l_pwd = self.l_cwd[:]
l_pwd.append(node)
#print "l_pwd: %s" % l_pwd
#print "ml_cwd: %s" % self.ml_cwd
self.l_allPaths.append(l_pwd)
def mknode(self, al_branchNodes):
"""
Create a set of nodes (branches) at current node. Analogous to
a UNIX mkdir call, however nodes can be any type (i.e. not
just "directories" but also "files")
"""
b_ret = True
# First check that none of these nodes already exist in the tree
l_branchNodes = []
for node in al_branchNodes:
l_path = self.l_cwd[:]
l_path.append(node)
#print l_path
#print self.ml_allPaths
#print self.b_pathOK(l_path)
if not self.b_pathOK(l_path):
l_branchNodes.append(node)
snodeBranch = C_snodeBranch(l_branchNodes)
for node in l_branchNodes:
depth = self.snode_current.depth()
# if (self.msnode_current != self.msnode_root):
snodeBranch.dict_branch[node].depth(depth+1)
snodeBranch.dict_branch[node].snode_parent = self.snode_current
self.snode_current.node_dictBranch(snodeBranch.dict_branch)
# Update the ml_allPaths
self.paths_update(al_branchNodes)
return b_ret
def cat(self, name):
'''
Returns the contents of the 'name'd element at this level.
'''
return self.snode_current.d_data[name]
def touch(self, name, data):
'''
Append 'data' to the current node d_data dictionary under key 'name'
'''
b_OK = True
# print("here!")
# print(self.snode_current)
# print(self.snode_current.d_nodes)
self.snode_current.d_data[name] = data
# print(self.snode_current)
return b_OK
def b_pathOK(self, al_path):
"""
Checks if the absolute path specified in the al_path
is valid for current tree
"""
b_OK = True
try: self.l_allPaths.index(al_path)
except: b_OK = False
return b_OK
def b_pathInTree(self, astr_path):
"""
Converts a string <astr_path> specifier to a list-based
*absolute* lookup, i.e. "/node1/node2/node3" is converted
to ['/' 'node1' 'node2' 'node3'].
The method also understands a paths that start with: '..' or
combination of '../../..' and is also aware that the root
node is its own parent.
If the path list conversion is valid (i.e. exists in the
space of existing paths, l_allPaths), return True and the
destination path list; else return False and the current
path list.
"""
if astr_path == '/': return True, ['/']
al_path = astr_path.split('/')
# Check for absolute path
if not len(al_path[0]):
al_path[0] = '/'
#print "returning %s : %s" % (self.b_pathOK(al_path), al_path)
return self.b_pathOK(al_path), al_path
# Here we are in relative mode...
# First, resolve any leading '..'
l_path = self.l_cwd[:]
if(al_path[0] == '..'):
while(al_path[0] == '..' and len(al_path)):
l_path = l_path[0:-1]
if(len(al_path) >= 2): al_path = al_path[1:]
else: al_path[0] = ''
#print "l_path = %s" % l_path
#print "al_path = %s (%d)" % (al_path, len(al_path[0]))
if(len(al_path[0])):
#print "extending %s with %s" % (l_path, al_path)
l_path.extend(al_path)
else:
l_path = self.l_cwd
l_path.extend(al_path)
#print "final path list = %s (%d)" % (l_path, len(l_path))
if(len(l_path)>=1 and l_path[0] != '/'): l_path.insert(0, '/')
if(len(l_path)>1): l_path[0] = ''
if(not len(l_path)): l_path = ['/']
#TODO: Possibly check for trailing '/', i.e. list ['']
str_path = '/'.join(l_path)
#print "final path str = %s" % str_path
b_valid, al_path = self.b_pathInTree(str_path)
return b_valid, al_path
def cdnode(self, astr_path):
"""
Change working node to astr_path.
The path is converted to a list, split on '/'
Returns the cdnode path
"""
# Start at the root and then navigate to the
# relevant node
l_absPath = []
b_valid, l_absPath = self.b_pathInTree(astr_path)
if b_valid:
#print "got cdpath = %s" % l_absPath
self.l_cwd = l_absPath[:]
self.snode_current = self.snode_root
self.sbranch_current = self.sbranch_root
#print l_absPath
for node in l_absPath[1:]:
self.snode_current = self.snode_current.d_nodes[node]
self.sbranch_current.dict_branch = self.snode_current.snode_parent.d_nodes
return self.l_cwd
def ls(self, astr_path="", **kwargs):
b_lsData = True
b_lsNodes = True
if len(astr_path): self.cdnode(astr_path)
str_nodes = self.str_lsnode(astr_path)
d_data = self.snode_current.d_data
for key, val in kwargs.iteritems():
if key == 'data': b_lsData = val
if key == 'nodes': b_lsNodes = val
if b_lsData and b_lsNodes:
return str_nodes, d_data
if b_lsData:
return d_data
if b_lsNodes:
return str_nodes
return str_nodes, d_data
def str_lsnode(self, astr_path=""):
"""
Print/return the set of nodes branching from current node as string
"""
self.sCore.reset()
str_cwd = self.cwd()
if len(astr_path): self.cdnode(astr_path)
for node in self.snode_current.d_nodes.keys():
self.sCore.write('%s\n' % node)
str_ls = self.sCore.strget()
print(str_ls)
if len(astr_path): self.cdnode(str_cwd)
return str_ls
def lstr_lsnode(self, astr_path=""):
"""
Return the string names of the set of nodes branching from
current node as list of strings
"""
self.sCore.reset()
str_cwd = self.cwd()
if len(astr_path): self.cdnode(astr_path)
lst = self.snode_current.d_nodes.keys()
if len(astr_path): self.cdnode(str_cwd)
return lst
def lsbranch(self, astr_path=""):
"""
Print/return the set of nodes in current branch
"""
self.sCore.reset()
str_cwd = self.cwd()
if len(astr_path): self.cdnode(astr_path)
self.sCore.write('%s' % self.sbranch_current.dict_branch.keys())
str_ls = self.sCore.strget()
print(str_ls)
if len(astr_path): self.cdnode(str_cwd)
return str_ls
def lstree(self, astr_path=""):
"""
Print/return the tree from the current node.
"""
self.sCore.reset()
str_cwd = self.cwd()
if len(astr_path): self.cdnode(astr_path)
str_ls = '%s' % self.snode_current
print(str_ls)
if len(astr_path): self.cdnode(str_cwd)
return str_ls
def lsmeta(self, astr_path=""):
"""
Print/return the "meta" information of the node, i.e.
o mustInclude
o mustNotInclude
o hitCount
"""
self.sCore.reset()
str_cwd = self.cwd()
if len(astr_path): self.cdnode(astr_path)
b_contentsFlag = self.snode_current.b_printContents
self.snode_current.b_printContents = False
str_ls = '%s' % self.snode_current
print(str_ls)
if len(astr_path): self.cdnode(str_cwd)
self.snode_current.b_printContents = b_contentsFlag
return str_ls
def tree_metaData_print(self, aval):
self.metaData_print(aval)
self.treeRecurse(self.treeNode_metaSet)
def treeNode_metaSet(self, astr_path, **kwargs):
'''
Sets the metaData_print bit on node at <astr_path>.
'''
self.cdnode(astr_path)
self.snode_current.metaData_print(self.b_printMetaData)
return True
def treeRecurse(self, afunc_nodeEval = None, astr_startPath = '/'):
"""
Recursively walk through a C_stree, starting from node
<astr_startPath>.
The <afunc_nodeEval> is a function that is called on a node
path. It is of form:
afunc_nodeEval(astr_startPath, **kwargs)
and must return either True or False.
"""
[b_valid, l_path ] = self.b_pathInTree(astr_startPath)
if b_valid and afunc_nodeEval:
b_valid = afunc_nodeEval(astr_startPath)
#print 'processing node: %s' % astr_startPath
if b_valid:
for node in self.lstr_lsnode(astr_startPath):
if astr_startPath == '/': recursePath = "/%s" % node
else: recursePath = '%s/%s' % (astr_startPath, node)
self.treeRecurse(afunc_nodeEval, recursePath)
#
# Simple error handling
def error_exit(self, astr_action, astr_error, astr_code):
print("%s: FATAL error occurred" % self.str_obj)
print("While %s," % astr_action)
print("%s" % astr_error)
print("\nReturning to system with code %s\n" % astr_code)
sys.exit(astr_code)