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.
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
30 KickstartCommand - The base abstract class for all kickstart commands.
31 Command objects are contained within a BaseHandler
34 from rhpl
.translate
import _
35 import rhpl
.translate
as translate
37 translate
.textdomain("pykickstart")
40 from pykickstart
.errors
import *
41 from pykickstart
.parser
import Packages
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.
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.
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.
83 for (key
, val
) in kwargs
.items():
84 if hasattr(self
, key
):
85 setattr(self
, key
, val
)
88 """Return a string formatted for output to a kickstart file. This
89 method must be provided by all subclasses.
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.
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
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
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
)
131 """Placeholder since DeprecatedCommands don't work anymore."""
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]
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)
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
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.
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.
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.
192 self
.packages
= Packages()
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
201 self
._writeOrder
= {}
203 self
._registerCommands
(mapping
=mapping
)
206 """Return a string formatted for output to a kickstart file."""
209 if self
.platform
!= "":
210 retval
+= "#platform=%s\n" % self
.platform
212 lst
= self
._writeOrder
.keys()
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
__()
226 def _insertSorted(self
, list, obj
):
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
__:
236 elif obj
.__class
__.__name
__ == list[i
].__class
__.__name
__:
239 elif obj
.__class
__.__name
__ < list[i
].__class
__.__name
__:
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])
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
)
263 self
._writeOrder
[cmdObj
.writePriority
] = [cmdObj
]
265 def _registerCommands(self
, mapping
={}):
267 from pykickstart
.handlers
.control
import commandMap
, dataMap
268 cMap
= commandMap
[self
.version
]
269 dMap
= dataMap
[self
.version
]
271 from pykickstart
.handlers
.control
import dataMap
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.
281 for (key
, val
) in self
.commands
.iteritems():
282 if val
.__class
__.__name
__ == cmdClass
.__name
__:
286 # If we didn't find an instance in self.commands, create one now.
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.
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():
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
)
335 """The base class for all data objects. This is an abstract class."""
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."
344 """Return a string formatted for output to a kickstart file."""
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
)