diff options
Diffstat (limited to 'scripts/lib/mic/3rdparty/pykickstart/parser.py')
| -rw-r--r-- | scripts/lib/mic/3rdparty/pykickstart/parser.py | 702 |
1 files changed, 0 insertions, 702 deletions
diff --git a/scripts/lib/mic/3rdparty/pykickstart/parser.py b/scripts/lib/mic/3rdparty/pykickstart/parser.py deleted file mode 100644 index 840a448673..0000000000 --- a/scripts/lib/mic/3rdparty/pykickstart/parser.py +++ /dev/null | |||
| @@ -1,702 +0,0 @@ | |||
| 1 | # | ||
| 2 | # parser.py: Kickstart file parser. | ||
| 3 | # | ||
| 4 | # Chris Lumens <clumens@redhat.com> | ||
| 5 | # | ||
| 6 | # Copyright 2005, 2006, 2007, 2008, 2011 Red Hat, Inc. | ||
| 7 | # | ||
| 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. | ||
| 14 | # | ||
| 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. | ||
| 21 | # | ||
| 22 | """ | ||
| 23 | Main kickstart file processing module. | ||
| 24 | |||
| 25 | This module exports several important classes: | ||
| 26 | |||
| 27 | Script - Representation of a single %pre, %post, or %traceback script. | ||
| 28 | |||
| 29 | Packages - Representation of the %packages section. | ||
| 30 | |||
| 31 | KickstartParser - The kickstart file parser state machine. | ||
| 32 | """ | ||
| 33 | |||
| 34 | from collections import Iterator | ||
| 35 | import os | ||
| 36 | import shlex | ||
| 37 | import sys | ||
| 38 | import tempfile | ||
| 39 | from copy import copy | ||
| 40 | from optparse import * | ||
| 41 | from urlgrabber import urlread | ||
| 42 | import urlgrabber.grabber as grabber | ||
| 43 | |||
| 44 | import constants | ||
| 45 | from errors import KickstartError, KickstartParseError, KickstartValueError, formatErrorMsg | ||
| 46 | from ko import KickstartObject | ||
| 47 | from sections import * | ||
| 48 | import version | ||
| 49 | |||
| 50 | import gettext | ||
| 51 | _ = lambda x: gettext.ldgettext("pykickstart", x) | ||
| 52 | |||
| 53 | STATE_END = "end" | ||
| 54 | STATE_COMMANDS = "commands" | ||
| 55 | |||
| 56 | ver = version.DEVEL | ||
| 57 | |||
| 58 | def _preprocessStateMachine (lineIter): | ||
| 59 | l = None | ||
| 60 | lineno = 0 | ||
| 61 | |||
| 62 | # Now open an output kickstart file that we are going to write to one | ||
| 63 | # line at a time. | ||
| 64 | (outF, outName) = tempfile.mkstemp("-ks.cfg", "", "/tmp") | ||
| 65 | |||
| 66 | while True: | ||
| 67 | try: | ||
| 68 | l = lineIter.next() | ||
| 69 | except StopIteration: | ||
| 70 | break | ||
| 71 | |||
| 72 | # At the end of the file? | ||
| 73 | if l == "": | ||
| 74 | break | ||
| 75 | |||
| 76 | lineno += 1 | ||
| 77 | url = None | ||
| 78 | |||
| 79 | ll = l.strip() | ||
| 80 | if not ll.startswith("%ksappend"): | ||
| 81 | os.write(outF, l) | ||
| 82 | continue | ||
| 83 | |||
| 84 | # Try to pull down the remote file. | ||
| 85 | try: | ||
| 86 | ksurl = ll.split(' ')[1] | ||
| 87 | except: | ||
| 88 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Illegal url for %%ksappend: %s") % ll) | ||
| 89 | |||
| 90 | try: | ||
| 91 | url = grabber.urlopen(ksurl) | ||
| 92 | except grabber.URLGrabError, e: | ||
| 93 | raise KickstartError, formatErrorMsg(lineno, msg=_("Unable to open %%ksappend file: %s") % e.strerror) | ||
| 94 | else: | ||
| 95 | # Sanity check result. Sometimes FTP doesn't catch a file | ||
| 96 | # is missing. | ||
| 97 | try: | ||
| 98 | if url.size < 1: | ||
| 99 | raise KickstartError, formatErrorMsg(lineno, msg=_("Unable to open %%ksappend file")) | ||
| 100 | except: | ||
| 101 | raise KickstartError, formatErrorMsg(lineno, msg=_("Unable to open %%ksappend file")) | ||
| 102 | |||
| 103 | # If that worked, write the remote file to the output kickstart | ||
| 104 | # file in one burst. Then close everything up to get ready to | ||
| 105 | # read ahead in the input file. This allows multiple %ksappend | ||
| 106 | # lines to exist. | ||
| 107 | if url is not None: | ||
| 108 | os.write(outF, url.read()) | ||
| 109 | url.close() | ||
| 110 | |||
| 111 | # All done - close the temp file and return its location. | ||
| 112 | os.close(outF) | ||
| 113 | return outName | ||
| 114 | |||
| 115 | def preprocessFromString (s): | ||
| 116 | """Preprocess the kickstart file, provided as the string str. This | ||
| 117 | method is currently only useful for handling %ksappend lines, | ||
| 118 | which need to be fetched before the real kickstart parser can be | ||
| 119 | run. Returns the location of the complete kickstart file. | ||
| 120 | """ | ||
| 121 | i = iter(s.splitlines(True) + [""]) | ||
| 122 | rc = _preprocessStateMachine (i.next) | ||
| 123 | return rc | ||
| 124 | |||
| 125 | def preprocessKickstart (f): | ||
| 126 | """Preprocess the kickstart file, given by the filename file. This | ||
| 127 | method is currently only useful for handling %ksappend lines, | ||
| 128 | which need to be fetched before the real kickstart parser can be | ||
| 129 | run. Returns the location of the complete kickstart file. | ||
| 130 | """ | ||
| 131 | try: | ||
| 132 | fh = urlopen(f) | ||
| 133 | except grabber.URLGrabError, e: | ||
| 134 | raise KickstartError, formatErrorMsg(0, msg=_("Unable to open input kickstart file: %s") % e.strerror) | ||
| 135 | |||
| 136 | rc = _preprocessStateMachine (iter(fh.readlines())) | ||
| 137 | fh.close() | ||
| 138 | return rc | ||
| 139 | |||
| 140 | class PutBackIterator(Iterator): | ||
| 141 | def __init__(self, iterable): | ||
| 142 | self._iterable = iter(iterable) | ||
| 143 | self._buf = None | ||
| 144 | |||
| 145 | def __iter__(self): | ||
| 146 | return self | ||
| 147 | |||
| 148 | def put(self, s): | ||
| 149 | self._buf = s | ||
| 150 | |||
| 151 | def next(self): | ||
| 152 | if self._buf: | ||
| 153 | retval = self._buf | ||
| 154 | self._buf = None | ||
| 155 | return retval | ||
| 156 | else: | ||
| 157 | return self._iterable.next() | ||
| 158 | |||
| 159 | ### | ||
| 160 | ### SCRIPT HANDLING | ||
| 161 | ### | ||
| 162 | class Script(KickstartObject): | ||
| 163 | """A class representing a single kickstart script. If functionality beyond | ||
| 164 | just a data representation is needed (for example, a run method in | ||
| 165 | anaconda), Script may be subclassed. Although a run method is not | ||
| 166 | provided, most of the attributes of Script have to do with running the | ||
| 167 | script. Instances of Script are held in a list by the Version object. | ||
| 168 | """ | ||
| 169 | def __init__(self, script, *args , **kwargs): | ||
| 170 | """Create a new Script instance. Instance attributes: | ||
| 171 | |||
| 172 | errorOnFail -- If execution of the script fails, should anaconda | ||
| 173 | stop, display an error, and then reboot without | ||
| 174 | running any other scripts? | ||
| 175 | inChroot -- Does the script execute in anaconda's chroot | ||
| 176 | environment or not? | ||
| 177 | interp -- The program that should be used to interpret this | ||
| 178 | script. | ||
| 179 | lineno -- The line number this script starts on. | ||
| 180 | logfile -- Where all messages from the script should be logged. | ||
| 181 | script -- A string containing all the lines of the script. | ||
| 182 | type -- The type of the script, which can be KS_SCRIPT_* from | ||
| 183 | pykickstart.constants. | ||
| 184 | """ | ||
| 185 | KickstartObject.__init__(self, *args, **kwargs) | ||
| 186 | self.script = "".join(script) | ||
| 187 | |||
| 188 | self.interp = kwargs.get("interp", "/bin/sh") | ||
| 189 | self.inChroot = kwargs.get("inChroot", False) | ||
| 190 | self.lineno = kwargs.get("lineno", None) | ||
| 191 | self.logfile = kwargs.get("logfile", None) | ||
| 192 | self.errorOnFail = kwargs.get("errorOnFail", False) | ||
| 193 | self.type = kwargs.get("type", constants.KS_SCRIPT_PRE) | ||
| 194 | |||
| 195 | def __str__(self): | ||
| 196 | """Return a string formatted for output to a kickstart file.""" | ||
| 197 | retval = "" | ||
| 198 | |||
| 199 | if self.type == constants.KS_SCRIPT_PRE: | ||
| 200 | retval += '\n%pre' | ||
| 201 | elif self.type == constants.KS_SCRIPT_POST: | ||
| 202 | retval += '\n%post' | ||
| 203 | elif self.type == constants.KS_SCRIPT_TRACEBACK: | ||
| 204 | retval += '\n%traceback' | ||
| 205 | |||
| 206 | if self.interp != "/bin/sh" and self.interp != "": | ||
| 207 | retval += " --interpreter=%s" % self.interp | ||
| 208 | if self.type == constants.KS_SCRIPT_POST and not self.inChroot: | ||
| 209 | retval += " --nochroot" | ||
| 210 | if self.logfile != None: | ||
| 211 | retval += " --logfile %s" % self.logfile | ||
| 212 | if self.errorOnFail: | ||
| 213 | retval += " --erroronfail" | ||
| 214 | |||
| 215 | if self.script.endswith("\n"): | ||
| 216 | if ver >= version.F8: | ||
| 217 | return retval + "\n%s%%end\n" % self.script | ||
| 218 | else: | ||
| 219 | return retval + "\n%s\n" % self.script | ||
| 220 | else: | ||
| 221 | if ver >= version.F8: | ||
| 222 | return retval + "\n%s\n%%end\n" % self.script | ||
| 223 | else: | ||
| 224 | return retval + "\n%s\n" % self.script | ||
| 225 | |||
| 226 | |||
| 227 | ## | ||
| 228 | ## PACKAGE HANDLING | ||
| 229 | ## | ||
| 230 | class Group: | ||
| 231 | """A class representing a single group in the %packages section.""" | ||
| 232 | def __init__(self, name="", include=constants.GROUP_DEFAULT): | ||
| 233 | """Create a new Group instance. Instance attributes: | ||
| 234 | |||
| 235 | name -- The group's identifier | ||
| 236 | include -- The level of how much of the group should be included. | ||
| 237 | Values can be GROUP_* from pykickstart.constants. | ||
| 238 | """ | ||
| 239 | self.name = name | ||
| 240 | self.include = include | ||
| 241 | |||
| 242 | def __str__(self): | ||
| 243 | """Return a string formatted for output to a kickstart file.""" | ||
| 244 | if self.include == constants.GROUP_REQUIRED: | ||
| 245 | return "@%s --nodefaults" % self.name | ||
| 246 | elif self.include == constants.GROUP_ALL: | ||
| 247 | return "@%s --optional" % self.name | ||
| 248 | else: | ||
| 249 | return "@%s" % self.name | ||
| 250 | |||
| 251 | def __cmp__(self, other): | ||
| 252 | if self.name < other.name: | ||
| 253 | return -1 | ||
| 254 | elif self.name > other.name: | ||
| 255 | return 1 | ||
| 256 | return 0 | ||
| 257 | |||
| 258 | class Packages(KickstartObject): | ||
| 259 | """A class representing the %packages section of the kickstart file.""" | ||
| 260 | def __init__(self, *args, **kwargs): | ||
| 261 | """Create a new Packages instance. Instance attributes: | ||
| 262 | |||
| 263 | addBase -- Should the Base group be installed even if it is | ||
| 264 | not specified? | ||
| 265 | default -- Should the default package set be selected? | ||
| 266 | excludedList -- A list of all the packages marked for exclusion in | ||
| 267 | the %packages section, without the leading minus | ||
| 268 | symbol. | ||
| 269 | excludeDocs -- Should documentation in each package be excluded? | ||
| 270 | groupList -- A list of Group objects representing all the groups | ||
| 271 | specified in the %packages section. Names will be | ||
| 272 | stripped of the leading @ symbol. | ||
| 273 | excludedGroupList -- A list of Group objects representing all the | ||
| 274 | groups specified for removal in the %packages | ||
| 275 | section. Names will be stripped of the leading | ||
| 276 | -@ symbols. | ||
| 277 | handleMissing -- If unknown packages are specified in the %packages | ||
| 278 | section, should it be ignored or not? Values can | ||
| 279 | be KS_MISSING_* from pykickstart.constants. | ||
| 280 | packageList -- A list of all the packages specified in the | ||
| 281 | %packages section. | ||
| 282 | instLangs -- A list of languages to install. | ||
| 283 | """ | ||
| 284 | KickstartObject.__init__(self, *args, **kwargs) | ||
| 285 | |||
| 286 | self.addBase = True | ||
| 287 | self.default = False | ||
| 288 | self.excludedList = [] | ||
| 289 | self.excludedGroupList = [] | ||
| 290 | self.excludeDocs = False | ||
| 291 | self.groupList = [] | ||
| 292 | self.handleMissing = constants.KS_MISSING_PROMPT | ||
| 293 | self.packageList = [] | ||
| 294 | self.instLangs = None | ||
| 295 | |||
| 296 | def __str__(self): | ||
| 297 | """Return a string formatted for output to a kickstart file.""" | ||
| 298 | pkgs = "" | ||
| 299 | |||
| 300 | if not self.default: | ||
| 301 | grps = self.groupList | ||
| 302 | grps.sort() | ||
| 303 | for grp in grps: | ||
| 304 | pkgs += "%s\n" % grp.__str__() | ||
| 305 | |||
| 306 | p = self.packageList | ||
| 307 | p.sort() | ||
| 308 | for pkg in p: | ||
| 309 | pkgs += "%s\n" % pkg | ||
| 310 | |||
| 311 | grps = self.excludedGroupList | ||
| 312 | grps.sort() | ||
| 313 | for grp in grps: | ||
| 314 | pkgs += "-%s\n" % grp.__str__() | ||
| 315 | |||
| 316 | p = self.excludedList | ||
| 317 | p.sort() | ||
| 318 | for pkg in p: | ||
| 319 | pkgs += "-%s\n" % pkg | ||
| 320 | |||
| 321 | if pkgs == "": | ||
| 322 | return "" | ||
| 323 | |||
| 324 | retval = "\n%packages" | ||
| 325 | |||
| 326 | if self.default: | ||
| 327 | retval += " --default" | ||
| 328 | if self.excludeDocs: | ||
| 329 | retval += " --excludedocs" | ||
| 330 | if not self.addBase: | ||
| 331 | retval += " --nobase" | ||
| 332 | if self.handleMissing == constants.KS_MISSING_IGNORE: | ||
| 333 | retval += " --ignoremissing" | ||
| 334 | if self.instLangs: | ||
| 335 | retval += " --instLangs=%s" % self.instLangs | ||
| 336 | |||
| 337 | if ver >= version.F8: | ||
| 338 | return retval + "\n" + pkgs + "\n%end\n" | ||
| 339 | else: | ||
| 340 | return retval + "\n" + pkgs + "\n" | ||
| 341 | |||
| 342 | def _processGroup (self, line): | ||
| 343 | op = OptionParser() | ||
| 344 | op.add_option("--nodefaults", action="store_true", default=False) | ||
| 345 | op.add_option("--optional", action="store_true", default=False) | ||
| 346 | |||
| 347 | (opts, extra) = op.parse_args(args=line.split()) | ||
| 348 | |||
| 349 | if opts.nodefaults and opts.optional: | ||
| 350 | raise KickstartValueError, _("Group cannot specify both --nodefaults and --optional") | ||
| 351 | |||
| 352 | # If the group name has spaces in it, we have to put it back together | ||
| 353 | # now. | ||
| 354 | grp = " ".join(extra) | ||
| 355 | |||
| 356 | if opts.nodefaults: | ||
| 357 | self.groupList.append(Group(name=grp, include=constants.GROUP_REQUIRED)) | ||
| 358 | elif opts.optional: | ||
| 359 | self.groupList.append(Group(name=grp, include=constants.GROUP_ALL)) | ||
| 360 | else: | ||
| 361 | self.groupList.append(Group(name=grp, include=constants.GROUP_DEFAULT)) | ||
| 362 | |||
| 363 | def add (self, pkgList): | ||
| 364 | """Given a list of lines from the input file, strip off any leading | ||
| 365 | symbols and add the result to the appropriate list. | ||
| 366 | """ | ||
| 367 | existingExcludedSet = set(self.excludedList) | ||
| 368 | existingPackageSet = set(self.packageList) | ||
| 369 | newExcludedSet = set() | ||
| 370 | newPackageSet = set() | ||
| 371 | |||
| 372 | excludedGroupList = [] | ||
| 373 | |||
| 374 | for pkg in pkgList: | ||
| 375 | stripped = pkg.strip() | ||
| 376 | |||
| 377 | if stripped[0] == "@": | ||
| 378 | self._processGroup(stripped[1:]) | ||
| 379 | elif stripped[0] == "-": | ||
| 380 | if stripped[1] == "@": | ||
| 381 | excludedGroupList.append(Group(name=stripped[2:])) | ||
| 382 | else: | ||
| 383 | newExcludedSet.add(stripped[1:]) | ||
| 384 | else: | ||
| 385 | newPackageSet.add(stripped) | ||
| 386 | |||
| 387 | # Groups have to be excluded in two different ways (note: can't use | ||
| 388 | # sets here because we have to store objects): | ||
| 389 | excludedGroupNames = map(lambda g: g.name, excludedGroupList) | ||
| 390 | |||
| 391 | # First, an excluded group may be cancelling out a previously given | ||
| 392 | # one. This is often the case when using %include. So there we should | ||
| 393 | # just remove the group from the list. | ||
| 394 | self.groupList = filter(lambda g: g.name not in excludedGroupNames, self.groupList) | ||
| 395 | |||
| 396 | # Second, the package list could have included globs which are not | ||
| 397 | # processed by pykickstart. In that case we need to preserve a list of | ||
| 398 | # excluded groups so whatever tool doing package/group installation can | ||
| 399 | # take appropriate action. | ||
| 400 | self.excludedGroupList.extend(excludedGroupList) | ||
| 401 | |||
| 402 | existingPackageSet = (existingPackageSet - newExcludedSet) | newPackageSet | ||
| 403 | existingExcludedSet = (existingExcludedSet - existingPackageSet) | newExcludedSet | ||
| 404 | |||
| 405 | self.packageList = list(existingPackageSet) | ||
| 406 | self.excludedList = list(existingExcludedSet) | ||
| 407 | |||
| 408 | |||
| 409 | ### | ||
| 410 | ### PARSER | ||
| 411 | ### | ||
| 412 | class KickstartParser: | ||
| 413 | """The kickstart file parser class as represented by a basic state | ||
| 414 | machine. To create a specialized parser, make a subclass and override | ||
| 415 | any of the methods you care about. Methods that don't need to do | ||
| 416 | anything may just pass. However, _stateMachine should never be | ||
| 417 | overridden. | ||
| 418 | """ | ||
| 419 | def __init__ (self, handler, followIncludes=True, errorsAreFatal=True, | ||
| 420 | missingIncludeIsFatal=True): | ||
| 421 | """Create a new KickstartParser instance. Instance attributes: | ||
| 422 | |||
| 423 | errorsAreFatal -- Should errors cause processing to halt, or | ||
| 424 | just print a message to the screen? This | ||
| 425 | is most useful for writing syntax checkers | ||
| 426 | that may want to continue after an error is | ||
| 427 | encountered. | ||
| 428 | followIncludes -- If %include is seen, should the included | ||
| 429 | file be checked as well or skipped? | ||
| 430 | handler -- An instance of a BaseHandler subclass. If | ||
| 431 | None, the input file will still be parsed | ||
| 432 | but no data will be saved and no commands | ||
| 433 | will be executed. | ||
| 434 | missingIncludeIsFatal -- Should missing include files be fatal, even | ||
| 435 | if errorsAreFatal is False? | ||
| 436 | """ | ||
| 437 | self.errorsAreFatal = errorsAreFatal | ||
| 438 | self.followIncludes = followIncludes | ||
| 439 | self.handler = handler | ||
| 440 | self.currentdir = {} | ||
| 441 | self.missingIncludeIsFatal = missingIncludeIsFatal | ||
| 442 | |||
| 443 | self._state = STATE_COMMANDS | ||
| 444 | self._includeDepth = 0 | ||
| 445 | self._line = "" | ||
| 446 | |||
| 447 | self.version = self.handler.version | ||
| 448 | |||
| 449 | global ver | ||
| 450 | ver = self.version | ||
| 451 | |||
| 452 | self._sections = {} | ||
| 453 | self.setupSections() | ||
| 454 | |||
| 455 | def _reset(self): | ||
| 456 | """Reset the internal variables of the state machine for a new kickstart file.""" | ||
| 457 | self._state = STATE_COMMANDS | ||
| 458 | self._includeDepth = 0 | ||
| 459 | |||
| 460 | def getSection(self, s): | ||
| 461 | """Return a reference to the requested section (s must start with '%'s), | ||
| 462 | or raise KeyError if not found. | ||
| 463 | """ | ||
| 464 | return self._sections[s] | ||
| 465 | |||
| 466 | def handleCommand (self, lineno, args): | ||
| 467 | """Given the list of command and arguments, call the Version's | ||
| 468 | dispatcher method to handle the command. Returns the command or | ||
| 469 | data object returned by the dispatcher. This method may be | ||
| 470 | overridden in a subclass if necessary. | ||
| 471 | """ | ||
| 472 | if self.handler: | ||
| 473 | self.handler.currentCmd = args[0] | ||
| 474 | self.handler.currentLine = self._line | ||
| 475 | retval = self.handler.dispatcher(args, lineno) | ||
| 476 | |||
| 477 | return retval | ||
| 478 | |||
| 479 | def registerSection(self, obj): | ||
| 480 | """Given an instance of a Section subclass, register the new section | ||
| 481 | with the parser. Calling this method means the parser will | ||
| 482 | recognize your new section and dispatch into the given object to | ||
| 483 | handle it. | ||
| 484 | """ | ||
| 485 | if not obj.sectionOpen: | ||
| 486 | raise TypeError, "no sectionOpen given for section %s" % obj | ||
| 487 | |||
| 488 | if not obj.sectionOpen.startswith("%"): | ||
| 489 | raise TypeError, "section %s tag does not start with a %%" % obj.sectionOpen | ||
| 490 | |||
| 491 | self._sections[obj.sectionOpen] = obj | ||
| 492 | |||
| 493 | def _finalize(self, obj): | ||
| 494 | """Called at the close of a kickstart section to take any required | ||
| 495 | actions. Internally, this is used to add scripts once we have the | ||
| 496 | whole body read. | ||
| 497 | """ | ||
| 498 | obj.finalize() | ||
| 499 | self._state = STATE_COMMANDS | ||
| 500 | |||
| 501 | def _handleSpecialComments(self, line): | ||
| 502 | """Kickstart recognizes a couple special comments.""" | ||
| 503 | if self._state != STATE_COMMANDS: | ||
| 504 | return | ||
| 505 | |||
| 506 | # Save the platform for s-c-kickstart. | ||
| 507 | if line[:10] == "#platform=": | ||
| 508 | self.handler.platform = self._line[11:] | ||
| 509 | |||
| 510 | def _readSection(self, lineIter, lineno): | ||
| 511 | obj = self._sections[self._state] | ||
| 512 | |||
| 513 | while True: | ||
| 514 | try: | ||
| 515 | line = lineIter.next() | ||
| 516 | if line == "": | ||
| 517 | # This section ends at the end of the file. | ||
| 518 | if self.version >= version.F8: | ||
| 519 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end.")) | ||
| 520 | |||
| 521 | self._finalize(obj) | ||
| 522 | except StopIteration: | ||
| 523 | break | ||
| 524 | |||
| 525 | lineno += 1 | ||
| 526 | |||
| 527 | # Throw away blank lines and comments, unless the section wants all | ||
| 528 | # lines. | ||
| 529 | if self._isBlankOrComment(line) and not obj.allLines: | ||
| 530 | continue | ||
| 531 | |||
| 532 | if line.startswith("%"): | ||
| 533 | args = shlex.split(line) | ||
| 534 | |||
| 535 | if args and args[0] == "%end": | ||
| 536 | # This is a properly terminated section. | ||
| 537 | self._finalize(obj) | ||
| 538 | break | ||
| 539 | elif args and args[0] == "%ksappend": | ||
| 540 | continue | ||
| 541 | elif args and (self._validState(args[0]) or args[0] in ["%include", "%ksappend"]): | ||
| 542 | # This is an unterminated section. | ||
| 543 | if self.version >= version.F8: | ||
| 544 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end.")) | ||
| 545 | |||
| 546 | # Finish up. We do not process the header here because | ||
| 547 | # kicking back out to STATE_COMMANDS will ensure that happens. | ||
| 548 | lineIter.put(line) | ||
| 549 | lineno -= 1 | ||
| 550 | self._finalize(obj) | ||
| 551 | break | ||
| 552 | else: | ||
| 553 | # This is just a line within a section. Pass it off to whatever | ||
| 554 | # section handles it. | ||
| 555 | obj.handleLine(line) | ||
| 556 | |||
| 557 | return lineno | ||
| 558 | |||
| 559 | def _validState(self, st): | ||
| 560 | """Is the given section tag one that has been registered with the parser?""" | ||
| 561 | return st in self._sections.keys() | ||
| 562 | |||
| 563 | def _tryFunc(self, fn): | ||
| 564 | """Call the provided function (which doesn't take any arguments) and | ||
| 565 | do the appropriate error handling. If errorsAreFatal is False, this | ||
| 566 | function will just print the exception and keep going. | ||
| 567 | """ | ||
| 568 | try: | ||
| 569 | fn() | ||
| 570 | except Exception, msg: | ||
| 571 | if self.errorsAreFatal: | ||
| 572 | raise | ||
| 573 | else: | ||
| 574 | print msg | ||
| 575 | |||
| 576 | def _isBlankOrComment(self, line): | ||
| 577 | return line.isspace() or line == "" or line.lstrip()[0] == '#' | ||
| 578 | |||
| 579 | def _stateMachine(self, lineIter): | ||
| 580 | # For error reporting. | ||
| 581 | lineno = 0 | ||
| 582 | |||
| 583 | while True: | ||
| 584 | # Get the next line out of the file, quitting if this is the last line. | ||
| 585 | try: | ||
| 586 | self._line = lineIter.next() | ||
| 587 | if self._line == "": | ||
| 588 | break | ||
| 589 | except StopIteration: | ||
| 590 | break | ||
| 591 | |||
| 592 | lineno += 1 | ||
| 593 | |||
| 594 | # Eliminate blank lines, whitespace-only lines, and comments. | ||
| 595 | if self._isBlankOrComment(self._line): | ||
| 596 | self._handleSpecialComments(self._line) | ||
| 597 | continue | ||
| 598 | |||
| 599 | # Remove any end-of-line comments. | ||
| 600 | sanitized = self._line.split("#")[0] | ||
| 601 | |||
| 602 | # Then split the line. | ||
| 603 | args = shlex.split(sanitized.rstrip()) | ||
| 604 | |||
| 605 | if args[0] == "%include": | ||
| 606 | # This case comes up primarily in ksvalidator. | ||
| 607 | if not self.followIncludes: | ||
| 608 | continue | ||
| 609 | |||
| 610 | if len(args) == 1 or not args[1]: | ||
| 611 | raise KickstartParseError, formatErrorMsg(lineno) | ||
| 612 | |||
| 613 | self._includeDepth += 1 | ||
| 614 | |||
| 615 | try: | ||
| 616 | self.readKickstart(args[1], reset=False) | ||
| 617 | except KickstartError: | ||
| 618 | # Handle the include file being provided over the | ||
| 619 | # network in a %pre script. This case comes up in the | ||
| 620 | # early parsing in anaconda. | ||
| 621 | if self.missingIncludeIsFatal: | ||
| 622 | raise | ||
| 623 | |||
| 624 | self._includeDepth -= 1 | ||
| 625 | continue | ||
| 626 | |||
| 627 | # Now on to the main event. | ||
| 628 | if self._state == STATE_COMMANDS: | ||
| 629 | if args[0] == "%ksappend": | ||
| 630 | # This is handled by the preprocess* functions, so continue. | ||
| 631 | continue | ||
| 632 | elif args[0][0] == '%': | ||
| 633 | # This is the beginning of a new section. Handle its header | ||
| 634 | # here. | ||
| 635 | newSection = args[0] | ||
| 636 | if not self._validState(newSection): | ||
| 637 | raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown kickstart section: %s" % newSection)) | ||
| 638 | |||
| 639 | self._state = newSection | ||
| 640 | obj = self._sections[self._state] | ||
| 641 | self._tryFunc(lambda: obj.handleHeader(lineno, args)) | ||
| 642 | |||
| 643 | # This will handle all section processing, kicking us back | ||
| 644 | # out to STATE_COMMANDS at the end with the current line | ||
| 645 | # being the next section header, etc. | ||
| 646 | lineno = self._readSection(lineIter, lineno) | ||
| 647 | else: | ||
| 648 | # This is a command in the command section. Dispatch to it. | ||
| 649 | self._tryFunc(lambda: self.handleCommand(lineno, args)) | ||
| 650 | elif self._state == STATE_END: | ||
| 651 | break | ||
| 652 | |||
| 653 | def readKickstartFromString (self, s, reset=True): | ||
| 654 | """Process a kickstart file, provided as the string str.""" | ||
| 655 | if reset: | ||
| 656 | self._reset() | ||
| 657 | |||
| 658 | # Add a "" to the end of the list so the string reader acts like the | ||
| 659 | # file reader and we only get StopIteration when we're after the final | ||
| 660 | # line of input. | ||
| 661 | i = PutBackIterator(s.splitlines(True) + [""]) | ||
| 662 | self._stateMachine (i) | ||
| 663 | |||
| 664 | def readKickstart(self, f, reset=True): | ||
| 665 | """Process a kickstart file, given by the filename f.""" | ||
| 666 | if reset: | ||
| 667 | self._reset() | ||
| 668 | |||
| 669 | # an %include might not specify a full path. if we don't try to figure | ||
| 670 | # out what the path should have been, then we're unable to find it | ||
| 671 | # requiring full path specification, though, sucks. so let's make | ||
| 672 | # the reading "smart" by keeping track of what the path is at each | ||
| 673 | # include depth. | ||
| 674 | if not os.path.exists(f): | ||
| 675 | if self.currentdir.has_key(self._includeDepth - 1): | ||
| 676 | if os.path.exists(os.path.join(self.currentdir[self._includeDepth - 1], f)): | ||
| 677 | f = os.path.join(self.currentdir[self._includeDepth - 1], f) | ||
| 678 | |||
| 679 | cd = os.path.dirname(f) | ||
| 680 | if not cd.startswith("/"): | ||
| 681 | cd = os.path.abspath(cd) | ||
| 682 | self.currentdir[self._includeDepth] = cd | ||
| 683 | |||
| 684 | try: | ||
| 685 | s = urlread(f) | ||
| 686 | except grabber.URLGrabError, e: | ||
| 687 | raise KickstartError, formatErrorMsg(0, msg=_("Unable to open input kickstart file: %s") % e.strerror) | ||
| 688 | |||
| 689 | self.readKickstartFromString(s, reset=False) | ||
| 690 | |||
| 691 | def setupSections(self): | ||
| 692 | """Install the sections all kickstart files support. You may override | ||
| 693 | this method in a subclass, but should avoid doing so unless you know | ||
| 694 | what you're doing. | ||
| 695 | """ | ||
| 696 | self._sections = {} | ||
| 697 | |||
| 698 | # Install the sections all kickstart files support. | ||
| 699 | self.registerSection(PreScriptSection(self.handler, dataObj=Script)) | ||
| 700 | self.registerSection(PostScriptSection(self.handler, dataObj=Script)) | ||
| 701 | self.registerSection(TracebackScriptSection(self.handler, dataObj=Script)) | ||
| 702 | self.registerSection(PackageSection(self.handler)) | ||
