1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 # BitBake Build System Python Library
6 # Copyright (C) 2003 Holger Schurig
7 # Copyright (C) 2003, 2004 Chris Larson
9 # Based on Gentoo's portage.py.
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License version 2 as
13 # published by the Free Software Foundation.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License along
21 # with this program; if not, write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
69 whitespace
= '\t\n\x0b\x0c\r '
70 lowercase
= 'abcdefghijklmnopqrstuvwxyz'
72 import sys
, os
, types
, re
, string
, bb
75 #projectdir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
76 projectdir
= os
.getcwd()
78 if "BBDEBUG" in os
.environ
:
79 level
= int(os
.environ
["BBDEBUG"])
81 bb
.msg
.set_debug_level(level
)
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 #######################################################################
100 def debug(lvl
, *args
):
101 bb
.msg
.std_debug(lvl
, ''.join(args
))
104 bb
.msg
.std_note(''.join(args
))
107 bb
.msg
.std_error(''.join(args
))
110 bb
.msg
.std_fatal(''.join(args
))
113 #######################################################################
114 #######################################################################
118 # PURPOSE: Basic file and directory tree related functions
120 #######################################################################
121 #######################################################################
124 """Create a directory like 'mkdir -p', but does not complain if
125 directory already exists like os.makedirs
128 debug(3, "mkdirhier(%s)" % dir)
131 debug(2, "created " + dir)
133 if e
.errno
!= 17: raise e
136 #######################################################################
140 def movefile(src
,dest
,newmtime
=None,sstat
=None):
141 """Moves a file from src to dest, preserving all permissions and
142 attributes; mtime will be preserved even when moving across
143 filesystems. Returns true on success and false on failure. Move is
147 #print "movefile("+src+","+dest+","+str(newmtime)+","+str(sstat)+")"
152 print "!!! Stating source file failed... movefile()"
160 dstat
=os
.lstat(os
.path
.dirname(dest
))
164 if stat
.S_ISLNK(dstat
[stat
.ST_MODE
]):
171 if stat
.S_ISLNK(sstat
[stat
.ST_MODE
]):
173 target
=os
.readlink(src
)
174 if destexists
and not stat
.S_ISDIR(dstat
[stat
.ST_MODE
]):
176 os
.symlink(target
,dest
)
177 # os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
179 return os
.lstat(dest
)
181 print "!!! failed to properly create symlink:"
182 print "!!!",dest
,"->",target
187 if sstat
[stat
.ST_DEV
]==dstat
[stat
.ST_DEV
]:
189 ret
=os
.rename(src
,dest
)
193 if e
[0]!=errno
.EXDEV
:
195 print "!!! Failed to move",src
,"to",dest
198 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
202 if stat
.S_ISREG(sstat
[stat
.ST_MODE
]):
203 try: # For safety copy then move it over.
204 shutil
.copyfile(src
,dest
+"#new")
205 os
.rename(dest
+"#new",dest
)
208 print '!!! copy',src
,'->',dest
,'failed.'
212 #we don't yet handle special, so we need to fall back to /bin/mv
213 a
=getstatusoutput("/bin/mv -f "+"'"+src
+"' '"+dest
+"'")
215 print "!!! Failed to move special file:"
216 print "!!! '"+src
+"' to '"+dest
+"'"
218 return None # failure
221 missingos
.lchown(dest
,sstat
[stat
.ST_UID
],sstat
[stat
.ST_GID
])
222 os
.chmod(dest
, stat
.S_IMODE(sstat
[stat
.ST_MODE
])) # Sticky is reset on chown
225 print "!!! Failed to chown/chmod/unlink in movefile()"
231 os
.utime(dest
,(newmtime
,newmtime
))
233 os
.utime(dest
, (sstat
[stat
.ST_ATIME
], sstat
[stat
.ST_MTIME
]))
234 newmtime
=sstat
[stat
.ST_MTIME
]
239 #######################################################################
240 #######################################################################
244 # PURPOSE: Download via HTTP, FTP, CVS, BITKEEPER, handling of MD5-signatures
247 #######################################################################
248 #######################################################################
251 """Decodes an URL into the tokens (scheme, network location, path,
252 user, password, parameters).
254 >>> decodeurl("http://www.google.com/index.html")
255 ('http', 'www.google.com', '/index.html', '', '', {})
257 CVS url with username, host and cvsroot. The cvs module to check out is in the
260 >>> decodeurl("cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg")
261 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'})
263 Dito, but this time the username has a password part. And we also request a special tag
266 >>> decodeurl("cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81")
267 ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'})
270 m
= re
.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url
)
272 raise MalformedUrl(url
)
274 type = m
.group('type')
275 location
= m
.group('location')
277 raise MalformedUrl(url
)
278 user
= m
.group('user')
279 parm
= m
.group('parm')
280 m
= re
.compile('(?P<host>[^/;]+)(?P<path>/[^;]+)').match(location
)
282 host
= m
.group('host')
283 path
= m
.group('path')
288 m
= re
.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user
)
290 user
= m
.group('user')
291 pswd
= m
.group('pswd')
298 for s
in parm
.split(';'):
302 return (type, host
, path
, user
, pswd
, p
)
304 #######################################################################
306 def encodeurl(decoded
):
307 """Encodes a URL from tokens (scheme, network location, path,
308 user, password, parameters).
310 >>> encodeurl(['http', 'www.google.com', '/index.html', '', '', {}])
311 'http://www.google.com/index.html'
313 CVS with username, host and cvsroot. The cvs module to check out is in the
316 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}])
317 'cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg'
319 Dito, but this time the username has a password part. And we also request a special tag
322 >>> encodeurl(['cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}])
323 'cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg'
326 (type, host
, path
, user
, pswd
, p
) = decoded
328 if not type or not path
:
329 fatal("invalid or missing parameters for url encoding")
340 for parm
in p
.keys():
341 url
+= ";%s=%s" % (parm
, p
[parm
])
345 #######################################################################
347 def which(path
, item
, direction
= 0):
348 """Useful function for locating a file in a PATH"""
350 for p
in (path
or "").split(':'):
351 if os
.path
.exists(os
.path
.join(p
, item
)):
352 found
= os
.path
.join(p
, item
)
357 #######################################################################
362 #######################################################################
363 #######################################################################
365 # SECTION: Dependency
367 # PURPOSE: Compare build & run dependencies
369 #######################################################################
370 #######################################################################
372 def tokenize(mystring
):
373 """Breaks a string like 'foo? (bar) oni? (blah (blah))' into (possibly embedded) lists:
379 >>> tokenize("(x y)")
381 >>> tokenize("(x y) b c")
382 [['x', 'y'], 'b', 'c']
383 >>> tokenize("foo? (bar) oni? (blah (blah))")
384 ['foo?', ['bar'], 'oni?', ['blah', ['blah']]]
385 >>> tokenize("sys-apps/linux-headers nls? (sys-devel/gettext)")
386 ['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']]
397 curlist
.append(accum
)
399 prevlists
.append(curlist
)
404 curlist
.append(accum
)
407 print "!!! tokenizer: Unmatched left parenthesis in:\n'"+mystring
+"'"
410 curlist
=prevlists
.pop()
411 curlist
.append(newlist
)
413 elif x
in whitespace
:
415 curlist
.append(accum
)
420 curlist
.append(accum
)
422 print "!!! tokenizer: Exiting with unterminated parenthesis in:\n'"+mystring
+"'"
427 #######################################################################
429 def evaluate(tokens
,mydefines
,allon
=0):
430 """Removes tokens based on whether conditional definitions exist or not.
433 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {})
434 ['sys-apps/linux-headers']
438 >>> evaluate(['sys-apps/linux-headers', '!nls?', ['sys-devel/gettext']], {})
439 ['sys-apps/linux-headers', ['sys-devel/gettext']]
443 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {"nls":1})
444 ['sys-apps/linux-headers', ['sys-devel/gettext']]
448 >>> evaluate(['sys-apps/linux-headers', 'nls?', ['sys-devel/gettext']], {}, True)
449 ['sys-apps/linux-headers', ['sys-devel/gettext']]
454 mytokens
= tokens
+ [] # this copies the list
456 while pos
< len(mytokens
):
457 if type(mytokens
[pos
]) == types
.ListType
:
458 evaluate(mytokens
[pos
], mydefines
)
459 if not len(mytokens
[pos
]):
462 elif mytokens
[pos
][-1] == "?":
463 cur
= mytokens
[pos
][:-1]
470 if (cur
[1:] in mydefines
) and (pos
< len(mytokens
)):
473 elif (cur
not in mydefines
) and (pos
< len(mytokens
)):
480 #######################################################################
482 def flatten(mytokens
):
483 """Converts nested arrays into a flat arrays:
485 >>> flatten([1,[2,3]])
487 >>> flatten(['sys-apps/linux-headers', ['sys-devel/gettext']])
488 ['sys-apps/linux-headers', 'sys-devel/gettext']
493 if type(x
)==types
.ListType
:
494 newlist
.extend(flatten(x
))
500 #######################################################################
502 _package_weights_
= {"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1} # dicts are unordered
503 _package_ends_
= ["pre", "p", "alpha", "beta", "rc", "cvs", "bk", "HEAD" ] # so we need ordered list
506 """Parses the last elements of a version number into a triplet, that can
509 >>> relparse('1.2_pre3')
520 mynewver
= myver
.split('_')
522 # an _package_weights_
523 number
= float(mynewver
[0])
525 for x
in _package_ends_
:
527 if mynewver
[1][:elen
] == x
:
529 p1
= _package_weights_
[x
]
531 p2
= float(mynewver
[1][elen
:])
536 # normal number or number with letter at end
537 divider
= len(myver
)-1
538 if myver
[divider
:] not in "1234567890":
540 p1
= ord(myver
[divider
:])
541 number
= float(myver
[0:divider
])
543 number
= float(myver
)
545 # normal number or number with letter at end
546 divider
= len(myver
)-1
547 if myver
[divider
:] not in "1234567890":
549 p1
= ord(myver
[divider
:])
550 number
= float(myver
[0:divider
])
552 number
= float(myver
)
553 return [number
,p1
,p2
]
556 #######################################################################
558 __ververify_cache__
= {}
560 def ververify(myorigval
,silent
=1):
561 """Returns 1 if given a valid version string, els 0. Valid versions are in the format
563 <v1>.<v2>...<vx>[a-z,_{_package_weights_}[vy]]
565 >>> ververify('2.4.20')
567 >>> ververify('2.4..20') # two dots
569 >>> ververify('2.x.20') # 'x' is not numeric
571 >>> ververify('2.4.20a')
573 >>> ververify('2.4.20cvs') # only one trailing letter
577 >>> ververify('test_a') # no version at all
579 >>> ververify('2.4.20_beta1')
581 >>> ververify('2.4.20_beta')
583 >>> ververify('2.4.20_wrongext') # _wrongext is no valid trailer
587 # Lookup the cache first
589 return __ververify_cache__
[myorigval
]
593 if len(myorigval
) == 0:
595 error("package version is empty")
596 __ververify_cache__
[myorigval
] = 0
598 myval
= myorigval
.split('.')
601 error("package name has empty version string")
602 __ververify_cache__
[myorigval
] = 0
604 # all but the last version must be a numeric
608 error("package version has two points in a row")
609 __ververify_cache__
[myorigval
] = 0
615 error("package version contains non-numeric '"+x
+"'")
616 __ververify_cache__
[myorigval
] = 0
618 if not len(myval
[-1]):
620 error("package version has trailing dot")
621 __ververify_cache__
[myorigval
] = 0
625 __ververify_cache__
[myorigval
] = 1
630 # ok, our last component is not a plain number or blank, let's continue
631 if myval
[-1][-1] in lowercase
:
633 foo
= int(myval
[-1][:-1])
635 __ververify_cache__
[myorigval
] = 1
639 # ok, maybe we have a 1_alpha or 1_beta2; let's see
640 ep
=string
.split(myval
[-1],"_")
643 error("package version has more than one letter at then end")
644 __ververify_cache__
[myorigval
] = 0
647 foo
= string
.atoi(ep
[0])
649 # this needs to be numeric, i.e. the "1" in "1_alpha"
651 error("package version must have numeric part before the '_'")
652 __ververify_cache__
[myorigval
] = 0
655 for mye
in _package_ends_
:
656 if ep
[1][0:len(mye
)] == mye
:
657 if len(mye
) == len(ep
[1]):
658 # no trailing numeric is ok
659 __ververify_cache__
[myorigval
] = 1
663 foo
= string
.atoi(ep
[1][len(mye
):])
664 __ververify_cache__
[myorigval
] = 1
667 # if no _package_weights_ work, *then* we return 0
670 error("package version extension after '_' is invalid")
671 __ververify_cache__
[myorigval
] = 0
675 def isjustname(mypkg
):
676 myparts
= string
.split(mypkg
,'-')
683 _isspecific_cache_
={}
685 def isspecific(mypkg
):
686 "now supports packages with no category"
688 return __isspecific_cache__
[mypkg
]
692 mysplit
= string
.split(mypkg
,"/")
693 if not isjustname(mysplit
[-1]):
694 __isspecific_cache__
[mypkg
] = 1
696 __isspecific_cache__
[mypkg
] = 0
700 #######################################################################
702 __pkgsplit_cache__
={}
704 def pkgsplit(mypkg
, silent
=1):
706 """This function can be used as a package verification function. If
707 it is a valid name, pkgsplit will return a list containing:
708 [pkgname, pkgversion(norev), pkgrev ].
714 >>> pkgsplit('glibc-1.2-8.9-r7')
715 >>> pkgsplit('glibc-2.2.5-r7')
716 ['glibc', '2.2.5', 'r7']
717 >>> pkgsplit('foo-1.2-1')
718 >>> pkgsplit('Mesa-3.0')
719 ['Mesa', '3.0', 'r0']
723 return __pkgsplit_cache__
[mypkg
]
727 myparts
= string
.split(mypkg
,'-')
730 error("package name without name or version part")
731 __pkgsplit_cache__
[mypkg
] = None
736 error("package name with empty name or version part")
737 __pkgsplit_cache__
[mypkg
] = None
742 ververify(myrev
, silent
)
743 if len(myrev
) and myrev
[0] == "r":
745 string
.atoi(myrev
[1:])
750 if ververify(myparts
[-2]):
751 if len(myparts
) == 2:
752 __pkgsplit_cache__
[mypkg
] = None
755 for x
in myparts
[:-2]:
757 __pkgsplit_cache__
[mypkg
]=None
759 # names can't have versiony looking parts
760 myval
=[string
.join(myparts
[:-2],"-"),myparts
[-2],myparts
[-1]]
761 __pkgsplit_cache__
[mypkg
]=myval
764 __pkgsplit_cache__
[mypkg
] = None
767 elif ververify(myparts
[-1],silent
):
770 print "!!! Name error in",mypkg
+": missing name part."
771 __pkgsplit_cache__
[mypkg
]=None
774 for x
in myparts
[:-1]:
776 if not silent
: error("package name has multiple version parts")
777 __pkgsplit_cache__
[mypkg
] = None
779 myval
= [string
.join(myparts
[:-1],"-"), myparts
[-1],"r0"]
780 __pkgsplit_cache__
[mypkg
] = myval
783 __pkgsplit_cache__
[mypkg
] = None
787 #######################################################################
789 __catpkgsplit_cache__
= {}
791 def catpkgsplit(mydata
,silent
=1):
792 """returns [cat, pkgname, version, rev ]
794 >>> catpkgsplit('sys-libs/glibc-1.2-r7')
795 ['sys-libs', 'glibc', '1.2', 'r7']
796 >>> catpkgsplit('glibc-1.2-r7')
797 [None, 'glibc', '1.2', 'r7']
801 return __catpkgsplit_cache__
[mydata
]
805 cat
= os
.path
.basename(os
.path
.dirname(mydata
))
806 mydata
= os
.path
.join(cat
, os
.path
.basename(mydata
))
807 if mydata
[-3:] == '.bb':
810 mysplit
= mydata
.split("/")
812 splitlen
= len(mysplit
)
815 p_split
= pkgsplit(mydata
,silent
)
817 retval
= [mysplit
[splitlen
- 2]]
818 p_split
= pkgsplit(mysplit
[splitlen
- 1],silent
)
820 __catpkgsplit_cache__
[mydata
] = None
822 retval
.extend(p_split
)
823 __catpkgsplit_cache__
[mydata
] = retval
827 #######################################################################
829 __vercmp_cache__
= {}
831 def vercmp(val1
,val2
):
832 """This takes two version strings and returns an integer to tell you whether
833 the versions are the same, val1>val2 or val2>val1.
839 >>> vercmp('1', '1.0')
841 >>> vercmp('1', '1.1')
843 >>> vercmp('1.1', '1_p2')
847 # quick short-circuit
850 valkey
= val1
+" "+val2
854 return __vercmp_cache__
[valkey
]
856 return - __vercmp_cache__
[val2
+" "+val1
]
862 # consider 1_p2 vc 1.1
863 # after expansion will become (1_p2,0) vc (1,1)
864 # then 1_p2 is compared with 1 before 0 is compared with 1
865 # to solve the bug we need to convert it to (1,0_p2)
866 # by splitting _prepart part and adding it back _after_expansion
868 val1_prepart
= val2_prepart
= ''
870 val1
, val1_prepart
= val1
.split('_', 1)
872 val2
, val2_prepart
= val2
.split('_', 1)
875 # FIXME: Is it needed? can val1/2 contain '-'?
877 val1
= string
.split(val1
,'-')
879 val1
[0] = val1
[0] +"."+ val1
[1]
880 val2
= string
.split(val2
,'-')
882 val2
[0] = val2
[0] +"."+ val2
[1]
884 val1
= string
.split(val1
[0],'.')
885 val2
= string
.split(val2
[0],'.')
887 # add back decimal point so that .03 does not become "3" !
888 for x
in range(1,len(val1
)):
889 if val1
[x
][0] == '0' :
890 val1
[x
] = '.' + val1
[x
]
891 for x
in range(1,len(val2
)):
892 if val2
[x
][0] == '0' :
893 val2
[x
] = '.' + val2
[x
]
895 # extend varion numbers
896 if len(val2
) < len(val1
):
897 val2
.extend(["0"]*(len(val1
)-len(val2
)))
898 elif len(val1
) < len(val2
):
899 val1
.extend(["0"]*(len(val2
)-len(val1
)))
901 # add back _prepart tails
903 val1
[-1] += '_' + val1_prepart
905 val2
[-1] += '_' + val2_prepart
906 # The above code will extend version numbers out so they
907 # have the same number of digits.
908 for x
in range(0,len(val1
)):
909 cmp1
= relparse(val1
[x
])
910 cmp2
= relparse(val2
[x
])
912 myret
= cmp1
[y
] - cmp2
[y
]
914 __vercmp_cache__
[valkey
] = myret
916 __vercmp_cache__
[valkey
] = 0
920 #######################################################################
922 def pkgcmp(pkg1
,pkg2
):
923 """ Compares two packages, which should have been split via
924 pkgsplit(). if the return value val is less than zero, then pkg2 is
925 newer than pkg1, zero if equal and positive if older.
927 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r7'])
929 >>> pkgcmp(['glibc', '2.2.5', 'r4'], ['glibc', '2.2.5', 'r7'])
931 >>> pkgcmp(['glibc', '2.2.5', 'r7'], ['glibc', '2.2.5', 'r2'])
935 mycmp
= vercmp(pkg1
[1],pkg2
[1])
940 r1
=string
.atoi(pkg1
[2][1:])
941 r2
=string
.atoi(pkg2
[2][1:])
949 #######################################################################
951 def dep_parenreduce(mysplit
, mypos
=0):
952 """Accepts a list of strings, and converts '(' and ')' surrounded items to sub-lists:
954 >>> dep_parenreduce([''])
956 >>> dep_parenreduce(['1', '2', '3'])
958 >>> dep_parenreduce(['1', '(', '2', '3', ')', '4'])
959 ['1', ['2', '3'], '4']
962 while mypos
< len(mysplit
):
963 if mysplit
[mypos
] == "(":
966 while mypos
< len(mysplit
):
967 if mysplit
[mypos
] == ")":
968 mysplit
[firstpos
:mypos
+1] = [mysplit
[firstpos
+1:mypos
]]
971 elif mysplit
[mypos
] == "(":
973 mysplit
= dep_parenreduce(mysplit
,mypos
)
979 def dep_opconvert(mysplit
, myuse
):
980 "Does dependency operator conversion"
984 while mypos
< len(mysplit
):
985 if type(mysplit
[mypos
]) == types
.ListType
:
986 newsplit
.append(dep_opconvert(mysplit
[mypos
],myuse
))
988 elif mysplit
[mypos
] == ")":
989 # mismatched paren, error
991 elif mysplit
[mypos
]=="||":
992 if ((mypos
+1)>=len(mysplit
)) or (type(mysplit
[mypos
+1])!=types
.ListType
):
993 # || must be followed by paren'd list
996 mynew
= dep_opconvert(mysplit
[mypos
+1],myuse
)
998 error("unable to satisfy OR dependancy: " + string
.join(mysplit
," || "))
1001 newsplit
.append(mynew
)
1003 elif mysplit
[mypos
][-1] == "?":
1004 # use clause, i.e "gnome? ( foo bar )"
1005 # this is a quick and dirty hack so that repoman can enable all USE vars:
1006 if (len(myuse
) == 1) and (myuse
[0] == "*"):
1007 # enable it even if it's ! (for repoman) but kill it if it's
1008 # an arch variable that isn't for this arch. XXX Sparc64?
1009 if (mysplit
[mypos
][:-1] not in settings
.usemask
) or \
1010 (mysplit
[mypos
][:-1]==settings
["ARCH"]):
1015 if mysplit
[mypos
][0] == "!":
1016 myusevar
= mysplit
[mypos
][1:-1]
1017 enabled
= not myusevar
in myuse
1018 #if myusevar in myuse:
1023 myusevar
=mysplit
[mypos
][:-1]
1024 enabled
= myusevar
in myuse
1025 #if myusevar in myuse:
1029 if (mypos
+2 < len(mysplit
)) and (mysplit
[mypos
+2] == ":"):
1032 # choose the first option
1033 if type(mysplit
[mypos
+1]) == types
.ListType
:
1034 newsplit
.append(dep_opconvert(mysplit
[mypos
+1],myuse
))
1036 newsplit
.append(mysplit
[mypos
+1])
1038 # choose the alternate option
1039 if type(mysplit
[mypos
+1]) == types
.ListType
:
1040 newsplit
.append(dep_opconvert(mysplit
[mypos
+3],myuse
))
1042 newsplit
.append(mysplit
[mypos
+3])
1047 if type(mysplit
[mypos
+1]) == types
.ListType
:
1048 newsplit
.append(dep_opconvert(mysplit
[mypos
+1],myuse
))
1050 newsplit
.append(mysplit
[mypos
+1])
1051 # otherwise, continue
1055 newsplit
.append(mysplit
[mypos
])
1060 """beautiful directed graph object"""
1064 #okeys = keys, in order they were added (to optimize firstzero() ordering)
1066 self
.__callback
_cache
=[]
1070 for key
in self
.okeys
:
1071 str += "%s:\t%s\n" % (key
, self
.dict[key
][1])
1074 def addnode(self
,mykey
,myparent
):
1075 if not mykey
in self
.dict:
1076 self
.okeys
.append(mykey
)
1078 self
.dict[mykey
]=[0,[]]
1080 self
.dict[mykey
]=[0,[myparent
]]
1081 self
.dict[myparent
][0]=self
.dict[myparent
][0]+1
1083 if myparent
and (not myparent
in self
.dict[mykey
][1]):
1084 self
.dict[mykey
][1].append(myparent
)
1085 self
.dict[myparent
][0]=self
.dict[myparent
][0]+1
1087 def delnode(self
,mykey
, ref
= 1):
1090 If ref is 1, remove references to this node from other nodes.
1091 If ref is 2, remove nodes that reference this node."""
1092 if not mykey
in self
.dict:
1094 for x
in self
.dict[mykey
][1]:
1095 self
.dict[x
][0]=self
.dict[x
][0]-1
1096 del self
.dict[mykey
]
1099 self
.okeys
.remove(mykey
)
1104 for k
in self
.okeys
:
1105 if mykey
in self
.dict[k
][1]:
1106 if ref
== 1 or ref
== 2:
1107 self
.dict[k
][1].remove(mykey
)
1111 self
.delnode(l
, ref
)
1114 "returns all nodes in the dictionary"
1115 return self
.dict.keys()
1117 def firstzero(self
):
1118 "returns first node with zero references, or NULL if no such node exists"
1119 for x
in self
.okeys
:
1120 if self
.dict[x
][0]==0:
1124 def firstnonzero(self
):
1125 "returns first node with nonzero references, or NULL if no such node exists"
1126 for x
in self
.okeys
:
1127 if self
.dict[x
][0]!=0:
1133 "returns all nodes with zero references, or NULL if no such node exists"
1135 for x
in self
.dict.keys():
1136 if self
.dict[x
][0]==0:
1140 def hasallzeros(self
):
1141 "returns 0/1, Are all nodes zeros? 1 : 0"
1143 for x
in self
.dict.keys():
1144 if self
.dict[x
][0]!=0:
1149 if len(self
.dict)==0:
1153 def hasnode(self
,mynode
):
1154 return mynode
in self
.dict
1156 def getparents(self
, item
):
1157 if not self
.hasnode(item
):
1159 return self
.dict[item
][1]
1161 def getchildren(self
, item
):
1162 if not self
.hasnode(item
):
1164 children
= [i
for i
in self
.okeys
if item
in self
.getparents(i
)]
1167 def walkdown(self
, item
, callback
, debug
= None, usecache
= False):
1168 if not self
.hasnode(item
):
1172 if self
.__callback
_cache
.count(item
):
1174 print "hit cache for item: %s" % item
1177 parents
= self
.getparents(item
)
1178 children
= self
.getchildren(item
)
1181 # print "%s is both parent and child of %s" % (p, item)
1183 self
.__callback
_cache
.append(p
)
1184 ret
= callback(self
, p
)
1189 print "eek, i'm my own parent!"
1192 print "item: %s, p: %s" % (item
, p
)
1193 ret
= self
.walkdown(p
, callback
, debug
, usecache
)
1197 self
.__callback
_cache
.append(item
)
1198 return callback(self
, item
)
1200 def walkup(self
, item
, callback
):
1201 if not self
.hasnode(item
):
1204 parents
= self
.getparents(item
)
1205 children
= self
.getchildren(item
)
1208 ret
= callback(self
, item
)
1213 print "eek, i'm my own child!"
1215 ret
= self
.walkup(c
, callback
)
1218 return callback(self
, item
)
1222 for x
in self
.dict.keys():
1223 mygraph
.dict[x
]=self
.dict[x
][:]
1224 mygraph
.okeys
=self
.okeys
[:]
1227 if __name__
== "__main__":