[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