Added gnuplot backend.
[smonitor.git] / monitor / extra / Gnuplot / termdefs.py
blob7b8518bb0bcf35ace220044d81b21a3ccf9bbdb5
1 # $Id: termdefs.py 302 2008-01-14 22:15:19Z bmcage $
3 # Copyright (C) 2001-2003 Michael Haggerty <mhagger@alum.mit.edu>
5 # This file is licensed under the GNU Lesser General Public License
6 # (LGPL). See LICENSE.txt for details.
8 """Terminal definition file.
10 This module describes the options available to gnuplot's various
11 terminals. For the moment, it only supports a few terminals, but the
12 infrastructure is here to add others as they are needed.
14 Part of the trick is that the 'set terminal' command takes myriad
15 suboptions with various argument types, and order is sometimes
16 significant. The other part of the trick is that there are over 50
17 terminal types, and each terminal has its own set of options.
19 The strategy here is to define a general mechanism for turning Python
20 keyword parameters into fragments of gnuplot command strings. There
21 are a number of classes derived from Arg that do this. Some take
22 string args, some boolean, etc. Then the list of options that each
23 terminal accepts is stored in the terminal_opts dictionary.
24 Gnuplot.hardcopy(), in turn, uses this dictionary to interpret its
25 keyword arguments and build the 'set terminal' command.
27 """
30 import types
32 import gp, Errors
35 class Arg:
36 """Process terminal subargs and return a command fragment.
38 Pull one or more arguments from keyw and output a list of strings
39 that will be appended to the 'set terminal' (separated by spaces).
40 Delete any used args from keyw. If no relevant options are found,
41 return None.
43 This is a base class for the actual argument-processing classes.
44 Derived classes must define a __call__(self, keyw) method
45 returning a list of strings or None.
47 """
49 pass
52 class ArgOneParam(Arg):
53 """Arg abstract base class specialized for exactly one parameter.
55 Members:
57 'argname' -- The name of the keyword argument used to pass
58 this argument to Python.
60 'default' -- The default value of the argument, used if no
61 keyword parameter is found. If this is None, then no
62 default is assumed.
64 """
66 def __init__(self, argname, default):
67 self.argname = argname
68 self.default = default
70 def get_option(self, keyw):
71 """Get the keyword argument corresponding to this Arg.
73 Look in keyw for the keyword argument needed by this Arg. If
74 it is found, delete it from keyw and return it. If it is not
75 found, return self.default.
77 """
79 try:
80 k = keyw[self.argname]
81 except KeyError:
82 return self.default
83 else:
84 del keyw[self.argname]
85 return k
88 class KeywordArg(ArgOneParam):
89 """Represent an argument that must be passed as a keyword to gnuplot.
91 Some gnuplot options take the form of single unquoted keywords
92 (possibly preceded by a fixed keyword). We allow those to be
93 passed as strings 'option="keyword"'. Check that the option
94 supplied is in the list of allowed options.
96 Members:
98 'fixedword' -- the fixed keyword that must precede the
99 variable keyword in the gnuplot command, or None if none
100 is required.
102 'options' -- a list of strings containing the legal
103 alternatives for this argument.
107 def __init__(self, argname, options, fixedword=None, default=None):
108 ArgOneParam.__init__(self, argname, default)
109 self.fixedword = fixedword
110 self.options = options
112 def __call__(self, keyw):
113 k = self.get_option(keyw)
115 if k is None:
116 return None
117 elif k in self.options:
118 if self.fixedword is None:
119 return [k]
120 else:
121 return [self.fixedword, k]
122 else:
123 raise Errors.OptionError(
124 'Illegal option %s="%s"' % (self.argname, k,))
127 class StringArg(ArgOneParam):
128 """An option taking a quoted string argument."""
130 def __init__(self, argname, fixedword=None, default=None):
131 ArgOneParam.__init__(self, argname, default)
132 self.fixedword = fixedword
134 def __call__(self, keyw):
135 k = self.get_option(keyw)
137 if k is None:
138 return None
139 elif type(k) is not types.StringType:
140 raise Errors.OptionError(
141 'Option %s must be a string' % (self.argname,))
142 else:
143 retval = []
144 if self.fixedword is not None:
145 retval.append(self.fixedword)
146 retval.append('"%s"' % k)
147 return retval
150 class BareStringArg(ArgOneParam):
151 """An arbitrary argument output without quotes.
153 The argument can be a string or anything with a str()
154 representation, or a tuple of such things. Thus this can be used
155 for strings (which will be output without quotation marks),
156 integers, floating point arguments, or multiple arguments of the
157 above types (which will be output separated by spaces). No
158 checking is done that the argument is sensible.
162 def __init__(self, argname, fixedword=None, default=None):
163 ArgOneParam.__init__(self, argname, default)
164 self.fixedword = fixedword
166 def __call__(self, keyw):
167 k = self.get_option(keyw)
169 if k is None:
170 return None
171 else:
172 retval = []
173 if self.fixedword is not None:
174 retval.append(self.fixedword)
175 if type(k) in (types.TupleType, types.ListType):
176 for i in k:
177 retval.append(str(i))
178 else:
179 retval.append(str(k))
180 return retval
183 class BooleanArg(ArgOneParam):
184 """An argument that takes a true/false value.
186 The argument should be 0 or 1. The option is output to gnuplot as
187 'trueval' if the argument is true or 'falseval' if the argument is
188 false. Either one can be 'None', in which case nothing is output.
189 'default' should also be 0 or 1.
193 def __init__(self, argname, trueval, falseval,
194 fixedword=None, default=None):
195 ArgOneParam.__init__(self, argname, default)
196 self.trueval = trueval
197 self.falseval = falseval
198 self.fixedword = fixedword
200 def __call__(self, keyw):
201 k = self.get_option(keyw)
202 if k is None:
203 return None
204 else:
205 retval = []
206 if self.fixedword is not None:
207 retval.append(self.fixedword)
208 if k:
209 val = self.trueval
210 else:
211 val = self.falseval
212 if val is not None:
213 retval.append(val)
214 return retval
217 class MutuallyExclusiveArgs(Arg):
218 """A group of args, of which either zero or one may be set, but not more.
220 Members:
222 subargs -- a list [('argname', arg), ...] of Arg instances.
223 'argname' is used to identify the corresponding arg in
224 error messages. (The name of the corresponding keyword
225 args is determined internally by each arg.)
229 def __init__(self, *subargs):
230 self.subargs = list(subargs)
232 def __call__(self, keyw):
233 foundargname = None
234 retval = None
235 for (argname, arg,) in self.subargs:
236 cmd = arg(keyw)
237 if cmd is not None:
238 if foundargname is not None:
239 raise Errors.OptionError(
240 'Arguments %s and %s cannot both be specified'
241 % (foundargname, argname,)
243 else:
244 foundargname = argname
245 retval = cmd
246 return retval # might be None
249 class KeywordOrBooleanArg(Arg):
250 """Allow a keyword arg to be specified either as a keyword or a boolean.
252 This arg type is the most flexible way to allow keyword parameters
253 to be specified. Say there is an option like 'fontsize' that can
254 take the values 'small' or 'large'. This could be represented as
256 'KeywordOrBooleanArg(options=["small", "large"], argname="fontsize")'
258 In that case, the fontsize could be specified in any of the
259 following ways:
261 'g.hardcopy(..., fontsize="small", ...)'
262 'g.hardcopy(..., fontsize="large", ...)'
263 'g.hardcopy(..., small=1, ...)'
264 'g.hardcopy(..., large=1, ...)'
266 If 'argname' is set to be 'None', then the first two possibilities
267 are omitted.
269 In the special case that there are exactly two alternatives, one
270 can also use:
272 'g.hardcopy(..., small=0, ...) # implies fontsize="large"'
273 'g.hardcopy(..., large=0, ...) # implies fontsize="small"'
275 Obviously care must be taken to ensure that none of the implied
276 keyword parameter names conflict with one another or with any of
277 the other Args allowed by a function.
279 Members:
281 'options' -- a list of strings representing allowed keyword
282 values. These options can be used as boolean values in
283 the style 'option=1'.
285 'argname' -- the name of the argname for the 'arg=value' style
286 of setting the argument. If 'None', then this style is
287 not allowed.
289 'fixedword' -- a fixed keyword that must precede the option,
290 or 'None'.
292 'default' -- the default option to set if nothing is set
293 explicitly, or None to leave nothing set in that case.
297 def __init__(self, options, argname=None, fixedword=None, default=None):
298 self.options = options
299 self.argname = argname
300 self.fixedword = fixedword
301 self.default = default
302 assert self.default is None or self.default in self.options, \
303 'default must be a valid option'
305 def __call__(self, keyw):
306 if self.argname is not None and self.argname in keyw:
307 k = keyw[self.argname]
308 del keyw[self.argname]
309 if k is None:
310 pass
311 elif k in self.options:
312 # Make sure it isn't contradicted by the corresponding boolean:
313 if k in keyw and not keyw[k]:
314 raise Errors.OptionError(
315 'Arguments %s and %s are contradictory'
316 % (self.argname, k,)
318 else:
319 # Store the option into the boolean to be processed below:
320 keyw[k] = 1
321 else:
322 raise Errors.OptionError(
323 'Illegal option %s=%s' % (self.argname, k,))
325 # Now scan the booleans and make sure that at most one is set:
326 option = None
327 for i in range(len(self.options)):
328 k = self.options[i]
329 if k in keyw:
330 newval = keyw[k]
331 del keyw[k]
332 if newval:
333 if option is not None:
334 raise Errors.OptionError(
335 'Arguments %s and %s cannot both be specified'
336 % (option, k,)
338 else:
339 option = k
340 else:
341 # newval was false. This is only legal if this
342 # option only has two possible values:
343 if len(self.options) == 2:
344 option = self.options[1 - i]
345 else:
346 pass
348 if option is None:
349 if self.default is None:
350 return None
351 else:
352 option = self.default
353 retval = []
354 if self.fixedword is not None:
355 retval.append(self.fixedword)
356 retval.append(option)
357 return retval
360 # Now we define the allowed options for a few terminal types. This
361 # table is used by Gnuplot.hardcopy() to construct the necessary 'set
362 # terminal' command.
364 terminal_opts = {}
366 terminal_opts['postscript'] = [
367 KeywordOrBooleanArg(
368 options=['landscape', 'portrait', 'eps', 'default'],
369 argname='mode',
371 KeywordOrBooleanArg(
372 options=['enhanced', 'noenhanced'],
373 # This default should probably be computed from the *current*
374 # value of GnuplotOpts, not at import time. ###
375 default=(gp.GnuplotOpts.prefer_enhanced_postscript
376 and 'enhanced'
377 or 'noenhanced'),
379 KeywordOrBooleanArg(options=['color', 'monochrome']),
380 KeywordOrBooleanArg(options=['solid', 'dashed']),
381 KeywordOrBooleanArg(
382 options=['defaultplex', 'simplex', 'duplex'],
383 argname='duplexing',
385 StringArg(argname='fontname'),
386 BareStringArg(argname='fontsize'),
389 terminal_opts['pdf'] = [
390 KeywordOrBooleanArg(
391 options=['landscape', 'portrait', 'eps', 'default'],
392 argname='mode',
394 KeywordOrBooleanArg(options=['color', 'monochrome']),
395 KeywordOrBooleanArg(options=['solid', 'dashed']),
396 KeywordOrBooleanArg(
397 options=['defaultplex', 'simplex', 'duplex'],
398 argname='duplexing',
400 StringArg(argname='fontname'),
401 BareStringArg(argname='fontsize'),
404 terminal_opts['png'] = [
405 KeywordOrBooleanArg(
406 options=['small', 'medium', 'large'],
407 argname='fontsize',
409 KeywordOrBooleanArg(options=['monochrome', 'gray', 'color']),
412 terminal_opts['fig'] = [
413 KeywordOrBooleanArg(options=['monochrome', 'color']),
414 KeywordOrBooleanArg(options=['small', 'big']),
415 BareStringArg(argname='pointsmax', fixedword='pointsmax'),
416 KeywordOrBooleanArg(options=['landscape', 'portrait']),
417 KeywordOrBooleanArg(options=['metric', 'inches']),
418 BareStringArg(argname='fontsize'),
419 BareStringArg(argname='size'), # needs a tuple of two doubles
420 BareStringArg(argname='thickness'),
421 BareStringArg(argname='depth'),
424 terminal_opts['cgm'] = [
425 KeywordOrBooleanArg(
426 options=['landscape', 'portrait', 'default'],
427 argname='mode',
429 KeywordOrBooleanArg(options=['color', 'monochrome']),
430 KeywordOrBooleanArg(options=['rotate', 'norotate']),
431 BareStringArg(argname='width', fixedword='width'),
432 BareStringArg(argname='linewidth', fixedword='linewidth'),
433 StringArg(argname='font'),
434 BareStringArg(argname='fontsize'),
437 terminal_opts['pict'] = [
438 KeywordOrBooleanArg(
439 options=['landscape', 'portrait', 'default'],
440 argname='mode',
442 KeywordOrBooleanArg(options=['color', 'monochrome']),
443 KeywordOrBooleanArg(options=['dashes', 'nodashes']),
445 # default font, which must be a valid pict font:
446 StringArg(argname='fontname'),
448 # default font size, in points:
449 BareStringArg(argname='fontsize'),
451 # width of plot in pixels:
452 BareStringArg(argname='width'),
454 # height of plot in pixels:
455 BareStringArg(argname='height'),
459 terminal_opts['mp'] = [
460 KeywordOrBooleanArg(options=['color', 'colour', 'monochrome']),
461 KeywordOrBooleanArg(options=['solid', 'dashed']),
462 KeywordOrBooleanArg(options=['notex', 'tex', 'latex']),
463 BareStringArg(argname='magnification'),
464 KeywordOrBooleanArg(options=['psnfss', 'psnfss-version7', 'nopsnfss']),
465 BareStringArg(argname='prologues'),
466 KeywordOrBooleanArg(options=['a4paper']),
467 KeywordOrBooleanArg(options=['amstex']),
468 StringArg(argname='fontname'),
469 BareStringArg(argname='fontsize'),
472 terminal_opts['svg'] = [
473 BareStringArg(argname='size', fixedword='size'), # tuple of two doubles
474 KeywordOrBooleanArg(options=['fixed', 'dynamic']),
475 StringArg(argname='fname', fixedword='fname'),
476 BareStringArg(argname='fsize', fixedword='fsize'),
477 KeywordOrBooleanArg(options=['enhanced', 'noenhanced']),
478 StringArg(argname='fontfile', fixedword='fontfile'),