Allow passing a command mapping to BaseHandler.__init__ for overriding
[pykickstart.git] / pykickstart / base.py
blobd85e6ab3c59efc554eb760f2df09f7f6e7279b54
2 # Chris Lumens <clumens@redhat.com>
4 # Copyright 2006, 2007 Red Hat, Inc.
6 # This software may be freely redistributed under the terms of the GNU
7 # general public license.
9 # You should have received a copy of the GNU General Public License
10 # along with this program; if not, write to the Free Software
11 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
13 """
14 Base classes for creating commands and syntax version object.
16 This module exports several important base classes:
18 BaseData - The base abstract class for all data objects. Data objects
19 are contained within a BaseHandler object.
21 BaseHandler - The base abstract class from which versioned kickstart
22 handler are derived. Subclasses of BaseHandler hold
23 BaseData and KickstartCommand objects.
25 DeprecatedCommand - A concrete subclass of KickstartCommand that should
26 be further subclassed by users of this module. When
27 a subclass is used, a warning message will be
28 printed.
30 KickstartCommand - The base abstract class for all kickstart commands.
31 Command objects are contained within a BaseHandler
32 object.
33 """
34 from rhpl.translate import _
35 import rhpl.translate as translate
37 translate.textdomain("pykickstart")
39 import warnings
40 from pykickstart.errors import *
41 from pykickstart.parser import Packages
43 ###
44 ### COMMANDS
45 ###
46 class KickstartCommand:
47 """The base class for all kickstart commands. This is an abstract class."""
48 def __init__(self, writePriority=0):
49 """Create a new KickstartCommand instance. This method must be
50 provided by all subclasses, but subclasses must call
51 KickstartCommand.__init__ first. Instance attributes:
53 currentCmd -- The name of the command in the input file that
54 caused this handler to be run.
55 handler -- A reference to the BaseHandler subclass this
56 command is contained withing. This is needed to
57 allow referencing of Data objects.
58 lineno -- The current line number in the input file.
59 writePriority -- An integer specifying when this command should be
60 printed when iterating over all commands' __str__
61 methods. The higher the number, the later this
62 command will be written. All commands with the
63 same priority will be written alphabetically.
64 """
66 # We don't want people using this class by itself.
67 if self.__class__ is KickstartCommand:
68 raise TypeError, "KickstartCommand is an abstract class."
70 self.writePriority = writePriority
72 # These will be set by the dispatcher.
73 self.currentCmd = ""
74 self.handler = None
75 self.lineno = 0
77 def __call__(self, *args, **kwargs):
78 """Set multiple attributes on a subclass of KickstartCommand at once
79 via keyword arguments. Valid attributes are anything specified in
80 a subclass, but unknown attributes will be ignored.
81 """
82 for (key, val) in kwargs.items():
83 if hasattr(self, key):
84 setattr(self, key, val)
86 def __str__(self):
87 """Return a string formatted for output to a kickstart file. This
88 method must be provided by all subclasses.
89 """
90 raise TypeError, "__str__() not implemented for KickstartCommand"
92 def parse(self, args):
93 """Parse the list of args and set data on the KickstartCommand object.
94 This method must be provided by all subclasses.
95 """
96 raise TypeError, "parse() not implemented for KickstartCommand"
98 # Set the contents of the opts object (an instance of optparse.Values
99 # returned by parse_args) as attributes on the KickstartCommand object.
100 # It's useful to call this from KickstartCommand subclasses after parsing
101 # the arguments.
102 def _setToSelf(self, optParser, opts):
103 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
104 setattr(self, key, getattr(opts, key))
106 # Sets the contents of the opts object (an instance of optparse.Values
107 # returned by parse_args) as attributes on the provided object obj. It's
108 # useful to call this from KickstartCommand subclasses that handle lists
109 # of objects (like partitions, network devices, etc.) and need to populate
110 # a Data object.
111 def _setToObj(self, optParser, opts, obj):
112 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
113 setattr(obj, key, getattr(opts, key))
115 class DeprecatedCommand(KickstartCommand):
116 """Specify that a command is deprecated and no longer has any function.
117 Any command that is deprecated should be subclassed from this class,
118 only specifying an __init__ method that calls the superclass's __init__.
120 def __init__(self, writePriority=None):
121 """Create a new DeprecatedCommand instance."""
122 KickstartCommand.__init__(self, writePriority)
124 def __str__(self):
125 """Placeholder since DeprecatedCommands don't work anymore."""
126 return ""
128 def parse(self, args):
129 """Print a warning message if the command is seen in the input file."""
130 mapping = {"lineno": self.lineno, "cmd": self.currentCmd}
131 warnings.warn(_("Ignoring deprecated command on line %(lineno)s: The %(cmd)s command 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 command.") % mapping, DeprecationWarning)
134 ### HANDLERS
136 class BaseHandler:
137 """Each version of kickstart syntax is provided by a subclass of this
138 class. These subclasses are what users will interact with for parsing,
139 extracting data, and writing out kickstart files. This is an abstract
140 class.
143 """version -- The version this syntax handler supports. This is set by
144 a class attribute of a BaseHandler subclass and is used to
145 set up the command dict. It is for read-only use.
147 version = None
149 def __init__(self, mapping={}):
150 """Create a new BaseHandler instance. This method must be provided by
151 all subclasses, but subclasses must call BaseHandler.__init__ first.
152 mapping is a custom map from command strings to classes, useful when
153 creating your own handler with special command objects. It is
154 otherwise unused and rarely needed.
156 Instance attributes:
158 commands -- A mapping from a string command to a KickstartCommand
159 subclass object that handles it. Multiple strings can
160 map to the same object, but only one instance of the
161 command object should ever exist. Most users should
162 never have to deal with this directly, as it is
163 manipulated internally and called through dispatcher.
164 packages -- An instance of pykickstart.parser.Packages which
165 describes the packages section of the input file.
166 platform -- A string describing the hardware platform, which is
167 needed only by system-config-kickstart.
168 scripts -- A list of pykickstart.parser.Script instances, which is
169 populated by KickstartParser.addScript and describes the
170 %pre/%post/%traceback script section of the input file.
173 # We don't want people using this class by itself.
174 if self.__class__ is BaseHandler:
175 raise TypeError, "BaseHandler is an abstract class."
177 # This isn't really a good place for these, but it's better than
178 # everything else I can think of.
179 self.scripts = []
180 self.packages = Packages()
181 self.platform = ""
183 self.commands = {}
185 # A dict keyed by an integer priority number, with each value being a
186 # list of KickstartCommand subclasses. This dict is maintained by
187 # registerCommand and used in __str__. No one else should be touching
188 # it.
189 self._writeOrder = {}
191 self._registerCommands(mapping=mapping)
193 def __str__(self):
194 """Return a string formatted for output to a kickstart file."""
195 retval = ""
197 if self.platform != "":
198 retval += "#platform=%s\n" % self.platform
200 lst = self._writeOrder.keys()
201 lst.sort()
203 for prio in lst:
204 for obj in self._writeOrder[prio]:
205 retval += obj.__str__()
207 for script in self.scripts:
208 retval += script.__str__()
210 retval += self.packages.__str__()
212 return retval
214 def _insertSorted(self, list, obj):
215 max = len(list)
216 i = 0
218 while i < max:
219 # If the two classes have the same name, it's because we are
220 # overriding an existing class with one from a later kickstart
221 # version, so remove the old one in favor of the new one.
222 if obj.__class__.__name__ > list[i].__class__.__name__:
223 i += 1
224 elif obj.__class__.__name__ == list[i].__class__.__name__:
225 list[i] = obj
226 return
227 elif obj.__class__.__name__ < list[i].__class__.__name__:
228 break
230 if i >= max:
231 list.append(obj)
232 else:
233 list.insert(i, obj)
235 def _setCommand(self, cmdObj):
236 # Add an attribute on this version object. We need this to provide a
237 # way for clients to access the command objects. We also need to strip
238 # off the version part from the front of the name.
239 if cmdObj.__class__.__name__.find("_") != -1:
240 name = cmdObj.__class__.__name__.split("_", 1)[1]
241 else:
242 name = cmdObj.__class__.__name__.lower()
244 setattr(self, name.lower(), cmdObj)
246 # Also, add the object into the _writeOrder dict in the right place.
247 if cmdObj.writePriority is not None:
248 if self._writeOrder.has_key(cmdObj.writePriority):
249 self._insertSorted(self._writeOrder[cmdObj.writePriority], cmdObj)
250 else:
251 self._writeOrder[cmdObj.writePriority] = [cmdObj]
253 def _registerCommands(self, mapping={}):
254 if mapping == {}:
255 from pykickstart.handlers.control import commandMap
256 mapping = commandMap[self.version]
258 for (cmdName, cmdClass) in mapping.iteritems():
259 # First make sure we haven't instantiated this command handler
260 # already. If we have, we just need to make another mapping to
261 # it in self.commands.
262 cmdObj = None
264 for (key, val) in self.commands.iteritems():
265 if val.__class__.__name__ == cmdClass.__name__:
266 cmdObj = val
267 break
269 # If we didn't find an instance in self.commands, create one now.
270 if cmdObj == None:
271 cmdObj = cmdClass()
272 self._setCommand(cmdObj)
274 # Finally, add the mapping to the commands dict.
275 self.commands[cmdName] = cmdObj
277 def dispatcher(self, cmd, cmdArgs, lineno):
278 """Given the command string cmd and the list of arguments cmdArgs, call
279 the appropriate KickstartCommand handler that has been previously
280 registered. lineno is needed for error reporting. If cmd does not
281 exist in the commands dict, KickstartParseError will be raised. A
282 handler of None for the given command is not an error.
284 if not self.commands.has_key(cmd):
285 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown command: %s" % cmd))
286 else:
287 if self.commands[cmd] != None:
288 self.commands[cmd].currentCmd = cmd
289 self.commands[cmd].handler = self
290 self.commands[cmd].lineno = lineno
291 self.commands[cmd].parse(cmdArgs)
293 def maskAllExcept(self, lst):
294 """Set all entries in the commands dict to None, except the ones in
295 the lst. All other commands will not be processed.
297 self._writeOrder = {}
299 for (key, val) in self.commands.iteritems():
300 if not key in lst:
301 self.commands[key] = None
303 def hasCommand(self, cmd):
304 """Return true if there is a handler for the string cmd."""
305 return hasattr(self, cmd)
308 ### DATA
310 class BaseData:
311 """The base class for all data objects. This is an abstract class."""
312 def __init__(self):
313 """Create a new BaseData instance. There are no attributes."""
315 # We don't want people using this class by itself.
316 if self.__class__ is BaseData:
317 raise TypeError, "BaseData is an abstract class."
319 def __str__(self):
320 """Return a string formatted for output to a kickstart file."""
321 return ""
323 def __call__(self, *args, **kwargs):
324 """Set multiple attributes on a subclass of BaseData at once via
325 keyword arguments. Valid attributes are anything specified in a
326 subclass, but unknown attributes will be ignored.
328 for (key, val) in kwargs.items():
329 if hasattr(self, key):
330 setattr(self, key, val)