| 1 | """
|
|---|
| 2 | @package core.gconsole
|
|---|
| 3 |
|
|---|
| 4 | @brief Command output widgets
|
|---|
| 5 |
|
|---|
| 6 | Classes:
|
|---|
| 7 | - goutput::CmdThread
|
|---|
| 8 | - goutput::GStdout
|
|---|
| 9 | - goutput::GStderr
|
|---|
| 10 | - goutput::GConsole
|
|---|
| 11 |
|
|---|
| 12 | (C) 2007-2015 by the GRASS Development Team
|
|---|
| 13 |
|
|---|
| 14 | This program is free software under the GNU General Public License
|
|---|
| 15 | (>=v2). Read the file COPYING that comes with GRASS for details.
|
|---|
| 16 |
|
|---|
| 17 | @author Michael Barton (Arizona State University)
|
|---|
| 18 | @author Martin Landa <landa.martin gmail.com>
|
|---|
| 19 | @author Vaclav Petras <wenzeslaus gmail.com> (refactoring)
|
|---|
| 20 | @author Anna Kratochvilova <kratochanna gmail.com> (refactoring)
|
|---|
| 21 | """
|
|---|
| 22 |
|
|---|
| 23 | from __future__ import print_function
|
|---|
| 24 |
|
|---|
| 25 | import os
|
|---|
| 26 | import sys
|
|---|
| 27 | import re
|
|---|
| 28 | import time
|
|---|
| 29 | import threading
|
|---|
| 30 |
|
|---|
| 31 | if sys.version_info.major == 2:
|
|---|
| 32 | import Queue
|
|---|
| 33 | else:
|
|---|
| 34 | import queue as Queue
|
|---|
| 35 |
|
|---|
| 36 | import codecs
|
|---|
| 37 | import locale
|
|---|
| 38 |
|
|---|
| 39 | import wx
|
|---|
| 40 | from wx.lib.newevent import NewEvent
|
|---|
| 41 |
|
|---|
| 42 | import grass.script as grass
|
|---|
| 43 | from grass.script import task as gtask
|
|---|
| 44 |
|
|---|
| 45 | from grass.pydispatch.signal import Signal
|
|---|
| 46 |
|
|---|
| 47 | from core import globalvar
|
|---|
| 48 | from core.gcmd import CommandThread, GError, GException
|
|---|
| 49 | from gui_core.forms import GUI
|
|---|
| 50 | from core.debug import Debug
|
|---|
| 51 | from core.settings import UserSettings
|
|---|
| 52 | from core.giface import Notification
|
|---|
| 53 | from gui_core.widgets import FormNotebook
|
|---|
| 54 |
|
|---|
| 55 | wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
|
|---|
| 56 | wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
|
|---|
| 57 | wxCmdRun, EVT_CMD_RUN = NewEvent()
|
|---|
| 58 | wxCmdDone, EVT_CMD_DONE = NewEvent()
|
|---|
| 59 | wxCmdAbort, EVT_CMD_ABORT = NewEvent()
|
|---|
| 60 | wxCmdPrepare, EVT_CMD_PREPARE = NewEvent()
|
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 | def GrassCmd(cmd, env=None, stdout=None, stderr=None):
|
|---|
| 64 | """Return GRASS command thread"""
|
|---|
| 65 | return CommandThread(cmd, env=env,
|
|---|
| 66 | stdout=stdout, stderr=stderr)
|
|---|
| 67 |
|
|---|
| 68 |
|
|---|
| 69 | class CmdThread(threading.Thread):
|
|---|
| 70 | """Thread for GRASS commands"""
|
|---|
| 71 | requestId = 0
|
|---|
| 72 |
|
|---|
| 73 | def __init__(self, receiver, requestQ=None, resultQ=None, **kwds):
|
|---|
| 74 | """
|
|---|
| 75 | :param receiver: event receiver (used in PostEvent)
|
|---|
| 76 | """
|
|---|
| 77 | threading.Thread.__init__(self, **kwds)
|
|---|
| 78 |
|
|---|
| 79 | if requestQ is None:
|
|---|
| 80 | self.requestQ = Queue.Queue()
|
|---|
| 81 | else:
|
|---|
| 82 | self.requestQ = requestQ
|
|---|
| 83 |
|
|---|
| 84 | if resultQ is None:
|
|---|
| 85 | self.resultQ = Queue.Queue()
|
|---|
| 86 | else:
|
|---|
| 87 | self.resultQ = resultQ
|
|---|
| 88 |
|
|---|
| 89 | self.setDaemon(True)
|
|---|
| 90 |
|
|---|
| 91 | self.requestCmd = None
|
|---|
| 92 |
|
|---|
| 93 | self.receiver = receiver
|
|---|
| 94 | self._want_abort_all = False
|
|---|
| 95 |
|
|---|
| 96 | self.start()
|
|---|
| 97 |
|
|---|
| 98 | def RunCmd(self, *args, **kwds):
|
|---|
| 99 | """Run command in queue
|
|---|
| 100 |
|
|---|
| 101 | :param args: unnamed command arguments
|
|---|
| 102 | :param kwds: named command arguments
|
|---|
| 103 |
|
|---|
| 104 | :return: request id in queue
|
|---|
| 105 | """
|
|---|
| 106 | CmdThread.requestId += 1
|
|---|
| 107 |
|
|---|
| 108 | self.requestCmd = None
|
|---|
| 109 | self.requestQ.put((CmdThread.requestId, args, kwds))
|
|---|
| 110 |
|
|---|
| 111 | return CmdThread.requestId
|
|---|
| 112 |
|
|---|
| 113 | def GetId(self):
|
|---|
| 114 | """Get id for next command"""
|
|---|
| 115 | return CmdThread.requestId + 1
|
|---|
| 116 |
|
|---|
| 117 | def SetId(self, id):
|
|---|
| 118 | """Set starting id"""
|
|---|
| 119 | CmdThread.requestId = id
|
|---|
| 120 |
|
|---|
| 121 | def run(self):
|
|---|
| 122 | os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
|
|---|
| 123 | while True:
|
|---|
| 124 | requestId, args, kwds = self.requestQ.get()
|
|---|
| 125 | for key in ('callable', 'onDone', 'onPrepare',
|
|---|
| 126 | 'userData', 'addLayer', 'notification'):
|
|---|
| 127 | if key in kwds:
|
|---|
| 128 | vars()[key] = kwds[key]
|
|---|
| 129 | del kwds[key]
|
|---|
| 130 | else:
|
|---|
| 131 | vars()[key] = None
|
|---|
| 132 |
|
|---|
| 133 | if not vars()['callable']:
|
|---|
| 134 | vars()['callable'] = GrassCmd
|
|---|
| 135 |
|
|---|
| 136 | requestTime = time.time()
|
|---|
| 137 |
|
|---|
| 138 | # prepare
|
|---|
| 139 | if self.receiver:
|
|---|
| 140 | event = wxCmdPrepare(cmd=args[0],
|
|---|
| 141 | time=requestTime,
|
|---|
| 142 | pid=requestId,
|
|---|
| 143 | onPrepare=vars()['onPrepare'],
|
|---|
| 144 | userData=vars()['userData'])
|
|---|
| 145 |
|
|---|
| 146 | wx.PostEvent(self.receiver, event)
|
|---|
| 147 |
|
|---|
| 148 | # run command
|
|---|
| 149 | event = wxCmdRun(cmd=args[0],
|
|---|
| 150 | pid=requestId,
|
|---|
| 151 | notification=vars()['notification'])
|
|---|
| 152 |
|
|---|
| 153 | wx.PostEvent(self.receiver, event)
|
|---|
| 154 |
|
|---|
| 155 | time.sleep(.1)
|
|---|
| 156 | self.requestCmd = vars()['callable'](*args, **kwds)
|
|---|
| 157 | if self._want_abort_all and self.requestCmd is not None:
|
|---|
| 158 | self.requestCmd.abort()
|
|---|
| 159 | if self.requestQ.empty():
|
|---|
| 160 | self._want_abort_all = False
|
|---|
| 161 |
|
|---|
| 162 | self.resultQ.put((requestId, self.requestCmd.run()))
|
|---|
| 163 |
|
|---|
| 164 | try:
|
|---|
| 165 | returncode = self.requestCmd.module.returncode
|
|---|
| 166 | except AttributeError:
|
|---|
| 167 | returncode = 0 # being optimistic
|
|---|
| 168 |
|
|---|
| 169 | try:
|
|---|
| 170 | aborted = self.requestCmd.aborted
|
|---|
| 171 | except AttributeError:
|
|---|
| 172 | aborted = False
|
|---|
| 173 |
|
|---|
| 174 | time.sleep(.1)
|
|---|
| 175 |
|
|---|
| 176 | # set default color table for raster data
|
|---|
| 177 | if UserSettings.Get(group='rasterLayer',
|
|---|
| 178 | key='colorTable', subkey='enabled') and \
|
|---|
| 179 | args[0][0][:2] == 'r.':
|
|---|
| 180 | colorTable = UserSettings.Get(group='rasterLayer',
|
|---|
| 181 | key='colorTable',
|
|---|
| 182 | subkey='selection')
|
|---|
| 183 | mapName = None
|
|---|
| 184 | if args[0][0] == 'r.mapcalc':
|
|---|
| 185 | try:
|
|---|
| 186 | mapName = args[0][1].split('=', 1)[0].strip()
|
|---|
| 187 | except KeyError:
|
|---|
| 188 | pass
|
|---|
| 189 | else:
|
|---|
| 190 | moduleInterface = GUI(show=None).ParseCommand(args[0])
|
|---|
| 191 | outputParam = moduleInterface.get_param(value='output',
|
|---|
| 192 | raiseError=False)
|
|---|
| 193 | if outputParam and outputParam['prompt'] == 'raster':
|
|---|
| 194 | mapName = outputParam['value']
|
|---|
| 195 |
|
|---|
| 196 | if mapName:
|
|---|
| 197 | argsColor = list(args)
|
|---|
| 198 | argsColor[0] = ['r.colors',
|
|---|
| 199 | 'map=%s' % mapName,
|
|---|
| 200 | 'color=%s' % colorTable]
|
|---|
| 201 | self.requestCmdColor = vars()['callable'](
|
|---|
| 202 | *argsColor, **kwds)
|
|---|
| 203 | self.resultQ.put((requestId, self.requestCmdColor.run()))
|
|---|
| 204 |
|
|---|
| 205 | if self.receiver:
|
|---|
| 206 | event = wxCmdDone(cmd=args[0],
|
|---|
| 207 | aborted=aborted,
|
|---|
| 208 | returncode=returncode,
|
|---|
| 209 | time=requestTime,
|
|---|
| 210 | pid=requestId,
|
|---|
| 211 | onDone=vars()['onDone'],
|
|---|
| 212 | userData=vars()['userData'],
|
|---|
| 213 | addLayer=vars()['addLayer'],
|
|---|
| 214 | notification=vars()['notification'])
|
|---|
| 215 |
|
|---|
| 216 | # send event
|
|---|
| 217 | wx.PostEvent(self.receiver, event)
|
|---|
| 218 |
|
|---|
| 219 | def abort(self, abortall=True):
|
|---|
| 220 | """Abort command(s)"""
|
|---|
| 221 | if abortall:
|
|---|
| 222 | self._want_abort_all = True
|
|---|
| 223 | if self.requestCmd is not None:
|
|---|
| 224 | self.requestCmd.abort()
|
|---|
| 225 | if self.requestQ.empty():
|
|---|
| 226 | self._want_abort_all = False
|
|---|
| 227 |
|
|---|
| 228 |
|
|---|
| 229 | class GStdout:
|
|---|
| 230 | """GConsole standard output
|
|---|
| 231 |
|
|---|
| 232 | Based on FrameOutErr.py
|
|---|
| 233 |
|
|---|
| 234 | Name: FrameOutErr.py
|
|---|
| 235 | Purpose: Redirecting stdout / stderr
|
|---|
| 236 | Author: Jean-Michel Fauth, Switzerland
|
|---|
| 237 | Copyright: (c) 2005-2007 Jean-Michel Fauth
|
|---|
| 238 | Licence: GPL
|
|---|
| 239 | """
|
|---|
| 240 |
|
|---|
| 241 | def __init__(self, receiver):
|
|---|
| 242 | """
|
|---|
| 243 | :param receiver: event receiver (used in PostEvent)
|
|---|
| 244 | """
|
|---|
| 245 | self.receiver = receiver
|
|---|
| 246 |
|
|---|
| 247 | def flush(self):
|
|---|
| 248 | pass
|
|---|
| 249 |
|
|---|
| 250 | def write(self, s):
|
|---|
| 251 | if len(s) == 0 or s == '\n':
|
|---|
| 252 | return
|
|---|
| 253 |
|
|---|
| 254 | for line in s.splitlines():
|
|---|
| 255 | if len(line) == 0:
|
|---|
| 256 | continue
|
|---|
| 257 |
|
|---|
| 258 | evt = wxCmdOutput(text=line + '\n',
|
|---|
| 259 | type='')
|
|---|
| 260 | wx.PostEvent(self.receiver, evt)
|
|---|
| 261 |
|
|---|
| 262 |
|
|---|
| 263 | class GStderr:
|
|---|
| 264 | """GConsole standard error output
|
|---|
| 265 |
|
|---|
| 266 | Based on FrameOutErr.py
|
|---|
| 267 |
|
|---|
| 268 | Name: FrameOutErr.py
|
|---|
| 269 | Purpose: Redirecting stdout / stderr
|
|---|
| 270 | Author: Jean-Michel Fauth, Switzerland
|
|---|
| 271 | Copyright: (c) 2005-2007 Jean-Michel Fauth
|
|---|
| 272 | Licence: GPL
|
|---|
| 273 | """
|
|---|
| 274 |
|
|---|
| 275 | def __init__(self, receiver):
|
|---|
| 276 | """
|
|---|
| 277 | :param receiver: event receiver (used in PostEvent)
|
|---|
| 278 | """
|
|---|
| 279 | self.receiver = receiver
|
|---|
| 280 | self.type = ''
|
|---|
| 281 | self.message = ''
|
|---|
| 282 | self.printMessage = False
|
|---|
| 283 |
|
|---|
| 284 | def flush(self):
|
|---|
| 285 | pass
|
|---|
| 286 |
|
|---|
| 287 | def write(self, s):
|
|---|
| 288 | if "GtkPizza" in s:
|
|---|
| 289 | return
|
|---|
| 290 |
|
|---|
| 291 | # remove/replace escape sequences '\b' or '\r' from stream
|
|---|
| 292 | progressValue = -1
|
|---|
| 293 |
|
|---|
| 294 | for line in s.splitlines():
|
|---|
| 295 | if len(line) == 0:
|
|---|
| 296 | continue
|
|---|
| 297 |
|
|---|
| 298 | if 'GRASS_INFO_PERCENT' in line:
|
|---|
| 299 | value = int(line.rsplit(':', 1)[1].strip())
|
|---|
| 300 | if value >= 0 and value < 100:
|
|---|
| 301 | progressValue = value
|
|---|
| 302 | else:
|
|---|
| 303 | progressValue = 0
|
|---|
| 304 | elif 'GRASS_INFO_MESSAGE' in line:
|
|---|
| 305 | self.type = 'message'
|
|---|
| 306 | self.message += line.split(':', 1)[1].strip() + '\n'
|
|---|
| 307 | elif 'GRASS_INFO_WARNING' in line:
|
|---|
| 308 | self.type = 'warning'
|
|---|
| 309 | self.message += line.split(':', 1)[1].strip() + '\n'
|
|---|
| 310 | elif 'GRASS_INFO_ERROR' in line:
|
|---|
| 311 | self.type = 'error'
|
|---|
| 312 | self.message += line.split(':', 1)[1].strip() + '\n'
|
|---|
| 313 | elif 'GRASS_INFO_END' in line:
|
|---|
| 314 | self.printMessage = True
|
|---|
| 315 | elif self.type == '':
|
|---|
| 316 | if len(line) == 0:
|
|---|
| 317 | continue
|
|---|
| 318 | evt = wxCmdOutput(text=line,
|
|---|
| 319 | type='')
|
|---|
| 320 | wx.PostEvent(self.receiver, evt)
|
|---|
| 321 | elif len(line) > 0:
|
|---|
| 322 | self.message += line.strip() + '\n'
|
|---|
| 323 |
|
|---|
| 324 | if self.printMessage and len(self.message) > 0:
|
|---|
| 325 | evt = wxCmdOutput(text=self.message,
|
|---|
| 326 | type=self.type)
|
|---|
| 327 | wx.PostEvent(self.receiver, evt)
|
|---|
| 328 |
|
|---|
| 329 | self.type = ''
|
|---|
| 330 | self.message = ''
|
|---|
| 331 | self.printMessage = False
|
|---|
| 332 |
|
|---|
| 333 | # update progress message
|
|---|
| 334 | if progressValue > -1:
|
|---|
| 335 | # self.gmgauge.SetValue(progressValue)
|
|---|
| 336 | evt = wxCmdProgress(value=progressValue)
|
|---|
| 337 | wx.PostEvent(self.receiver, evt)
|
|---|
| 338 |
|
|---|
| 339 |
|
|---|
| 340 | # Occurs when an ignored command is called.
|
|---|
| 341 | # Attribute cmd contains command (as a list).
|
|---|
| 342 | gIgnoredCmdRun, EVT_IGNORED_CMD_RUN = NewEvent()
|
|---|
| 343 |
|
|---|
| 344 |
|
|---|
| 345 | class GConsole(wx.EvtHandler):
|
|---|
| 346 | """
|
|---|
| 347 | """
|
|---|
| 348 |
|
|---|
| 349 | def __init__(self, guiparent=None, giface=None, ignoredCmdPattern=None):
|
|---|
| 350 | """
|
|---|
| 351 | :param guiparent: parent window for created GUI objects
|
|---|
| 352 | :param lmgr: layer manager window (TODO: replace by giface)
|
|---|
| 353 | :param ignoredCmdPattern: regular expression specifying commads
|
|---|
| 354 | to be ignored (e.g. @c '^d\..*' for
|
|---|
| 355 | display commands)
|
|---|
| 356 | """
|
|---|
| 357 | wx.EvtHandler.__init__(self)
|
|---|
| 358 |
|
|---|
| 359 | # Signal when some map is created or updated by a module.
|
|---|
| 360 | # attributes: name: map name, ltype: map type,
|
|---|
| 361 | self.mapCreated = Signal('GConsole.mapCreated')
|
|---|
| 362 | # emitted when map display should be re-render
|
|---|
| 363 | self.updateMap = Signal('GConsole.updateMap')
|
|---|
| 364 | # emitted when log message should be written
|
|---|
| 365 | self.writeLog = Signal('GConsole.writeLog')
|
|---|
| 366 | # emitted when command log message should be written
|
|---|
| 367 | self.writeCmdLog = Signal('GConsole.writeCmdLog')
|
|---|
| 368 | # emitted when warning message should be written
|
|---|
| 369 | self.writeWarning = Signal('GConsole.writeWarning')
|
|---|
| 370 | # emitted when error message should be written
|
|---|
| 371 | self.writeError = Signal('GConsole.writeError')
|
|---|
| 372 |
|
|---|
| 373 | self._guiparent = guiparent
|
|---|
| 374 | self._giface = giface
|
|---|
| 375 | self._ignoredCmdPattern = ignoredCmdPattern
|
|---|
| 376 |
|
|---|
| 377 | # create queues
|
|---|
| 378 | self.requestQ = Queue.Queue()
|
|---|
| 379 | self.resultQ = Queue.Queue()
|
|---|
| 380 |
|
|---|
| 381 | self.cmdOutputTimer = wx.Timer(self)
|
|---|
| 382 | self.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
|
|---|
| 383 | self.Bind(EVT_CMD_RUN, self.OnCmdRun)
|
|---|
| 384 | self.Bind(EVT_CMD_DONE, self.OnCmdDone)
|
|---|
| 385 | self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
|
|---|
| 386 |
|
|---|
| 387 | # stream redirection
|
|---|
| 388 | self.cmdStdOut = GStdout(receiver=self)
|
|---|
| 389 | self.cmdStdErr = GStderr(receiver=self)
|
|---|
| 390 |
|
|---|
| 391 | # thread
|
|---|
| 392 | self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
|
|---|
| 393 |
|
|---|
| 394 | def Redirect(self):
|
|---|
| 395 | """Redirect stdout/stderr
|
|---|
| 396 | """
|
|---|
| 397 | if Debug.GetLevel() == 0 and grass.debug_level(force=True) == 0:
|
|---|
| 398 | # don't redirect when debugging is enabled
|
|---|
| 399 | sys.stdout = self.cmdStdOut
|
|---|
| 400 | sys.stderr = self.cmdStdErr
|
|---|
| 401 | else:
|
|---|
| 402 | enc = locale.getdefaultlocale()[1]
|
|---|
| 403 | if enc:
|
|---|
| 404 | if sys.version_info.major == 2:
|
|---|
| 405 | sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
|
|---|
| 406 | sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
|
|---|
| 407 | else:
|
|---|
| 408 | # https://stackoverflow.com/questions/4374455/how-to-set-sys-stdout-encoding-in-python-3
|
|---|
| 409 | sys.stdout = codecs.getwriter(enc)(sys.__stdout__.detach())
|
|---|
| 410 | sys.stderr = codecs.getwriter(enc)(sys.__stderr__.detach())
|
|---|
| 411 | else:
|
|---|
| 412 | sys.stdout = sys.__stdout__
|
|---|
| 413 | sys.stderr = sys.__stderr__
|
|---|
| 414 |
|
|---|
| 415 | def WriteLog(self, text, style=None, wrap=None,
|
|---|
| 416 | notification=Notification.HIGHLIGHT):
|
|---|
| 417 | """Generic method for writing log message in
|
|---|
| 418 | given style
|
|---|
| 419 |
|
|---|
| 420 | :param text: text line
|
|---|
| 421 | :param notification: form of notification
|
|---|
| 422 | """
|
|---|
| 423 | self.writeLog.emit(text=text, wrap=wrap,
|
|---|
| 424 | notification=notification)
|
|---|
| 425 |
|
|---|
| 426 | def WriteCmdLog(self, text, pid=None,
|
|---|
| 427 | notification=Notification.MAKE_VISIBLE):
|
|---|
| 428 | """Write message in selected style
|
|---|
| 429 |
|
|---|
| 430 | :param text: message to be printed
|
|---|
| 431 | :param pid: process pid or None
|
|---|
| 432 | :param notification: form of notification
|
|---|
| 433 | """
|
|---|
| 434 | self.writeCmdLog.emit(text=text, pid=pid,
|
|---|
| 435 | notification=notification)
|
|---|
| 436 |
|
|---|
| 437 | def WriteWarning(self, text):
|
|---|
| 438 | """Write message in warning style"""
|
|---|
| 439 | self.writeWarning.emit(text=text)
|
|---|
| 440 |
|
|---|
| 441 | def WriteError(self, text):
|
|---|
| 442 | """Write message in error style"""
|
|---|
| 443 | self.writeError.emit(text=text)
|
|---|
| 444 |
|
|---|
| 445 | def RunCmd(self, command, compReg=True, env=None, skipInterface=False,
|
|---|
| 446 | onDone=None, onPrepare=None, userData=None, addLayer=None,
|
|---|
| 447 | notification=Notification.MAKE_VISIBLE):
|
|---|
| 448 | """Run command typed into console command prompt (GPrompt).
|
|---|
| 449 |
|
|---|
| 450 | .. todo::
|
|---|
| 451 | Document the other event.
|
|---|
| 452 | .. todo::
|
|---|
| 453 | Solve problem with the other event (now uses gOutputText
|
|---|
| 454 | event but there is no text, use onPrepare handler instead?)
|
|---|
| 455 | .. todo::
|
|---|
| 456 | Skip interface is ignored and determined always automatically.
|
|---|
| 457 |
|
|---|
| 458 | Posts event EVT_IGNORED_CMD_RUN when command which should be ignored
|
|---|
| 459 | (according to ignoredCmdPattern) is run.
|
|---|
| 460 | For example, see layer manager which handles d.* on its own.
|
|---|
| 461 |
|
|---|
| 462 | :param command: command given as a list (produced e.g. by utils.split())
|
|---|
| 463 | :param compReg: True use computation region
|
|---|
| 464 | :param notification: form of notification
|
|---|
| 465 | :param bool skipInterface: True to do not launch GRASS interface
|
|---|
| 466 | parser when command has no arguments
|
|---|
| 467 | given
|
|---|
| 468 | :param onDone: function to be called when command is finished
|
|---|
| 469 | :param onPrepare: function to be called before command is launched
|
|---|
| 470 | :param addLayer: to be passed in the mapCreated signal
|
|---|
| 471 | :param userData: data defined for the command
|
|---|
| 472 | """
|
|---|
| 473 | if len(command) == 0:
|
|---|
| 474 | Debug.msg(2, "GPrompt:RunCmd(): empty command")
|
|---|
| 475 | return
|
|---|
| 476 |
|
|---|
| 477 | # update history file
|
|---|
| 478 | self.UpdateHistoryFile(' '.join(command))
|
|---|
| 479 |
|
|---|
| 480 | if command[0] in globalvar.grassCmd:
|
|---|
| 481 | # send GRASS command without arguments to GUI command interface
|
|---|
| 482 | # except ignored commands (event is emitted)
|
|---|
| 483 | if self._ignoredCmdPattern and \
|
|---|
| 484 | re.compile(self._ignoredCmdPattern).search(' '.join(command)) and \
|
|---|
| 485 | '--help' not in command and '--ui' not in command:
|
|---|
| 486 | event = gIgnoredCmdRun(cmd=command)
|
|---|
| 487 | wx.PostEvent(self, event)
|
|---|
| 488 | return
|
|---|
| 489 |
|
|---|
| 490 | else:
|
|---|
| 491 | # other GRASS commands (r|v|g|...)
|
|---|
| 492 | try:
|
|---|
| 493 | task = GUI(show=None).ParseCommand(command)
|
|---|
| 494 | except GException as e:
|
|---|
| 495 | GError(parent=self._guiparent,
|
|---|
| 496 | message=unicode(e),
|
|---|
| 497 | showTraceback=False)
|
|---|
| 498 | return
|
|---|
| 499 |
|
|---|
| 500 | hasParams = False
|
|---|
| 501 | if task:
|
|---|
| 502 | options = task.get_options()
|
|---|
| 503 | hasParams = options['params'] and options['flags']
|
|---|
| 504 | # check for <input>=-
|
|---|
| 505 | for p in options['params']:
|
|---|
| 506 | if p.get('prompt', '') == 'input' and \
|
|---|
| 507 | p.get('element', '') == 'file' and \
|
|---|
| 508 | p.get('age', 'new') == 'old' and \
|
|---|
| 509 | p.get('value', '') == '-':
|
|---|
| 510 | GError(
|
|---|
| 511 | parent=self._guiparent,
|
|---|
| 512 | message=_(
|
|---|
| 513 | "Unable to run command:\n%(cmd)s\n\n"
|
|---|
| 514 | "Option <%(opt)s>: read from standard input is not "
|
|---|
| 515 | "supported by wxGUI") % {
|
|---|
| 516 | 'cmd': ' '.join(command),
|
|---|
| 517 | 'opt': p.get(
|
|---|
| 518 | 'name',
|
|---|
| 519 | '')})
|
|---|
| 520 | return
|
|---|
| 521 |
|
|---|
| 522 | if len(command) == 1:
|
|---|
| 523 | if command[0].startswith('g.gui.'):
|
|---|
| 524 | import imp
|
|---|
| 525 | import inspect
|
|---|
| 526 | pyFile = command[0]
|
|---|
| 527 | if sys.platform == 'win32':
|
|---|
| 528 | pyFile += '.py'
|
|---|
| 529 | pyPath = os.path.join(
|
|---|
| 530 | os.environ['GISBASE'], 'scripts', pyFile)
|
|---|
| 531 | if not os.path.exists(pyPath):
|
|---|
| 532 | pyPath = os.path.join(
|
|---|
| 533 | os.environ['GRASS_ADDON_BASE'], 'scripts', pyFile)
|
|---|
| 534 | if not os.path.exists(pyPath):
|
|---|
| 535 | GError(
|
|---|
| 536 | parent=self._guiparent,
|
|---|
| 537 | message=_("Module <%s> not found.") %
|
|---|
| 538 | command[0])
|
|---|
| 539 | pymodule = imp.load_source(
|
|---|
| 540 | command[0].replace('.', '_'), pyPath)
|
|---|
| 541 | pymain = inspect.getargspec(pymodule.main)
|
|---|
| 542 | if pymain and 'giface' in pymain.args:
|
|---|
| 543 | pymodule.main(self._giface)
|
|---|
| 544 | return
|
|---|
| 545 |
|
|---|
| 546 | # no arguments given
|
|---|
| 547 | if hasParams and \
|
|---|
| 548 | not isinstance(self._guiparent, FormNotebook):
|
|---|
| 549 | # also parent must be checked, see #3135 for details
|
|---|
| 550 | try:
|
|---|
| 551 | GUI(parent=self._guiparent,
|
|---|
| 552 | giface=self._giface).ParseCommand(command)
|
|---|
| 553 | except GException as e:
|
|---|
| 554 | print(e, file=sys.stderr)
|
|---|
| 555 |
|
|---|
| 556 | return
|
|---|
| 557 |
|
|---|
| 558 | if env:
|
|---|
| 559 | env = env.copy()
|
|---|
| 560 | else:
|
|---|
| 561 | env = os.environ.copy()
|
|---|
| 562 | # activate computational region (set with g.region)
|
|---|
| 563 | # for all non-display commands.
|
|---|
| 564 | if compReg and "GRASS_REGION" in env:
|
|---|
| 565 | del env["GRASS_REGION"]
|
|---|
| 566 |
|
|---|
| 567 | # process GRASS command with argument
|
|---|
| 568 | self.cmdThread.RunCmd(command,
|
|---|
| 569 | stdout=self.cmdStdOut,
|
|---|
| 570 | stderr=self.cmdStdErr,
|
|---|
| 571 | onDone=onDone, onPrepare=onPrepare,
|
|---|
| 572 | userData=userData, addLayer=addLayer,
|
|---|
| 573 | env=env,
|
|---|
| 574 | notification=notification)
|
|---|
| 575 | self.cmdOutputTimer.Start(50)
|
|---|
| 576 |
|
|---|
| 577 | # we don't need to change computational region settings
|
|---|
| 578 | # because we work on a copy
|
|---|
| 579 | else:
|
|---|
| 580 | # Send any other command to the shell. Send output to
|
|---|
| 581 | # console output window
|
|---|
| 582 | #
|
|---|
| 583 | # Check if the script has an interface (avoid double-launching
|
|---|
| 584 | # of the script)
|
|---|
| 585 |
|
|---|
| 586 | # check if we ignore the command (similar to grass commands part)
|
|---|
| 587 | if self._ignoredCmdPattern and \
|
|---|
| 588 | re.compile(self._ignoredCmdPattern).search(' '.join(command)):
|
|---|
| 589 | event = gIgnoredCmdRun(cmd=command)
|
|---|
| 590 | wx.PostEvent(self, event)
|
|---|
| 591 | return
|
|---|
| 592 |
|
|---|
| 593 | skipInterface = True
|
|---|
| 594 | if os.path.splitext(command[0])[1] in ('.py', '.sh'):
|
|---|
| 595 | try:
|
|---|
| 596 | sfile = open(command[0], "r")
|
|---|
| 597 | for line in sfile.readlines():
|
|---|
| 598 | if len(line) < 2:
|
|---|
| 599 | continue
|
|---|
| 600 | if line[0] is '#' and line[1] is '%':
|
|---|
| 601 | skipInterface = False
|
|---|
| 602 | break
|
|---|
| 603 | sfile.close()
|
|---|
| 604 | except IOError:
|
|---|
| 605 | pass
|
|---|
| 606 |
|
|---|
| 607 | if len(command) == 1 and not skipInterface:
|
|---|
| 608 | try:
|
|---|
| 609 | task = gtask.parse_interface(command[0])
|
|---|
| 610 | except:
|
|---|
| 611 | task = None
|
|---|
| 612 | else:
|
|---|
| 613 | task = None
|
|---|
| 614 |
|
|---|
| 615 | if task:
|
|---|
| 616 | # process GRASS command without argument
|
|---|
| 617 | GUI(parent=self._guiparent,
|
|---|
| 618 | giface=self._giface).ParseCommand(command)
|
|---|
| 619 | else:
|
|---|
| 620 | self.cmdThread.RunCmd(command,
|
|---|
| 621 | stdout=self.cmdStdOut,
|
|---|
| 622 | stderr=self.cmdStdErr,
|
|---|
| 623 | onDone=onDone, onPrepare=onPrepare,
|
|---|
| 624 | userData=userData, addLayer=addLayer,
|
|---|
| 625 | env=env,
|
|---|
| 626 | notification=notification)
|
|---|
| 627 | self.cmdOutputTimer.Start(50)
|
|---|
| 628 |
|
|---|
| 629 | def GetLog(self, err=False):
|
|---|
| 630 | """Get widget used for logging
|
|---|
| 631 |
|
|---|
| 632 | .. todo::
|
|---|
| 633 | what's this?
|
|---|
| 634 |
|
|---|
| 635 | :param bool err: True to get stderr widget
|
|---|
| 636 | """
|
|---|
| 637 | if err:
|
|---|
| 638 | return self.cmdStdErr
|
|---|
| 639 |
|
|---|
| 640 | return self.cmdStdOut
|
|---|
| 641 |
|
|---|
| 642 | def GetCmd(self):
|
|---|
| 643 | """Get running command or None"""
|
|---|
| 644 | return self.requestQ.get()
|
|---|
| 645 |
|
|---|
| 646 | def OnCmdAbort(self, event):
|
|---|
| 647 | """Abort running command"""
|
|---|
| 648 | self.cmdThread.abort()
|
|---|
| 649 | event.Skip()
|
|---|
| 650 |
|
|---|
| 651 | def OnCmdRun(self, event):
|
|---|
| 652 | """Run command"""
|
|---|
| 653 | self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)),
|
|---|
| 654 | notification=event.notification)
|
|---|
| 655 | event.Skip()
|
|---|
| 656 |
|
|---|
| 657 | def OnCmdDone(self, event):
|
|---|
| 658 | """Command done (or aborted)
|
|---|
| 659 |
|
|---|
| 660 | Sends signal mapCreated if map is recognized in output
|
|---|
| 661 | parameters or for specific modules (as r.colors).
|
|---|
| 662 | """
|
|---|
| 663 | # Process results here
|
|---|
| 664 | try:
|
|---|
| 665 | ctime = time.time() - event.time
|
|---|
| 666 | if ctime < 60:
|
|---|
| 667 | stime = _("%d sec") % int(ctime)
|
|---|
| 668 | else:
|
|---|
| 669 | mtime = int(ctime / 60)
|
|---|
| 670 | stime = _("%(min)d min %(sec)d sec") % {
|
|---|
| 671 | 'min': mtime, 'sec': int(ctime - (mtime * 60))}
|
|---|
| 672 | except KeyError:
|
|---|
| 673 | # stopped deamon
|
|---|
| 674 | stime = _("unknown")
|
|---|
| 675 |
|
|---|
| 676 | if event.aborted:
|
|---|
| 677 | # Thread aborted (using our convention of None return)
|
|---|
| 678 | self.WriteWarning(_('Please note that the data are left in'
|
|---|
| 679 | ' inconsistent state and may be corrupted'))
|
|---|
| 680 | msg = _('Command aborted')
|
|---|
| 681 | else:
|
|---|
| 682 | msg = _('Command finished')
|
|---|
| 683 |
|
|---|
| 684 | self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime),
|
|---|
| 685 | notification=event.notification)
|
|---|
| 686 |
|
|---|
| 687 | if event.onDone:
|
|---|
| 688 | event.onDone(event)
|
|---|
| 689 |
|
|---|
| 690 | self.cmdOutputTimer.Stop()
|
|---|
| 691 |
|
|---|
| 692 | if event.cmd[0] == 'g.gisenv':
|
|---|
| 693 | Debug.SetLevel()
|
|---|
| 694 | self.Redirect()
|
|---|
| 695 |
|
|---|
| 696 | # do nothing when no map added
|
|---|
| 697 | if event.returncode != 0 or event.aborted:
|
|---|
| 698 | event.Skip()
|
|---|
| 699 | return
|
|---|
| 700 |
|
|---|
| 701 | if event.cmd[0] not in globalvar.grassCmd:
|
|---|
| 702 | return
|
|---|
| 703 |
|
|---|
| 704 | # find which maps were created
|
|---|
| 705 | try:
|
|---|
| 706 | task = GUI(show=None).ParseCommand(event.cmd)
|
|---|
| 707 | except GException as e:
|
|---|
| 708 | print(e, file=sys.stderr)
|
|---|
| 709 | task = None
|
|---|
| 710 | return
|
|---|
| 711 |
|
|---|
| 712 | name = task.get_name()
|
|---|
| 713 | for p in task.get_options()['params']:
|
|---|
| 714 | prompt = p.get('prompt', '')
|
|---|
| 715 | if prompt in (
|
|---|
| 716 | 'raster', 'vector', 'raster_3d') and p.get(
|
|---|
| 717 | 'value', None):
|
|---|
| 718 | if p.get('age', 'old') == 'new' or name in (
|
|---|
| 719 | 'r.colors', 'r3.colors', 'v.colors', 'v.proj', 'r.proj'):
|
|---|
| 720 | # if multiple maps (e.g. r.series.interp), we need add each
|
|---|
| 721 | if p.get('multiple', False):
|
|---|
| 722 | lnames = p.get('value').split(',')
|
|---|
| 723 | # in case multiple input (old) maps in r.colors
|
|---|
| 724 | # we don't want to rerender it multiple times! just
|
|---|
| 725 | # once
|
|---|
| 726 | if p.get('age', 'old') == 'old':
|
|---|
| 727 | lnames = lnames[0:1]
|
|---|
| 728 | else:
|
|---|
| 729 | lnames = [p.get('value')]
|
|---|
| 730 | for lname in lnames:
|
|---|
| 731 | if '@' not in lname:
|
|---|
| 732 | lname += '@' + grass.gisenv()['MAPSET']
|
|---|
| 733 | if grass.find_file(lname, element=p.get('element'))[
|
|---|
| 734 | 'fullname']:
|
|---|
| 735 | self.mapCreated.emit(
|
|---|
| 736 | name=lname, ltype=prompt, add=event.addLayer)
|
|---|
| 737 | if name == 'r.mask':
|
|---|
| 738 | self.updateMap.emit()
|
|---|
| 739 |
|
|---|
| 740 | event.Skip()
|
|---|
| 741 |
|
|---|
| 742 | def OnProcessPendingOutputWindowEvents(self, event):
|
|---|
| 743 | wx.GetApp().ProcessPendingEvents()
|
|---|
| 744 |
|
|---|
| 745 | def UpdateHistoryFile(self, command):
|
|---|
| 746 | """Update history file
|
|---|
| 747 |
|
|---|
| 748 | :param command: the command given as a string
|
|---|
| 749 | """
|
|---|
| 750 | env = grass.gisenv()
|
|---|
| 751 | try:
|
|---|
| 752 | filePath = os.path.join(env['GISDBASE'],
|
|---|
| 753 | env['LOCATION_NAME'],
|
|---|
| 754 | env['MAPSET'],
|
|---|
| 755 | '.bash_history')
|
|---|
| 756 | fileHistory = codecs.open(filePath, encoding='utf-8', mode='a')
|
|---|
| 757 | except IOError as e:
|
|---|
| 758 | GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
|
|---|
| 759 | {'filePath': filePath, 'error': e},
|
|---|
| 760 | parent=self._guiparent)
|
|---|
| 761 | return
|
|---|
| 762 |
|
|---|
| 763 | try:
|
|---|
| 764 | fileHistory.write(command + os.linesep)
|
|---|
| 765 | finally:
|
|---|
| 766 | fileHistory.close()
|
|---|
| 767 |
|
|---|
| 768 | # update wxGUI prompt
|
|---|
| 769 | if self._giface:
|
|---|
| 770 | self._giface.UpdateCmdHistory(command)
|
|---|