GRASS Programmer's Manual  6.4.4(2014)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
gcmd.py
Go to the documentation of this file.
1 """!
2 @package core.gcmd
3 
4 @brief wxGUI command interface
5 
6 Classes:
7  - gcmd::GError
8  - gcmd::GWarning
9  - gcmd::GMessage
10  - gcmd::GException
11  - gcmd::Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
12  - gcmd::Command
13  - gcmd::CommandThread
14 
15 Functions:
16  - RunCommand
17  - GetDefaultEncoding
18 
19 (C) 2007-2008, 2010-2011 by the GRASS Development Team
20 
21 This program is free software under the GNU General Public License
22 (>=v2). Read the file COPYING that comes with GRASS for details.
23 
24 @author Jachym Cepicky
25 @author Martin Landa <landa.martin gmail.com>
26 """
27 
28 import os
29 import sys
30 import time
31 import errno
32 import signal
33 import locale
34 import traceback
35 import copy
36 
37 import wx
38 
39 try:
40  import subprocess
41 except:
42  compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
43  sys.path.append(compatPath)
44  import subprocess
45 if subprocess.mswindows:
46  from win32file import ReadFile, WriteFile
47  from win32pipe import PeekNamedPipe
48  import msvcrt
49 else:
50  import select
51  import fcntl
52 from threading import Thread
53 
54 from grass.script import core as grass
55 
56 from core import globalvar
57 from core.debug import Debug
58 
59 def GetRealCmd(cmd):
60  """!Return real command name - only for MS Windows
61  """
62  if sys.platform == 'win32':
63  for ext in globalvar.grassScripts.keys():
64  if cmd in globalvar.grassScripts[ext]:
65  return cmd + ext
66 
67  return cmd
68 
69 def DecodeString(string):
70  """!Decode string using system encoding
71 
72  @param string string to be decoded
73 
74  @return decoded string
75  """
76  if not string:
77  return string
78 
79  try:
80  enc = locale.getdefaultlocale()[1]
81  except ValueError, e:
82  sys.stderr.write(_("ERROR: %s\n") % str(e))
83  return string
84 
85  if enc:
86  Debug.msg(5, "DecodeString(): enc=%s" % enc)
87  return string.decode(enc)
88 
89  return string
90 
91 def EncodeString(string):
92  """!Return encoded string using system locales
93 
94  @param string string to be encoded
95 
96  @return encoded string
97  """
98  if not string:
99  return string
100  enc = locale.getdefaultlocale()[1]
101  if enc:
102  Debug.msg(5, "EncodeString(): enc=%s" % enc)
103  return string.encode(enc)
104 
105  return string
106 
107 class GError:
108  def __init__(self, message, parent = None, caption = None, showTraceback = True):
109  if not caption:
110  caption = _('Error')
111  style = wx.OK | wx.ICON_ERROR | wx.CENTRE
112  exc_type, exc_value, exc_traceback = sys.exc_info()
113  if exc_traceback:
114  exception = traceback.format_exc()
115  reason = exception.splitlines()[-1].split(':', 1)[-1].strip()
116 
117  if Debug.GetLevel() > 0 and exc_traceback:
118  sys.stderr.write(exception)
119 
120  if showTraceback and exc_traceback:
121  wx.MessageBox(parent = parent,
122  message = message + '\n\n%s: %s\n\n%s' % \
123  (_('Reason'),
124  reason, exception),
125  caption = caption,
126  style = style)
127  else:
128  wx.MessageBox(parent = parent,
129  message = message,
130  caption = caption,
131  style = style)
132 
133 class GWarning:
134  def __init__(self, message, parent = None):
135  caption = _('Warning')
136  style = wx.OK | wx.ICON_WARNING | wx.CENTRE
137  wx.MessageBox(parent = parent,
138  message = message,
139  caption = caption,
140  style = style)
141 
142 class GMessage:
143  def __init__(self, message, parent = None):
144  caption = _('Message')
145  style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE
146  wx.MessageBox(parent = parent,
147  message = message,
148  caption = caption,
149  style = style)
150 
151 class GException(Exception):
152  def __init__(self, value = ''):
153  self.value = value
154 
155  def __str__(self):
156  return self.value
157 
158 class Popen(subprocess.Popen):
159  """!Subclass subprocess.Popen"""
160  def __init__(self, args, **kwargs):
161  if subprocess.mswindows:
162  args = map(EncodeString, args)
163 
164  subprocess.Popen.__init__(self, args, **kwargs)
165 
166  def recv(self, maxsize = None):
167  return self._recv('stdout', maxsize)
168 
169  def recv_err(self, maxsize = None):
170  return self._recv('stderr', maxsize)
171 
172  def send_recv(self, input = '', maxsize = None):
173  return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
174 
175  def get_conn_maxsize(self, which, maxsize):
176  if maxsize is None:
177  maxsize = 1024
178  elif maxsize < 1:
179  maxsize = 1
180  return getattr(self, which), maxsize
181 
182  def _close(self, which):
183  getattr(self, which).close()
184  setattr(self, which, None)
185 
186  def kill(self):
187  """!Try to kill running process"""
188  if subprocess.mswindows:
189  import win32api
190  handle = win32api.OpenProcess(1, 0, self.pid)
191  return (0 != win32api.TerminateProcess(handle, 0))
192  else:
193  try:
194  os.kill(-self.pid, signal.SIGTERM) # kill whole group
195  except OSError:
196  pass
197 
198  if subprocess.mswindows:
199  def send(self, input):
200  if not self.stdin:
201  return None
202 
203  try:
204  x = msvcrt.get_osfhandle(self.stdin.fileno())
205  (errCode, written) = WriteFile(x, input)
206  except ValueError:
207  return self._close('stdin')
208  except (subprocess.pywintypes.error, Exception), why:
209  if why[0] in (109, errno.ESHUTDOWN):
210  return self._close('stdin')
211  raise
212 
213  return written
214 
215  def _recv(self, which, maxsize):
216  conn, maxsize = self.get_conn_maxsize(which, maxsize)
217  if conn is None:
218  return None
219 
220  try:
221  x = msvcrt.get_osfhandle(conn.fileno())
222  (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
223  if maxsize < nAvail:
224  nAvail = maxsize
225  if nAvail > 0:
226  (errCode, read) = ReadFile(x, nAvail, None)
227  except ValueError:
228  return self._close(which)
229  except (subprocess.pywintypes.error, Exception), why:
230  if why[0] in (109, errno.ESHUTDOWN):
231  return self._close(which)
232  raise
233 
234  if self.universal_newlines:
235  read = self._translate_newlines(read)
236  return read
237 
238  else:
239  def send(self, input):
240  if not self.stdin:
241  return None
242 
243  if not select.select([], [self.stdin], [], 0)[1]:
244  return 0
245 
246  try:
247  written = os.write(self.stdin.fileno(), input)
248  except OSError, why:
249  if why[0] == errno.EPIPE: #broken pipe
250  return self._close('stdin')
251  raise
252 
253  return written
254 
255  def _recv(self, which, maxsize):
256  conn, maxsize = self.get_conn_maxsize(which, maxsize)
257  if conn is None:
258  return None
259 
260  flags = fcntl.fcntl(conn, fcntl.F_GETFL)
261  if not conn.closed:
262  fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
263 
264  try:
265  if not select.select([conn], [], [], 0)[0]:
266  return ''
267 
268  r = conn.read(maxsize)
269 
270  if not r:
271  return self._close(which)
272 
273  if self.universal_newlines:
274  r = self._translate_newlines(r)
275  return r
276  finally:
277  if not conn.closed:
278  fcntl.fcntl(conn, fcntl.F_SETFL, flags)
279 
280 message = "Other end disconnected!"
281 
282 def recv_some(p, t = .1, e = 1, tr = 5, stderr = 0):
283  if tr < 1:
284  tr = 1
285  x = time.time()+t
286  y = []
287  r = ''
288  pr = p.recv
289  if stderr:
290  pr = p.recv_err
291  while time.time() < x or r:
292  r = pr()
293  if r is None:
294  if e:
295  raise Exception(message)
296  else:
297  break
298  elif r:
299  y.append(r)
300  else:
301  time.sleep(max((x-time.time())/tr, 0))
302  return ''.join(y)
303 
304 def send_all(p, data):
305  while len(data):
306  sent = p.send(data)
307  if sent is None:
308  raise Exception(message)
309  data = buffer(data, sent)
310 
311 class Command:
312  """!Run command in separate thread. Used for commands launched
313  on the background.
314 
315  If stdout/err is redirected, write() method is required for the
316  given classes.
317 
318  @code
319  cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
320 
321  if cmd.returncode == None:
322  print 'RUNNING?'
323  elif cmd.returncode == 0:
324  print 'SUCCESS'
325  else:
326  print 'FAILURE (%d)' % cmd.returncode
327  @endcode
328 
329  @param cmd command given as list
330  @param stdin standard input stream
331  @param verbose verbose level [0, 3] (--q, --v)
332  @param wait wait for child execution terminated
333  @param rerr error handling (when CmdError raised).
334  True for redirection to stderr, False for GUI dialog,
335  None for no operation (quiet mode)
336  @param stdout redirect standard output or None
337  @param stderr redirect standard error output or None
338  """
339  def __init__ (self, cmd, stdin = None,
340  verbose = None, wait = True, rerr = False,
341  stdout = None, stderr = None):
342  Debug.msg(1, "gcmd.Command(): %s" % ' '.join(cmd))
343  self.cmd = cmd
344  self.stderr = stderr
345 
346  #
347  # set verbosity level
348  #
349  verbose_orig = None
350  if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
351  ('--v' not in self.cmd and '--verbose' not in self.cmd):
352  if verbose is not None:
353  if verbose == 0:
354  self.cmd.append('--quiet')
355  elif verbose == 3:
356  self.cmd.append('--verbose')
357  else:
358  verbose_orig = os.getenv("GRASS_VERBOSE")
359  os.environ["GRASS_VERBOSE"] = str(verbose)
360 
361  #
362  # create command thread
363  #
364  self.cmdThread = CommandThread(cmd, stdin,
365  stdout, stderr)
366  self.cmdThread.start()
367 
368  if wait:
369  self.cmdThread.join()
370  if self.cmdThread.module:
371  self.cmdThread.module.wait()
372  self.returncode = self.cmdThread.module.returncode
373  else:
374  self.returncode = 1
375  else:
376  self.cmdThread.join(0.5)
377  self.returncode = None
378 
379  if self.returncode is not None:
380  Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
381  (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
382  if rerr is not None and self.returncode != 0:
383  if rerr is False: # GUI dialog
384  raise GException("%s '%s'%s%s%s %s%s" % \
385  (_("Execution failed:"),
386  ' '.join(self.cmd),
387  os.linesep, os.linesep,
388  _("Details:"),
389  os.linesep,
390  _("Error: ") + self.__GetError()))
391  elif rerr == sys.stderr: # redirect message to sys
392  stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
393  stderr.write("%sDetails:%s%s" % (os.linesep,
394  _("Error: ") + self.__GetError(),
395  os.linesep))
396  else:
397  pass # nop
398  else:
399  Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
400  (' '.join(cmd), wait, self.cmdThread.isAlive()))
401 
402  if verbose_orig:
403  os.environ["GRASS_VERBOSE"] = verbose_orig
404  elif "GRASS_VERBOSE" in os.environ:
405  del os.environ["GRASS_VERBOSE"]
406 
407  def __ReadOutput(self, stream):
408  """!Read stream and return list of lines
409 
410  @param stream stream to be read
411  """
412  lineList = []
413 
414  if stream is None:
415  return lineList
416 
417  while True:
418  line = stream.readline()
419  if not line:
420  break
421  line = line.replace('%s' % os.linesep, '').strip()
422  lineList.append(line)
423 
424  return lineList
425 
426  def __ReadErrOutput(self):
427  """!Read standard error output and return list of lines"""
428  return self.__ReadOutput(self.cmdThread.module.stderr)
429 
430  def __ProcessStdErr(self):
431  """
432  Read messages/warnings/errors from stderr
433 
434  @return list of (type, message)
435  """
436  if self.stderr is None:
437  lines = self.__ReadErrOutput()
438  else:
439  lines = self.cmdThread.error.strip('%s' % os.linesep). \
440  split('%s' % os.linesep)
441 
442  msg = []
443 
444  type = None
445  content = ""
446  for line in lines:
447  if len(line) == 0:
448  continue
449  if 'GRASS_' in line: # error or warning
450  if 'GRASS_INFO_WARNING' in line: # warning
451  type = "WARNING"
452  elif 'GRASS_INFO_ERROR' in line: # error
453  type = "ERROR"
454  elif 'GRASS_INFO_END': # end of message
455  msg.append((type, content))
456  type = None
457  content = ""
458 
459  if type:
460  content += line.split(':', 1)[1].strip()
461  else: # stderr
462  msg.append((None, line.strip()))
463 
464  return msg
465 
466  def __GetError(self):
467  """!Get error message or ''"""
468  if not self.cmdThread.module:
469  return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
470 
471  for type, msg in self.__ProcessStdErr():
472  if type == 'ERROR':
473  enc = locale.getdefaultlocale()[1]
474  if enc:
475  return unicode(msg, enc)
476  else:
477  return msg
478 
479  return ''
480 
481 class CommandThread(Thread):
482  """!Create separate thread for command. Used for commands launched
483  on the background."""
484  def __init__ (self, cmd, env = None, stdin = None,
485  stdout = sys.stdout, stderr = sys.stderr):
486  """
487  @param cmd command (given as list)
488  @param env environmental variables
489  @param stdin standard input stream
490  @param stdout redirect standard output or None
491  @param stderr redirect standard error output or None
492  """
493  Thread.__init__(self)
494 
495  self.cmd = cmd
496  self.stdin = stdin
497  self.stdout = stdout
498  self.stderr = stderr
499  self.env = env
500 
501  self.module = None
502  self.error = ''
503 
504  self._want_abort = False
505  self.aborted = False
506 
507  self.setDaemon(True)
508 
509  # set message formatting
510  self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
511  os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
512 
513  def __del__(self):
514  if self.message_format:
515  os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
516  else:
517  del os.environ["GRASS_MESSAGE_FORMAT"]
518 
519  def run(self):
520  """!Run command"""
521  if len(self.cmd) == 0:
522  return
523 
524  Debug.msg(1, "gcmd.CommandThread(): %s" % ' '.join(self.cmd))
525 
526  self.startTime = time.time()
527 
528  # TODO: replace ugly hack below
529  args = self.cmd
530  if sys.platform == 'win32' and os.path.splitext(self.cmd[0])[1] == '.py':
531  # Python GUI script should be replaced by Shell scripts
532  os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts'))
533  args = [sys.executable, self.cmd[0]] + self.cmd[1:]
534  if sys.platform == 'win32' and \
535  self.cmd[0] in globalvar.grassScripts[globalvar.SCT_EXT]:
536  args[0] = self.cmd[0] + globalvar.SCT_EXT
537  env = copy.deepcopy(self.env)
538  # FIXME: env is None? hmph. borrow sys.path instead.
539  #env['PATH'] = os.path.join(os.getenv('GISBASE').replace('/', '\\'), 'scripts') + \
540  # os.pathsep + env['PATH']
541  scriptdir = os.path.join(os.getenv('GISBASE').replace('/', '\\'), 'scripts')
542  if scriptdir not in sys.path:
543  sys.path.append(scriptdir)
544  else:
545  env = self.env
546 
547  try:
548  self.module = Popen(args,
549  stdin = subprocess.PIPE,
550  stdout = subprocess.PIPE,
551  stderr = subprocess.PIPE,
552  shell = sys.platform == "win32",
553  env = env)
554 
555  except OSError, e:
556  self.error = str(e)
557  print >> sys.stderr, e
558  return 1
559 
560  if self.stdin: # read stdin if requested ...
561  self.module.stdin.write(self.stdin)
562  self.module.stdin.close()
563 
564  # redirect standard outputs...
565  self._redirect_stream()
566 
567  def _redirect_stream(self):
568  """!Redirect stream"""
569  if self.stdout:
570  # make module stdout/stderr non-blocking
571  out_fileno = self.module.stdout.fileno()
572  if not subprocess.mswindows:
573  flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
574  fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
575 
576  if self.stderr:
577  # make module stdout/stderr non-blocking
578  out_fileno = self.module.stderr.fileno()
579  if not subprocess.mswindows:
580  flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
581  fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
582 
583  # wait for the process to end, sucking in stuff until it does end
584  while self.module.poll() is None:
585  if self._want_abort: # abort running process
586  self.module.kill()
587  self.aborted = True
588  return
589  if self.stdout:
590  line = recv_some(self.module, e = 0, stderr = 0)
591  self.stdout.write(line)
592  if self.stderr:
593  line = recv_some(self.module, e = 0, stderr = 1)
594  self.stderr.write(line)
595  if len(line) > 0:
596  self.error = line
597 
598  # get the last output
599  if self.stdout:
600  line = recv_some(self.module, e = 0, stderr = 0)
601  self.stdout.write(line)
602  if self.stderr:
603  line = recv_some(self.module, e = 0, stderr = 1)
604  self.stderr.write(line)
605  if len(line) > 0:
606  self.error = line
607 
608  def abort(self):
609  """!Abort running process, used by main thread to signal an abort"""
610  self._want_abort = True
611 
612 def _formatMsg(text):
613  """!Format error messages for dialogs
614  """
615  message = ''
616  for line in text.splitlines():
617  if len(line) == 0:
618  continue
619  elif 'GRASS_INFO_MESSAGE' in line:
620  message += line.split(':', 1)[1].strip() + '\n'
621  elif 'GRASS_INFO_WARNING' in line:
622  message += line.split(':', 1)[1].strip() + '\n'
623  elif 'GRASS_INFO_ERROR' in line:
624  message += line.split(':', 1)[1].strip() + '\n'
625  elif 'GRASS_INFO_END' in line:
626  return message
627  else:
628  message += line.strip() + '\n'
629 
630  return message
631 
632 def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
633  parent = None, read = False, stdin = None, getErrorMsg = False, **kwargs):
634  """!Run GRASS command
635 
636  @param prog program to run
637  @param flags flags given as a string
638  @param overwrite, quiet, verbose flags
639  @param parent parent window for error messages
640  @param read fetch stdout
641  @param stdin stdin or None
642  @param getErrorMsg get error messages on failure
643  @param kwargs program parameters
644 
645  @return returncode (read == False and getErrorMsg == False)
646  @return returncode, messages (read == False and getErrorMsg == True)
647  @return stdout (read == True and getErrorMsg == False)
648  @return returncode, stdout, messages (read == True and getErrorMsg == True)
649  @return stdout, stderr
650  """
651  cmdString = ' '.join(grass.make_command(prog, flags, overwrite,
652  quiet, verbose, **kwargs))
653 
654  Debug.msg(1, "gcmd.RunCommand(): %s" % cmdString)
655 
656  kwargs['stderr'] = subprocess.PIPE
657 
658  if read:
659  kwargs['stdout'] = subprocess.PIPE
660 
661  if stdin:
662  kwargs['stdin'] = subprocess.PIPE
663 
664  ps = grass.start_command(GetRealCmd(prog), flags, overwrite, quiet, verbose, **kwargs)
665 
666  Debug.msg(2, "gcmd.RunCommand(): command started")
667 
668  if stdin:
669  ps.stdin.write(stdin)
670  ps.stdin.close()
671  ps.stdin = None
672 
673  Debug.msg(3, "gcmd.RunCommand(): decoding string")
674  stdout, stderr = map(DecodeString, ps.communicate())
675 
676  ret = ps.returncode
677  Debug.msg(1, "gcmd.RunCommand(): get return code %d" % ret)
678 
679  Debug.msg(3, "gcmd.RunCommand(): print error")
680  if ret != 0 and parent:
681  Debug.msg(2, "gcmd.RunCommand(): error %s" % stderr)
682  if (stderr == None):
683  Debug.msg(2, "gcmd.RunCommand(): nothing to print ???")
684  else:
685  GError(parent = parent,
686  message = stderr)
687 
688  Debug.msg(3, "gcmd.RunCommand(): print read error")
689  if not read:
690  if not getErrorMsg:
691  return ret
692  else:
693  return ret, _formatMsg(stderr)
694 
695  if stdout:
696  Debug.msg(2, "gcmd.RunCommand(): return stdout\n'%s'" % stdout)
697  else:
698  Debug.msg(2, "gcmd.RunCommand(): return stdout = None")
699  if not getErrorMsg:
700  return stdout
701 
702  Debug.msg(2, "gcmd.RunCommand(): return ret, stdout")
703  if read and getErrorMsg:
704  return ret, stdout, _formatMsg(stderr)
705 
706  Debug.msg(2, "gcmd.RunCommand(): return result")
707  return stdout, _formatMsg(stderr)
708 
709 def GetDefaultEncoding(forceUTF8 = False):
710  """!Get default system encoding
711 
712  @param forceUTF8 force 'UTF-8' if encoding is not defined
713 
714  @return system encoding (can be None)
715  """
716  enc = locale.getdefaultlocale()[1]
717  if forceUTF8 and (enc is None or enc == 'UTF8'):
718  return 'UTF-8'
719 
720  Debug.msg(1, "GetSystemEncoding(): %s" % enc)
721  return enc
def __ProcessStdErr
Definition: gcmd.py:430
def send_recv
Definition: gcmd.py:172
def __init__
Definition: gcmd.py:341
def __GetError
Get error message or ''.
Definition: gcmd.py:466
def EncodeString
Return encoded string using system locales.
Definition: gcmd.py:91
def DecodeString
Decode string using system encoding.
Definition: gcmd.py:69
def __init__
Definition: gcmd.py:143
def __init__
Definition: gcmd.py:108
wxGUI debugging
def get_conn_maxsize
Definition: gcmd.py:175
def send
Definition: gcmd.py:199
def abort
Abort running process, used by main thread to signal an abort.
Definition: gcmd.py:608
def __init__
Definition: gcmd.py:152
Subclass subprocess.Popen.
Definition: gcmd.py:158
#define max(x, y)
Definition: draw2.c:69
Create separate thread for command.
Definition: gcmd.py:481
def GetRealCmd
Return real command name - only for MS Windows.
Definition: gcmd.py:59
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
def send_all
Definition: gcmd.py:304
def recv_err
Definition: gcmd.py:169
Run command in separate thread.
Definition: gcmd.py:311
def GetDefaultEncoding
Get default system encoding.
Definition: gcmd.py:709
def recv
Definition: gcmd.py:166
def kill
Try to kill running process.
Definition: gcmd.py:186
def _close
Definition: gcmd.py:182
def __ReadOutput
Read stream and return list of lines.
Definition: gcmd.py:407
def recv_some
Definition: gcmd.py:282
def __ReadErrOutput
Read standard error output and return list of lines.
Definition: gcmd.py:426
def __init__
Definition: gcmd.py:134
def run
Run command.
Definition: gcmd.py:519
def _redirect_stream
Redirect stream.
Definition: gcmd.py:567
def __init__
Definition: gcmd.py:160
def __str__
Definition: gcmd.py:155
def RunCommand
Run GRASS command.
Definition: gcmd.py:633
def _recv
Definition: gcmd.py:215