Squashed 'yocto-poky/' content from commit ea562de

git-subtree-dir: yocto-poky
git-subtree-split: ea562de57590c966cd5a75fda8defecd397e6436
diff --git a/scripts/lib/wic/3rdparty/pykickstart/parser.py b/scripts/lib/wic/3rdparty/pykickstart/parser.py
new file mode 100644
index 0000000..9c9674b
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/parser.py
@@ -0,0 +1,619 @@
+#
+# parser.py:  Kickstart file parser.
+#
+# Chris Lumens <clumens@redhat.com>
+#
+# Copyright 2005, 2006, 2007, 2008, 2011 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use, modify,
+# copy, or redistribute it subject to the terms and conditions of the GNU
+# General Public License v.2.  This program is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
+# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  Any Red Hat
+# trademarks that are incorporated in the source code or documentation are not
+# subject to the GNU General Public License and may only be used or replicated
+# with the express permission of Red Hat, Inc. 
+#
+"""
+Main kickstart file processing module.
+
+This module exports several important classes:
+
+    Script - Representation of a single %pre, %post, or %traceback script.
+
+    Packages - Representation of the %packages section.
+
+    KickstartParser - The kickstart file parser state machine.
+"""
+
+from collections import Iterator
+import os
+import shlex
+import sys
+import tempfile
+from copy import copy
+from optparse import *
+
+import constants
+from errors import KickstartError, KickstartParseError, KickstartValueError, formatErrorMsg
+from ko import KickstartObject
+from sections import *
+import version
+
+import gettext
+_ = lambda x: gettext.ldgettext("pykickstart", x)
+
+STATE_END = "end"
+STATE_COMMANDS = "commands"
+
+ver = version.DEVEL
+
+
+class PutBackIterator(Iterator):
+    def __init__(self, iterable):
+        self._iterable = iter(iterable)
+        self._buf = None
+
+    def __iter__(self):
+        return self
+
+    def put(self, s):
+        self._buf = s
+
+    def next(self):
+        if self._buf:
+            retval = self._buf
+            self._buf = None
+            return retval
+        else:
+            return self._iterable.next()
+
+###
+### SCRIPT HANDLING
+###
+class Script(KickstartObject):
+    """A class representing a single kickstart script.  If functionality beyond
+       just a data representation is needed (for example, a run method in
+       anaconda), Script may be subclassed.  Although a run method is not
+       provided, most of the attributes of Script have to do with running the
+       script.  Instances of Script are held in a list by the Version object.
+    """
+    def __init__(self, script, *args , **kwargs):
+        """Create a new Script instance.  Instance attributes:
+
+           errorOnFail -- If execution of the script fails, should anaconda
+                          stop, display an error, and then reboot without
+                          running any other scripts?
+           inChroot    -- Does the script execute in anaconda's chroot
+                          environment or not?
+           interp      -- The program that should be used to interpret this
+                          script.
+           lineno      -- The line number this script starts on.
+           logfile     -- Where all messages from the script should be logged.
+           script      -- A string containing all the lines of the script.
+           type        -- The type of the script, which can be KS_SCRIPT_* from
+                          pykickstart.constants.
+        """
+        KickstartObject.__init__(self, *args, **kwargs)
+        self.script = "".join(script)
+
+        self.interp = kwargs.get("interp", "/bin/sh")
+        self.inChroot = kwargs.get("inChroot", False)
+        self.lineno = kwargs.get("lineno", None)
+        self.logfile = kwargs.get("logfile", None)
+        self.errorOnFail = kwargs.get("errorOnFail", False)
+        self.type = kwargs.get("type", constants.KS_SCRIPT_PRE)
+
+    def __str__(self):
+        """Return a string formatted for output to a kickstart file."""
+        retval = ""
+
+        if self.type == constants.KS_SCRIPT_PRE:
+            retval += '\n%pre'
+        elif self.type == constants.KS_SCRIPT_POST:
+            retval += '\n%post'
+        elif self.type == constants.KS_SCRIPT_TRACEBACK:
+            retval += '\n%traceback'
+
+        if self.interp != "/bin/sh" and self.interp != "":
+            retval += " --interpreter=%s" % self.interp
+        if self.type == constants.KS_SCRIPT_POST and not self.inChroot:
+            retval += " --nochroot"
+        if self.logfile != None:
+            retval += " --logfile %s" % self.logfile
+        if self.errorOnFail:
+            retval += " --erroronfail"
+
+        if self.script.endswith("\n"):
+            if ver >= version.F8:
+                return retval + "\n%s%%end\n" % self.script
+            else:
+                return retval + "\n%s\n" % self.script
+        else:
+            if ver >= version.F8:
+                return retval + "\n%s\n%%end\n" % self.script
+            else:
+                return retval + "\n%s\n" % self.script
+
+
+##
+## PACKAGE HANDLING
+##
+class Group:
+    """A class representing a single group in the %packages section."""
+    def __init__(self, name="", include=constants.GROUP_DEFAULT):
+        """Create a new Group instance.  Instance attributes:
+
+           name    -- The group's identifier
+           include -- The level of how much of the group should be included.
+                      Values can be GROUP_* from pykickstart.constants.
+        """
+        self.name = name
+        self.include = include
+
+    def __str__(self):
+        """Return a string formatted for output to a kickstart file."""
+        if self.include == constants.GROUP_REQUIRED:
+            return "@%s --nodefaults" % self.name
+        elif self.include == constants.GROUP_ALL:
+            return "@%s --optional" % self.name
+        else:
+            return "@%s" % self.name
+
+    def __cmp__(self, other):
+        if self.name < other.name:
+            return -1
+        elif self.name > other.name:
+            return 1
+        return 0
+
+class Packages(KickstartObject):
+    """A class representing the %packages section of the kickstart file."""
+    def __init__(self, *args, **kwargs):
+        """Create a new Packages instance.  Instance attributes:
+
+           addBase       -- Should the Base group be installed even if it is
+                            not specified?
+           default       -- Should the default package set be selected?
+           excludedList  -- A list of all the packages marked for exclusion in
+                            the %packages section, without the leading minus
+                            symbol.
+           excludeDocs   -- Should documentation in each package be excluded?
+           groupList     -- A list of Group objects representing all the groups
+                            specified in the %packages section.  Names will be
+                            stripped of the leading @ symbol.
+           excludedGroupList -- A list of Group objects representing all the
+                                groups specified for removal in the %packages
+                                section.  Names will be stripped of the leading
+                                -@ symbols.
+           handleMissing -- If unknown packages are specified in the %packages
+                            section, should it be ignored or not?  Values can
+                            be KS_MISSING_* from pykickstart.constants.
+           packageList   -- A list of all the packages specified in the
+                            %packages section.
+           instLangs     -- A list of languages to install.
+        """
+        KickstartObject.__init__(self, *args, **kwargs)
+
+        self.addBase = True
+        self.default = False
+        self.excludedList = []
+        self.excludedGroupList = []
+        self.excludeDocs = False
+        self.groupList = []
+        self.handleMissing = constants.KS_MISSING_PROMPT
+        self.packageList = []
+        self.instLangs = None
+
+    def __str__(self):
+        """Return a string formatted for output to a kickstart file."""
+        pkgs = ""
+
+        if not self.default:
+            grps = self.groupList
+            grps.sort()
+            for grp in grps:
+                pkgs += "%s\n" % grp.__str__()
+
+            p = self.packageList
+            p.sort()
+            for pkg in p:
+                pkgs += "%s\n" % pkg
+
+            grps = self.excludedGroupList
+            grps.sort()
+            for grp in grps:
+                pkgs += "-%s\n" % grp.__str__()
+
+            p = self.excludedList
+            p.sort()
+            for pkg in p:
+                pkgs += "-%s\n" % pkg
+
+            if pkgs == "":
+                return ""
+
+        retval = "\n%packages"
+
+        if self.default:
+            retval += " --default"
+        if self.excludeDocs:
+            retval += " --excludedocs"
+        if not self.addBase:
+            retval += " --nobase"
+        if self.handleMissing == constants.KS_MISSING_IGNORE:
+            retval += " --ignoremissing"
+        if self.instLangs:
+            retval += " --instLangs=%s" % self.instLangs
+
+        if ver >= version.F8:
+            return retval + "\n" + pkgs + "\n%end\n"
+        else:
+            return retval + "\n" + pkgs + "\n"
+
+    def _processGroup (self, line):
+        op = OptionParser()
+        op.add_option("--nodefaults", action="store_true", default=False)
+        op.add_option("--optional", action="store_true", default=False)
+
+        (opts, extra) = op.parse_args(args=line.split())
+
+        if opts.nodefaults and opts.optional:
+            raise KickstartValueError, _("Group cannot specify both --nodefaults and --optional")
+
+        # If the group name has spaces in it, we have to put it back together
+        # now.
+        grp = " ".join(extra)
+
+        if opts.nodefaults:
+            self.groupList.append(Group(name=grp, include=constants.GROUP_REQUIRED))
+        elif opts.optional:
+            self.groupList.append(Group(name=grp, include=constants.GROUP_ALL))
+        else:
+            self.groupList.append(Group(name=grp, include=constants.GROUP_DEFAULT))
+
+    def add (self, pkgList):
+        """Given a list of lines from the input file, strip off any leading
+           symbols and add the result to the appropriate list.
+        """
+        existingExcludedSet = set(self.excludedList)
+        existingPackageSet = set(self.packageList)
+        newExcludedSet = set()
+        newPackageSet = set()
+
+        excludedGroupList = []
+
+        for pkg in pkgList:
+            stripped = pkg.strip()
+
+            if stripped[0] == "@":
+                self._processGroup(stripped[1:])
+            elif stripped[0] == "-":
+                if stripped[1] == "@":
+                    excludedGroupList.append(Group(name=stripped[2:]))
+                else:
+                    newExcludedSet.add(stripped[1:])
+            else:
+                newPackageSet.add(stripped)
+
+        # Groups have to be excluded in two different ways (note: can't use
+        # sets here because we have to store objects):
+        excludedGroupNames = map(lambda g: g.name, excludedGroupList)
+
+        # First, an excluded group may be cancelling out a previously given
+        # one.  This is often the case when using %include.  So there we should
+        # just remove the group from the list.
+        self.groupList = filter(lambda g: g.name not in excludedGroupNames, self.groupList)
+
+        # Second, the package list could have included globs which are not
+        # processed by pykickstart.  In that case we need to preserve a list of
+        # excluded groups so whatever tool doing package/group installation can
+        # take appropriate action.
+        self.excludedGroupList.extend(excludedGroupList)
+
+        existingPackageSet = (existingPackageSet - newExcludedSet) | newPackageSet
+        existingExcludedSet = (existingExcludedSet - existingPackageSet) | newExcludedSet
+
+        self.packageList = list(existingPackageSet)
+        self.excludedList = list(existingExcludedSet)
+
+
+###
+### PARSER
+###
+class KickstartParser:
+    """The kickstart file parser class as represented by a basic state
+       machine.  To create a specialized parser, make a subclass and override
+       any of the methods you care about.  Methods that don't need to do
+       anything may just pass.  However, _stateMachine should never be
+       overridden.
+    """
+    def __init__ (self, handler, followIncludes=True, errorsAreFatal=True,
+                  missingIncludeIsFatal=True):
+        """Create a new KickstartParser instance.  Instance attributes:
+
+           errorsAreFatal        -- Should errors cause processing to halt, or
+                                    just print a message to the screen?  This
+                                    is most useful for writing syntax checkers
+                                    that may want to continue after an error is
+                                    encountered.
+           followIncludes        -- If %include is seen, should the included
+                                    file be checked as well or skipped?
+           handler               -- An instance of a BaseHandler subclass.  If
+                                    None, the input file will still be parsed
+                                    but no data will be saved and no commands
+                                    will be executed.
+           missingIncludeIsFatal -- Should missing include files be fatal, even
+                                    if errorsAreFatal is False?
+        """
+        self.errorsAreFatal = errorsAreFatal
+        self.followIncludes = followIncludes
+        self.handler = handler
+        self.currentdir = {}
+        self.missingIncludeIsFatal = missingIncludeIsFatal
+
+        self._state = STATE_COMMANDS
+        self._includeDepth = 0
+        self._line = ""
+
+        self.version = self.handler.version
+
+        global ver
+        ver = self.version
+
+        self._sections = {}
+        self.setupSections()
+
+    def _reset(self):
+        """Reset the internal variables of the state machine for a new kickstart file."""
+        self._state = STATE_COMMANDS
+        self._includeDepth = 0
+
+    def getSection(self, s):
+        """Return a reference to the requested section (s must start with '%'s),
+           or raise KeyError if not found.
+        """
+        return self._sections[s]
+
+    def handleCommand (self, lineno, args):
+        """Given the list of command and arguments, call the Version's
+           dispatcher method to handle the command.  Returns the command or
+           data object returned by the dispatcher.  This method may be
+           overridden in a subclass if necessary.
+        """
+        if self.handler:
+            self.handler.currentCmd = args[0]
+            self.handler.currentLine = self._line
+            retval = self.handler.dispatcher(args, lineno)
+
+            return retval
+
+    def registerSection(self, obj):
+        """Given an instance of a Section subclass, register the new section
+           with the parser.  Calling this method means the parser will
+           recognize your new section and dispatch into the given object to
+           handle it.
+        """
+        if not obj.sectionOpen:
+            raise TypeError, "no sectionOpen given for section %s" % obj
+
+        if not obj.sectionOpen.startswith("%"):
+            raise TypeError, "section %s tag does not start with a %%" % obj.sectionOpen
+
+        self._sections[obj.sectionOpen] = obj
+
+    def _finalize(self, obj):
+        """Called at the close of a kickstart section to take any required
+           actions.  Internally, this is used to add scripts once we have the
+           whole body read.
+        """
+        obj.finalize()
+        self._state = STATE_COMMANDS
+
+    def _handleSpecialComments(self, line):
+        """Kickstart recognizes a couple special comments."""
+        if self._state != STATE_COMMANDS:
+            return
+
+        # Save the platform for s-c-kickstart.
+        if line[:10] == "#platform=":
+            self.handler.platform = self._line[11:]
+
+    def _readSection(self, lineIter, lineno):
+        obj = self._sections[self._state]
+
+        while True:
+            try:
+                line = lineIter.next()
+                if line == "":
+                    # This section ends at the end of the file.
+                    if self.version >= version.F8:
+                        raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end."))
+
+                    self._finalize(obj)
+            except StopIteration:
+                break
+
+            lineno += 1
+
+            # Throw away blank lines and comments, unless the section wants all
+            # lines.
+            if self._isBlankOrComment(line) and not obj.allLines:
+                continue
+
+            if line.startswith("%"):
+                args = shlex.split(line)
+
+                if args and args[0] == "%end":
+                    # This is a properly terminated section.
+                    self._finalize(obj)
+                    break
+                elif args and args[0] == "%ksappend":
+                    continue
+                elif args and (self._validState(args[0]) or args[0] in ["%include", "%ksappend"]):
+                    # This is an unterminated section.
+                    if self.version >= version.F8:
+                        raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end."))
+
+                    # Finish up.  We do not process the header here because
+                    # kicking back out to STATE_COMMANDS will ensure that happens.
+                    lineIter.put(line)
+                    lineno -= 1
+                    self._finalize(obj)
+                    break
+            else:
+                # This is just a line within a section.  Pass it off to whatever
+                # section handles it.
+                obj.handleLine(line)
+
+        return lineno
+
+    def _validState(self, st):
+        """Is the given section tag one that has been registered with the parser?"""
+        return st in self._sections.keys()
+
+    def _tryFunc(self, fn):
+        """Call the provided function (which doesn't take any arguments) and
+           do the appropriate error handling.  If errorsAreFatal is False, this
+           function will just print the exception and keep going.
+        """
+        try:
+            fn()
+        except Exception, msg:
+            if self.errorsAreFatal:
+                raise
+            else:
+                print msg
+
+    def _isBlankOrComment(self, line):
+        return line.isspace() or line == "" or line.lstrip()[0] == '#'
+
+    def _stateMachine(self, lineIter):
+        # For error reporting.
+        lineno = 0
+
+        while True:
+            # Get the next line out of the file, quitting if this is the last line.
+            try:
+                self._line = lineIter.next()
+                if self._line == "":
+                    break
+            except StopIteration:
+                break
+
+            lineno += 1
+
+            # Eliminate blank lines, whitespace-only lines, and comments.
+            if self._isBlankOrComment(self._line):
+                self._handleSpecialComments(self._line)
+                continue
+
+            # Remove any end-of-line comments.
+            sanitized = self._line.split("#")[0]
+
+            # Then split the line.
+            args = shlex.split(sanitized.rstrip())
+
+            if args[0] == "%include":
+                # This case comes up primarily in ksvalidator.
+                if not self.followIncludes:
+                    continue
+
+                if len(args) == 1 or not args[1]:
+                    raise KickstartParseError, formatErrorMsg(lineno)
+
+                self._includeDepth += 1
+
+                try:
+                    self.readKickstart(args[1], reset=False)
+                except KickstartError:
+                    # Handle the include file being provided over the
+                    # network in a %pre script.  This case comes up in the
+                    # early parsing in anaconda.
+                    if self.missingIncludeIsFatal:
+                        raise
+
+                self._includeDepth -= 1
+                continue
+
+            # Now on to the main event.
+            if self._state == STATE_COMMANDS:
+                if args[0] == "%ksappend":
+                    # This is handled by the preprocess* functions, so continue.
+                    continue
+                elif args[0][0] == '%':
+                    # This is the beginning of a new section.  Handle its header
+                    # here.
+                    newSection = args[0]
+                    if not self._validState(newSection):
+                        raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown kickstart section: %s" % newSection))
+
+                    self._state = newSection
+                    obj = self._sections[self._state]
+                    self._tryFunc(lambda: obj.handleHeader(lineno, args))
+
+                    # This will handle all section processing, kicking us back
+                    # out to STATE_COMMANDS at the end with the current line
+                    # being the next section header, etc.
+                    lineno = self._readSection(lineIter, lineno)
+                else:
+                    # This is a command in the command section.  Dispatch to it.
+                    self._tryFunc(lambda: self.handleCommand(lineno, args))
+            elif self._state == STATE_END:
+                break
+
+    def readKickstartFromString (self, s, reset=True):
+        """Process a kickstart file, provided as the string str."""
+        if reset:
+            self._reset()
+
+        # Add a "" to the end of the list so the string reader acts like the
+        # file reader and we only get StopIteration when we're after the final
+        # line of input.
+        i = PutBackIterator(s.splitlines(True) + [""])
+        self._stateMachine (i)
+
+    def readKickstart(self, f, reset=True):
+        """Process a kickstart file, given by the filename f."""
+        if reset:
+            self._reset()
+
+        # an %include might not specify a full path.  if we don't try to figure
+        # out what the path should have been, then we're unable to find it
+        # requiring full path specification, though, sucks.  so let's make
+        # the reading "smart" by keeping track of what the path is at each
+        # include depth.
+        if not os.path.exists(f):
+            if self.currentdir.has_key(self._includeDepth - 1):
+                if os.path.exists(os.path.join(self.currentdir[self._includeDepth - 1], f)):
+                    f = os.path.join(self.currentdir[self._includeDepth - 1], f)
+
+        cd = os.path.dirname(f)
+        if not cd.startswith("/"):
+            cd = os.path.abspath(cd)
+        self.currentdir[self._includeDepth] = cd
+
+        try:
+            s = file(f).read()
+        except IOError, e:
+            raise KickstartError, formatErrorMsg(0, msg=_("Unable to open input kickstart file: %s") % e.strerror)
+
+        self.readKickstartFromString(s, reset=False)
+
+    def setupSections(self):
+        """Install the sections all kickstart files support.  You may override
+           this method in a subclass, but should avoid doing so unless you know
+           what you're doing.
+        """
+        self._sections = {}
+
+        # Install the sections all kickstart files support.
+        self.registerSection(PreScriptSection(self.handler, dataObj=Script))
+        self.registerSection(PostScriptSection(self.handler, dataObj=Script))
+        self.registerSection(TracebackScriptSection(self.handler, dataObj=Script))
+        self.registerSection(PackageSection(self.handler))