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