GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
task.py
Go to the documentation of this file.
1 """!@package grass.script.task
2 
3 @brief GRASS Python scripting module (task)
4 
5 Get interface description of GRASS commands
6 
7 Based on gui/wxpython/gui_modules/menuform.py
8 
9 Usage:
10 
11 @code
12 from grass.script import task as gtask
13 
14 gtask.command_info('r.info')
15 ...
16 @endcode
17 
18 (C) 2011 by the GRASS Development Team
19 This program is free software under the GNU General Public
20 License (>=v2). Read the file COPYING that comes with GRASS
21 for details.
22 
23 @author Martin Landa <landa.martin gmail.com>
24 """
25 
26 import types
27 import string
28 try:
29  import xml.etree.ElementTree as etree
30 except ImportError:
31  import elementtree.ElementTree as etree # Python <= 2.4
32 
33 from core import *
34 
35 class grassTask:
36  """!This class holds the structures needed for filling by the
37  parser
38 
39  Parameter blackList is a dictionary with fixed structure, eg.
40 
41  @code
42  blackList = {'items' : {'d.legend' : { 'flags' : ['m'],
43  'params' : [] }},
44  'enabled': True}
45  @endcode
46 
47  @param path full path
48  @param blackList hide some options in the GUI (dictionary)
49  """
50  def __init__(self, path = None, blackList = None):
51  self.path = path
52  self.name = _('unknown')
53  self.params = list()
54  self.description = ''
55  self.label = ''
56  self.flags = list()
57  self.keywords = list()
58  self.errorMsg = ''
59  self.firstParam = None
60  if blackList:
61  self.blackList = blackList
62  else:
63  self.blackList = { 'enabled' : False, 'items' : {} }
64 
65  if path is not None:
66  try:
67  processTask(tree = etree.fromstring(get_interface_description(path)),
68  task = self)
69  except ScriptError, e:
70  self.errorMsg = e.value
71 
72  self.define_first()
73 
74  def define_first(self):
75  """!Define first parameter
76  """
77  if len(self.params) > 0:
78  self.firstParam = self.params[0]['name']
79 
80  def get_error_msg(self):
81  """!Get error message ('' for no error)
82  """
83  return self.errorMsg
84 
85  def get_name(self):
86  """!Get task name
87  """
88  return self.name
89 
90  def get_description(self, full = True):
91  """!Get module's description
92 
93  @param full True for label + desc
94  """
95  if self.label:
96  if full:
97  return self.label + ' ' + self.description
98  else:
99  return self.label
100  else:
101  return self.description
102 
103  def get_keywords(self):
104  """!Get module's keywords
105  """
106  return self.keywords
107 
108  def get_list_params(self, element = 'name'):
109  """!Get list of parameters
110 
111  @param element element name
112  """
113  params = []
114  for p in self.params:
115  params.append(p[element])
116 
117  return params
118 
119  def get_list_flags(self, element = 'name'):
120  """!Get list of flags
121 
122  @param element element name
123  """
124  flags = []
125  for p in self.flags:
126  flags.append(p[element])
127 
128  return flags
129 
130  def get_param(self, value, element = 'name', raiseError = True):
131  """!Find and return a param by name
132 
133  @param value param's value
134  @param element element name
135  @param raiseError True for raise on error
136  """
137  try:
138  for p in self.params:
139  val = p[element]
140  if val is None:
141  continue
142  if type(val) in (types.ListType, types.TupleType):
143  if value in val:
144  return p
145  elif type(val) == types.StringType:
146  if p[element][:len(value)] == value:
147  return p
148  else:
149  if p[element] == value:
150  return p
151  except KeyError:
152  pass
153 
154  if raiseError:
155  raise ValueError, _("Parameter element '%(element)s' not found: '%(value)s'") % \
156  { 'element' : element, 'value' : value }
157  else:
158  return None
159 
160  def get_flag(self, aFlag):
161  """!Find and return a flag by name
162 
163  Raises ValueError when the flag is not found.
164 
165  @param aFlag name of the flag
166  """
167  for f in self.flags:
168  if f['name'] == aFlag:
169  return f
170  raise ValueError, _("Flag not found: %s") % aFlag
171 
172  def getCmdError(self):
173  """!Get error string produced by getCmd(ignoreErrors = False)
174 
175  @return list of errors
176  """
177  errorList = list()
178  # determine if suppress_required flag is given
179  for f in self.flags:
180  if f['value'] and f['suppress_required']:
181  return errorList
182 
183  for p in self.params:
184  if not p.get('value', '') and p.get('required', False):
185  if not p.get('default', ''):
186  desc = p.get('label', '')
187  if not desc:
188  desc = p['description']
189  errorList.append(_("Parameter '%(name)s' (%(desc)s) is missing.") % \
190  {'name' : p['name'], 'desc' : desc })
191 
192  return errorList
193 
194  def getCmd(self, ignoreErrors = False, ignoreRequired = False):
195  """!Produce an array of command name and arguments for feeding
196  into some execve-like command processor.
197 
198  @param ignoreErrors True to return whatever has been built so
199  far, even though it would not be a correct command for GRASS
200  @param ignoreRequired True to ignore required flags, otherwise
201  '<required>' is shown
202  """
203  cmd = [self.name]
204 
205  suppress_required = False
206  for flag in self.flags:
207  if flag['value']:
208  if len(flag['name']) > 1: # e.g. overwrite
209  cmd += [ '--' + flag['name'] ]
210  else:
211  cmd += [ '-' + flag['name'] ]
212  if flag['suppress_required']:
213  suppress_required = True
214  for p in self.params:
215  if p.get('value','') == '' and p.get('required', False):
216  if p.get('default', '') != '':
217  cmd += [ '%s=%s' % (p['name'], p['default']) ]
218  elif ignoreErrors and not suppress_required and not ignoreRequired:
219  cmd += [ '%s=%s' % (p['name'], _('<required>')) ]
220  elif p.get('value','') != '' and p['value'] != p.get('default','') :
221  # Output only values that have been set, and different from defaults
222  cmd += [ '%s=%s' % (p['name'], p['value']) ]
223 
224  errList = self.getCmdError()
225  if ignoreErrors is False and errList:
226  raise ValueError, '\n'.join(errList)
227 
228  return cmd
229 
230  def get_options(self):
231  """!Get options
232  """
233  return { 'flags' : self.flags,
234  'params' : self.params }
235 
236  def has_required(self):
237  """!Check if command has at least one required paramater
238  """
239  for p in self.params:
240  if p.get('required', False):
241  return True
242 
243  return False
244 
245  def set_param(self, aParam, aValue, element = 'value'):
246  """!Set param value/values.
247  """
248  try:
249  param = self.get_param(aParam)
250  except ValueError:
251  return
252 
253  param[element] = aValue
254 
255  def set_flag(self, aFlag, aValue, element = 'value'):
256  """!Enable / disable flag.
257  """
258  try:
259  param = self.get_flag(aFlag)
260  except ValueError:
261  return
262 
263  param[element] = aValue
264 
265  def set_options(self, opts):
266  """!Set flags and parameters
267 
268  @param opts list of flags and parameters"""
269  for opt in opts:
270  if opt[0] == '-': # flag
271  self.set_flag(opt.lstrip('-'), True)
272  else: # parameter
273  key, value = opt.split('=', 1)
274  self.set_param(key, value)
275 
277  """!A ElementTree handler for the --interface-description output,
278  as defined in grass-interface.dtd. Extend or modify this and the
279  DTD if the XML output of GRASS' parser is extended or modified.
280 
281  @param tree root tree node
282  @param task grassTask instance or None
283  @param blackList list of flags/params to hide
284 
285  @return grassTask instance
286  """
287  def __init__(self, tree, task = None, blackList = None):
288  if task:
289  self.task = task
290  else:
291  self.task = grassTask()
292  if blackList:
293  self.task.blackList = blackList
294 
295  self.root = tree
296 
297  self._process_module()
298  self._process_params()
299  self._process_flags()
300  self.task.define_first()
301 
302  def _process_module(self):
303  """!Process module description
304  """
305  self.task.name = self.root.get('name', default = 'unknown')
306 
307  # keywords
308  for keyword in self._get_node_text(self.root, 'keywords').split(','):
309  self.task.keywords.append(keyword.strip())
310 
311  self.task.label = self._get_node_text(self.root, 'label')
312  self.task.description = self._get_node_text(self.root, 'description')
313 
314  def _process_params(self):
315  """!Process parameters
316  """
317  for p in self.root.findall('parameter'):
318  # gisprompt
319  node_gisprompt = p.find('gisprompt')
320  gisprompt = False
321  age = element = prompt = None
322  if node_gisprompt is not None:
323  gisprompt = True
324  age = node_gisprompt.get('age', '')
325  element = node_gisprompt.get('element', '')
326  prompt = node_gisprompt.get('prompt', '')
327 
328  # value(s)
329  values = []
330  values_desc = []
331  node_values = p.find('values')
332  if node_values is not None:
333  for pv in node_values.findall('value'):
334  values.append(self._get_node_text(pv, 'name'))
335  desc = self._get_node_text(pv, 'description')
336  if desc:
337  values_desc.append(desc)
338 
339  # keydesc
340  key_desc = []
341  node_key_desc = p.find('keydesc')
342  if node_key_desc is not None:
343  for ki in node_key_desc.findall('item'):
344  key_desc.append(ki.text)
345 
346  if p.get('multiple', 'no') == 'yes':
347  multiple = True
348  else:
349  multiple = False
350  if p.get('required', 'no') == 'yes':
351  required = True
352  else:
353  required = False
354 
355  if self.task.blackList['enabled'] and \
356  self.task.name in self.task.blackList['items'] and \
357  p.get('name') in self.task.blackList['items'][self.task.name].get('params', []):
358  hidden = True
359  else:
360  hidden = False
361 
362  self.task.params.append( {
363  "name" : p.get('name'),
364  "type" : p.get('type'),
365  "required" : required,
366  "multiple" : multiple,
367  "label" : self._get_node_text(p, 'label'),
368  "description" : self._get_node_text(p, 'description'),
369  'gisprompt' : gisprompt,
370  'age' : age,
371  'element' : element,
372  'prompt' : prompt,
373  "guisection" : self._get_node_text(p, 'guisection'),
374  "guidependency" : self._get_node_text(p, 'guidependency'),
375  "default" : self._get_node_text(p, 'default'),
376  "values" : values,
377  "values_desc" : values_desc,
378  "value" : '',
379  "key_desc" : key_desc,
380  "hidden" : hidden
381  })
382 
383  def _process_flags(self):
384  """!Process flags
385  """
386  for p in self.root.findall('flag'):
387  if self.task.blackList['enabled'] and \
388  self.task.name in self.task.blackList['items'] and \
389  p.get('name') in self.task.blackList['items'][self.task.name].get('flags', []):
390  hidden = True
391  else:
392  hidden = False
393 
394  if p.find('suppress_required') is not None:
395  suppress_required = True
396  else:
397  suppress_required = False
398 
399  self.task.flags.append( {
400  "name" : p.get('name'),
401  "label" : self._get_node_text(p, 'label'),
402  "description" : self._get_node_text(p, 'description'),
403  "guisection" : self._get_node_text(p, 'guisection'),
404  "suppress_required" : suppress_required,
405  "value" : False,
406  "hidden" : hidden
407  } )
408 
409  def _get_node_text(self, node, tag, default = ''):
410  """!Get node text"""
411  p = node.find(tag)
412  if p is not None:
413  return string.join(string.split(p.text), ' ')
414 
415  return default
416 
417  def get_task(self):
418  """!Get grassTask instance"""
419  return self.task
420 
422  """!Returns the XML description for the GRASS cmd.
423 
424  The DTD must be located in $GISBASE/etc/grass-interface.dtd,
425  otherwise the parser will not succeed.
426 
427  @param cmd command (name of GRASS module)
428  """
429  try:
430  if sys.platform == 'win32' and os.path.splitext(cmd)[1] == '.py':
431  os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts'))
432  args = [sys.executable, cmd, '--interface-description']
433  else:
434  args = [cmd, '--interface-description']
435 
436  cmdout, cmderr = Popen(args, stdout = PIPE,
437  stderr = PIPE).communicate()
438 
439  except OSError, e:
440  raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'."
441  "\n\nDetails: %(det)s") % { 'cmd' : cmd, 'det' : e }
442 
443  # if cmderr and cmderr[:7] != 'WARNING':
444  # raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'."
445  # "\n\nDetails: %(det)s") % { 'cmd': cmd, 'det' : decode(cmderr) }
446 
447  return cmdout.replace('grass-interface.dtd', os.path.join(os.getenv('GISBASE'), 'etc', 'grass-interface.dtd'))
448 
449 def parse_interface(name, parser = processTask, blackList = None):
450  """!Parse interface of given GRASS module
451 
452  @param name name of GRASS module to be parsed
453  """
454  enc = locale.getdefaultlocale()[1]
455  if enc and enc.lower() == "cp932":
456  p = re.compile('encoding="' + enc + '"', re.IGNORECASE)
457  tree = etree.fromstring(p.sub('encoding="utf-8"',
458  get_interface_description(name).decode(enc).encode("utf-8")))
459  else:
460  tree = etree.fromstring(get_interface_description(name))
461 
462  return parser(tree, blackList = blackList).get_task()
463 
464 def command_info(cmd):
465  """!Returns meta information for any GRASS command as dictionary
466  with entries for description, keywords, usage, flags, and
467  parameters, e.g.
468 
469  @code
470  >>> gtask.command_info('g.tempfile')
471 
472  {'keywords': ['general', 'map management'],
473  'params': [{'gisprompt': False, 'multiple': False, 'name': 'pid', 'guidependency': '',
474  'default': '', 'age': None, 'required': True, 'value': '',
475  'label': '', 'guisection': '', 'key_desc': [], 'values': [], 'values_desc': [],
476  'prompt': None, 'hidden': False, 'element': None, 'type': 'integer',
477  'description': 'Process id to use when naming the tempfile'}],
478  'flags': [{'description': 'Verbose module output', 'value': False, 'label': '', 'guisection': '',
479  'suppress_required': False, 'hidden': False, 'name': 'verbose'}, {'description': 'Quiet module output',
480  'value': False, 'label': '', 'guisection': '', 'suppress_required': False, 'hidden': False, 'name': 'quiet'}],
481  'description': 'Creates a temporary file and prints the file name.',
482  'usage': 'g.tempfile pid=integer [--verbose] [--quiet]'
483  }
484 
485  >>> gtask.command_info('v.buffer')['keywords']
486 
487  ['vector', 'geometry', 'buffer']
488  @endcode
489  """
490  task = parse_interface(cmd)
491  cmdinfo = {}
492 
493  cmdinfo['description'] = task.get_description()
494  cmdinfo['keywords'] = task.get_keywords()
495  cmdinfo['flags'] = flags = task.get_options()['flags']
496  cmdinfo['params'] = params = task.get_options()['params']
497 
498  usage = task.get_name()
499  flags_short = list()
500  flags_long = list()
501  for f in flags:
502  fname = f.get('name', 'unknown')
503  if len(fname) > 1:
504  flags_long.append(fname)
505  else:
506  flags_short.append(fname)
507 
508  if len(flags_short) > 1:
509  usage += ' [-' + ''.join(flags_short) + ']'
510 
511  for p in params:
512  ptype = ','.join(p.get('key_desc', []))
513  if not ptype:
514  ptype = p.get('type', '')
515  req = p.get('required', False)
516  if not req:
517  usage += ' ['
518  else:
519  usage += ' '
520  usage += p['name'] + '=' + ptype
521  if p.get('multiple', False):
522  usage += '[,' + ptype + ',...]'
523  if not req:
524  usage += ']'
525 
526  for key in flags_long:
527  usage += ' [--' + key + ']'
528 
529  cmdinfo['usage'] = usage
530 
531  return cmdinfo