[PATCH 04/14] Switch Anaconda to argparse

Martin Kolman mkolman at redhat.com
Thu Aug 21 20:10:25 UTC 2014


Optparse is deprecated since Python 2.7 and unlike argparse will not be developed
further.

As we lost option.process when switching to argparse, so we need handle the
set_const case ourselves in parse_boot_cmdline.

(cherry picked from commit dbb5c709bad1fe67a6b02dd9a492db30a38f9a44)

Related: rhbz:#1088459
Signed-off-by: Martin Kolman <mkolman at redhat.com>
---
 anaconda                        | 164 ++++++++++-----------
 pyanaconda/anaconda_argparse.py | 306 ++++++++++++++++++++++++++++++++++++++++
 pyanaconda/anaconda_optparse.py | 249 --------------------------------
 3 files changed, 391 insertions(+), 328 deletions(-)
 create mode 100644 pyanaconda/anaconda_argparse.py
 delete mode 100644 pyanaconda/anaconda_optparse.py

diff --git a/anaconda b/anaconda
index bd5f5cd..26236db 100755
--- a/anaconda
+++ b/anaconda
@@ -2,7 +2,7 @@
 #
 # anaconda: The Red Hat Linux Installation program
 #
-# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
+# Copyright (C) 1999-2014
 # Red Hat, Inc.  All rights reserved.
 #
 # This program is free software; you can redistribute it and/or modify
@@ -211,112 +211,118 @@ def getAnacondaVersion():
     from pyanaconda import _isys
     return _isys.getAnacondaVersion()
 
-def parseOptions(argv=None, cmdline=None):
-    from pyanaconda.anaconda_optparse import AnacondaOptionParser
-    from pyanaconda.anaconda_optparse import HelpTextParser
+def parseArguments(argv=None, boot_cmdline=None):
+    from pyanaconda.anaconda_argparse import AnacondaArgumentParser
+    from pyanaconda.anaconda_argparse import HelpTextParser
 
     # NOTE: for each long option (like '--repo'), AnacondaOptionParser
     # checks the boot arguments for bootarg_prefix+option ('inst.repo').
     # If require_prefix is False, it also accepts the option without the
     # bootarg_prefix ('repo').
     # See anaconda_optparse.py and BootArgs (in flags.py) for details.
-    op = AnacondaOptionParser(version="%prog " + getAnacondaVersion(),
-                              bootarg_prefix="inst.", require_prefix=False)
+    ap = AnacondaArgumentParser(bootarg_prefix="inst.", require_prefix=False)
     help_parser = HelpTextParser("/usr/share/anaconda/anaconda_options.txt")
 
     # NOTE: store_false options will *not* get negated when the user does
     # "option=0" on the boot commandline (store_true options do, though).
     # Basically, don't use store_false unless the option starts with "no".
 
+    # YET ANOTHER NOTE: If you change anything here:
+    # a) document its usage in docs/boot-options.txt
+    # b) be prepared to maintain it for a very long time
+    # If this seems like too much trouble, *don't add a new option*!
+
+    # Version
+    ap.add_argument('--version', action='version', version="%(prog)s " + getAnacondaVersion())
+
     # Interface
-    op.add_option("-C", "--cmdline", dest="display_mode", action="store_const", const="c",
-                  default="g")
-    op.add_option("-G", "--graphical", dest="display_mode", action="store_const", const="g")
-    op.add_option("-T", "--text", dest="display_mode", action="store_const", const="t")
-    op.add_option("-S", "--script", dest="display_mode", action="store_const", const="s")
+    ap.add_argument("-C", "--cmdline", dest="display_mode", action="store_const", const="c",
+                    default="g")
+    ap.add_argument("-G", "--graphical", dest="display_mode", action="store_const", const="g")
+    ap.add_argument("-T", "--text", dest="display_mode", action="store_const", const="t")
+    ap.add_argument("-S", "--script", dest="display_mode", action="store_const", const="s")
 
     # Network
-    op.add_option("--noipv4", action="store_true", default=False)
-    op.add_option("--noipv6", action="store_true", default=False)
-    op.add_option("--proxy")
+    ap.add_argument("--noipv4", action="store_true", default=False)
+    ap.add_argument("--noipv6", action="store_true", default=False)
+    ap.add_argument("--proxy")
 
     # Method of operation
-    op.add_option("--autostep", action="store_true", default=False)
-    op.add_option("-d", "--debug", dest="debug", action="store_true", default=False)
-    op.add_option("--ks", dest="ksfile", action="store_const", const="/run/install/ks.cfg")
-    op.add_option("--kickstart", dest="ksfile")
-    op.add_option("--rescue", dest="rescue", action="store_true", default=False)
-    op.add_option("--targetarch", "rpmarch", dest="targetArch", type="string")
-    op.add_option("--armplatform", dest="armPlatform", type="string")
-    op.add_option("--multilib", dest="multiLib", action="store_true", default=False)
-
-    op.add_option("-m", "--method", dest="method", default=None)
-    op.add_option("--askmethod", dest="askmethod", action="store_true", default=False)
-    op.add_option("--repo", dest="method", default=None)
-    op.add_option("--stage2", dest="stage2", default=None)
-    op.add_option("--noverifyssl", action="store_true", default=False)
-
-    op.add_option("--liveinst", action="store_true", default=False)
+    ap.add_argument("--autostep", action="store_true", default=False)
+    ap.add_argument("-d", "--debug", dest="debug", action="store_true", default=False)
+    ap.add_argument("--ks", dest="ksfile", action="store_const", const="/run/install/ks.cfg")
+    ap.add_argument("--kickstart", dest="ksfile")
+    ap.add_argument("--rescue", dest="rescue", action="store_true", default=False)
+    ap.add_argument("--targetarch", "rpmarch", dest="targetArch", type=str)
+    ap.add_argument("--armplatform", dest="armPlatform", type=str)
+    ap.add_argument("--multilib", dest="multiLib", action="store_true", default=False)
+
+    ap.add_argument("-m", "--method", dest="method", default=None)
+    ap.add_argument("--askmethod", dest="askmethod", action="store_true", default=False)
+    ap.add_argument("--repo", dest="method", default=None)
+    ap.add_argument("--stage2", dest="stage2", default=None)
+    ap.add_argument("--noverifyssl", action="store_true", default=False)
+    ap.add_argument("--liveinst", action="store_true", default=False)
 
     # Display
-    op.add_option("--headless", dest="isHeadless", action="store_true", default=False)
-    op.add_option("--nofb")
-    op.add_option("--resolution", dest="runres", default=None)
-    op.add_option("--usefbx", dest="xdriver", action="store_const", const="fbdev")
-    op.add_option("--vnc", action="store_true", default=False)
-    op.add_option("--vncconnect")
-    op.add_option("--vncpassword", default="")
-    op.add_option("--xdriver", dest="xdriver", action="store", type="string", default=None)
+    ap.add_argument("--headless", dest="isHeadless", action="store_true", default=False)
+    ap.add_argument("--nofb")
+    ap.add_argument("--resolution", dest="runres", default=None)
+    ap.add_argument("--usefbx", dest="xdriver", action="store_const", const="fbdev")
+    ap.add_argument("--vnc", action="store_true", default=False)
+    ap.add_argument("--vncconnect")
+    ap.add_argument("--vncpassword", default="")
+    ap.add_argument("--xdriver", dest="xdriver", action="store", type=str, default=None)
 
     # Language
-    op.add_option("--keymap")
-    op.add_option("--kbdtype")
-    op.add_option("--lang")
+    ap.add_argument("--keymap")
+    ap.add_argument("--kbdtype")
+    ap.add_argument("--lang")
 
     # Obvious
-    op.add_option("--loglevel")
-    op.add_option("--syslog")
+    ap.add_argument("--loglevel")
+    ap.add_argument("--syslog")
 
-    op.add_option("--noselinux", dest="selinux", action="store_false", default=True)
-    op.add_option("--selinux", action="store_true")
+    ap.add_argument("--noselinux", dest="selinux", action="store_false", default=True)
+    ap.add_argument("--selinux", action="store_true")
 
-    op.add_option("--nompath", dest="mpath", action="store_false", default=True)
-    op.add_option("--mpath", action="store_true")
+    ap.add_argument("--nompath", dest="mpath", action="store_false", default=True)
+    ap.add_argument("--mpath", action="store_true")
 
-    op.add_option("--nodmraid", dest="dmraid", action="store_false", default=True)
-    op.add_option("--dmraid", action="store_true")
+    ap.add_argument("--nodmraid", dest="dmraid", action="store_false", default=True)
+    ap.add_argument("--dmraid", action="store_true")
 
-    op.add_option("--noibft", dest="ibft", action="store_false", default=True)
-    op.add_option("--ibft", action="store_true")
-    op.add_option("--noiscsi", dest="iscsi", action="store_false", default=False)
-    op.add_option("--iscsi", action="store_true")
+    ap.add_argument("--noibft", dest="ibft", action="store_false", default=True)
+    ap.add_argument("--ibft", action="store_true")
+    ap.add_argument("--noiscsi", dest="iscsi", action="store_false", default=False)
+    ap.add_argument("--iscsi", action="store_true")
 
     # Geolocation
-    op.add_option("--geoloc")
+    ap.add_argument("--geoloc")
 
     # Miscellaneous
-    op.add_option("--module", action="append", default=[])
-    op.add_option("--nomount", dest="rescue_nomount", action="store_true", default=False)
-    op.add_option("--updates", dest="updateSrc", action="store", type="string")
-    op.add_option("--dlabel", action="store_true", default=False)
-    op.add_option("--image", action="append", dest="images", default=[],
-       metavar="IMAGE_SPEC", help=help_parser.help_text("image"))
-    op.add_option("--dirinstall", action="store_true", default=False,
-       help=help_parser.help_text("dirinstall"))
-    op.add_option("--memcheck", action="store_true", default=True)
-    op.add_option("--nomemcheck", action="store_false", dest="memcheck")
-    op.add_option("--leavebootorder", action="store_true", default=False)
-    op.add_option("--noeject", action="store_false", dest="eject", default=True)
-    op.add_option("--extlinux", action="store_true", default=False)
-    op.add_option("--mpathfriendlynames", action="store_true", default=True)
-
-    # some defaults change based on cmdline flags
-    if cmdline is not None:
-        if "console" in cmdline:
-            op.set_defaults(display_mode="t")
-
-    (opts_, args_) = op.parse_args(argv, cmdline=cmdline)
-    return (opts_, args_, op.deprecated_bootargs)
+    ap.add_argument("--module", action="append", default=[])
+    ap.add_argument("--nomount", dest="rescue_nomount", action="store_true", default=False)
+    ap.add_argument("--updates", dest="updateSrc", action="store", type=str)
+    ap.add_argument("--dlabel", action="store_true", default=False)
+    ap.add_argument("--image", action="append", dest="images", default=[],
+                    metavar="IMAGE_SPEC", help=help_parser.help_text("image"))
+    ap.add_argument("--dirinstall", action="store_true", default=False,
+                    help=help_parser.help_text("dirinstall"))
+    ap.add_argument("--memcheck", action="store_true", default=True)
+    ap.add_argument("--nomemcheck", action="store_false", dest="memcheck")
+    ap.add_argument("--leavebootorder", action="store_true", default=False)
+    ap.add_argument("--noeject", action="store_false", dest="eject", default=True)
+    ap.add_argument("--extlinux", action="store_true", default=False)
+    ap.add_argument("--mpathfriendlynames", action="store_true", default=True)
+
+    # some defaults change based on boot_cmdline flags
+    if boot_cmdline is not None:
+        if "console" in boot_cmdline:
+            ap.set_defaults(display_mode="t")
+
+    namespace = ap.parse_args(argv, boot_cmdline=boot_cmdline)
+    return (namespace, ap.deprecated_bootargs)
 
 def setupPythonPath():
     # First add our updates path
@@ -681,7 +687,7 @@ if __name__ == "__main__":
     # check if the CLI help is requested and return it at once,
     # without importing random stuff and spamming stdout
     if ("--help" in sys.argv) or ("-h" in sys.argv):
-        parseOptions()
+        parseArguments()
 
     print "Starting installer, one moment..."
 
@@ -708,7 +714,7 @@ if __name__ == "__main__":
 
     # do this early so we can set flags before initializing logging
     from pyanaconda.flags import flags, can_touch_runtime_system
-    (opts, args, depr) = parseOptions(cmdline=flags.cmdline)
+    (opts, depr) = parseArguments(boot_cmdline=flags.cmdline)
 
     if opts.askmethod:
         flags.askmethod = True
@@ -1065,7 +1071,7 @@ if __name__ == "__main__":
     if anaconda.displayMode == 'c':
         flags.ksprompt = False
 
-    from pyanaconda.anaconda_optparse import name_path_pairs
+    from pyanaconda.anaconda_argparse import name_path_pairs
 
     image_count = 0
     try:
diff --git a/pyanaconda/anaconda_argparse.py b/pyanaconda/anaconda_argparse.py
new file mode 100644
index 0000000..e868bee
--- /dev/null
+++ b/pyanaconda/anaconda_argparse.py
@@ -0,0 +1,306 @@
+#
+# anaconda_optparse.py: option parsing for anaconda (CLI and boot args)
+#
+# Copyright (C) 2012 Red Hat, Inc.  All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#   Will Woods <wwoods at redhat.com>
+#   Martin Kolman <mkolman at redhat.com>
+
+DESCRIPTION = "Anaconda is the installation program used by Fedora," \
+              "Red Hat Enterprise Linux and some other distributions."
+
+import itertools
+import os
+
+from argparse import ArgumentParser, ArgumentError
+
+from pyanaconda.flags import BootArgs
+
+import logging
+log = logging.getLogger("anaconda")
+
+class AnacondaArgumentParser(ArgumentParser):
+    """
+    Subclass of ArgumentParser that also examines boot arguments.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """
+        If the "bootarg_prefix" keyword argument is set, it's assumed that all
+        bootargs will start with that prefix.
+
+        "require_prefix" is a bool:
+            False: accept the argument with or without the prefix.
+            True: ignore the argument without the prefix. (default)
+        """
+        self._boot_arg = dict()
+        self.deprecated_bootargs = []
+        self.bootarg_prefix = kwargs.pop("bootarg_prefix", "")
+        self.require_prefix = kwargs.pop("require_prefix", True)
+        ArgumentParser.__init__(self, description=DESCRIPTION, *args, **kwargs)
+
+    def add_argument(self, *args, **kwargs):
+        """
+        Add a new option - like ArgumentParser.add_argument.
+
+        The long options will be added to the list of boot args, unless
+        the keyword argument 'bootarg' is set to False.
+
+        Positional arguments that don't start with '-' are considered extra
+        boot args to look for.
+
+        NOTE: conflict_handler is currently ignored for boot args - they will
+        always raise ArgumentError if they conflict.
+        """
+        # TODO: add kwargs to make an option commandline-only or boot-arg-only
+        flags = [a for a in args if a.startswith('-')]
+        bootargs = [a for a in args if not a.startswith('-')]
+        do_bootarg = kwargs.pop("bootarg", True)
+        option = super(AnacondaArgumentParser, self).add_argument(*flags, **kwargs)
+        # make a generator that returns only the long opts without the -- prefix
+        long_opts = (o[2:] for o in option.option_strings if o.startswith("--"))
+        bootargs += (flag for flag in long_opts)
+        if do_bootarg:
+            for b in bootargs:
+                if b in self._boot_arg:
+                    raise ArgumentError(
+                        "conflicting bootopt string: %s" % b, option)
+                else:
+                    self._boot_arg[b] = option
+        return option
+
+    def _get_bootarg_option(self, arg):
+        """
+        Find the correct Option for a given bootarg (if one exists)
+
+        :param string arg: boot option
+
+        :returns: argparse option object or None if no suitable option is found
+        :rtype argparse option or None
+        """
+        if self.bootarg_prefix and arg.startswith(self.bootarg_prefix):
+            prefixed_option = True
+            arg = arg[len(self.bootarg_prefix):]
+        else:
+            prefixed_option = False
+        option = self._boot_arg.get(arg)
+
+        if self.require_prefix and not prefixed_option:
+            return None
+        if option and self.bootarg_prefix and not prefixed_option:
+            self.deprecated_bootargs.append(arg)
+        return option
+
+    def parse_boot_cmdline(self, boot_cmdline, namespace):
+        """
+        Parse the boot cmdline and set appropriate namespace according to
+        the options set by add_argument.
+
+        boot_cmdline can be given as a string (to be parsed by BootArgs), or a
+        dict (or any object with .iteritems()) of {bootarg:value} pairs.
+
+        If boot_cmdline is None, the boot_cmdline data will be whatever BootArgs reads
+        by default (/proc/cmdline, /run/initramfs/etc/cmdline, /etc/cmdline).
+
+        If an option requires a value but the boot arg doesn't provide one,
+        we'll quietly not set anything.
+
+        :param boot_cmdline: the Anaconda boot command line arguments
+        :type boot_cmdline: string, dict or None
+
+        :param namespace: argparse Namespace instance
+        :type namespace: argparse Namespace
+
+        :returns: an argparse Namespace instance
+        :rtype: Namespace
+        """
+        if boot_cmdline is None or type(boot_cmdline) is str:
+            bootargs = BootArgs(boot_cmdline)
+        else:
+            bootargs = boot_cmdline
+        self.deprecated_bootargs = []
+        # go over all options corresponding to current boot cmdline
+        # and do any modifications necessary
+        # NOTE: program cmdline overrides boot cmdline
+        for arg, val in bootargs.iteritems():
+            option = self._get_bootarg_option(arg)
+            if option is None:
+                # this boot option is unknown to Anaconda, skip it
+                continue
+            if getattr(namespace, option.dest) is not None:
+                # if the option is already set on program command line,
+                # we ignore any boot options that might modify it
+                continue
+            if option.nargs != 0 and val is None:
+                # nargs == 0 -> option does not take any values, skip it
+                continue  # TODO: emit a warning or something there?
+            if option.nargs == 0 and option.const is not None:
+                # nargs == 0 & constr == True -> store_true
+                # (we could also check the class, but it begins with an
+                # underscore, so it would be ugly)
+                # special case: "mpath=0" would otherwise set mpath to True
+                if option.const is True and val in ("0", "no", "off"):
+                    setattr(namespace, option.dest, False)
+                # Set all other set_const cases to the const specified
+                else:
+                    setattr(namespace, option.dest, option.const)
+
+                # anaconda considers cases such as noselinux=off to be a negative
+                # concord, which is to say that selinux will be set to False and
+                # we hate you.
+
+                continue
+            setattr(namespace, option.dest, val)
+        return namespace
+
+    # pylint: disable-msg=W0221
+    def parse_args(self, args=None, boot_cmdline=None):
+        """
+        Like ArgumentParser.parse_args(), but also parses the boot cmdline.
+        (see parse_boot_cmdline for details on that process.)
+        Program cmdline arguments will override boot cmdline arguments.
+
+        :param args: program command line arguments
+        :type args: string or None
+
+        :param boot_cmdline: the Anaconda boot command line arguments
+        :type boot_cmdline: string, dict or None
+
+        :returns: an argparse Namespace instance
+        :rtype: Namespace
+        """
+        # parse arguments (if any) and return the resulting namespace
+        namespace = ArgumentParser.parse_args(self, args)
+        # now parse boot options (if any) and modify the namespace accordingly
+        namespace = self.parse_boot_cmdline(boot_cmdline, namespace)
+        # and return the resulting namespace
+        return namespace
+
+def name_path_pairs(image_specs):
+    """Processes and verifies image file specifications. Generates pairs
+       of names and paths.
+
+       :param image_specs: a list of image specifications
+       :type image_specs: list of str
+
+       Each image spec in image_specs has format <path>[:<name>] where
+       <path> is the path to a local file and <name> is an optional
+       name used to identify the disk in UI. <name> may not contain colons
+       or slashes.
+
+       If no name given in specification, synthesizes name from basename
+       of path. Since two distinct paths may have the same basename, handles
+       name collisions by synthesizing a different name for the colliding
+       name.
+
+       Raises an exception if:
+         * A path is empty
+         * A path specifies a non-existant file
+         * A path specifies a directory
+         * Duplicate paths are specified
+         * A name contains a "/"
+    """
+    image_specs = (spec.rsplit(":", 1) for spec in image_specs)
+    path_name_pairs = ((image_spec[0], image_spec[1].strip() if len(image_spec) == 2 else None) for image_spec in image_specs)
+
+    paths_seen = []
+    names_seen = []
+    for (path, name) in path_name_pairs:
+        if path == "":
+            raise ValueError("empty path specified for image file")
+        path = os.path.abspath(path)
+        if not os.path.exists(path):
+            raise ValueError("non-existant path %s specified for image file" % path)
+        if os.path.isdir(path):
+            raise ValueError("directory path %s specified for image file" % path)
+        if path in paths_seen:
+            raise ValueError("path %s specified twice for image file" % path)
+        paths_seen.append(path)
+
+        if name and "/" in name:
+            raise ValueError("improperly formatted image file name %s, includes slashes" % name)
+
+        if not name:
+            name = os.path.splitext(os.path.basename(path))[0]
+
+        if name in names_seen:
+            names = ("%s_%d" % (name, n) for n in itertools.count())
+            name = itertools.dropwhile(lambda n: n in names_seen, names).next()
+        names_seen.append(name)
+
+        yield name, path
+
+class HelpTextParser(object):
+    """Class to parse help text from file and make it available to option
+       parser.
+    """
+
+    def __init__(self, path):
+        """ Initializer
+            :param path: The absolute path to the help text file
+        """
+        if not os.path.isabs(path):
+            raise ValueError("path %s is not an absolute path" % path)
+        self._path = path
+
+        self._help_text = None
+
+    def read(self, lines):
+        """Reads option, help text pairs from a text file.
+
+           Each pair is separated from the next by an empty line.
+           The option comes first, followed by any number of lines of help text.
+
+           :param lines: a sequence of lines of text
+        """
+        if not lines:
+            return
+        expect_option = True
+        option = None
+        text = []
+        for line in (line.strip() for line in lines):
+            if line == "":
+                expect_option = True
+            elif expect_option:
+                if option:
+                    yield option, " ".join(text)
+                option = line
+                text = []
+                expect_option = False
+            else:
+                text.append(line)
+        yield option, " ".join(text)
+
+    def help_text(self, option):
+        """
+        Returns the help text corresponding to the given command-line option.
+        If no help text is available, returns the empty string.
+
+        :param str option: The name of the option
+
+        :rtype: str
+        """
+        if self._help_text is None:
+            self._help_text = {}
+            try:
+                with open(self._path) as lines:
+                    for parsed_option, parsed_text in self.read(lines):
+                        self._help_text[parsed_option] = parsed_text
+            except StandardError:
+                log.error("error reading help text file %s", self._path)
+
+        return self._help_text.get(option, "")
diff --git a/pyanaconda/anaconda_optparse.py b/pyanaconda/anaconda_optparse.py
deleted file mode 100644
index 5296987..0000000
--- a/pyanaconda/anaconda_optparse.py
+++ /dev/null
@@ -1,249 +0,0 @@
-#
-# anaconda_optparse.py: option parsing for anaconda (CLI and boot args)
-#
-# Copyright (C) 2012 Red Hat, Inc.  All rights reserved.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
-#
-# Authors:
-#   Will Woods <wwoods at redhat.com>
-
-import itertools
-import os
-
-from pyanaconda.flags import BootArgs
-from optparse import OptionParser, OptionConflictError
-
-import logging
-log = logging.getLogger("anaconda")
-
-class AnacondaOptionParser(OptionParser):
-    """
-    Subclass of OptionParser that also examines boot arguments.
-
-    If the "bootarg_prefix" keyword argument is set, it's assumed that all
-    bootargs will start with that prefix.
-
-    "require_prefix" is a bool:
-        False: accept the argument with or without the prefix.
-        True: ignore the argument without the prefix. (default)
-    """
-    def __init__(self, *args, **kwargs):
-        self._boot_arg = dict()
-        self.deprecated_bootargs = []
-        self.bootarg_prefix = kwargs.pop("bootarg_prefix","")
-        self.require_prefix = kwargs.pop("require_prefix",True)
-        OptionParser.__init__(self, *args, **kwargs)
-
-    def add_option(self, *args, **kwargs):
-        """
-        Add a new option - like OptionParser.add_option.
-
-        The long options will be added to the list of boot args, unless
-        the keyword argument 'bootarg' is set to False.
-
-        Positional arguments that don't start with '-' are considered extra
-        boot args to look for.
-
-        NOTE: conflict_handler is currently ignored for boot args - they will
-        always raise OptionConflictError if they conflict.
-        """
-        # TODO: add kwargs to make an option commandline-only or boot-arg-only
-        flags = [a for a in args if a.startswith('-')]
-        bootargs = [a for a in args if not a.startswith('-')]
-        do_bootarg = kwargs.pop("bootarg", True)
-        option = OptionParser.add_option(self, *flags, **kwargs)
-        bootargs += (flag[2:] for flag in option._long_opts)
-        if do_bootarg:
-            for b in bootargs:
-                if b in self._boot_arg:
-                    raise OptionConflictError(
-                          "conflicting bootopt string: %s" % b, option)
-                else:
-                    self._boot_arg[b] = option
-        return option
-
-    def _get_bootarg_option(self, arg):
-        """Find the correct Option for a given bootarg (if one exists)"""
-        if self.bootarg_prefix and arg.startswith(self.bootarg_prefix):
-            prefixed_option = True
-            arg = arg[len(self.bootarg_prefix):]
-        else:
-            prefixed_option = False
-        option = self._boot_arg.get(arg)
-
-        if self.require_prefix and not prefixed_option:
-            return None
-        if option and self.bootarg_prefix and not prefixed_option:
-            self.deprecated_bootargs.append(arg)
-        return option
-
-    def parse_boot_cmdline(self, cmdline, values=None):
-        """
-        Parse the boot cmdline and set appropriate values according to
-        the options set by add_option.
-
-        cmdline can be given as a string (to be parsed by BootArgs), or a
-        dict (or any object with .iteritems()) of {bootarg:value} pairs.
-
-        If cmdline is None, the cmdline data will be whatever BootArgs reads
-        by default (/proc/cmdline, /run/initramfs/etc/cmdline, /etc/cmdline).
-
-        If an option requires a value but the boot arg doesn't provide one,
-        we'll quietly not set anything.
-        """
-        if cmdline is None or type(cmdline) is str:
-            bootargs = BootArgs(cmdline)
-        else:
-            bootargs = cmdline
-        self.deprecated_bootargs = []
-        for arg, val in bootargs.iteritems():
-            option = self._get_bootarg_option(arg)
-            if option is None:
-                continue
-            if option.takes_value() and val is None:
-                continue # TODO: emit a warning or something there?
-            if option.action == "store_true" and val in ("0", "no", "off"):
-                # special case: "mpath=0" would otherwise set mpath to True
-                setattr(values, option.dest, False)
-                continue
-            option.process(arg, val, values, self)
-        return values
-
-    # pylint: disable-msg=W0221
-    def parse_args(self, args=None, values=None, cmdline=None):
-        """
-        Like OptionParser.parse_args(), but also parses the boot cmdline.
-        (see parse_boot_cmdline for details on that process.)
-        Commandline arguments will override boot arguments.
-        """
-        if values is None:
-            values = self.get_default_values()
-        v = self.parse_boot_cmdline(cmdline, values)
-        return OptionParser.parse_args(self, args, v)
-
-def name_path_pairs(image_specs):
-    """Processes and verifies image file specifications. Generates pairs
-       of names and paths.
-
-       :param image_specs: a list of image specifications
-       :type image_specs: list of str
-
-       Each image spec in image_specs has format <path>[:<name>] where
-       <path> is the path to a local file and <name> is an optional
-       name used to identify the disk in UI. <name> may not contain colons
-       or slashes.
-
-       If no name given in specification, synthesizes name from basename
-       of path. Since two distinct paths may have the same basename, handles
-       name collisions by synthesizing a different name for the colliding
-       name.
-
-       Raises an exception if:
-         * A path is empty
-         * A path specifies a non-existant file
-         * A path specifies a directory
-         * Duplicate paths are specified
-         * A name contains a "/"
-    """
-    image_specs = (spec.rsplit(":", 1) for spec in image_specs)
-    path_name_pairs = ((image_spec[0], image_spec[1].strip() if len(image_spec) == 2 else None) for image_spec in image_specs)
-
-    paths_seen = []
-    names_seen = []
-    for (path, name) in path_name_pairs:
-        if path == "":
-            raise ValueError("empty path specified for image file")
-        path = os.path.abspath(path)
-        if not os.path.exists(path):
-            raise ValueError("non-existant path %s specified for image file" % path)
-        if os.path.isdir(path):
-            raise ValueError("directory path %s specified for image file" % path)
-        if path in paths_seen:
-            raise ValueError("path %s specified twice for image file" % path)
-        paths_seen.append(path)
-
-        if name and "/" in name:
-            raise ValueError("improperly formatted image file name %s, includes slashes" % name)
-
-        if not name:
-            name = os.path.splitext(os.path.basename(path))[0]
-
-        if name in names_seen:
-            names = ("%s_%d" % (name, n) for n in itertools.count())
-            name = itertools.dropwhile(lambda n: n in names_seen, names).next()
-        names_seen.append(name)
-
-        yield name, path
-
-class HelpTextParser(object):
-    """Class to parse help text from file and make it available to option
-       parser.
-    """
-
-    def __init__(self, path):
-        """ Initializer
-            :param path: The absolute path to the help text file
-        """
-        if not os.path.isabs(path):
-            raise ValueError("path %s is not an absolute path" % path)
-        self._path = path
-
-        self._help_text = None
-
-    def read(self, lines):
-        """Reads option, help text pairs from a text file.
-
-           Each pair is separated from the next by an empty line.
-           The option comes first, followed by any number of lines of help text.
-
-           :param lines: a sequence of lines of text
-        """
-        if not lines:
-            return
-        expect_option = True
-        option = None
-        text = []
-        for line in (line.strip() for line in lines):
-            if line == "":
-                expect_option = True
-            elif expect_option:
-                if option:
-                    yield option, " ".join(text)
-                option = line
-                text = []
-                expect_option = False
-            else:
-                text.append(line)
-        yield option, " ".join(text)
-
-    def help_text(self, option):
-        """
-        Returns the help text corresponding to the given command-line option.
-        If no help text is available, returns the empty string.
-
-        :param str option: The name of the option
-
-        :rtype: str
-        """
-        if self._help_text is None:
-            self._help_text = {}
-            try:
-                with open(self._path) as lines:
-                    for parsed_option, parsed_text in self.read(lines):
-                        self._help_text[parsed_option] = parsed_text
-            except StandardError:
-                log.error("error reading help text file %s", self._path)
-
-        return self._help_text.get(option, "")
-- 
1.9.3



More information about the anaconda-patches mailing list