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.
68 whitespace = '\t\n\x0b\x0c\r '
69 lowercase = 'abcdefghijklmnopqrstuvwxyz'
71 import sys, os, types, re, string
73 #projectdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
74 projectdir = os.getcwd()
78 if "BBDEBUG" in os.environ:
79 level = int(os.environ["BBDEBUG"])
85 class VarExpandError(Exception):
88 class MalformedUrl(Exception):
89 """Exception raised when encountering an invalid url"""
92 #######################################################################
93 #######################################################################
97 # PURPOSE: little functions to make yourself known
99 #######################################################################
100 #######################################################################
105 def debug(lvl, *args):
106 if debug_level >= lvl:
107 print debug_prepend + 'DEBUG:', ''.join(args)
110 print debug_prepend + 'NOTE:', ''.join(args)
113 print debug_prepend + 'ERROR:', ''.join(args)
116 print debug_prepend + 'ERROR:', ''.join(args)
120 #######################################################################
121 #######################################################################
125 # PURPOSE: Basic file and directory tree related functions
127 #######################################################################
128 #######################################################################
131 """Create a directory like 'mkdir -p', but does not complain if
132 directory already exists like os.makedirs
135 debug(3, "mkdirhier(%s)" % dir)
138 debug(2, "created " + dir)
140 if e.errno != 17: raise e
143 #######################################################################
147 def movefile(src,dest,newmtime=None,sstat=None):
148 """Moves a file from src to dest, preserving all permissions and
149 attributes; mtime will be preserved even when moving across
150 filesystems. Returns true on success and false on failure. Move is
154 #print "movefile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
159 print "!!! Stating source file failed... movefile()"
167 dstat=os.lstat(os.path.dirname(dest))
171 if stat.S_ISLNK(dstat[stat.ST_MODE]):
178 if stat.S_ISLNK(sstat[stat.ST_MODE]):
180 target=os.readlink(src)
181 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
183 os.symlink(target,dest)
184 # os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
186 return os.lstat(dest)
188 print "!!! failed to properly create symlink:"
189 print "!!!",dest,"->",target
194 if sstat[stat.ST_DEV]==dstat[stat.ST_DEV]:
196 ret=os.rename(src,dest)
200 if e[0]!=errno.EXDEV:
202 print "!!! Failed to move",src,"to",dest
205 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
209 if stat.S_ISREG(sstat[stat.ST_MODE]):
210 try: # For safety copy then move it over.
211 shutil.copyfile(src,dest+"#new")
212 os.rename(dest+"#new",dest)
215 print '!!! copy',src,'->',dest,'failed.'
219 #we don't yet handle special, so we need to fall back to /bin/mv
220 a=getstatusoutput("/bin/mv -f "+"'"+src+"' '"+dest+"'")
222 print "!!! Failed to move special file:"
223 print "!!! '"+src+"' to '"+dest+"'"
225 return None # failure
228 missingos.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
229 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
232 print "!!! Failed to chown/chmod/unlink in movefile()"
238 os.utime(dest,(newmtime,newmtime))
240 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
241 newmtime=sstat[stat.ST_MTIME]
246 #######################################################################
247 #######################################################################
251 # PURPOSE: Download via HTTP, FTP, CVS, BITKEEPER, handling of MD5-signatures
254 #######################################################################
255 #######################################################################
258 """Decodes an URL into the tokens (scheme, network location, path,
259 user, password, parameters).
261 >>> decodeurl("http://www.google.com/index.html")
262 ('http', 'www.google.com', '/index.html', '', '', {})
264 CVS url with username, host and cvsroot. The cvs module to check out is in the
267 >>> decodeurl("cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg")
268 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'})
270 Dito, but this time the username has a password part. And we also request a special tag
273 >>> decodeurl("cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81")
274 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'})
277 m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
279 raise MalformedUrl(url)
281 type = m.group('type')
282 location = m.group('location')
284 raise MalformedUrl(url)
285 user = m.group('user')
286 parm = m.group('parm')
287 m = re.compile('(?P<host>[^/;]+)(?P<path>/[^;]+)').match(location)
289 host = m.group('host')
290 path = m.group('path')
295 m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
297 user = m.group('user')
298 pswd = m.group('pswd')
305 for s in parm.split(';'):
309 return (type, host, path, user, pswd, p)
311 #######################################################################
313 def encodeurl(decoded):
314 """Encodes a URL from tokens (scheme, network location, path,
315 user, password, parameters).
317 >>> encodeurl(['http', 'www.google.com', '/index.html', '', '', {}])
318 'http://www.google.com/index.html'
320 CVS with username, host and cvsroot. The cvs module to check out is in the
323 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}])
324 'cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg'
326 Dito, but this time the username has a password part. And we also request a special tag
329 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}])
330 'cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg'
333 (type, host, path, user, pswd, p) = decoded
335 if not type or not path:
336 fatal("invalid or missing parameters for url encoding")
347 for parm in p.keys():
348 url += ";%s=%s" % (parm, p[parm])
352 #######################################################################
354 def which(path, item, direction = 0):
355 """Useful function for locating a file in a PATH"""
357 for p in (path or "").split(':'):
358 if os.path.exists(os.path.join(p, item)):
359 found = os.path.join(p, item)
364 #######################################################################
369 #######################################################################
370 #######################################################################
372 # SECTION: Dependency
374 # PURPOSE: Compare build & run dependencies
376 #######################################################################
377 #######################################################################
379 def tokenize(mystring):
380 """Breaks a string like 'foo? (bar) oni? (blah (blah))' into (possibly embedded) lists:
386 >>> tokenize("(x y)")
388 >>> tokenize("(x y) b c")
389 [['x', 'y'], 'b', 'c']
390 >>> tokenize("foo? (bar) oni? (blah (blah))")
391 ['foo?', ['bar'], 'oni?', ['blah', ['blah']]]
392 >>> tokenize("sys-apps/linux-headers nls? (sys-devel/gettext)")
393 ['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']]
404 curlist.append(accum)
406 prevlists.append(curlist)
411 curlist.append(accum)
414 print "!!! tokenizer: Unmatched left parenthesis in:\n'"+mystring+"'"
417 curlist=prevlists.pop()
418 curlist.append(newlist)
420 elif x in whitespace:
422 curlist.append(accum)
427 curlist.append(accum)
429 print "!!! tokenizer: Exiting with unterminated parenthesis in:\n'"+mystring+"'"
434 #######################################################################
436 def evaluate(tokens,mydefines,allon=0):
437 """Removes tokens based on whether conditional definitions exist or not.
440 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {})
441 ['sys-apps/linux-headers']
445 >>> evaluate(['sys-apps/linux-headers', '!nls?', ['sys-devel/gettext']], {})
446 ['sys-apps/linux-headers', ['sys-devel/gettext']]
450 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {"nls":1})
451 ['sys-apps/linux-headers', ['sys-devel/gettext']]
455 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {}, True)
456 ['sys-apps/linux-headers', ['sys-devel/gettext']]
461 mytokens = tokens + [] # this copies the list
463 while pos < len(mytokens):
464 if type(mytokens[pos]) == types.ListType:
465 evaluate(mytokens[pos], mydefines)
466 if not len(mytokens[pos]):
469 elif mytokens[pos][-1] == "?":
470 cur = mytokens[pos][:-1]
477 if (cur[1:] in mydefines) and (pos < len(mytokens)):
480 elif (cur not in mydefines) and (pos < len(mytokens)):
487 #######################################################################
489 def flatten(mytokens):
490 """Converts nested arrays into a flat arrays:
492 >>> flatten([1,[2,3]])
494 >>> flatten(['sys-apps/linux-headers', ['sys-devel/gettext']])
495 ['sys-apps/linux-headers', 'sys-devel/gettext']
500 if type(x)==types.ListType:
501 newlist.extend(flatten(x))
507 #######################################################################
509 _package_weights_ = {"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1} # dicts are unordered
510 _package_ends_ = ["pre", "p", "alpha", "beta", "rc", "cvs", "bk", "HEAD" ] # so we need ordered list
513 """Parses the last elements of a version number into a triplet, that can
516 >>> relparse('1.2_pre3')
527 mynewver = myver.split('_')
529 # an _package_weights_
530 number = float(mynewver[0])
532 for x in _package_ends_:
534 if mynewver[1][:elen] == x:
536 p1 = _package_weights_[x]
538 p2 = float(mynewver[1][elen:])
543 # normal number or number with letter at end
544 divider = len(myver)-1
545 if myver[divider:] not in "1234567890":
547 p1 = ord(myver[divider:])
548 number = float(myver[0:divider])
550 number = float(myver)
552 # normal number or number with letter at end
553 divider = len(myver)-1
554 if myver[divider:] not in "1234567890":
556 p1 = ord(myver[divider:])
557 number = float(myver[0:divider])
559 number = float(myver)
560 return [number,p1,p2]
563 #######################################################################
565 __ververify_cache__ = {}
567 def ververify(myorigval,silent=1):
568 """Returns 1 if given a valid version string, els 0. Valid versions are in the format
570 <v1>.<v2>...<vx>[a-z,_{_package_weights_}[vy]]
572 >>> ververify('2.4.20')
574 >>> ververify('2.4..20') # two dots
576 >>> ververify('2.x.20') # 'x' is not numeric
578 >>> ververify('2.4.20a')
580 >>> ververify('2.4.20cvs') # only one trailing letter
584 >>> ververify('test_a') # no version at all
586 >>> ververify('2.4.20_beta1')
588 >>> ververify('2.4.20_beta')
590 >>> ververify('2.4.20_wrongext') # _wrongext is no valid trailer
594 # Lookup the cache first
596 return __ververify_cache__[myorigval]
600 if len(myorigval) == 0:
602 error("package version is empty")
603 __ververify_cache__[myorigval] = 0
605 myval = myorigval.split('.')
608 error("package name has empty version string")
609 __ververify_cache__[myorigval] = 0
611 # all but the last version must be a numeric
615 error("package version has two points in a row")
616 __ververify_cache__[myorigval] = 0
622 error("package version contains non-numeric '"+x+"'")
623 __ververify_cache__[myorigval] = 0
625 if not len(myval[-1]):
627 error("package version has trailing dot")
628 __ververify_cache__[myorigval] = 0
632 __ververify_cache__[myorigval] = 1
637 # ok, our last component is not a plain number or blank, let's continue
638 if myval[-1][-1] in lowercase:
640 foo = int(myval[-1][:-1])
642 __ververify_cache__[myorigval] = 1
646 # ok, maybe we have a 1_alpha or 1_beta2; let's see
647 ep=string.split(myval[-1],"_")
650 error("package version has more than one letter at then end")
651 __ververify_cache__[myorigval] = 0
654 foo = string.atoi(ep[0])
656 # this needs to be numeric, i.e. the "1" in "1_alpha"
658 error("package version must have numeric part before the '_'")
659 __ververify_cache__[myorigval] = 0
662 for mye in _package_ends_:
663 if ep[1][0:len(mye)] == mye:
664 if len(mye) == len(ep[1]):
665 # no trailing numeric is ok
666 __ververify_cache__[myorigval] = 1
670 foo = string.atoi(ep[1][len(mye):])
671 __ververify_cache__[myorigval] = 1
674 # if no _package_weights_ work, *then* we return 0
677 error("package version extension after '_' is invalid")
678 __ververify_cache__[myorigval] = 0
682 def isjustname(mypkg):
683 myparts = string.split(mypkg,'-')
690 _isspecific_cache_={}
692 def isspecific(mypkg):
693 "now supports packages with no category"
695 return __isspecific_cache__[mypkg]
699 mysplit = string.split(mypkg,"/")
700 if not isjustname(mysplit[-1]):
701 __isspecific_cache__[mypkg] = 1
703 __isspecific_cache__[mypkg] = 0
707 #######################################################################
709 __pkgsplit_cache__={}
711 def pkgsplit(mypkg, silent=1):
713 """This function can be used as a package verification function. If
714 it is a valid name, pkgsplit will return a list containing:
715 [pkgname, pkgversion(norev), pkgrev ].
721 >>> pkgsplit('glibc-1.2-8.9-r7')
722 >>> pkgsplit('glibc-2.2.5-r7')
723 ['glibc', '2.2.5', 'r7']
724 >>> pkgsplit('foo-1.2-1')
725 >>> pkgsplit('Mesa-3.0')
726 ['Mesa', '3.0', 'r0']
730 return __pkgsplit_cache__[mypkg]
734 myparts = string.split(mypkg,'-')
737 error("package name without name or version part")
738 __pkgsplit_cache__[mypkg] = None
743 error("package name with empty name or version part")
744 __pkgsplit_cache__[mypkg] = None
749 ververify(myrev, silent)
750 if len(myrev) and myrev[0] == "r":
752 string.atoi(myrev[1:])
757 if ververify(myparts[-2]):
758 if len(myparts) == 2:
759 __pkgsplit_cache__[mypkg] = None
762 for x in myparts[:-2]:
764 __pkgsplit_cache__[mypkg]=None
766 # names can't have versiony looking parts
767 myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]]
768 __pkgsplit_cache__[mypkg]=myval
771 __pkgsplit_cache__[mypkg] = None
774 elif ververify(myparts[-1],silent):
777 print "!!! Name error in",mypkg+": missing name part."
778 __pkgsplit_cache__[mypkg]=None
781 for x in myparts[:-1]:
783 if not silent: error("package name has multiple version parts")
784 __pkgsplit_cache__[mypkg] = None
786 myval = [string.join(myparts[:-1],"-"), myparts[-1],"r0"]
787 __pkgsplit_cache__[mypkg] = myval
790 __pkgsplit_cache__[mypkg] = None
794 #######################################################################
796 __catpkgsplit_cache__ = {}
798 def catpkgsplit(mydata,silent=1):
799 """returns [cat, pkgname, version, rev ]
801 >>> catpkgsplit('sys-libs/glibc-1.2-r7')
802 ['sys-libs', 'glibc', '1.2', 'r7']
803 >>> catpkgsplit('glibc-1.2-r7')
804 [None, 'glibc', '1.2', 'r7']
808 return __catpkgsplit_cache__[mydata]
812 cat = os.path.basename(os.path.dirname(mydata))
813 mydata = os.path.join(cat, os.path.basename(mydata))
814 if mydata[-3:] == '.bb':
817 mysplit = mydata.split("/")
819 splitlen = len(mysplit)
822 p_split = pkgsplit(mydata,silent)
824 retval = [mysplit[splitlen - 2]]
825 p_split = pkgsplit(mysplit[splitlen - 1],silent)
827 __catpkgsplit_cache__[mydata] = None
829 retval.extend(p_split)
830 __catpkgsplit_cache__[mydata] = retval
834 #######################################################################
836 __vercmp_cache__ = {}
838 def vercmp(val1,val2):
839 """This takes two version strings and returns an integer to tell you whether
840 the versions are the same, val1>val2 or val2>val1.
846 >>> vercmp('1', '1.0')
848 >>> vercmp('1', '1.1')
850 >>> vercmp('1.1', '1_p2')
854 # quick short-circuit
857 valkey = val1+" "+val2
861 return __vercmp_cache__[valkey]
863 return - __vercmp_cache__[val2+" "+val1]
869 # consider 1_p2 vc 1.1
870 # after expansion will become (1_p2,0) vc (1,1)
871 # then 1_p2 is compared with 1 before 0 is compared with 1
872 # to solve the bug we need to convert it to (1,0_p2)
873 # by splitting _prepart part and adding it back _after_expansion
875 val1_prepart = val2_prepart = ''
877 val1, val1_prepart = val1.split('_', 1)
879 val2, val2_prepart = val2.split('_', 1)
882 # FIXME: Is it needed? can val1/2 contain '-'?
884 val1 = string.split(val1,'-')
886 val1[0] = val1[0] +"."+ val1[1]
887 val2 = string.split(val2,'-')
889 val2[0] = val2[0] +"."+ val2[1]
891 val1 = string.split(val1[0],'.')
892 val2 = string.split(val2[0],'.')
894 # add back decimal point so that .03 does not become "3" !
895 for x in range(1,len(val1)):
896 if val1[x][0] == '0' :
897 val1[x] = '.' + val1[x]
898 for x in range(1,len(val2)):
899 if val2[x][0] == '0' :
900 val2[x] = '.' + val2[x]
902 # extend varion numbers
903 if len(val2) < len(val1):
904 val2.extend(["0"]*(len(val1)-len(val2)))
905 elif len(val1) < len(val2):
906 val1.extend(["0"]*(len(val2)-len(val1)))
908 # add back _prepart tails
910 val1[-1] += '_' + val1_prepart
912 val2[-1] += '_' + val2_prepart
913 # The above code will extend version numbers out so they
914 # have the same number of digits.
915 for x in range(0,len(val1)):
916 cmp1 = relparse(val1[x])
917 cmp2 = relparse(val2[x])
919 myret = cmp1[y] - cmp2[y]
921 __vercmp_cache__[valkey] = myret
923 __vercmp_cache__[valkey] = 0
927 #######################################################################
929 def pkgcmp(pkg1,pkg2):
930 """ Compares two packages, which should have been split via
931 pkgsplit(). if the return value val is less than zero, then pkg2 is
932 newer than pkg1, zero if equal and positive if older.
934 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r7'])
936 >>> pkgcmp(['glibc', '2.2.5', 'r4'], ['glibc', '2.2.5', 'r7'])
938 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r2'])
942 mycmp = vercmp(pkg1[1],pkg2[1])
947 r1=string.atoi(pkg1[2][1:])
948 r2=string.atoi(pkg2[2][1:])
956 #######################################################################
958 def dep_parenreduce(mysplit, mypos=0):
959 """Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists:
961 >>> dep_parenreduce([''])
963 >>> dep_parenreduce(['1', '2', '3'])
965 >>> dep_parenreduce(['1', '(', '2', '3', ')', '4'])
966 ['1', ['2', '3'], '4']
969 while mypos < len(mysplit):
970 if mysplit[mypos] == "(":
973 while mypos < len(mysplit):
974 if mysplit[mypos] == ")":
975 mysplit[firstpos:mypos+1] = [mysplit[firstpos+1:mypos]]
978 elif mysplit[mypos] == "(":
980 mysplit = dep_parenreduce(mysplit,mypos)
986 def dep_opconvert(mysplit, myuse):
987 "Does dependency operator conversion"
991 while mypos < len(mysplit):
992 if type(mysplit[mypos]) == types.ListType:
993 newsplit.append(dep_opconvert(mysplit[mypos],myuse))
995 elif mysplit[mypos] == ")":
996 # mismatched paren, error
998 elif mysplit[mypos]=="||":
999 if ((mypos+1)>=len(mysplit)) or (type(mysplit[mypos+1])!=types.ListType):
1000 # || must be followed by paren'd list
1003 mynew = dep_opconvert(mysplit[mypos+1],myuse)
1004 except Exception, e:
1005 error("unable to satisfy OR dependancy: " + string.join(mysplit," || "))
1008 newsplit.append(mynew)
1010 elif mysplit[mypos][-1] == "?":
1011 # use clause, i.e "gnome? ( foo bar )"
1012 # this is a quick and dirty hack so that repoman can enable all USE vars:
1013 if (len(myuse) == 1) and (myuse[0] == "*"):
1014 # enable it even if it's ! (for repoman) but kill it if it's
1015 # an arch variable that isn't for this arch. XXX Sparc64?
1016 if (mysplit[mypos][:-1] not in settings.usemask) or \
1017 (mysplit[mypos][:-1]==settings["ARCH"]):
1022 if mysplit[mypos][0] == "!":
1023 myusevar = mysplit[mypos][1:-1]
1024 enabled = not myusevar in myuse
1025 #if myusevar in myuse:
1030 myusevar=mysplit[mypos][:-1]
1031 enabled = myusevar in myuse
1032 #if myusevar in myuse:
1036 if (mypos +2 < len(mysplit)) and (mysplit[mypos+2] == ":"):
1039 # choose the first option
1040 if type(mysplit[mypos+1]) == types.ListType:
1041 newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1043 newsplit.append(mysplit[mypos+1])
1045 # choose the alternate option
1046 if type(mysplit[mypos+1]) == types.ListType:
1047 newsplit.append(dep_opconvert(mysplit[mypos+3],myuse))
1049 newsplit.append(mysplit[mypos+3])
1054 if type(mysplit[mypos+1]) == types.ListType:
1055 newsplit.append(dep_opconvert(mysplit[mypos+1],myuse))
1057 newsplit.append(mysplit[mypos+1])
1058 # otherwise, continue
1062 newsplit.append(mysplit[mypos])
1067 """beautiful directed graph object"""
1071 #okeys = keys, in order they were added (to optimize firstzero() ordering)
1073 self.__callback_cache=[]
1077 for key in self.okeys:
1078 str += "%s:\t%s\n" % (key, self.dict[key][1])
1081 def addnode(self,mykey,myparent):
1082 if not mykey in self.dict:
1083 self.okeys.append(mykey)
1085 self.dict[mykey]=[0,[]]
1087 self.dict[mykey]=[0,[myparent]]
1088 self.dict[myparent][0]=self.dict[myparent][0]+1
1090 if myparent and (not myparent in self.dict[mykey][1]):
1091 self.dict[mykey][1].append(myparent)
1092 self.dict[myparent][0]=self.dict[myparent][0]+1
1094 def delnode(self,mykey, ref = 1):
1097 If ref is 1, remove references to this node from other nodes.
1098 If ref is 2, remove nodes that reference this node."""
1099 if not mykey in self.dict:
1101 for x in self.dict[mykey][1]:
1102 self.dict[x][0]=self.dict[x][0]-1
1103 del self.dict[mykey]
1106 self.okeys.remove(mykey)
1111 for k in self.okeys:
1112 if mykey in self.dict[k][1]:
1113 if ref == 1 or ref == 2:
1114 self.dict[k][1].remove(mykey)
1118 self.delnode(l, ref)
1121 "returns all nodes in the dictionary"
1122 return self.dict.keys()
1124 def firstzero(self):
1125 "returns first node with zero references, or NULL if no such node exists"
1126 for x in self.okeys:
1127 if self.dict[x][0]==0:
1131 def firstnonzero(self):
1132 "returns first node with nonzero references, or NULL if no such node exists"
1133 for x in self.okeys:
1134 if self.dict[x][0]!=0:
1140 "returns all nodes with zero references, or NULL if no such node exists"
1142 for x in self.dict.keys():
1143 if self.dict[x][0]==0:
1147 def hasallzeros(self):
1148 "returns 0/1, Are all nodes zeros? 1 : 0"
1150 for x in self.dict.keys():
1151 if self.dict[x][0]!=0:
1156 if len(self.dict)==0:
1160 def hasnode(self,mynode):
1161 return mynode in self.dict
1163 def getparents(self, item):
1164 if not self.hasnode(item):
1166 return self.dict[item][1]
1168 def getchildren(self, item):
1169 if not self.hasnode(item):
1171 children = [i for i in self.okeys if item in self.getparents(i)]
1174 def walkdown(self, item, callback, debug = None, usecache = False):
1175 if not self.hasnode(item):
1179 if self.__callback_cache.count(item):
1181 print "hit cache for item: %s" % item
1184 parents = self.getparents(item)
1185 children = self.getchildren(item)
1188 # print "%s is both parent and child of %s" % (p, item)
1190 self.__callback_cache.append(p)
1191 ret = callback(self, p)
1196 print "eek, i'm my own parent!"
1199 print "item: %s, p: %s" % (item, p)
1200 ret = self.walkdown(p, callback, debug, usecache)
1204 self.__callback_cache.append(item)
1205 return callback(self, item)
1207 def walkup(self, item, callback):
1208 if not self.hasnode(item):
1211 parents = self.getparents(item)
1212 children = self.getchildren(item)
1215 ret = callback(self, item)
1220 print "eek, i'm my own child!"
1222 ret = self.walkup(c, callback)
1225 return callback(self, item)
1229 for x in self.dict.keys():
1230 mygraph.dict[x]=self.dict[x][:]
1231 mygraph.okeys=self.okeys[:]
1234 if __name__ == "__main__":