Call parent class tests first.
[pykickstart.git] / pykickstart / parser.py
blob55ec9b8f2e68327c77a631a382d63e55fd85bfc1
2 # parser.py: Kickstart file parser.
4 # Chris Lumens <clumens@redhat.com>
6 # Copyright 2005, 2006, 2007, 2008 Red Hat, Inc.
8 # This copyrighted material is made available to anyone wishing to use, modify,
9 # copy, or redistribute it subject to the terms and conditions of the GNU
10 # General Public License v.2. This program is distributed in the hope that it
11 # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
12 # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 # See the GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc., 51
17 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
18 # trademarks that are incorporated in the source code or documentation are not
19 # subject to the GNU General Public License and may only be used or replicated
20 # with the express permission of Red Hat, Inc.
22 """
23 Main kickstart file processing module.
25 This module exports several important classes:
27 Script - Representation of a single %pre, %post, or %traceback script.
29 Packages - Representation of the %packages section.
31 KickstartParser - The kickstart file parser state machine.
32 """
34 import os
35 import shlex
36 import sys
37 import string
38 import tempfile
39 from copy import copy
40 from optparse import *
41 from urlgrabber import urlopen
42 import urlgrabber.grabber as grabber
44 from constants import *
45 from errors import *
46 from ko import *
47 from options import *
48 from version import *
50 import gettext
51 _ = lambda x: gettext.ldgettext("pykickstart", x)
53 STATE_END = 0
54 STATE_COMMANDS = 1
55 STATE_PACKAGES = 2
56 STATE_SCRIPT_HDR = 3
57 STATE_SCRIPT = 4
59 # FIXME: This is a hack until I have time to think about making the parser
60 # itself support multiple syntax versions. Yes, I know this means it will
61 # never be fixed.
62 ver = DEVEL
64 def _preprocessStateMachine (provideLineFn):
65 l = None
66 lineno = 0
68 # Now open an output kickstart file that we are going to write to one
69 # line at a time.
70 (outF, outName) = tempfile.mkstemp("-ks.cfg", "", "/tmp")
72 while True:
73 try:
74 l = provideLineFn()
75 except StopIteration:
76 break
78 # At the end of the file?
79 if l == "":
80 break
82 lineno += 1
83 url = None
85 ll = l.strip()
86 if string.find(ll, "%ksappend") == -1:
87 os.write(outF, l)
88 continue
90 # Try to pull down the remote file.
91 try:
92 ksurl = string.split(ll, ' ')[1]
93 except:
94 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Illegal url for %%ksappend: %s") % ll)
96 try:
97 url = grabber.urlopen(ksurl)
98 except grabber.URLGrabError, e:
99 raise KickstartError, formatErrorMsg(lineno, msg=_("Unable to open %%ksappend file: ") % e.strerror)
100 else:
101 # Sanity check result. Sometimes FTP doesn't catch a file
102 # is missing.
103 try:
104 if url.info()["content-length"] < 1:
105 raise KickstartError, formatErrorMsg(lineno, msg=_("Unable to open %%ksappend file"))
106 except:
107 raise KickstartError, formatErrorMsg(lineno, msg=_("Unable to open %%ksappend file"))
109 # If that worked, write the remote file to the output kickstart
110 # file in one burst. Then close everything up to get ready to
111 # read ahead in the input file. This allows multiple %ksappend
112 # lines to exist.
113 if url is not None:
114 os.write(outF, url.read())
115 url.close()
117 # All done - close the temp file and return its location.
118 os.close(outF)
119 return outName
121 def preprocessFromString (str):
122 """Preprocess the kickstart file, provided as the string str. This
123 method is currently only useful for handling %ksappend lines,
124 which need to be fetched before the real kickstart parser can be
125 run. Returns the location of the complete kickstart file.
127 i = iter(str.splitlines(True) + [""])
128 rc = _preprocessStateMachine (lambda: i.next())
129 return rc
131 def preprocessKickstart (file):
132 """Preprocess the kickstart file, given by the filename file. This
133 method is currently only useful for handling %ksappend lines,
134 which need to be fetched before the real kickstart parser can be
135 run. Returns the location of the complete kickstart file.
137 fh = urlopen(file)
138 rc = _preprocessStateMachine (lambda: fh.readline())
139 fh.close()
140 return rc
143 ### SCRIPT HANDLING
145 class Script(KickstartObject):
146 """A class representing a single kickstart script. If functionality beyond
147 just a data representation is needed (for example, a run method in
148 anaconda), Script may be subclassed. Although a run method is not
149 provided, most of the attributes of Script have to do with running the
150 script. Instances of Script are held in a list by the Version object.
152 def __init__(self, script, *args , **kwargs):
153 """Create a new Script instance. Instance attributes:
155 errorOnFail -- If execution of the script fails, should anaconda
156 stop, display an error, and then reboot without
157 running any other scripts?
158 inChroot -- Does the script execute in anaconda's chroot
159 environment or not?
160 interp -- The program that should be used to interpret this
161 script.
162 lineno -- The line number this script starts on.
163 logfile -- Where all messages from the script should be logged.
164 script -- A string containing all the lines of the script.
165 type -- The type of the script, which can be KS_SCRIPT_* from
166 pykickstart.constants.
168 KickstartObject.__init__(self, *args, **kwargs)
169 self.script = string.join(script, "")
171 self.interp = kwargs.get("interp", "/bin/sh")
172 self.inChroot = kwargs.get("inChroot", False)
173 self.lineno = kwargs.get("lineno", None)
174 self.logfile = kwargs.get("logfile", None)
175 self.errorOnFail = kwargs.get("errorOnFail", False)
176 self.type = kwargs.get("type", KS_SCRIPT_PRE)
178 def __str__(self):
179 """Return a string formatted for output to a kickstart file."""
180 if self.preceededInclude is not None:
181 retval = "\n%%include %s\n" % self.preceededInclude
182 else:
183 retval = ""
185 if self.type == KS_SCRIPT_PRE:
186 retval += '\n%pre'
187 elif self.type == KS_SCRIPT_POST:
188 retval += '\n%post'
189 elif self.type == KS_SCRIPT_TRACEBACK:
190 retval += '\n%traceback'
192 if self.interp != "/bin/sh" and self.interp != "":
193 retval += " --interpreter=%s" % self.interp
194 if self.type == KS_SCRIPT_POST and not self.inChroot:
195 retval += " --nochroot"
196 if self.logfile != None:
197 retval += " --logfile %s" % self.logfile
198 if self.errorOnFail:
199 retval += " --erroronfail"
201 if self.script.endswith("\n"):
202 if ver >= F8:
203 return retval + "\n%s%%end\n" % self.script
204 else:
205 return retval + "\n%s\n" % self.script
206 else:
207 if ver >= F8:
208 return retval + "\n%s\n%%end\n" % self.script
209 else:
210 return retval + "\n%s\n" % self.script
214 ## PACKAGE HANDLING
216 class Group:
217 """A class representing a single group in the %packages section."""
218 def __init__(self, name="", include=GROUP_DEFAULT):
219 """Create a new Group instance. Instance attributes:
221 name -- The group's identifier
222 include -- The level of how much of the group should be included.
223 Values can be GROUP_* from pykickstart.constants.
225 self.name = name
226 self.include = include
228 def __str__(self):
229 """Return a string formatted for output to a kickstart file."""
230 if self.include == GROUP_REQUIRED:
231 return "@%s --nodefaults" % self.name
232 elif self.include == GROUP_ALL:
233 return "@%s --optional" % self.name
234 else:
235 return "@%s" % self.name
237 def __cmp__(self, other):
238 if self.name < other.name:
239 return -1
240 elif self.name > other.name:
241 return 1
242 return 0
244 class Packages(KickstartObject):
245 """A class representing the %packages section of the kickstart file."""
246 def __init__(self, *args, **kwargs):
247 """Create a new Packages instance. Instance attributes:
249 addBase -- Should the Base group be installed even if it is
250 not specified?
251 default -- Should the default package set be selected?
252 excludedList -- A list of all the packages marked for exclusion in
253 the %packages section, without the leading minus
254 symbol.
255 excludeDocs -- Should documentation in each package be excluded?
256 groupList -- A list of Group objects representing all the groups
257 specified in the %packages section. Names will be
258 stripped of the leading @ symbol.
259 handleMissing -- If unknown packages are specified in the %packages
260 section, should it be ignored or not? Values can
261 be KS_MISSING_* from pykickstart.constants.
262 packageList -- A list of all the packages specified in the
263 %packages section.
264 instLangs -- A list of languages to install.
266 KickstartObject.__init__(self, *args, **kwargs)
268 self.addBase = True
269 self.default = False
270 self.excludedList = []
271 self.excludeDocs = False
272 self.groupList = []
273 self.handleMissing = KS_MISSING_PROMPT
274 self.packageList = []
275 self.instLangs = None
277 def __str__(self):
278 """Return a string formatted for output to a kickstart file."""
279 pkgs = ""
281 if not self.default:
282 grps = self.groupList
283 grps.sort()
284 for grp in grps:
285 pkgs += "%s\n" % grp.__str__()
287 p = self.packageList
288 p.sort()
289 for pkg in p:
290 pkgs += "%s\n" % pkg
292 p = self.excludedList
293 p.sort()
294 for pkg in p:
295 pkgs += "-%s\n" % pkg
297 if pkgs == "":
298 return ""
300 if self.preceededInclude is not None:
301 retval = "\n%%include %s\n" % self.preceededInclude
302 else:
303 retval = ""
305 retval += "\n%packages"
307 if self.default:
308 retval += " --default"
309 if self.excludeDocs:
310 retval += " --excludedocs"
311 if not self.addBase:
312 retval += " --nobase"
313 if self.handleMissing == KS_MISSING_IGNORE:
314 retval += " --ignoremissing"
315 if self.instLangs:
316 retval += " --instLangs=%s" % self.instLangs
318 if ver >= F8:
319 return retval + "\n" + pkgs + "\n%end\n"
320 else:
321 return retval + "\n" + pkgs + "\n"
323 def _processGroup (self, line):
324 op = OptionParser()
325 op.add_option("--nodefaults", action="store_true", default=False)
326 op.add_option("--optional", action="store_true", default=False)
328 (opts, extra) = op.parse_args(args=line.split())
330 if opts.nodefaults and opts.optional:
331 raise KickstartValueError, _("Group cannot specify both --nodefaults and --optional")
333 # If the group name has spaces in it, we have to put it back together
334 # now.
335 grp = " ".join(extra)
337 if opts.nodefaults:
338 self.groupList.append(Group(name=grp, include=GROUP_REQUIRED))
339 elif opts.optional:
340 self.groupList.append(Group(name=grp, include=GROUP_ALL))
341 else:
342 self.groupList.append(Group(name=grp, include=GROUP_DEFAULT))
344 def add (self, pkgList):
345 """Given a list of lines from the input file, strip off any leading
346 symbols and add the result to the appropriate list.
348 existingExcludedSet = set(self.excludedList)
349 existingPackageSet = set(self.packageList)
350 newExcludedSet = set()
351 newPackageSet = set()
353 for pkg in pkgList:
354 stripped = pkg.strip()
356 if stripped[0] == "@":
357 self._processGroup(stripped[1:])
358 elif stripped[0] == "-":
359 # Support syntax for removing a previously included group. If
360 # the provided group does not exist, it's not an error.
361 if stripped[1] == "@":
362 try:
363 self.groupList = filter(lambda g: g.name != stripped[2:], self.groupList)
364 except ValueError:
365 pass
366 else:
367 newExcludedSet.add(stripped[1:])
368 else:
369 newPackageSet.add(stripped)
371 existingPackageSet = (existingPackageSet - newExcludedSet) | newPackageSet
372 existingExcludedSet = (existingExcludedSet - existingPackageSet) | newExcludedSet
374 self.packageList = list(existingPackageSet)
375 self.excludedList = list(existingExcludedSet)
379 ### PARSER
381 class KickstartParser:
382 """The kickstart file parser class as represented by a basic state
383 machine. To create a specialized parser, make a subclass and override
384 any of the methods you care about. Methods that don't need to do
385 anything may just pass. However, _stateMachine should never be
386 overridden.
388 def __init__ (self, handler, followIncludes=True, errorsAreFatal=True,
389 missingIncludeIsFatal=True):
390 """Create a new KickstartParser instance. Instance attributes:
392 errorsAreFatal -- Should errors cause processing to halt, or
393 just print a message to the screen? This
394 is most useful for writing syntax checkers
395 that may want to continue after an error is
396 encountered.
397 followIncludes -- If %include is seen, should the included
398 file be checked as well or skipped?
399 handler -- An instance of a BaseHandler subclass. If
400 None, the input file will still be parsed
401 but no data will be saved and no commands
402 will be executed.
403 missingIncludeIsFatal -- Should missing include files be fatal, even
404 if errorsAreFatal is False?
406 self.errorsAreFatal = errorsAreFatal
407 self.followIncludes = followIncludes
408 self.handler = handler
409 self.currentdir = {}
410 self.missingIncludeIsFatal = missingIncludeIsFatal
411 self._reset()
413 self._line = ""
415 self.version = self.handler.version
417 global ver
418 ver = self.version
420 def _reset(self):
421 """Reset the internal variables of the state machine for a new kickstart file."""
422 self._state = STATE_COMMANDS
423 self._script = None
424 self._includeDepth = 0
425 self._preceededInclude = None
427 def addScript (self):
428 """Create a new Script instance and add it to the Version object. This
429 is called when the end of a script section is seen and may be
430 overridden in a subclass if necessary.
432 if string.join(self._script["body"]).strip() == "":
433 return
435 kwargs = {"interp": self._script["interp"],
436 "inChroot": self._script["chroot"],
437 "lineno": self._script["lineno"],
438 "logfile": self._script["log"],
439 "errorOnFail": self._script["errorOnFail"],
440 "type": self._script["type"]}
442 if self._preceededInclude is not None:
443 kwargs["preceededInclude"] = self._preceededInclude
444 self._preceededInclude = None
446 s = Script (self._script["body"], **kwargs)
448 if self.handler:
449 self.handler.scripts.append(s)
451 def addPackages (self, line):
452 """Add the single package, exclude, or group into the Version's
453 Packages instance. This method may be overridden in a subclass
454 if necessary.
456 if self.handler:
457 self.handler.packages.add([line])
459 def handleCommand (self, lineno, args):
460 """Given the list of command and arguments, call the Version's
461 dispatcher method to handle the command. This method may be
462 overridden in a subclass if necessary.
464 if self.handler:
465 self.handler.currentCmd = args[0]
466 self.handler.currentLine = self._line
467 self.handler.dispatcher(args, lineno, self._preceededInclude)
468 self._preceededInclude = None
470 def handlePackageHdr (self, lineno, args):
471 """Process the arguments to the %packages header and set attributes
472 on the Version's Packages instance appropriate. This method may be
473 overridden in a subclass if necessary.
475 op = KSOptionParser(version=self.version)
476 op.add_option("--excludedocs", dest="excludedocs", action="store_true",
477 default=False)
478 op.add_option("--ignoremissing", dest="ignoremissing",
479 action="store_true", default=False)
480 op.add_option("--nobase", dest="nobase", action="store_true",
481 default=False)
482 op.add_option("--ignoredeps", dest="resolveDeps", action="store_false",
483 deprecated=FC4, removed=F9)
484 op.add_option("--resolvedeps", dest="resolveDeps", action="store_true",
485 deprecated=FC4, removed=F9)
486 op.add_option("--default", dest="defaultPackages", action="store_true",
487 default=False, introduced=F7)
488 op.add_option("--instLangs", dest="instLangs", type="string",
489 default="", introduced=F9)
491 (opts, extra) = op.parse_args(args=args[1:], lineno=lineno)
493 self.handler.packages.excludeDocs = opts.excludedocs
494 self.handler.packages.addBase = not opts.nobase
495 if opts.ignoremissing:
496 self.handler.packages.handleMissing = KS_MISSING_IGNORE
497 else:
498 self.handler.packages.handleMissing = KS_MISSING_PROMPT
500 if opts.defaultPackages:
501 self.handler.packages.default = True
503 if opts.instLangs:
504 self.handler.packages.instLangs = opts.instLangs
506 if self._preceededInclude is not None:
507 self.handler.packages.preceededInclude = self._preceededInclude
508 self._preceededInclude = None
510 def handleScriptHdr (self, lineno, args):
511 """Process the arguments to a %pre/%post/%traceback header for later
512 setting on a Script instance once the end of the script is found.
513 This method may be overridden in a subclass if necessary.
515 op = KSOptionParser(version=self.version)
516 op.add_option("--erroronfail", dest="errorOnFail", action="store_true",
517 default=False)
518 op.add_option("--interpreter", dest="interpreter", default="/bin/sh")
519 op.add_option("--log", "--logfile", dest="log")
521 if args[0] == "%pre" or args[0] == "%traceback":
522 self._script["chroot"] = False
523 elif args[0] == "%post":
524 self._script["chroot"] = True
525 op.add_option("--nochroot", dest="nochroot", action="store_true",
526 default=False)
528 (opts, extra) = op.parse_args(args=args[1:], lineno=lineno)
530 self._script["interp"] = opts.interpreter
531 self._script["lineno"] = lineno
532 self._script["log"] = opts.log
533 self._script["errorOnFail"] = opts.errorOnFail
534 if hasattr(opts, "nochroot"):
535 self._script["chroot"] = not opts.nochroot
537 def _stateMachine (self, provideLineFn):
538 # For error reporting.
539 lineno = 0
540 needLine = True
542 while True:
543 if needLine:
544 try:
545 self._line = provideLineFn()
546 except StopIteration:
547 break
549 lineno += 1
550 needLine = False
552 # At the end of an included file
553 if self._line == "" and self._includeDepth > 0:
554 break
556 # Don't eliminate whitespace or comments from scripts.
557 if self._line.isspace() or (self._line != "" and self._line.lstrip()[0] == '#'):
558 # Save the platform for s-c-kickstart, though.
559 if self._line[:10] == "#platform=" and self._state == STATE_COMMANDS:
560 self.handler.platform = self._line[11:]
562 if self._state == STATE_SCRIPT:
563 self._script["body"].append(self._line)
565 needLine = True
566 continue
568 # We only want to split the line if we're outside of a script,
569 # as inside the script might involve some pretty weird quoting
570 # that shlex doesn't understand.
571 if self._state == STATE_SCRIPT:
572 # Have we found a state transition? If so, we still want
573 # to split. Otherwise, args won't be set but we'll fall through
574 # all the way to the last case.
575 if self._line != "" and string.split(self._line.lstrip())[0] in \
576 ["%end", "%post", "%pre", "%traceback", "%include", "%packages", "%ksappend"]:
577 args = shlex.split(self._line)
578 else:
579 args = None
580 else:
581 # Remove any end-of-line comments.
582 ind = self._line.find("#")
583 if (ind > -1):
584 h = self._line[:ind]
585 else:
586 h = self._line
588 self._line = h.rstrip()
589 args = shlex.split(self._line)
591 if args and args[0] == "%include":
592 self._preceededInclude = args[1]
594 # This case comes up primarily in ksvalidator.
595 if not self.followIncludes:
596 needLine = True
597 continue
599 if not args[1]:
600 raise KickstartParseError, formatErrorMsg(lineno)
601 else:
602 self._includeDepth += 1
604 try:
605 self.readKickstart (args[1], reset=False)
606 except IOError:
607 # Handle the include file being provided over the
608 # network in a %pre script. This case comes up in the
609 # early parsing in anaconda.
610 if self.missingIncludeIsFatal:
611 raise
613 self._includeDepth -= 1
614 needLine = True
615 continue
617 if self._state == STATE_COMMANDS:
618 if not args and self._includeDepth == 0:
619 self._state = STATE_END
620 elif args[0] == "%ksappend":
621 needLine = True
622 elif args[0] in ["%pre", "%post", "%traceback"]:
623 self._state = STATE_SCRIPT_HDR
624 elif args[0] == "%packages":
625 self._state = STATE_PACKAGES
626 elif args[0][0] == '%':
627 # This error is too difficult to continue from, without
628 # lots of resync code. So just print this one and quit.
629 raise KickstartParseError, formatErrorMsg(lineno)
630 else:
631 needLine = True
633 if self.errorsAreFatal:
634 self.handleCommand(lineno, args)
635 else:
636 try:
637 self.handleCommand(lineno, args)
638 except Exception, msg:
639 print msg
641 elif self._state == STATE_PACKAGES:
642 if not args and self._includeDepth == 0:
643 if self.version >= F8 :
644 warnings.warn(_("%s does not end with %%end. This syntax has been deprecated. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to use this updated syntax.") % "%packages", DeprecationWarning)
646 self._state = STATE_END
647 elif args[0] == "%end":
648 self._state = STATE_COMMANDS
649 needLine = True
650 elif args[0] == "%ksappend":
651 needLine = True
652 elif args[0] in ["%pre", "%post", "%traceback"]:
653 self._state = STATE_SCRIPT_HDR
654 elif args[0] == "%packages":
655 needLine = True
657 if self.errorsAreFatal:
658 self.handlePackageHdr (lineno, args)
659 else:
660 try:
661 self.handlePackageHdr (lineno, args)
662 except Exception, msg:
663 print msg
664 elif args[0][0] == '%':
665 # This error is too difficult to continue from, without
666 # lots of resync code. So just print this one and quit.
667 raise KickstartParseError, formatErrorMsg(lineno)
668 else:
669 needLine = True
670 self.addPackages (string.rstrip(self._line))
672 elif self._state == STATE_SCRIPT_HDR:
673 needLine = True
674 self._script = {"body": [], "interp": "/bin/sh", "log": None,
675 "errorOnFail": False, lineno: None}
677 if not args and self._includeDepth == 0:
678 self._state = STATE_END
679 elif args[0] == "%pre":
680 self._state = STATE_SCRIPT
681 self._script["type"] = KS_SCRIPT_PRE
682 elif args[0] == "%post":
683 self._state = STATE_SCRIPT
684 self._script["type"] = KS_SCRIPT_POST
685 elif args[0] == "%traceback":
686 self._state = STATE_SCRIPT
687 self._script["type"] = KS_SCRIPT_TRACEBACK
688 elif args[0][0] == '%':
689 # This error is too difficult to continue from, without
690 # lots of resync code. So just print this one and quit.
691 raise KickstartParseError, formatErrorMsg(lineno)
693 if self.errorsAreFatal:
694 self.handleScriptHdr (lineno, args)
695 else:
696 try:
697 self.handleScriptHdr (lineno, args)
698 except Exception, msg:
699 print msg
701 elif self._state == STATE_SCRIPT:
702 if self._line in ["%end", ""] and self._includeDepth == 0:
703 if self._line == "" and self.version >= F8:
704 warnings.warn(_("%s does not end with %%end. This syntax has been deprecated. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to use this updated syntax.") % _("Script"), DeprecationWarning)
706 # If we're at the end of the kickstart file, add the script.
707 self.addScript()
708 self._state = STATE_END
709 elif args and args[0] in ["%end", "%pre", "%post", "%traceback", "%packages", "%ksappend"]:
710 # Otherwise we're now at the start of the next section.
711 # Figure out what kind of a script we just finished
712 # reading, add it to the list, and switch to the initial
713 # state.
714 self.addScript()
715 self._state = STATE_COMMANDS
717 if args[0] == "%end":
718 needLine = True
719 else:
720 # Otherwise just add to the current script body.
721 self._script["body"].append(self._line)
722 needLine = True
724 elif self._state == STATE_END:
725 break
727 def readKickstartFromString (self, str, reset=True):
728 """Process a kickstart file, provided as the string str."""
729 if reset:
730 self._reset()
732 # Add a "" to the end of the list so the string reader acts like the
733 # file reader and we only get StopIteration when we're after the final
734 # line of input.
735 i = iter(str.splitlines(True) + [""])
736 self._stateMachine (lambda: i.next())
738 def readKickstart(self, f, reset=True):
739 """Process a kickstart file, given by the filename f."""
740 if reset:
741 self._reset()
743 # an %include might not specify a full path. if we don't try to figure
744 # out what the path should have been, then we're unable to find it
745 # requiring full path specification, though, sucks. so let's make
746 # the reading "smart" by keeping track of what the path is at each
747 # include depth.
748 if not os.path.exists(f):
749 if self.currentdir.has_key(self._includeDepth - 1):
750 if os.path.exists(os.path.join(self.currentdir[self._includeDepth - 1], f)):
751 f = os.path.join(self.currentdir[self._includeDepth - 1], f)
753 cd = os.path.dirname(f)
754 if not cd.startswith("/"):
755 cd = os.path.abspath(cd)
756 self.currentdir[self._includeDepth] = cd
758 fh = urlopen(f)
759 self._stateMachine (lambda: fh.readline())
760 fh.close()