New version.
[pykickstart/EL-5.git] / pykickstart / base.py
blobe5b54e35ebeb63f43246ab7344ce5df2110d45e9
2 # Chris Lumens <clumens@redhat.com>
4 # Copyright 2006, 2007, 2008 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 Base classes for creating commands and syntax version object.
23 This module exports several important base classes:
25 BaseData - The base abstract class for all data objects. Data objects
26 are contained within a BaseHandler object.
28 BaseHandler - The base abstract class from which versioned kickstart
29 handler are derived. Subclasses of BaseHandler hold
30 BaseData and KickstartCommand objects.
32 DeprecatedCommand - An abstract subclass of KickstartCommand that should
33 be further subclassed by users of this module. When
34 a subclass is used, a warning message will be
35 printed.
37 KickstartCommand - The base abstract class for all kickstart commands.
38 Command objects are contained within a BaseHandler
39 object.
40 """
41 import gettext
42 gettext.textdomain("pykickstart")
43 _ = lambda x: gettext.ldgettext("pykickstart", x)
45 import warnings
46 from pykickstart.errors import *
47 from pykickstart.parser import Packages
48 from pykickstart.version import versionToString
51 ###
52 ### COMMANDS
53 ###
54 class KickstartCommand(object):
55 """The base class for all kickstart commands. This is an abstract class."""
56 def __init__(self, writePriority=0):
57 """Create a new KickstartCommand instance. This method must be
58 provided by all subclasses, but subclasses must call
59 KickstartCommand.__init__ first. Instance attributes:
61 currentCmd -- The name of the command in the input file that
62 caused this handler to be run.
63 currentLine -- The current unprocessed line from the input file
64 that caused this handler to be run.
65 handler -- A reference to the BaseHandler subclass this
66 command is contained withing. This is needed to
67 allow referencing of Data objects.
68 lineno -- The current line number in the input file.
69 writePriority -- An integer specifying when this command should be
70 printed when iterating over all commands' __str__
71 methods. The higher the number, the later this
72 command will be written. All commands with the
73 same priority will be written alphabetically.
74 """
76 # We don't want people using this class by itself.
77 if self.__class__ is KickstartCommand:
78 raise TypeError, "KickstartCommand is an abstract class."
80 self.writePriority = writePriority
82 # These will be set by the dispatcher.
83 self.currentCmd = ""
84 self.currentLine = ""
85 self.handler = None
86 self.lineno = 0
88 def __call__(self, *args, **kwargs):
89 """Set multiple attributes on a subclass of KickstartCommand at once
90 via keyword arguments. Valid attributes are anything specified in
91 a subclass, but unknown attributes will be ignored.
92 """
93 for (key, val) in kwargs.items():
94 if hasattr(self, key):
95 setattr(self, key, val)
97 def __str__(self):
98 """Return a string formatted for output to a kickstart file. This
99 method must be provided by all subclasses.
101 raise TypeError, "__str__() not implemented for KickstartCommand"
103 def parse(self, args):
104 """Parse the list of args and set data on the KickstartCommand object.
105 This method must be provided by all subclasses.
107 raise TypeError, "parse() not implemented for KickstartCommand"
110 def apply(self, instroot="/"):
111 """Write out the configuration related to the KickstartCommand object.
112 Subclasses which do not provide this method will not have their
113 configuration written out.
115 return
117 # Set the contents of the opts object (an instance of optparse.Values
118 # returned by parse_args) as attributes on the KickstartCommand object.
119 # It's useful to call this from KickstartCommand subclasses after parsing
120 # the arguments.
121 def _setToSelf(self, optParser, opts):
122 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
123 setattr(self, key, getattr(opts, key))
125 # Sets the contents of the opts object (an instance of optparse.Values
126 # returned by parse_args) as attributes on the provided object obj. It's
127 # useful to call this from KickstartCommand subclasses that handle lists
128 # of objects (like partitions, network devices, etc.) and need to populate
129 # a Data object.
130 def _setToObj(self, optParser, opts, obj):
131 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
132 setattr(obj, key, getattr(opts, key))
134 class DeprecatedCommand(KickstartCommand):
135 """Specify that a command is deprecated and no longer has any function.
136 Any command that is deprecated should be subclassed from this class,
137 only specifying an __init__ method that calls the superclass's __init__.
138 This is an abstract class.
140 def __init__(self, writePriority=None):
141 # We don't want people using this class by itself.
142 if self.__class__ is KickstartCommand:
143 raise TypeError, "DeprecatedCommand is an abstract class."
145 """Create a new DeprecatedCommand instance."""
146 KickstartCommand.__init__(self, writePriority)
148 def __str__(self):
149 """Placeholder since DeprecatedCommands don't work anymore."""
150 return ""
152 def parse(self, args):
153 """Print a warning message if the command is seen in the input file."""
154 mapping = {"lineno": self.lineno, "cmd": self.currentCmd}
155 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)
159 ### HANDLERS
161 class BaseHandler(object):
162 """Each version of kickstart syntax is provided by a subclass of this
163 class. These subclasses are what users will interact with for parsing,
164 extracting data, and writing out kickstart files. This is an abstract
165 class.
168 """version -- The version this syntax handler supports. This is set by
169 a class attribute of a BaseHandler subclass and is used to
170 set up the command dict. It is for read-only use.
172 version = None
174 def __init__(self, mapping={}):
175 """Create a new BaseHandler instance. This method must be provided by
176 all subclasses, but subclasses must call BaseHandler.__init__ first.
177 mapping is a custom map from command strings to classes, useful when
178 creating your own handler with special command objects. It is
179 otherwise unused and rarely needed.
181 Instance attributes:
183 commands -- A mapping from a string command to a KickstartCommand
184 subclass object that handles it. Multiple strings can
185 map to the same object, but only one instance of the
186 command object should ever exist. Most users should
187 never have to deal with this directly, as it is
188 manipulated internally and called through dispatcher.
189 currentLine -- The current unprocessed line from the input file
190 that caused this handler to be run.
191 packages -- An instance of pykickstart.parser.Packages which
192 describes the packages section of the input file.
193 platform -- A string describing the hardware platform, which is
194 needed only by system-config-kickstart.
195 scripts -- A list of pykickstart.parser.Script instances, which is
196 populated by KickstartParser.addScript and describes the
197 %pre/%post/%traceback script section of the input file.
200 # We don't want people using this class by itself.
201 if self.__class__ is BaseHandler:
202 raise TypeError, "BaseHandler is an abstract class."
204 # This isn't really a good place for these, but it's better than
205 # everything else I can think of.
206 self.scripts = []
207 self.packages = Packages()
208 self.platform = ""
210 # These will be set by the dispatcher.
211 self.commands = {}
212 self.currentLine = 0
214 # A dict keyed by an integer priority number, with each value being a
215 # list of KickstartCommand subclasses. This dict is maintained by
216 # registerCommand and used in __str__. No one else should be touching
217 # it.
218 self._writeOrder = {}
220 self._registerCommands(mapping=mapping)
222 def __str__(self):
223 """Return a string formatted for output to a kickstart file."""
224 retval = ""
226 if self.platform != "":
227 retval += "#platform=%s\n" % self.platform
229 retval += "#version=%s\n" % versionToString(self.version)
231 lst = self._writeOrder.keys()
232 lst.sort()
234 for prio in lst:
235 for obj in self._writeOrder[prio]:
236 retval += obj.__str__()
238 for script in self.scripts:
239 retval += script.__str__()
241 retval += self.packages.__str__()
243 return retval
245 def _insertSorted(self, list, obj):
246 length = len(list)
247 i = 0
249 while i < length:
250 # If the two classes have the same name, it's because we are
251 # overriding an existing class with one from a later kickstart
252 # version, so remove the old one in favor of the new one.
253 if obj.__class__.__name__ > list[i].__class__.__name__:
254 i += 1
255 elif obj.__class__.__name__ == list[i].__class__.__name__:
256 list[i] = obj
257 return
258 elif obj.__class__.__name__ < list[i].__class__.__name__:
259 break
261 if i >= length:
262 list.append(obj)
263 else:
264 list.insert(i, obj)
266 def _setCommand(self, cmdObj):
267 # Add an attribute on this version object. We need this to provide a
268 # way for clients to access the command objects. We also need to strip
269 # off the version part from the front of the name.
270 if cmdObj.__class__.__name__.find("_") != -1:
271 name = unicode(cmdObj.__class__.__name__.split("_", 1)[1])
272 else:
273 name = unicode(cmdObj.__class__.__name__).lower()
275 setattr(self, name.lower(), cmdObj)
277 # Also, add the object into the _writeOrder dict in the right place.
278 if cmdObj.writePriority is not None:
279 if self._writeOrder.has_key(cmdObj.writePriority):
280 self._insertSorted(self._writeOrder[cmdObj.writePriority], cmdObj)
281 else:
282 self._writeOrder[cmdObj.writePriority] = [cmdObj]
284 def _registerCommands(self, mapping={}):
285 if mapping == {}:
286 from pykickstart.handlers.control import commandMap, dataMap
287 cMap = commandMap[self.version]
288 dMap = dataMap[self.version]
289 else:
290 from pykickstart.handlers.control import dataMap
291 cMap = mapping
292 dMap = dataMap[self.version]
294 for (cmdName, cmdClass) in cMap.iteritems():
295 # First make sure we haven't instantiated this command handler
296 # already. If we have, we just need to make another mapping to
297 # it in self.commands.
298 cmdObj = None
300 for (key, val) in self.commands.iteritems():
301 if val.__class__.__name__ == cmdClass.__name__:
302 cmdObj = val
303 break
305 # If we didn't find an instance in self.commands, create one now.
306 if cmdObj == None:
307 cmdObj = cmdClass()
308 self._setCommand(cmdObj)
310 # Finally, add the mapping to the commands dict.
311 self.commands[cmdName] = cmdObj
312 self.commands[cmdName].handler = self
314 # We also need to create attributes for the various data objects.
315 # No checks here because dMap is a bijection. At least, that's what
316 # the comment says. Hope no one screws that up.
317 for (dataName, dataClass) in dMap.iteritems():
318 setattr(self, dataName, dataClass)
320 def dispatcher(self, args, lineno):
321 """Given a split up line of the input file and the current line number,
322 call the appropriate KickstartCommand handler that has been
323 previously registered. lineno is needed for error reporting. If
324 cmd does not exist in the commands dict, KickstartParseError will be
325 raised. A handler of None for the given command is not an error.
327 cmd = args[0]
329 if not self.commands.has_key(cmd):
330 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown command: %s" % cmd))
331 elif self.commands[cmd] != None:
332 self.commands[cmd].currentCmd = cmd
333 self.commands[cmd].currentLine = self.currentLine
334 self.commands[cmd].lineno = lineno
335 self.commands[cmd].parse(args[1:])
337 def maskAllExcept(self, lst):
338 """Set all entries in the commands dict to None, except the ones in
339 the lst. All other commands will not be processed.
341 self._writeOrder = {}
343 for (key, val) in self.commands.iteritems():
344 if not key in lst:
345 self.commands[key] = None
347 def hasCommand(self, cmd):
348 """Return true if there is a handler for the string cmd."""
349 return hasattr(self, cmd)
353 ### DATA
355 class BaseData(object):
356 """The base class for all data objects. This is an abstract class."""
357 def __init__(self):
358 """Create a new BaseData instance. There are no attributes."""
360 # We don't want people using this class by itself.
361 if self.__class__ is BaseData:
362 raise TypeError, "BaseData is an abstract class."
364 def __str__(self):
365 """Return a string formatted for output to a kickstart file."""
366 return ""
368 def __call__(self, *args, **kwargs):
369 """Set multiple attributes on a subclass of BaseData at once via
370 keyword arguments. Valid attributes are anything specified in a
371 subclass, but unknown attributes will be ignored.
373 for (key, val) in kwargs.items():
374 if hasattr(self, key):
375 setattr(self, key, val)