2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5 BitBake Build System Python Library
7 Copyright (C) 2003 Holger Schurig
8 Copyright (C) 2003, 2004 Chris Larson
10 Based on Gentoo's portage.py.
12 This program is free software; you can redistribute it and/or modify it under
13 the terms of the GNU General Public License as published by the Free Software
14 Foundation; either version 2 of the License, or (at your option) any later
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
23 Place, Suite 330, Boston, MA 02111-1307 USA.
26 __version__ = "1.3.3.4"
66 whitespace = '\t\n\x0b\x0c\r '
67 lowercase = 'abcdefghijklmnopqrstuvwxyz'
69 import sys, os, types, re, string
71 #projectdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
72 projectdir = os.getcwd()
76 if "BBDEBUG" in os.environ:
77 level = int(os.environ["BBDEBUG"])
83 class VarExpandError(Exception):
86 class MalformedUrl(Exception):
87 """Exception raised when encountering an invalid url"""
90 #######################################################################
91 #######################################################################
95 # PURPOSE: little functions to make yourself known
97 #######################################################################
98 #######################################################################
103 def debug(lvl, *args):
104 if debug_level >= lvl:
105 print debug_prepend + 'DEBUG:', ''.join(args)
108 print debug_prepend + 'NOTE:', ''.join(args)
111 print debug_prepend + 'ERROR:', ''.join(args)
114 print debug_prepend + 'ERROR:', ''.join(args)
118 #######################################################################
119 #######################################################################
123 # PURPOSE: Basic file and directory tree related functions
125 #######################################################################
126 #######################################################################
129 """Create a directory like 'mkdir -p', but does not complain if
130 directory already exists like os.makedirs
133 debug(3, "mkdirhier(%s)" % dir)
136 debug(2, "created " + dir)
138 if e.errno != 17: raise e
141 #######################################################################
145 def movefile(src,dest,newmtime=None,sstat=None):
146 """Moves a file from src to dest, preserving all permissions and
147 attributes; mtime will be preserved even when moving across
148 filesystems. Returns true on success and false on failure. Move is
152 #print "movefile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
157 print "!!! Stating source file failed... movefile()"
165 dstat=os.lstat(os.path.dirname(dest))
169 if stat.S_ISLNK(dstat[stat.ST_MODE]):
176 if stat.S_ISLNK(sstat[stat.ST_MODE]):
178 target=os.readlink(src)
179 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
181 os.symlink(target,dest)
182 # os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
184 return os.lstat(dest)
186 print "!!! failed to properly create symlink:"
187 print "!!!",dest,"->",target
192 if sstat[stat.ST_DEV]==dstat[stat.ST_DEV]:
194 ret=os.rename(src,dest)
198 if e[0]!=errno.EXDEV:
200 print "!!! Failed to move",src,"to",dest
203 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
207 if stat.S_ISREG(sstat[stat.ST_MODE]):
208 try: # For safety copy then move it over.
209 shutil.copyfile(src,dest+"#new")
210 os.rename(dest+"#new",dest)
213 print '!!! copy',src,'->',dest,'failed.'
217 #we don't yet handle special, so we need to fall back to /bin/mv
218 a=getstatusoutput("/bin/mv -f "+"'"+src+"' '"+dest+"'")
220 print "!!! Failed to move special file:"
221 print "!!! '"+src+"' to '"+dest+"'"
223 return None # failure
226 missingos.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
227 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
230 print "!!! Failed to chown/chmod/unlink in movefile()"
236 os.utime(dest,(newmtime,newmtime))
238 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
239 newmtime=sstat[stat.ST_MTIME]
244 #######################################################################
245 #######################################################################
249 # PURPOSE: Download via HTTP, FTP, CVS, BITKEEPER, handling of MD5-signatures
252 #######################################################################
253 #######################################################################
256 """Decodes an URL into the tokens (scheme, network location, path,
257 user, password, parameters).
259 >>> decodeurl("http://www.google.com/index.html")
260 ('http', 'www.google.com', '/index.html', '', '', {})
262 CVS url with username, host and cvsroot. The cvs module to check out is in the
265 >>> decodeurl("cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg")
266 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'})
268 Dito, but this time the username has a password part. And we also request a special tag
271 >>> decodeurl("cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81")
272 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'})
275 m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
277 raise MalformedUrl(url)
279 type = m.group('type')
280 location = m.group('location')
282 raise MalformedUrl(url)
283 user = m.group('user')
284 parm = m.group('parm')
285 m = re.compile('(?P<host>[^/;]+)(?P<path>/[^;]+)').match(location)
287 host = m.group('host')
288 path = m.group('path')
293 m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
295 user = m.group('user')
296 pswd = m.group('pswd')
303 for s in parm.split(';'):
307 return (type, host, path, user, pswd, p)
309 #######################################################################
311 def encodeurl(decoded):
312 """Encodes a URL from tokens (scheme, network location, path,
313 user, password, parameters).
315 >>> encodeurl(['http', 'www.google.com', '/index.html', '', '', {}])
316 'http://www.google.com/index.html'
318 CVS with username, host and cvsroot. The cvs module to check out is in the
321 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}])
322 'cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg'
324 Dito, but this time the username has a password part. And we also request a special tag
327 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}])
328 'cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg'
331 (type, host, path, user, pswd, p) = decoded
333 if not type or not path:
334 fatal("invalid or missing parameters for url encoding")
345 for parm in p.keys():
346 url += ";%s=%s" % (parm, p[parm])
350 #######################################################################
352 def which(path, item, direction = 0):
353 """Useful function for locating a file in a PATH"""
355 for p in (path or "").split(':'):
356 if os.path.exists(os.path.join(p, item)):
357 found = os.path.join(p, item)
362 #######################################################################
367 #######################################################################
368 #######################################################################
370 # SECTION: Dependency
372 # PURPOSE: Compare build & run dependencies
374 #######################################################################
375 #######################################################################
377 def tokenize(mystring):
378 """Breaks a string like 'foo? (bar) oni? (blah (blah))' into (possibly embedded) lists:
384 >>> tokenize("(x y)")
386 >>> tokenize("(x y) b c")
387 [['x', 'y'], 'b', 'c']
388 >>> tokenize("foo? (bar) oni? (blah (blah))")
389 ['foo?', ['bar'], 'oni?', ['blah', ['blah']]]
390 >>> tokenize("sys-apps/linux-headers nls? (sys-devel/gettext)")
391 ['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']]
402 curlist.append(accum)
404 prevlists.append(curlist)
409 curlist.append(accum)
412 print "!!! tokenizer: Unmatched left parenthesis in:\n'"+mystring+"'"
415 curlist=prevlists.pop()
416 curlist.append(newlist)
418 elif x in whitespace:
420 curlist.append(accum)
425 curlist.append(accum)
427 print "!!! tokenizer: Exiting with unterminated parenthesis in:\n'"+mystring+"'"
432 #######################################################################
434 def evaluate(tokens,mydefines,allon=0):
435 """Removes tokens based on whether conditional definitions exist or not.
438 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {})
439 ['sys-apps/linux-headers']
443 >>> evaluate(['sys-apps/linux-headers', '!nls?', ['sys-devel/gettext']], {})
444 ['sys-apps/linux-headers', ['sys-devel/gettext']]
448 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {"nls":1})
449 ['sys-apps/linux-headers', ['sys-devel/gettext']]
453 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {}, True)
454 ['sys-apps/linux-headers', ['sys-devel/gettext']]
459 mytokens = tokens + [] # this copies the list
461 while pos < len(mytokens):
462 if type(mytokens[pos]) == types.ListType:
463 evaluate(mytokens[pos], mydefines)
464 if not len(mytokens[pos]):
467 elif mytokens[pos][-1] == "?":
468 cur = mytokens[pos][:-1]
475 if (cur[1:] in mydefines) and (pos < len(mytokens)):
478 elif (cur not in mydefines) and (pos < len(mytokens)):
485 #######################################################################
487 def flatten(mytokens):
488 """Converts nested arrays into a flat arrays:
490 >>> flatten([1,[2,3]])
492 >>> flatten(['sys-apps/linux-headers', ['sys-devel/gettext']])
493 ['sys-apps/linux-headers', 'sys-devel/gettext']
498 if type(x)==types.ListType:
499 newlist.extend(flatten(x))
505 #######################################################################
507 _package_weights_ = {"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1} # dicts are unordered
508 _package_ends_ = ["pre", "p", "alpha", "beta", "rc", "cvs", "bk", "HEAD" ] # so we need ordered list
511 """Parses the last elements of a version number into a triplet, that can
514 >>> relparse('1.2_pre3')
525 mynewver = myver.split('_')
527 # an _package_weights_
528 number = float(mynewver[0])
530 for x in _package_ends_:
532 if mynewver[1][:elen] == x:
534 p1 = _package_weights_[x]
536 p2 = float(mynewver[1][elen:])
541 # normal number or number with letter at end
542 divider = len(myver)-1
543 if myver[divider:] not in "1234567890":
545 p1 = ord(myver[divider:])
546 number = float(myver[0:divider])
548 number = float(myver)
550 # normal number or number with letter at end
551 divider = len(myver)-1
552 if myver[divider:] not in "1234567890":
554 p1 = ord(myver[divider:])
555 number = float(myver[0:divider])
557 number = float(myver)
558 return [number,p1,p2]
561 #######################################################################
563 __ververify_cache__ = {}
565 def ververify(myorigval,silent=1):
566 """Returns 1 if given a valid version string, els 0. Valid versions are in the format
568 <v1>.<v2>...<vx>[a-z,_{_package_weights_}[vy]]
570 >>> ververify('2.4.20')
572 >>> ververify('2.4..20') # two dots
574 >>> ververify('2.x.20') # 'x' is not numeric
576 >>> ververify('2.4.20a')
578 >>> ververify('2.4.20cvs') # only one trailing letter
582 >>> ververify('test_a') # no version at all
584 >>> ververify('2.4.20_beta1')
586 >>> ververify('2.4.20_beta')
588 >>> ververify('2.4.20_wrongext') # _wrongext is no valid trailer
592 # Lookup the cache first
594 return __ververify_cache__[myorigval]
598 if len(myorigval) == 0:
600 error("package version is empty")
601 __ververify_cache__[myorigval] = 0
603 myval = myorigval.split('.')
606 error("package name has empty version string")
607 __ververify_cache__[myorigval] = 0
609 # all but the last version must be a numeric
613 error("package version has two points in a row")
614 __ververify_cache__[myorigval] = 0
620 error("package version contains non-numeric '"+x+"'")
621 __ververify_cache__[myorigval] = 0
623 if not len(myval[-1]):
625 error("package version has trailing dot")
626 __ververify_cache__[myorigval] = 0
630 __ververify_cache__[myorigval] = 1
635 # ok, our last component is not a plain number or blank, let's continue
636 if myval[-1][-1] in lowercase:
638 foo = int(myval[-1][:-1])
640 __ververify_cache__[myorigval] = 1
644 # ok, maybe we have a 1_alpha or 1_beta2; let's see
645 ep=string.split(myval[-1],"_")
648 error("package version has more than one letter at then end")
649 __ververify_cache__[myorigval] = 0
652 foo = string.atoi(ep[0])
654 # this needs to be numeric, i.e. the "1" in "1_alpha"
656 error("package version must have numeric part before the '_'")
657 __ververify_cache__[myorigval] = 0
660 for mye in _package_ends_:
661 if ep[1][0:len(mye)] == mye:
662 if len(mye) == len(ep[1]):
663 # no trailing numeric is ok
664 __ververify_cache__[myorigval] = 1
668 foo = string.atoi(ep[1][len(mye):])
669 __ververify_cache__[myorigval] = 1
672 # if no _package_weights_ work, *then* we return 0
675 error("package version extension after '_' is invalid")
676 __ververify_cache__[myorigval] = 0
680 def isjustname(mypkg):
681 myparts = string.split(mypkg,'-')
688 _isspecific_cache_={}
690 def isspecific(mypkg):
691 "now supports packages with no category"
693 return __isspecific_cache__[mypkg]
697 mysplit = string.split(mypkg,"/")
698 if not isjustname(mysplit[-1]):
699 __isspecific_cache__[mypkg] = 1
701 __isspecific_cache__[mypkg] = 0
705 #######################################################################
707 __pkgsplit_cache__={}
709 def pkgsplit(mypkg, silent=1):
711 """This function can be used as a package verification function. If
712 it is a valid name, pkgsplit will return a list containing:
713 [pkgname, pkgversion(norev), pkgrev ].
719 >>> pkgsplit('glibc-1.2-8.9-r7')
720 >>> pkgsplit('glibc-2.2.5-r7')
721 ['glibc', '2.2.5', 'r7']
722 >>> pkgsplit('foo-1.2-1')
723 >>> pkgsplit('Mesa-3.0')
724 ['Mesa', '3.0', 'r0']
728 return __pkgsplit_cache__[mypkg]
732 myparts = string.split(mypkg,'-')
735 error("package name without name or version part")
736 __pkgsplit_cache__[mypkg] = None
741 error("package name with empty name or version part")
742 __pkgsplit_cache__[mypkg] = None
747 ververify(myrev, silent)
748 if len(myrev) and myrev[0] == "r":
750 string.atoi(myrev[1:])
755 if ververify(myparts[-2]):
756 if len(myparts) == 2:
757 __pkgsplit_cache__[mypkg] = None
760 for x in myparts[:-2]:
762 __pkgsplit_cache__[mypkg]=None
764 # names can't have versiony looking parts
765 myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]]
766 __pkgsplit_cache__[mypkg]=myval
769 __pkgsplit_cache__[mypkg] = None
772 elif ververify(myparts[-1],silent):
775 print "!!! Name error in",mypkg+": missing name part."
776 __pkgsplit_cache__[mypkg]=None
779 for x in myparts[:-1]:
781 if not silent: error("package name has multiple version parts")
782 __pkgsplit_cache__[mypkg] = None
784 myval = [string.join(myparts[:-1],"-"), myparts[-1],"r0"]
785 __pkgsplit_cache__[mypkg] = myval
788 __pkgsplit_cache__[mypkg] = None
792 #######################################################################
794 __catpkgsplit_cache__ = {}
796 def catpkgsplit(mydata,silent=1):
797 """returns [cat, pkgname, version, rev ]
799 >>> catpkgsplit('sys-libs/glibc-1.2-r7')
800 ['sys-libs', 'glibc', '1.2', 'r7']
801 >>> catpkgsplit('glibc-1.2-r7')
802 [None, 'glibc', '1.2', 'r7']
806 return __catpkgsplit_cache__[mydata]
810 cat = os.path.basename(os.path.dirname(mydata))
811 mydata = os.path.join(cat, os.path.basename(mydata))
812 if mydata[-3:] == '.bb':
815 mysplit = mydata.split("/")
817 splitlen = len(mysplit)
820 p_split = pkgsplit(mydata,silent)
822 retval = [mysplit[splitlen - 2]]
823 p_split = pkgsplit(mysplit[splitlen - 1],silent)
825 __catpkgsplit_cache__[mydata] = None
827 retval.extend(p_split)
828 __catpkgsplit_cache__[mydata] = retval
832 #######################################################################
834 __vercmp_cache__ = {}
836 def vercmp(val1,val2):
837 """This takes two version strings and returns an integer to tell you whether
838 the versions are the same, val1>val2 or val2>val1.
844 >>> vercmp('1', '1.0')
846 >>> vercmp('1', '1.1')
848 >>> vercmp('1.1', '1_p2')
852 # quick short-circuit
855 valkey = val1+" "+val2
859 return __vercmp_cache__[valkey]
861 return - __vercmp_cache__[val2+" "+val1]
867 # consider 1_p2 vc 1.1
868 # after expansion will become (1_p2,0) vc (1,1)
869 # then 1_p2 is compared with 1 before 0 is compared with 1
870 # to solve the bug we need to convert it to (1,0_p2)
871 # by splitting _prepart part and adding it back _after_expansion
873 val1_prepart = val2_prepart = ''
875 val1, val1_prepart = val1.split('_', 1)
877 val2, val2_prepart = val2.split('_', 1)
880 # FIXME: Is it needed? can val1/2 contain '-'?
882 val1 = string.split(val1,'-')
884 val1[0] = val1[0] +"."+ val1[1]
885 val2 = string.split(val2,'-')
887 val2[0] = val2[0] +"."+ val2[1]
889 val1 = string.split(val1[0],'.')
890 val2 = string.split(val2[0],'.')
892 # add back decimal point so that .03 does not become "3" !
893 for x in range(1,len(val1)):
894 if val1[x][0] == '0' :
895 val1[x] = '.' + val1[x]
896 for x in range(1,len(val2)):
897 if val2[x][0] == '0' :
898 val2[x] = '.' + val2[x]
900 # extend varion numbers
901 if len(val2) < len(val1):
902 val2.extend(["0"]*(len(val1)-len(val2)))
903 elif len(val1) < len(val2):
904 val1.extend(["0"]*(len(val2)-len(val1)))
906 # add back _prepart tails
908 val1[-1] += '_' + val1_prepart
910 val2[-1] += '_' + val2_prepart
911 # The above code will extend version numbers out so they
912 # have the same number of digits.
913 for x in range(0,len(val1)):
914 cmp1 = relparse(val1[x])
915 cmp2 = relparse(val2[x])
917 myret = cmp1[y] - cmp2[y]
919 __vercmp_cache__[valkey] = myret
921 __vercmp_cache__[valkey] = 0
925 #######################################################################
927 def pkgcmp(pkg1,pkg2):
928 """ Compares two packages, which should have been split via
929 pkgsplit(). if the return value val is less than zero, then pkg2 is
930 newer than pkg1, zero if equal and positive if older.
932 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r7'])
934 >>> pkgcmp(['glibc', '2.2.5', 'r4'], ['glibc', '2.2.5', 'r7'])
936 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r2'])
940 mycmp = vercmp(pkg1[1],pkg2[1])
945 r1=string.atoi(pkg1[2][1:])
946 r2=string.atoi(pkg2[2][1:])
954 #######################################################################
956 def dep_parenreduce(mysplit, mypos=0):
957 """Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists:
959 >>> dep_parenreduce([''])
961 >>> dep_parenreduce(['1', '2', '3'])
963 >>> dep_parenreduce(['1', '(', '2', '3', ')', '4'])
964 ['1', ['2', '3'], '4']
967 while mypos < len(mysplit):
968 if mysplit[mypos] == "(":
971 while mypos < len(mysplit):
972 if mysplit[mypos] == ")":
973 mysplit[firstpos:mypos+1] = [mysplit[firstpos+1:mypos]]
976 elif mysplit[mypos] == "(":
978 mysplit = dep_parenreduce(mysplit,mypos)
984 def dep_opconvert(mysplit, myuse):
985 "Does dependency operator conversion"
989 while mypos < len(mysplit):
990 if type(mysplit[mypos]) == types.ListType:
991 newsplit.append(dep_opconvert(mysplit[mypos],myuse))
993 elif mysplit[mypos] == ")":
994 # mismatched paren, error
996 elif mysplit[mypos]=="||":
997 if ((mypos+1)>=len(mysplit)) or (type(mysplit[mypos+1])!=types.ListType):
998 # || must be followed by paren'd list
1001 mynew = dep_opconvert(mysplit[mypos+1],myuse)
1002 except Exception, e:
1003 error("unable to satisfy OR dependancy: " + string.join(mysplit," || "))
1006 newsplit.append(mynew)
1008 elif mysplit[mypos][-1] == "?":
1009 # use clause, i.e "gnome? ( foo bar )"
1010 # this is a quick and dirty hack so that repoman can enable all USE vars:
1011 if (len(myuse) == 1) and (myuse[0] == "*"):
1012 # enable it even if it's ! (for repoman) but kill it if it's
1013 # an arch variable that isn't for this arch. XXX Sparc64?
1014 if (mysplit[mypos][:-1] not in settings.usemask) or \
1015 (mysplit[mypos][:-1]==settings["ARCH"]):
1020 if mysplit[mypos][0] == "!":
1021 myusevar = mysplit[mypos][1:-1]
1022 enabled = not myusevar in myuse
1023 #if myusevar in myuse:
1028 myusevar=mysplit[mypos][:-1]
1029 enabled = myusevar in myuse
1030 #if myusevar in myuse:
1034 if (mypos +2 < len(mysplit)) and (mysplit[mypos+2] == ":"):
1037 # choose the first option
1038 if type(mysplit[mypos+1]) == types.ListType:
1039 newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1041 newsplit.append(mysplit[mypos+1])
1043 # choose the alternate option
1044 if type(mysplit[mypos+1]) == types.ListType:
1045 newsplit.append(dep_opconvert(mysplit[mypos+3],myuse))
1047 newsplit.append(mysplit[mypos+3])
1052 if type(mysplit[mypos+1]) == types.ListType:
1053 newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1055 newsplit.append(mysplit[mypos+1])
1056 # otherwise, continue
1060 newsplit.append(mysplit[mypos])
1065 """beautiful directed graph object"""
1069 #okeys = keys, in order they were added (to optimize firstzero() ordering)
1071 self.__callback_cache=[]
1075 for key in self.okeys:
1076 str += "%s:\t%s\n" % (key, self.dict[key][1])
1079 def addnode(self,mykey,myparent):
1080 if not mykey in self.dict:
1081 self.okeys.append(mykey)
1083 self.dict[mykey]=[0,[]]
1085 self.dict[mykey]=[0,[myparent]]
1086 self.dict[myparent][0]=self.dict[myparent][0]+1
1088 if myparent and (not myparent in self.dict[mykey][1]):
1089 self.dict[mykey][1].append(myparent)
1090 self.dict[myparent][0]=self.dict[myparent][0]+1
1092 def delnode(self,mykey, ref = 1):
1095 If ref is 1, remove references to this node from other nodes.
1096 If ref is 2, remove nodes that reference this node."""
1097 if not mykey in self.dict:
1099 for x in self.dict[mykey][1]:
1100 self.dict[x][0]=self.dict[x][0]-1
1101 del self.dict[mykey]
1104 self.okeys.remove(mykey)
1109 for k in self.okeys:
1110 if mykey in self.dict[k][1]:
1111 if ref == 1 or ref == 2:
1112 self.dict[k][1].remove(mykey)
1116 self.delnode(l, ref)
1119 "returns all nodes in the dictionary"
1120 return self.dict.keys()
1122 def firstzero(self):
1123 "returns first node with zero references, or NULL if no such node exists"
1124 for x in self.okeys:
1125 if self.dict[x][0]==0:
1129 def firstnonzero(self):
1130 "returns first node with nonzero references, or NULL if no such node exists"
1131 for x in self.okeys:
1132 if self.dict[x][0]!=0:
1138 "returns all nodes with zero references, or NULL if no such node exists"
1140 for x in self.dict.keys():
1141 if self.dict[x][0]==0:
1145 def hasallzeros(self):
1146 "returns 0/1, Are all nodes zeros? 1 : 0"
1148 for x in self.dict.keys():
1149 if self.dict[x][0]!=0:
1154 if len(self.dict)==0:
1158 def hasnode(self,mynode):
1159 return mynode in self.dict
1161 def getparents(self, item):
1162 if not self.hasnode(item):
1164 return self.dict[item][1]
1166 def getchildren(self, item):
1167 if not self.hasnode(item):
1169 children = [i for i in self.okeys if item in self.getparents(i)]
1172 def walkdown(self, item, callback, debug = None, usecache = False):
1173 if not self.hasnode(item):
1177 if self.__callback_cache.count(item):
1179 print "hit cache for item: %s" % item
1182 parents = self.getparents(item)
1183 children = self.getchildren(item)
1186 # print "%s is both parent and child of %s" % (p, item)
1188 self.__callback_cache.append(p)
1189 ret = callback(self, p)
1194 print "eek, i'm my own parent!"
1197 print "item: %s, p: %s" % (item, p)
1198 ret = self.walkdown(p, callback, debug, usecache)
1202 self.__callback_cache.append(item)
1203 return callback(self, item)
1205 def walkup(self, item, callback):
1206 if not self.hasnode(item):
1209 parents = self.getparents(item)
1210 children = self.getchildren(item)
1213 ret = callback(self, item)
1218 print "eek, i'm my own child!"
1220 ret = self.walkup(c, callback)
1223 return callback(self, item)
1227 for x in self.dict.keys():
1228 mygraph.dict[x]=self.dict[x][:]
1229 mygraph.okeys=self.okeys[:]
1232 if __name__ == "__main__":