Fix harddrive install method error checking (#242492, patch from Andrew Edmunds
[pykickstart.git] / pykickstart / base.py
blobda3c81d8e94f055d94150ca726933e5cabc55c14
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 - An abstract 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
44 ###
45 ### COMMANDS
46 ###
47 class KickstartCommand:
48 """The base class for all kickstart commands. This is an abstract class."""
49 def __init__(self, writePriority=0):
50 """Create a new KickstartCommand instance. This method must be
51 provided by all subclasses, but subclasses must call
52 KickstartCommand.__init__ first. Instance attributes:
54 currentLine -- The current unprocessed line from the input file
55 that caused this handler to be run.
56 handler -- A reference to the BaseHandler subclass this
57 command is contained withing. This is needed to
58 allow referencing of Data objects.
59 lineno -- The current line number in the input file.
60 writePriority -- An integer specifying when this command should be
61 printed when iterating over all commands' __str__
62 methods. The higher the number, the later this
63 command will be written. All commands with the
64 same priority will be written alphabetically.
65 """
67 # We don't want people using this class by itself.
68 if self.__class__ is KickstartCommand:
69 raise TypeError, "KickstartCommand is an abstract class."
71 self.writePriority = writePriority
73 # These will be set by the dispatcher.
74 self.currentLine = ""
75 self.handler = None
76 self.lineno = 0
78 def __call__(self, *args, **kwargs):
79 """Set multiple attributes on a subclass of KickstartCommand at once
80 via keyword arguments. Valid attributes are anything specified in
81 a subclass, but unknown attributes will be ignored.
82 """
83 for (key, val) in kwargs.items():
84 if hasattr(self, key):
85 setattr(self, key, val)
87 def __str__(self):
88 """Return a string formatted for output to a kickstart file. This
89 method must be provided by all subclasses.
90 """
91 raise TypeError, "__str__() not implemented for KickstartCommand"
93 def parse(self, args):
94 """Parse the list of args and set data on the KickstartCommand object.
95 This method must be provided by all subclasses.
96 """
97 raise TypeError, "parse() not implemented for KickstartCommand"
99 # Set the contents of the opts object (an instance of optparse.Values
100 # returned by parse_args) as attributes on the KickstartCommand object.
101 # It's useful to call this from KickstartCommand subclasses after parsing
102 # the arguments.
103 def _setToSelf(self, optParser, opts):
104 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
105 setattr(self, key, getattr(opts, key))
107 # Sets the contents of the opts object (an instance of optparse.Values
108 # returned by parse_args) as attributes on the provided object obj. It's
109 # useful to call this from KickstartCommand subclasses that handle lists
110 # of objects (like partitions, network devices, etc.) and need to populate
111 # a Data object.
112 def _setToObj(self, optParser, opts, obj):
113 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
114 setattr(obj, key, getattr(opts, key))
116 class DeprecatedCommand(KickstartCommand):
117 """Specify that a command is deprecated and no longer has any function.
118 Any command that is deprecated should be subclassed from this class,
119 only specifying an __init__ method that calls the superclass's __init__.
120 This is an abstract class.
122 def __init__(self, writePriority=None):
123 # We don't want people using this class by itself.
124 if self.__class__ is KickstartCommand:
125 raise TypeError, "DeprecatedCommand is an abstract class."
127 """Create a new DeprecatedCommand instance."""
128 KickstartCommand.__init__(self, writePriority)
130 def __str__(self):
131 """Placeholder since DeprecatedCommands don't work anymore."""
132 return ""
134 def parse(self, args):
135 """Print a warning message if the command is seen in the input file."""
136 if self.currentLine.strip() != "":
137 cmd = self.currentLine.split()[0]
138 else:
139 cmd = ""
141 mapping = {"lineno": self.lineno, "cmd": cmd}
142 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)
146 ### HANDLERS
148 class BaseHandler:
149 """Each version of kickstart syntax is provided by a subclass of this
150 class. These subclasses are what users will interact with for parsing,
151 extracting data, and writing out kickstart files. This is an abstract
152 class.
155 """version -- The version this syntax handler supports. This is set by
156 a class attribute of a BaseHandler subclass and is used to
157 set up the command dict. It is for read-only use.
159 version = None
161 def __init__(self, mapping={}):
162 """Create a new BaseHandler instance. This method must be provided by
163 all subclasses, but subclasses must call BaseHandler.__init__ first.
164 mapping is a custom map from command strings to classes, useful when
165 creating your own handler with special command objects. It is
166 otherwise unused and rarely needed.
168 Instance attributes:
170 commands -- A mapping from a string command to a KickstartCommand
171 subclass object that handles it. Multiple strings can
172 map to the same object, but only one instance of the
173 command object should ever exist. Most users should
174 never have to deal with this directly, as it is
175 manipulated internally and called through dispatcher.
176 packages -- An instance of pykickstart.parser.Packages which
177 describes the packages section of the input file.
178 platform -- A string describing the hardware platform, which is
179 needed only by system-config-kickstart.
180 scripts -- A list of pykickstart.parser.Script instances, which is
181 populated by KickstartParser.addScript and describes the
182 %pre/%post/%traceback script section of the input file.
185 # We don't want people using this class by itself.
186 if self.__class__ is BaseHandler:
187 raise TypeError, "BaseHandler is an abstract class."
189 # This isn't really a good place for these, but it's better than
190 # everything else I can think of.
191 self.scripts = []
192 self.packages = Packages()
193 self.platform = ""
195 self.commands = {}
197 # A dict keyed by an integer priority number, with each value being a
198 # list of KickstartCommand subclasses. This dict is maintained by
199 # registerCommand and used in __str__. No one else should be touching
200 # it.
201 self._writeOrder = {}
203 self._registerCommands(mapping=mapping)
205 def __str__(self):
206 """Return a string formatted for output to a kickstart file."""
207 retval = ""
209 if self.platform != "":
210 retval += "#platform=%s\n" % self.platform
212 lst = self._writeOrder.keys()
213 lst.sort()
215 for prio in lst:
216 for obj in self._writeOrder[prio]:
217 retval += obj.__str__()
219 for script in self.scripts:
220 retval += script.__str__()
222 retval += self.packages.__str__()
224 return retval
226 def _insertSorted(self, list, obj):
227 max = len(list)
228 i = 0
230 while i < max:
231 # If the two classes have the same name, it's because we are
232 # overriding an existing class with one from a later kickstart
233 # version, so remove the old one in favor of the new one.
234 if obj.__class__.__name__ > list[i].__class__.__name__:
235 i += 1
236 elif obj.__class__.__name__ == list[i].__class__.__name__:
237 list[i] = obj
238 return
239 elif obj.__class__.__name__ < list[i].__class__.__name__:
240 break
242 if i >= max:
243 list.append(obj)
244 else:
245 list.insert(i, obj)
247 def _setCommand(self, cmdObj):
248 # Add an attribute on this version object. We need this to provide a
249 # way for clients to access the command objects. We also need to strip
250 # off the version part from the front of the name.
251 if cmdObj.__class__.__name__.find("_") != -1:
252 name = unicode(cmdObj.__class__.__name__.split("_", 1)[1])
253 else:
254 name = unicode(cmdObj.__class__.__name__).lower()
256 setattr(self, name.lower(), cmdObj)
258 # Also, add the object into the _writeOrder dict in the right place.
259 if cmdObj.writePriority is not None:
260 if self._writeOrder.has_key(cmdObj.writePriority):
261 self._insertSorted(self._writeOrder[cmdObj.writePriority], cmdObj)
262 else:
263 self._writeOrder[cmdObj.writePriority] = [cmdObj]
265 def _registerCommands(self, mapping={}):
266 if mapping == {}:
267 from pykickstart.handlers.control import commandMap, dataMap
268 cMap = commandMap[self.version]
269 dMap = dataMap[self.version]
270 else:
271 from pykickstart.handlers.control import dataMap
272 cMap = mapping
273 dMap = dataMap[self.version]
275 for (cmdName, cmdClass) in cMap.iteritems():
276 # First make sure we haven't instantiated this command handler
277 # already. If we have, we just need to make another mapping to
278 # it in self.commands.
279 cmdObj = None
281 for (key, val) in self.commands.iteritems():
282 if val.__class__.__name__ == cmdClass.__name__:
283 cmdObj = val
284 break
286 # If we didn't find an instance in self.commands, create one now.
287 if cmdObj == None:
288 cmdObj = cmdClass()
289 self._setCommand(cmdObj)
291 # Finally, add the mapping to the commands dict.
292 self.commands[cmdName] = cmdObj
294 # We also need to create attributes for the various data objects.
295 # No checks here because dMap is a bijection. At least, that's what
296 # the comment says. Hope no one screws that up.
297 for (dataName, dataClass) in dMap.iteritems():
298 setattr(self, dataName, dataClass)
300 def dispatcher(self, args, lineno):
301 """Given a split up line of the input file and the current line number,
302 call the appropriate KickstartCommand handler that has been
303 previously registered. lineno is needed for error reporting. If
304 cmd does not exist in the commands dict, KickstartParseError will be
305 raised. A handler of None for the given command is not an error.
307 cmd = args[0]
309 if not self.commands.has_key(cmd):
310 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown command: %s" % cmd))
311 elif self.commands[cmd] != None:
312 self.commands[cmd].handler = self
313 self.commands[cmd].lineno = lineno
314 self.commands[cmd].parse(args[1:])
316 def maskAllExcept(self, lst):
317 """Set all entries in the commands dict to None, except the ones in
318 the lst. All other commands will not be processed.
320 self._writeOrder = {}
322 for (key, val) in self.commands.iteritems():
323 if not key in lst:
324 self.commands[key] = None
326 def hasCommand(self, cmd):
327 """Return true if there is a handler for the string cmd."""
328 return hasattr(self, cmd)
332 ### DATA
334 class BaseData:
335 """The base class for all data objects. This is an abstract class."""
336 def __init__(self):
337 """Create a new BaseData instance. There are no attributes."""
339 # We don't want people using this class by itself.
340 if self.__class__ is BaseData:
341 raise TypeError, "BaseData is an abstract class."
343 def __str__(self):
344 """Return a string formatted for output to a kickstart file."""
345 return ""
347 def __call__(self, *args, **kwargs):
348 """Set multiple attributes on a subclass of BaseData at once via
349 keyword arguments. Valid attributes are anything specified in a
350 subclass, but unknown attributes will be ignored.
352 for (key, val) in kwargs.items():
353 if hasattr(self, key):
354 setattr(self, key, val)