Add a version attribute so we no longer have to compare class names.
[pykickstart.git] / pykickstart / commands / base.py
blob43244713244ed986a38ebb8b6430981d4fd0ee09
2 # Chris Lumens <clumens@redhat.com>
4 # Copyright 2006 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.
142 def __init__(self):
143 """Create a new BaseHandler instance. This method must be provided by
144 all subclasses, but subclasses must call BaseHandler.__init__ first.
145 Instance attributes:
147 commands -- A mapping from a string command to a KickstartCommand
148 subclass object that handles it. Multiple strings can
149 map to the same object, but only one instance of the
150 handler object should ever exist. Most users should
151 never have to deal with this directly, as it is
152 manipulated through registerCommand and dispatcher.
153 packages -- An instance of pykickstart.parser.Packages which
154 describes the packages section of the input file.
155 platform -- A string describing the hardware platform, which is
156 needed only by system-config-kickstart.
157 scripts -- A list of pykickstart.parser.Script instances, which is
158 populated by KickstartParser.addScript and describes the
159 %pre/%post/%traceback script section of the input file.
160 version -- The version this syntax handler supports, which saves
161 having to compare things like class names. This is
162 one of the constants from version.py and is for
163 read-only use.
166 # We don't want people using this class by itself.
167 if self.__class__ is BaseHandler:
168 raise TypeError, "BaseHandler is an abstract class."
170 self.commands = {}
171 self.version = None
173 # This isn't really a good place for these, but it's better than
174 # everything else I can think of.
175 self.scripts = []
176 self.packages = Packages()
177 self.platform = ""
179 # A dict keyed by an integer priority number, with each value being a
180 # list of KickstartCommand subclasses. This dict is maintained by
181 # registerCommand and used in __str__. No one else should be touching
182 # it.
183 self._writeOrder = {}
185 def __str__(self):
186 """Return a string formatted for output to a kickstart file."""
187 retval = ""
189 if self.platform != "":
190 retval += "#platform=%s\n" % self.platform
192 lst = self._writeOrder.keys()
193 lst.sort()
195 for prio in lst:
196 for obj in self._writeOrder[prio]:
197 retval += obj.__str__()
199 for script in self.scripts:
200 retval += script.__str__()
202 retval += self.packages.__str__()
204 return retval
206 def _insertSorted(self, list, obj):
207 max = len(list)
208 i = 0
210 while i < max:
211 # If the two classes have the same name, it's because we are
212 # overriding an existing class with one from a later kickstart
213 # version, so remove the old one in favor of the new one.
214 if obj.__class__.__name__ > list[i].__class__.__name__:
215 i += 1
216 elif obj.__class__.__name__ == list[i].__class__.__name__:
217 list[i] = obj
218 return
219 elif obj.__class__.__name__ < list[i].__class__.__name__:
220 break
222 if i >= max:
223 list.append(obj)
224 else:
225 list.insert(i, obj)
227 def _setCommand(self, cmdObj):
228 # Add an attribute on this version object. We need this to provide a
229 # way for clients to access the command objects.
230 setattr(self, cmdObj.__class__.__name__.lower(), cmdObj)
232 # Also, add the object into the _writeOrder dict in the right place.
233 if cmdObj.writePriority is not None:
234 if self._writeOrder.has_key(cmdObj.writePriority):
235 self._insertSorted(self._writeOrder[cmdObj.writePriority], cmdObj)
236 else:
237 self._writeOrder[cmdObj.writePriority] = [cmdObj]
239 def dispatcher(self, cmd, cmdArgs, lineno):
240 """Given the command string cmd and the list of arguments cmdArgs, call
241 the appropriate KickstartCommand handler that has been previously
242 registered. lineno is needed for error reporting. If cmd does not
243 exist in the commands dict, KickstartParseError will be raised. A
244 handler of None for the given command is not an error.
246 if not self.commands.has_key(cmd):
247 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown command: %s" % cmd))
248 else:
249 if self.commands[cmd] != None:
250 self.commands[cmd].currentCmd = cmd
251 self.commands[cmd].handler = self
252 self.commands[cmd].lineno = lineno
253 self.commands[cmd].parse(cmdArgs)
255 def empty(self):
256 """Set all entries in the commands dict to None so no commands
257 will be processed.
259 self._writeOrder = {}
261 for (key, val) in self.commands.iteritems():
262 self.commands[key] = None
264 def hasCommand(self, cmd):
265 """Return true if there is a handler for the string cmd."""
266 return hasattr(self, cmd)
268 def overrideCommand(self, cmdObj):
269 """Override an existing mapping with a new instance of a command
270 handler class. There must already be a mapping from at least one
271 string command to a command object as set up by registerCommand,
272 as this method looks for instances with the same class name as
273 cmdObj to replace.
275 found = False
277 # We have to update all the entries in the commands dict with a
278 # reference to the new handler.
279 for (key, val) in self.commands.iteritems():
280 if val.__class__.__name__ == cmdObj.__class__.__name__:
281 found = True
282 self.commands[key] = cmdObj
284 if found:
285 self._setCommand(cmdObj)
287 def registerCommand(self, cmdObj, cmdList):
288 """Set up a mapping from each string command in cmdList to the instance
289 of the KickstartCommand subclass object cmdObj. Using a list of
290 commands allows for aliasing commands to each other. Also create a
291 new attribute on this BaseHandler subclass named
292 cmdObj.__class__.__name__ with a value of cmdObj.
294 # Add the new command object to the dict for all given command strings.
295 for str in cmdList:
296 self.commands[str] = cmdObj
298 self._setCommand(cmdObj)
300 def unregisterCommand(self, cmdObj):
301 """Remove support for a command from this handler instance. The
302 parameter cmdObj is a command handler class (not an instance) that
303 should be removed. This removes support for all string commands
304 that map to cmdObj, as well as support for writing it and calling
305 it on the handler. The most common use for this method is to
306 remove commands that were deprecated in a previous version.
308 # First remove any keys in the commands dict that map to an instance
309 # of the given command object.
310 for (key, val) in self.commands.items():
311 if val.__class__.__name__ == cmdObj.__name__:
312 self.commands.pop(key)
314 # Then remove any keys in the _writeOrder dict that do the same. To
315 # do this we have to search each list in _writeOrder and remove any
316 # with a matching name. Then if that was the only command in that
317 # list, we should remove the key entirely.
318 for (key, val) in self._writeOrder.items():
319 for ele in val:
320 if ele.__class__.__name__ == cmdObj.__name__:
321 val.remove(ele)
322 break
324 if len(val) == 0:
325 del(self._writeOrder[key])
327 # Finally, remove the attribute on the handler object itself.
328 delattr(self, cmdObj.__name__.lower())
331 ### DATA
333 class BaseData:
334 """The base class for all data objects. This is an abstract class."""
335 def __init__(self):
336 """Create a new BaseData instance. There are no attributes."""
338 # We don't want people using this class by itself.
339 if self.__class__ is BaseData:
340 raise TypeError, "BaseData is an abstract class."
342 def __str__(self):
343 """Return a string formatted for output to a kickstart file."""
344 return ""
346 def __call__(self, *args, **kwargs):
347 """Set multiple attributes on a subclass of BaseData at once via
348 keyword arguments. Valid attributes are anything specified in a
349 subclass, but unknown attributes will be ignored.
351 for (key, val) in kwargs.items():
352 if hasattr(self, key):
353 setattr(self, key, val)