If an option parser object has the same option added twice, allow the later one
[pykickstart.git] / pykickstart / options.py
blobf76bab2d066eeec1e3e218c3f2f9c5a7e898f1a0
2 # Chris Lumens <clumens@redhat.com>
4 # Copyright 2005, 2006, 2007 Red Hat, Inc.
6 # This copyrighted material is made available to anyone wishing to use, modify,
7 # copy, or redistribute it subject to the terms and conditions of the GNU
8 # General Public License v.2. This program is distributed in the hope that it
9 # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10 # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 # See the GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along with
14 # this program; if not, write to the Free Software Foundation, Inc., 51
15 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16 # trademarks that are incorporated in the source code or documentation are not
17 # subject to the GNU General Public License and may only be used or replicated
18 # with the express permission of Red Hat, Inc.
20 """
21 Specialized option handling.
23 This module exports two classes:
25 KSOptionParser - A specialized subclass of OptionParser to be used
26 in BaseHandler subclasses.
28 KSOption - A specialized subclass of Option.
29 """
30 import warnings
31 from copy import copy
32 from optparse import *
34 from constants import *
35 from errors import *
37 from rhpl.translate import _
38 import rhpl.translate as translate
40 translate.textdomain("pykickstart")
42 class KSOptionParser(OptionParser):
43 """A specialized subclass of optparse.OptionParser to handle extra option
44 attribute checking, work error reporting into the KickstartParseError
45 framework, and to turn off the default help.
46 """
47 def exit(self, status=0, msg=None):
48 pass
50 def error(self, msg):
51 if self.lineno != None:
52 raise KickstartParseError, formatErrorMsg(self.lineno, msg=msg)
53 else:
54 raise KickstartParseError, msg
56 def keys(self):
57 retval = []
59 for opt in self.option_list:
60 if opt not in retval:
61 retval.append(opt.dest)
63 return retval
65 def _init_parsing_state (self):
66 OptionParser._init_parsing_state(self)
67 self.option_seen = {}
69 def check_values (self, values, args):
70 def seen(self, option):
71 return self.option_seen.has_key(option)
73 def usedTooNew(self, option):
74 return option.introduced and option.introduced > self.version
76 def usedDeprecated(self, option):
77 return option.deprecated
79 def usedRemoved(self, option):
80 return option.removed and option.removed <= self.version
82 for option in filter(lambda o: isinstance(o, Option), self.option_list):
83 if option.required and not seen(self, option):
84 raise KickstartValueError, formatErrorMsg(self.lineno, _("Option %s is required") % option)
85 elif seen(self, option) and usedTooNew(self, option):
86 mapping = {"option": option, "intro": option.introduced, "version": self.version}
87 self.error(_("The option %(option)s was introduced in version %(intro)s, but you are using kickstart syntax version %(version)s") % mapping)
88 elif seen(self, option) and usedDeprecated(self, option):
89 mapping = {"lineno": self.lineno, "option": option}
90 warnings.warn(_("Ignoring deprecated option on line %(lineno)s: The %(option)s option has been deprecated and no longer has any effect. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to remove this option.") % mapping, DeprecationWarning)
91 elif seen(self, option) and usedRemoved(self, option):
92 mapping = {"option": option, "removed": option.removed, "version": self.version}
93 self.error(_("The option %(option)s was removed in version %(removed)s, but you are using kickstart syntax version %(version)s") % mapping)
95 return (values, args)
97 def __init__(self, map={}, lineno=None, version=None):
98 """Create a new KSOptionParser instance. Each KickstartCommand
99 subclass should create one instance of KSOptionParser, providing
100 at least the lineno attribute. map and version are not required.
101 Instance attributes:
103 map -- A mapping from option strings to different values.
104 lineno -- The line number of the line this KSOptionParser instance
105 is processing.
106 version -- The version of the kickstart syntax we are checking
107 against.
109 OptionParser.__init__(self, option_class=KSOption,
110 add_help_option=False,
111 conflict_handler="resolve")
112 self.map = map
113 self.lineno = lineno
114 self.version = version
116 # Creates a new Option class that supports several new attributes:
117 # - required: any option with this attribute must be supplied or an exception
118 # is thrown
119 # - introduced: the kickstart syntax version that this option first appeared
120 # in - an exception will be raised if the option is used and
121 # the specified syntax version is less than the value of this
122 # attribute
123 # - deprecated: the kickstart syntax version that this option was deprecated
124 # in - a DeprecationWarning will be thrown if the option is
125 # used and the specified syntax version is greater than the
126 # value of this attribute
127 # - removed: the kickstart syntax version that this option was removed in - an
128 # exception will be raised if the option is used and the specified
129 # syntax version is greated than the value of this attribute
130 # Also creates a new type:
131 # - ksboolean: support various kinds of boolean values on an option
132 # And two new actions:
133 # - map : allows you to define an opt -> val mapping such that dest gets val
134 # when opt is seen
135 # - map_extend: allows you to define an opt -> [val1, ... valn] mapping such
136 # that dest gets a list of vals built up when opt is seen
137 class KSOption (Option):
138 ATTRS = Option.ATTRS + ['introduced', 'deprecated', 'removed', 'required']
139 ACTIONS = Option.ACTIONS + ("map", "map_extend",)
140 STORE_ACTIONS = Option.STORE_ACTIONS + ("map", "map_extend",)
142 TYPES = Option.TYPES + ("ksboolean",)
143 TYPE_CHECKER = copy(Option.TYPE_CHECKER)
145 def _check_required(self):
146 if self.required and not self.takes_value():
147 raise OptionError(_("Required flag set for option that doesn't take a value"), self)
149 def _check_ksboolean(option, opt, value):
150 if value.lower() in ("on", "yes", "true", "1"):
151 return True
152 elif value.lower() in ("off", "no", "false", "0"):
153 return False
154 else:
155 mapping = {"opt": opt, "value": value}
156 raise OptionValueError(_("Option %(opt)s: invalid boolean value: %(value)r") % mapping)
158 def _check_string(option, opt, value):
159 if len(value) > 2 and value.startswith("--"):
160 mapping = {"opt": opt, "value": value}
161 raise OptionValueError(_("Option %(opt)s: invalid string value: %(value)r") % mapping)
162 else:
163 return value
165 # Make sure _check_required() is called from the constructor!
166 CHECK_METHODS = Option.CHECK_METHODS + [_check_required]
167 TYPE_CHECKER.update({"ksboolean": _check_ksboolean, "string": _check_string})
169 def process (self, opt, value, values, parser):
170 Option.process(self, opt, value, values, parser)
171 parser.option_seen[self] = 1
173 # Override default take_action method to handle our custom actions.
174 def take_action(self, action, dest, opt, value, values, parser):
175 if action == "map":
176 values.ensure_value(dest, parser.map[opt.lstrip('-')])
177 elif action == "map_extend":
178 values.ensure_value(dest, []).extend(parser.map[opt.lstrip('-')])
179 else:
180 Option.take_action(self, action, dest, opt, value, values, parser)