4
@brief Command output widgets
12
(C) 2007-2014 by the GRASS Development Team
14
This program is free software under the GNU General Public License
15
(>=v2). Read the file COPYING that comes with GRASS for details.
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)
33
from wx.lib.newevent import NewEvent
35
import grass.script as grass
36
from grass.script import task as gtask
38
from grass.pydispatch.signal import Signal
40
from core import globalvar
41
from core.gcmd import CommandThread, GError, GException
42
from core.utils import _
43
from gui_core.forms import GUI
44
from core.debug import Debug
45
from core.settings import UserSettings
46
from core.giface import Notification
49
wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
50
wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
51
wxCmdRun, EVT_CMD_RUN = NewEvent()
52
wxCmdDone, EVT_CMD_DONE = NewEvent()
53
wxCmdAbort, EVT_CMD_ABORT = NewEvent()
54
wxCmdPrepare, EVT_CMD_PREPARE = NewEvent()
57
def GrassCmd(cmd, env=None, stdout=None, stderr=None):
58
"""Return GRASS command thread"""
59
return CommandThread(cmd, env=env,
60
stdout=stdout, stderr=stderr)
63
class CmdThread(threading.Thread):
64
"""Thread for GRASS commands"""
67
def __init__(self, receiver, requestQ=None, resultQ=None, **kwds):
69
:param receiver: event receiver (used in PostEvent)
71
threading.Thread.__init__(self, **kwds)
74
self.requestQ = Queue.Queue()
76
self.requestQ = requestQ
79
self.resultQ = Queue.Queue()
81
self.resultQ = resultQ
85
self.requestCmd = None
87
self.receiver = receiver
88
self._want_abort_all = False
92
def RunCmd(self, *args, **kwds):
93
"""Run command in queue
95
:param args: unnamed command arguments
96
:param kwds: named command arguments
98
:return: request id in queue
100
CmdThread.requestId += 1
102
self.requestCmd = None
103
self.requestQ.put((CmdThread.requestId, args, kwds))
105
return CmdThread.requestId
108
"""Get id for next command"""
109
return CmdThread.requestId + 1
112
"""Set starting id"""
113
CmdThread.requestId = id
116
os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
118
requestId, args, kwds = self.requestQ.get()
119
for key in ('callable', 'onDone', 'onPrepare', 'userData', 'notification'):
121
vars()[key] = kwds[key]
126
if not vars()['callable']:
127
vars()['callable'] = GrassCmd
129
requestTime = time.time()
133
event = wxCmdPrepare(cmd=args[0],
136
onPrepare=vars()['onPrepare'],
137
userData=vars()['userData'])
139
wx.PostEvent(self.receiver, event)
142
event = wxCmdRun(cmd=args[0],
144
notification=vars()['notification'])
146
wx.PostEvent(self.receiver, event)
149
self.requestCmd = vars()['callable'](*args, **kwds)
150
if self._want_abort_all and self.requestCmd is not None:
151
self.requestCmd.abort()
152
if self.requestQ.empty():
153
self._want_abort_all = False
155
self.resultQ.put((requestId, self.requestCmd.run()))
158
returncode = self.requestCmd.module.returncode
159
except AttributeError:
160
returncode = 0 # being optimistic
163
aborted = self.requestCmd.aborted
164
except AttributeError:
169
# set default color table for raster data
170
if UserSettings.Get(group='rasterLayer',
171
key='colorTable', subkey='enabled') and \
172
args[0][0][:2] == 'r.':
173
colorTable = UserSettings.Get(group='rasterLayer',
177
if args[0][0] == 'r.mapcalc':
179
mapName = args[0][1].split('=', 1)[0].strip()
183
moduleInterface = GUI(show=None).ParseCommand(args[0])
184
outputParam = moduleInterface.get_param(value='output',
186
if outputParam and outputParam['prompt'] == 'raster':
187
mapName = outputParam['value']
190
argsColor = list(args)
191
argsColor[0] = ['r.colors',
193
'color=%s' % colorTable]
194
self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
195
self.resultQ.put((requestId, self.requestCmdColor.run()))
198
event = wxCmdDone(cmd=args[0],
200
returncode=returncode,
203
onDone=vars()['onDone'],
204
userData=vars()['userData'],
205
notification=vars()['notification'])
208
wx.PostEvent(self.receiver, event)
210
def abort(self, abortall=True):
211
"""Abort command(s)"""
213
self._want_abort_all = True
214
if self.requestCmd is not None:
215
self.requestCmd.abort()
216
if self.requestQ.empty():
217
self._want_abort_all = False
220
"""GConsole standard output
222
Based on FrameOutErr.py
225
Purpose: Redirecting stdout / stderr
226
Author: Jean-Michel Fauth, Switzerland
227
Copyright: (c) 2005-2007 Jean-Michel Fauth
230
def __init__(self, receiver):
232
:param receiver: event receiver (used in PostEvent)
234
self.receiver = receiver
240
if len(s) == 0 or s == '\n':
243
for line in s.splitlines():
247
evt = wxCmdOutput(text=line + '\n',
249
wx.PostEvent(self.receiver, evt)
253
"""GConsole standard error output
255
Based on FrameOutErr.py
258
Purpose: Redirecting stdout / stderr
259
Author: Jean-Michel Fauth, Switzerland
260
Copyright: (c) 2005-2007 Jean-Michel Fauth
263
def __init__(self, receiver):
265
:param receiver: event receiver (used in PostEvent)
267
self.receiver = receiver
270
self.printMessage = False
279
# remove/replace escape sequences '\b' or '\r' from stream
282
for line in s.splitlines():
286
if 'GRASS_INFO_PERCENT' in line:
287
value = int(line.rsplit(':', 1)[1].strip())
288
if value >= 0 and value < 100:
289
progressValue = value
292
elif 'GRASS_INFO_MESSAGE' in line:
293
self.type = 'message'
294
self.message += line.split(':', 1)[1].strip() + '\n'
295
elif 'GRASS_INFO_WARNING' in line:
296
self.type = 'warning'
297
self.message += line.split(':', 1)[1].strip() + '\n'
298
elif 'GRASS_INFO_ERROR' in line:
300
self.message += line.split(':', 1)[1].strip() + '\n'
301
elif 'GRASS_INFO_END' in line:
302
self.printMessage = True
303
elif self.type == '':
306
evt = wxCmdOutput(text=line,
308
wx.PostEvent(self.receiver, evt)
310
self.message += line.strip() + '\n'
312
if self.printMessage and len(self.message) > 0:
313
evt = wxCmdOutput(text=self.message,
315
wx.PostEvent(self.receiver, evt)
319
self.printMessage = False
321
# update progress message
322
if progressValue > -1:
323
# self.gmgauge.SetValue(progressValue)
324
evt = wxCmdProgress(value=progressValue)
325
wx.PostEvent(self.receiver, evt)
328
# Occurs when an ignored command is called.
329
# Attribute cmd contains command (as a list).
330
gIgnoredCmdRun, EVT_IGNORED_CMD_RUN = NewEvent()
333
class GConsole(wx.EvtHandler):
336
def __init__(self, guiparent=None, giface=None, ignoredCmdPattern=None):
338
:param guiparent: parent window for created GUI objects
339
:param lmgr: layer manager window (TODO: replace by giface)
340
:param ignoredCmdPattern: regular expression specifying commads
341
to be ignored (e.g. @c '^d\..*' for
344
wx.EvtHandler.__init__(self)
346
# Signal when some map is created or updated by a module.
347
# attributes: name: map name, ltype: map type,
348
self.mapCreated = Signal('GConsole.mapCreated')
349
# emitted when map display should be re-render
350
self.updateMap = Signal('GConsole.updateMap')
351
# emitted when log message should be written
352
self.writeLog = Signal('GConsole.writeLog')
353
# emitted when command log message should be written
354
self.writeCmdLog = Signal('GConsole.writeCmdLog')
355
# emitted when warning message should be written
356
self.writeWarning = Signal('GConsole.writeWarning')
357
# emitted when error message should be written
358
self.writeError = Signal('GConsole.writeError')
360
self._guiparent = guiparent
361
self._giface = giface
362
self._ignoredCmdPattern = ignoredCmdPattern
365
self.requestQ = Queue.Queue()
366
self.resultQ = Queue.Queue()
368
self.cmdOutputTimer = wx.Timer(self)
369
self.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
370
self.Bind(EVT_CMD_RUN, self.OnCmdRun)
371
self.Bind(EVT_CMD_DONE, self.OnCmdDone)
372
self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
375
self.cmdStdOut = GStdout(receiver=self)
376
self.cmdStdErr = GStderr(receiver=self)
379
self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
382
"""Redirect stdout/stderr
384
if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0:
385
# don't redirect when debugging is enabled
386
sys.stdout = self.cmdStdOut
387
sys.stderr = self.cmdStdErr
389
enc = locale.getdefaultlocale()[1]
391
sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
392
sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
394
sys.stdout = sys.__stdout__
395
sys.stderr = sys.__stderr__
397
def WriteLog(self, text, style=None, wrap=None,
398
notification=Notification.HIGHLIGHT):
399
"""Generic method for writing log message in
402
:param text: text line
403
:param notification: form of notification
405
self.writeLog.emit(text=text, wrap=wrap,
406
notification=notification)
408
def WriteCmdLog(self, text, pid=None, notification=Notification.MAKE_VISIBLE):
409
"""Write message in selected style
411
:param text: message to be printed
412
:param pid: process pid or None
413
:param notification: form of notification
415
self.writeCmdLog.emit(text=text, pid=pid,
416
notification=notification)
418
def WriteWarning(self, text):
419
"""Write message in warning style"""
420
self.writeWarning.emit(text=text)
422
def WriteError(self, text):
423
"""Write message in error style"""
424
self.writeError.emit(text=text)
426
def RunCmd(self, command, compReg=True, skipInterface=False,
427
onDone=None, onPrepare=None, userData=None, notification=Notification.MAKE_VISIBLE):
428
"""Run command typed into console command prompt (GPrompt).
431
Document the other event.
433
Solve problem with the other event (now uses gOutputText
434
event but there is no text, use onPrepare handler instead?)
436
Posts event EVT_IGNORED_CMD_RUN when command which should be ignored
437
(according to ignoredCmdPattern) is run.
438
For example, see layer manager which handles d.* on its own.
440
:param command: command given as a list (produced e.g. by utils.split())
441
:param compReg: True use computation region
442
:param notification: form of notification
443
:param bool skipInterface: True to do not launch GRASS interface
444
parser when command has no arguments
446
:param onDone: function to be called when command is finished
447
:param onPrepare: function to be called before command is launched
448
:param userData: data defined for the command
450
if len(command) == 0:
451
Debug.msg(2, "GPrompt:RunCmd(): empty command")
454
# update history file
455
self.UpdateHistoryFile(' '.join(command))
457
if command[0] in globalvar.grassCmd:
458
# send GRASS command without arguments to GUI command interface
459
# except ignored commands (event is emitted)
461
if self._ignoredCmdPattern and \
462
re.compile(self._ignoredCmdPattern).search(' '.join(command)) and \
463
'--help' not in command and '--ui' not in command:
464
event = gIgnoredCmdRun(cmd=command)
465
wx.PostEvent(self, event)
469
# other GRASS commands (r|v|g|...)
471
task = GUI(show=None).ParseCommand(command)
472
except GException as e:
473
GError(parent=self._guiparent,
480
options = task.get_options()
481
hasParams = options['params'] and options['flags']
482
# check for <input>=-
483
for p in options['params']:
484
if p.get('prompt', '') == 'input' and \
485
p.get('element', '') == 'file' and \
486
p.get('age', 'new') == 'old' and \
487
p.get('value', '') == '-':
488
GError(parent=self._guiparent,
489
message=_("Unable to run command:\n%(cmd)s\n\n"
490
"Option <%(opt)s>: read from standard input is not "
491
"supported by wxGUI") % {'cmd': ' '.join(command),
492
'opt': p.get('name', '')})
495
if len(command) == 1 and hasParams and \
496
command[0] != 'v.krige':
499
GUI(parent=self._guiparent, giface=self._giface).ParseCommand(command)
500
except GException as e:
501
print >> sys.stderr, e
504
# activate computational region (set with g.region)
505
# for all non-display commands.
507
tmpreg = os.getenv("GRASS_REGION")
508
if "GRASS_REGION" in os.environ:
509
del os.environ["GRASS_REGION"]
511
# process GRASS command with argument
512
self.cmdThread.RunCmd(command,
513
stdout=self.cmdStdOut,
514
stderr=self.cmdStdErr,
515
onDone=onDone, onPrepare=onPrepare,
517
env=os.environ.copy(),
518
notification=notification)
519
self.cmdOutputTimer.Start(50)
521
# deactivate computational region and return to display settings
522
if compReg and tmpreg:
523
os.environ["GRASS_REGION"] = tmpreg
525
# Send any other command to the shell. Send output to
526
# console output window
528
# Check if the script has an interface (avoid double-launching
531
# check if we ignore the command (similar to grass commands part)
532
if self._ignoredCmdPattern and \
533
re.compile(self._ignoredCmdPattern).search(' '.join(command)):
534
event = gIgnoredCmdRun(cmd=command)
535
wx.PostEvent(self, event)
539
if os.path.splitext(command[0])[1] in ('.py', '.sh'):
541
sfile = open(command[0], "r")
542
for line in sfile.readlines():
545
if line[0] is '#' and line[1] is '%':
546
skipInterface = False
552
if len(command) == 1 and not skipInterface:
554
task = gtask.parse_interface(command[0])
561
# process GRASS command without argument
562
GUI(parent=self._guiparent, giface=self._giface).ParseCommand(command)
564
self.cmdThread.RunCmd(command,
565
stdout=self.cmdStdOut,
566
stderr=self.cmdStdErr,
567
onDone=onDone, onPrepare=onPrepare,
569
notification=notification)
570
self.cmdOutputTimer.Start(50)
572
def GetLog(self, err=False):
573
"""Get widget used for logging
578
:param bool err: True to get stderr widget
581
return self.cmdStdErr
583
return self.cmdStdOut
586
"""Get running command or None"""
587
return self.requestQ.get()
589
def OnCmdAbort(self, event):
590
"""Abort running command"""
591
self.cmdThread.abort()
594
def OnCmdRun(self, event):
596
self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)),
597
notification=event.notification)
600
def OnCmdDone(self, event):
601
"""Command done (or aborted)
603
Sends signal mapCreated if map is recognized in output
604
parameters or for specific modules (as r.colors).
606
# Process results here
608
ctime = time.time() - event.time
610
stime = _("%d sec") % int(ctime)
612
mtime = int(ctime / 60)
613
stime = _("%(min)d min %(sec)d sec") % {'min': mtime,
614
'sec': int(ctime - (mtime * 60))}
620
# Thread aborted (using our convention of None return)
621
self.WriteWarning(_('Please note that the data are left in'
622
' inconsistent state and may be corrupted'))
623
msg = _('Command aborted')
625
msg = _('Command finished')
627
self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime),
628
notification=event.notification)
631
event.onDone(cmd=event.cmd, returncode=event.returncode)
633
self.cmdOutputTimer.Stop()
635
if event.cmd[0] == 'g.gisenv':
639
# do nothing when no map added
640
if event.returncode != 0 or event.aborted:
644
if event.cmd[0] not in globalvar.grassCmd:
647
# find which maps were created
649
task = GUI(show=None).ParseCommand(event.cmd)
650
except GException as e:
651
print >> sys.stderr, e
655
name = task.get_name()
656
for p in task.get_options()['params']:
657
prompt = p.get('prompt', '')
658
if prompt in ('raster', 'vector', '3d-raster') and p.get('value', None):
659
if p.get('age', 'old') == 'new' or \
660
name in ('r.colors', 'r3.colors', 'v.colors', 'v.proj', 'r.proj'):
661
# if multiple maps (e.g. r.series.interp), we need add each
662
if p.get('multiple', False):
663
lnames = p.get('value').split(',')
664
# in case multiple input (old) maps in r.colors
665
# we don't want to rerender it multiple times! just once
666
if p.get('age', 'old') == 'old':
669
lnames = [p.get('value')]
672
lname += '@' + grass.gisenv()['MAPSET']
673
self.mapCreated.emit(name=lname, ltype=prompt)
675
self.updateMap.emit()
679
def OnProcessPendingOutputWindowEvents(self, event):
680
wx.GetApp().ProcessPendingEvents()
682
def UpdateHistoryFile(self, command):
683
"""Update history file
685
:param command: the command given as a string
689
filePath = os.path.join(env['GISDBASE'],
690
env['LOCATION_NAME'],
693
fileHistory = codecs.open(filePath, encoding='utf-8', mode='a')
695
GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
696
{'filePath': filePath, 'error': e},
697
parent=self._guiparent)
701
fileHistory.write(command + os.linesep)
705
# update wxGUI prompt
707
self._giface.UpdateCmdHistory(command)