1
2
3
4
5
6 """
7 :mod:`gromacs.core` -- Core functionality
8 =========================================
9
10 Here the basic command class :class:`GromacsCommand` is defined. All
11 Gromacs command classes in :mod:`gromacs.tools` are automatically
12 generated from it.
13
14 .. autoclass:: Command
15 :members: __call__, run, transform_args, Popen, help,
16 command_name
17
18 .. autoclass:: GromacsCommand
19 :members: __call__, run, transform_args, Popen, help,
20 check_failure, gmxdoc
21 :inherited-members:
22
23 .. autoclass:: PopenWithInput
24 :members:
25 """
26 __docformat__ = "restructuredtext en"
27
28 import sys
29 import re
30 import subprocess
31 from subprocess import STDOUT, PIPE
32 import warnings
33 import errno
34
35 import logging
36 logger = logging.getLogger('gromacs.core')
37
38
39 from gromacs import GromacsError, GromacsFailureWarning
40
42 """Wrap simple script or command."""
43
44
45
46 command_name = None
47
49 """Set up the command class.
50
51 The arguments can always be provided as standard positional
52 arguments such as
53
54 ``"-c", "config.conf", "-o", "output.dat", "--repeats=3", "-v", "input.dat"``
55
56 In addition one can also use keyword arguments such as
57
58 ``c="config.conf", o="output.dat", repeats=3, v=True``
59
60 These are automatically transformed appropriately according to
61 simple rules:
62
63 * Any single-character keywords are assumed to be POSIX-style
64 options and will be prefixed with a single dash and the value
65 separated by a space.
66
67 * Any other keyword is assumed to be a GNU-style long option
68 and thus will be prefixed with two dashes and the value will
69 be joined directly with an equals sign and no space.
70
71 If this does not work (as for instance for the options of the
72 UNIX ``find`` command) then provide options and values in the
73 sequence of positional arguments.
74 """
75
76 self.args = args
77 self.kwargs = kwargs
78
79 - def run(self,*args,**kwargs):
80 """Run the command; args/kwargs are added or replace the ones given to the constructor."""
81 _args, _kwargs = self._combine_arglist(args, kwargs)
82 return self._run_command(*_args, **_kwargs)
83
85 """Combine the default values and the supplied values."""
86 _args = self.args + args
87 _kwargs = self.kwargs.copy()
88 _kwargs.update(kwargs)
89 return _args, _kwargs
90
92 """Execute the command; see the docs for __call__."""
93 use_input = kwargs.pop('use_input', True)
94 p = self.Popen(*args, **kwargs)
95 out, err = p.communicate(use_input=use_input)
96 rc = p.returncode
97 result = rc, out, err
98 return result
99
101 """Returns the command line (without pipes) as a list."""
102
103 return [self.command_name] + self.transform_args(*args,**kwargs)
104
106 """Returns the commandline that run() uses (without pipes)."""
107
108 _args, _kwargs = self._combine_arglist(args, kwargs)
109 return self._commandline(*_args, **_kwargs)
110
111 - def Popen(self, *args,**kwargs):
112 """Returns a special Popen instance (:class:`PopenWithInput`).
113
114 The instance has its input pre-set so that calls to
115 :meth:`~PopenWithInput.communicate` will not need to supply
116 input. This is necessary if one wants to chain the output from
117 one command to an input from another.
118
119 :TODO:
120 Write example.
121 """
122
123 stderr = kwargs.pop('stderr', STDOUT)
124 if stderr is False:
125 stderr = PIPE
126 elif stderr is True:
127 stderr = None
128
129 stdout = kwargs.pop('stdout', None)
130 if stdout is False:
131 stdout = PIPE
132 elif stdout is True:
133 stdout = None
134
135 stdin = kwargs.pop('stdin', None)
136 input = kwargs.pop('input', None)
137 if input:
138 stdin = PIPE
139 if type(input) is str:
140
141 if not input.endswith('\n'):
142 input += '\n'
143 else:
144 try:
145
146 input = '\n'.join(map(str, input)) + '\n'
147 except TypeError:
148
149 pass
150
151 cmd = self._commandline(*args, **kwargs)
152
153 try:
154 p = PopenWithInput(cmd, stdin=stdin, stderr=stderr, stdout=stdout,
155 universal_newlines=True, input=input)
156 except OSError,err:
157 logger.error(" ".join(cmd))
158 if err.errno == errno.ENOENT:
159 errmsg = "Failed to find command %r, maybe its not on PATH or GMXRC must be sourced?" % self.command_name
160 logger.fatal(errmsg)
161 raise OSError(errmsg)
162 else:
163 logger.exception("Setting up command %r raised an exception." % self.command_name)
164 raise
165 logger.debug(p.command_string)
166 return p
167
190
191 - def help(self,long=False):
192 """Print help; same as using ``?`` in ``ipython``. long=True also gives call signature."""
193 print "\ncommand: %s\n\n" % self.command_name
194 print self.__doc__
195 if long:
196 print "\ncall method: command():\n"
197 print self.__call__.__doc__
198
200 """Run command with the given arguments::
201
202 rc,stdout,stderr = command(*args, input=None, **kwargs)
203
204 All positional parameters \*args and all gromacs \*\*kwargs are passed on
205 to the Gromacs command. input and output keywords allow communication
206 with the process via the python subprocess module.
207
208 :Arguments:
209 *input* : string, sequence
210 to be fed to the process' standard input;
211 elements of a sequence are concatenated with
212 newlines, including a trailing one [``None``]
213 *stdin*
214 ``None`` or automatically set to ``PIPE`` if input given [``None``]
215 *stdout*
216 how to handle the program's stdout stream [``None``]
217
218 filehandle
219 anything that behaves like a file object
220 ``None`` or ``True``
221 to see output on screen
222 ``False`` or ``PIPE``
223 returns the output as a string in the stdout parameter
224
225 *stderr*
226 how to handle the stderr stream [``STDOUT``]
227
228 ``STDOUT``
229 merges standard error with the standard out stream
230 ``False`` or ``PIPE``
231 returns the output as a string in the stderr return parameter
232 ``None`` or ``True``
233 keeps it on stderr (and presumably on screen)
234
235 All other kwargs are passed on to the Gromacs tool.
236
237 :Returns:
238
239 The shell return code rc of the command is always returned. Depending
240 on the value of output, various strings are filled with output from the
241 command.
242
243 :Notes:
244
245 By default, the process stdout and stderr are merged.
246
247 In order to chain different commands via pipes one must use the special
248 :class:`PopenWithInput` object (see :meth:`GromacsCommand.Popen` method) instead of the simple
249 call described here and first construct the pipeline explicitly and then
250 call the :meth:`PopenWithInput.communicate` method.
251
252 ``STDOUT`` and ``PIPE`` are objects provided by the :mod:`subprocess` module. Any
253 python stream can be provided and manipulated. This allows for chaining
254 of commands. Use ::
255
256 from subprocess import PIPE, STDOUT
257
258 when requiring these special streams (and the special boolean
259 switches ``True``/``False`` cannot do what you need.)
260
261 (TODO: example for chaining commands)
262 """
263 return self.run(*args,**kwargs)
264
265
267 """Base class for wrapping a g_* command.
268
269 Limitations: User must have sourced ``GMXRC`` so that the python script can
270 inherit the environment and find the gromacs programs.
271
272 The class doc string is dynamically replaced by the documentation of the
273 gromacs command when an instance is created.
274 """
275
276
277 command_name = None
278 doc_pattern = """.*?(?P<DOCS>DESCRIPTION.*)"""
279 gmxfatal_pattern = """----+\n # ---- decorator line
280 \s*Program\s+(?P<program_name>\w+), # Program name,
281 \s+VERSION\s+(?P<version>[\w.]+)\s*\n # VERSION 4.0.5
282 (?P<message>.*?)\n # full message, multiple lines
283 \s* # empty line (?)
284 ----+\n # ---- decorator line
285 """
286
287
288
289
290
291
292
293 failuremodes = ('raise', 'warn', None)
294
296 """Set up the command with gromacs flags as keyword arguments.
297
298 The following are generic instructions; refer to the Gromacs
299 command usage information that should have appeared before
300 this generic documentation.
301
302 As an example, a generic Gromacs command could use the following flags::
303
304 cmd = GromacsCommand('v', f=['md1.xtc','md2.xtc'], o='processed.xtc', t=200, ...)
305
306 which would correspond to running the command in the shell as ::
307
308 GromacsCommand -v -f md1.xtc md2.xtc -o processed.xtc -t 200
309
310 **Gromacs command line arguments**
311
312 Gromacs boolean switches (such as ``-v``) are given as python
313 positional arguments (``'v'``) or as keyword argument (``v=True``);
314 note the quotes in the first case. Negating a boolean switch can be
315 done with ``'nov'``, ``nov=True`` or ``v=False`` (and even ``nov=False``
316 works as expected: it is the same as ``v=True``).
317
318 Any Gromacs options that take parameters are handled as keyword
319 arguments. If an option takes multiple arguments (such as the
320 multi-file input ``-f file1 file2 ...``) then the list of files must be
321 supplied as a python list.
322
323 If a keyword has the python value ``None`` then it will *not* be
324 added to the Gromacs command line; this allows for flexible
325 scripting if it is not known in advance if an input file is
326 needed. In this case the default value of the gromacs tool
327 is used.
328
329 Keywords must be legal python keywords or the interpreter raises a
330 :exc:`SyntaxError` but of course Gromacs commandline arguments are
331 not required to be legal python. In this case "quote" the option
332 with an underscore (``_``) and the underscore will be silently
333 stripped. For instance, ``-or`` translates to the illegal keyword
334 ``or`` so it must be underscore-quoted::
335
336 cmd(...., _or='mindistres.xvg')
337
338 **Command execution**
339
340 The command is executed with the :meth:`~GromacsCommand.run` method or by
341 calling it as a function. The two next lines are equivalent::
342
343 cmd(...)
344 cmd.run(...)
345
346 When the command is run one can override options that were given at
347 initialization or one can add additional ones. The same rules for
348 supplying Gromacs flags apply as described above.
349
350 **Non-Gromacs keyword arguments**
351
352 The other keyword arguments (listed below) are not passed on to the
353 Gromacs tool but determine how the command class behaves. They are
354 only useful when instantiating a class. This is mostly of interest
355 to developers.
356
357 :Keywords:
358 *failure*
359 determines how a failure of the gromacs command is treated; it
360 can be one of the following:
361
362 'raise'
363 raises GromacsError if command fails
364 'warn'
365 issue a :exc:`GromacsFailureWarning`
366 ``None``
367 just continue silently
368
369 *doc* : string
370 additional documentation []
371 """
372
373 self.failuremode = kwargs.pop('failure','raise')
374 self.extra_doc = kwargs.pop('doc',None)
375 if not self.failuremode in self.failuremodes:
376 raise ValueError('failuremode must be one of\n%(failuremodes)r' % vars(self))
377 self.gmxargs = self._combineargs(*args, **kwargs)
378 self.__doc__ = self.gmxdoc
379
381 """Combine the default values and the supplied values."""
382 gmxargs = self.gmxargs.copy()
383 gmxargs.update(self._combineargs(*args,**kwargs))
384 return (), gmxargs
385
386 - def check_failure(self, result, msg='Gromacs tool failed', command_string=None):
387 rc, out, err = result
388 if not command_string is None:
389 msg += '\nCommand invocation: ' + str(command_string)
390 had_success = (rc == 0)
391 if not had_success:
392 gmxoutput = "\n".join([x for x in [out, err] if not x is None])
393 m = re.search(self.gmxfatal_pattern, gmxoutput, re.VERBOSE | re.DOTALL)
394 if m:
395 formatted_message = ['GMX_FATAL '+line for line in m.group('message').split('\n')]
396 msg = "\n".join(\
397 [msg, "Gromacs command %(program_name)r fatal error message:" % m.groupdict()] +
398 formatted_message)
399 if self.failuremode == 'raise':
400 raise GromacsError(rc, msg)
401 elif self.failuremode == 'warn':
402 warnings.warn(msg + '\nError code: %r\n' % rc, category=GromacsFailureWarning)
403 elif self.failuremode is None:
404 pass
405 else:
406 raise ValueError('unknown failure mode %r' % self.failuremode)
407 return had_success
408
410 """Add switches as 'options' with value True to the options dict."""
411 d = dict([(arg, True) for arg in args])
412 d.update(kwargs)
413 return d
414
416 """Build list of arguments from the dict; keys must be valid gromacs flags."""
417 arglist = []
418 for flag,value in kwargs.items():
419
420 flag = str(flag)
421 if flag.startswith('_'):
422 flag = flag[1:]
423 if not flag.startswith('-'):
424 flag = '-' + flag
425 if value is True:
426 arglist.append(flag)
427 elif value is False:
428 if flag.startswith('-no'):
429
430 arglist.append('-'+flag[3:])
431 else:
432 arglist.append('-no'+flag[1:])
433 elif value is None:
434 pass
435 else:
436 try:
437 arglist.extend([flag] + value)
438 except TypeError:
439 arglist.extend([flag, value])
440 return map(str, arglist)
441
443 """Execute the gromacs command; see the docs for __call__."""
444 use_input = kwargs.pop('use_input', True)
445 p = self.Popen(*args, **kwargs)
446 out, err = p.communicate(use_input=use_input)
447 rc = p.returncode
448 result = rc, out, err
449 self.check_failure(result, command_string=p.command_string)
450 return result
451
456
458 """Extract standard gromacs doc by running the program and chopping the header."""
459
460
461
462
463 old_level = logger.getEffectiveLevel()
464 logger.setLevel(9999)
465 try:
466 rc,docs,nothing = self.run('h', stdout=PIPE, use_input=False)
467 finally:
468 logger.setLevel(old_level)
469 m = re.match(self.doc_pattern, docs, re.DOTALL)
470 if m is None:
471 return "(No Gromacs documentation available)"
472 return m.group('DOCS')
473
475 doc = """Usage for the underlying Gromacs tool (cached)."""
476 def fget(self):
477 if not (hasattr(self, '__doc_cache') and self.__doc_cache):
478 self.__doc_cache = self._get_gmx_docs()
479 docs = self.__doc_cache
480 if self.extra_doc:
481 docs = '\n'.join([self.extra_doc,'',
482 "Documentation of the gromacs tool", 34*'=',
483 docs])
484 return docs
485 return locals()
486 gmxdoc = property(**gmxdoc())
487
488
489
536