GRASS Programmer's Manual
6.4.2(2012)
|
00001 """!@package grass.script.task 00002 00003 @brief GRASS Python scripting module (task) 00004 00005 Get interface description of GRASS commands 00006 00007 Based on gui/wxpython/gui_modules/menuform.py 00008 00009 Usage: 00010 00011 @code 00012 from grass.script import task as gtask 00013 00014 gtask.command_info('r.info') 00015 ... 00016 @endcode 00017 00018 (C) 2011 by the GRASS Development Team 00019 This program is free software under the GNU General Public 00020 License (>=v2). Read the file COPYING that comes with GRASS 00021 for details. 00022 00023 @author Martin Landa <landa.martin gmail.com> 00024 """ 00025 00026 import types 00027 import string 00028 try: 00029 import xml.etree.ElementTree as etree 00030 except ImportError: 00031 import elementtree.ElementTree as etree # Python <= 2.4 00032 00033 from core import * 00034 00035 class grassTask: 00036 """!This class holds the structures needed for filling by the 00037 parser 00038 00039 Parameter blackList is a dictionary with fixed structure, eg. 00040 00041 @code 00042 blackList = {'items' : {'d.legend' : { 'flags' : ['m'], 00043 'params' : [] }}, 00044 'enabled': True} 00045 @endcode 00046 00047 @param path full path 00048 @param blackList hide some options in the GUI (dictionary) 00049 """ 00050 def __init__(self, path = None, blackList = None): 00051 self.path = path 00052 self.name = _('unknown') 00053 self.params = list() 00054 self.description = '' 00055 self.label = '' 00056 self.flags = list() 00057 self.keywords = list() 00058 self.errorMsg = '' 00059 self.firstParam = None 00060 if blackList: 00061 self.blackList = blackList 00062 else: 00063 self.blackList = { 'enabled' : False, 'items' : {} } 00064 00065 if path is not None: 00066 try: 00067 processTask(tree = etree.fromstring(get_interface_description(path)), 00068 task = self) 00069 except ScriptError, e: 00070 self.errorMsg = e.value 00071 00072 self.define_first() 00073 00074 def define_first(self): 00075 """!Define first parameter 00076 """ 00077 if len(self.params) > 0: 00078 self.firstParam = self.params[0]['name'] 00079 00080 def get_error_msg(self): 00081 """!Get error message ('' for no error) 00082 """ 00083 return self.errorMsg 00084 00085 def get_name(self): 00086 """!Get task name 00087 """ 00088 return self.name 00089 00090 def get_description(self, full = True): 00091 """!Get module's description 00092 00093 @param full True for label + desc 00094 """ 00095 if self.label: 00096 if full: 00097 return self.label + ' ' + self.description 00098 else: 00099 return self.label 00100 else: 00101 return self.description 00102 00103 def get_keywords(self): 00104 """!Get module's keywords 00105 """ 00106 return self.keywords 00107 00108 def get_list_params(self, element = 'name'): 00109 """!Get list of parameters 00110 00111 @param element element name 00112 """ 00113 params = [] 00114 for p in self.params: 00115 params.append(p[element]) 00116 00117 return params 00118 00119 def get_list_flags(self, element = 'name'): 00120 """!Get list of flags 00121 00122 @param element element name 00123 """ 00124 flags = [] 00125 for p in self.flags: 00126 flags.append(p[element]) 00127 00128 return flags 00129 00130 def get_param(self, value, element = 'name', raiseError = True): 00131 """!Find and return a param by name 00132 00133 @param value param's value 00134 @param element element name 00135 @param raiseError True for raise on error 00136 """ 00137 try: 00138 for p in self.params: 00139 val = p[element] 00140 if val is None: 00141 continue 00142 if type(val) in (types.ListType, types.TupleType): 00143 if value in val: 00144 return p 00145 elif type(val) == types.StringType: 00146 if p[element][:len(value)] == value: 00147 return p 00148 else: 00149 if p[element] == value: 00150 return p 00151 except KeyError: 00152 pass 00153 00154 if raiseError: 00155 raise ValueError, _("Parameter element '%(element)s' not found: '%(value)s'") % \ 00156 { 'element' : element, 'value' : value } 00157 else: 00158 return None 00159 00160 def get_flag(self, aFlag): 00161 """!Find and return a flag by name 00162 00163 Raises ValueError when the flag is not found. 00164 00165 @param aFlag name of the flag 00166 """ 00167 for f in self.flags: 00168 if f['name'] == aFlag: 00169 return f 00170 raise ValueError, _("Flag not found: %s") % aFlag 00171 00172 def getCmdError(self): 00173 """!Get error string produced by getCmd(ignoreErrors = False) 00174 00175 @return list of errors 00176 """ 00177 errorList = list() 00178 # determine if suppress_required flag is given 00179 for f in self.flags: 00180 if f['value'] and f['suppress_required']: 00181 return errorList 00182 00183 for p in self.params: 00184 if not p.get('value', '') and p.get('required', False): 00185 if not p.get('default', ''): 00186 desc = p.get('label', '') 00187 if not desc: 00188 desc = p['description'] 00189 errorList.append(_("Parameter '%(name)s' (%(desc)s) is missing.") % \ 00190 {'name' : p['name'], 'desc' : desc }) 00191 00192 return errorList 00193 00194 def getCmd(self, ignoreErrors = False, ignoreRequired = False): 00195 """!Produce an array of command name and arguments for feeding 00196 into some execve-like command processor. 00197 00198 @param ignoreErrors True to return whatever has been built so 00199 far, even though it would not be a correct command for GRASS 00200 @param ignoreRequired True to ignore required flags, otherwise 00201 '<required>' is shown 00202 """ 00203 cmd = [self.name] 00204 00205 suppress_required = False 00206 for flag in self.flags: 00207 if flag['value']: 00208 if len(flag['name']) > 1: # e.g. overwrite 00209 cmd += [ '--' + flag['name'] ] 00210 else: 00211 cmd += [ '-' + flag['name'] ] 00212 if flag['suppress_required']: 00213 suppress_required = True 00214 for p in self.params: 00215 if p.get('value','') == '' and p.get('required', False): 00216 if p.get('default', '') != '': 00217 cmd += [ '%s=%s' % (p['name'], p['default']) ] 00218 elif ignoreErrors and not suppress_required and not ignoreRequired: 00219 cmd += [ '%s=%s' % (p['name'], _('<required>')) ] 00220 elif p.get('value','') != '' and p['value'] != p.get('default','') : 00221 # Output only values that have been set, and different from defaults 00222 cmd += [ '%s=%s' % (p['name'], p['value']) ] 00223 00224 errList = self.getCmdError() 00225 if ignoreErrors is False and errList: 00226 raise ValueError, '\n'.join(errList) 00227 00228 return cmd 00229 00230 def get_options(self): 00231 """!Get options 00232 """ 00233 return { 'flags' : self.flags, 00234 'params' : self.params } 00235 00236 def has_required(self): 00237 """!Check if command has at least one required paramater 00238 """ 00239 for p in self.params: 00240 if p.get('required', False): 00241 return True 00242 00243 return False 00244 00245 def set_param(self, aParam, aValue, element = 'value'): 00246 """!Set param value/values. 00247 """ 00248 try: 00249 param = self.get_param(aParam) 00250 except ValueError: 00251 return 00252 00253 param[element] = aValue 00254 00255 def set_flag(self, aFlag, aValue, element = 'value'): 00256 """!Enable / disable flag. 00257 """ 00258 try: 00259 param = self.get_flag(aFlag) 00260 except ValueError: 00261 return 00262 00263 param[element] = aValue 00264 00265 def set_options(self, opts): 00266 """!Set flags and parameters 00267 00268 @param opts list of flags and parameters""" 00269 for opt in opts: 00270 if opt[0] == '-': # flag 00271 self.set_flag(opt.lstrip('-'), True) 00272 else: # parameter 00273 key, value = opt.split('=', 1) 00274 self.set_param(key, value) 00275 00276 class processTask: 00277 """!A ElementTree handler for the --interface-description output, 00278 as defined in grass-interface.dtd. Extend or modify this and the 00279 DTD if the XML output of GRASS' parser is extended or modified. 00280 00281 @param tree root tree node 00282 @param task grassTask instance or None 00283 @param blackList list of flags/params to hide 00284 00285 @return grassTask instance 00286 """ 00287 def __init__(self, tree, task = None, blackList = None): 00288 if task: 00289 self.task = task 00290 else: 00291 self.task = grassTask() 00292 if blackList: 00293 self.task.blackList = blackList 00294 00295 self.root = tree 00296 00297 self._process_module() 00298 self._process_params() 00299 self._process_flags() 00300 self.task.define_first() 00301 00302 def _process_module(self): 00303 """!Process module description 00304 """ 00305 self.task.name = self.root.get('name', default = 'unknown') 00306 00307 # keywords 00308 for keyword in self._get_node_text(self.root, 'keywords').split(','): 00309 self.task.keywords.append(keyword.strip()) 00310 00311 self.task.label = self._get_node_text(self.root, 'label') 00312 self.task.description = self._get_node_text(self.root, 'description') 00313 00314 def _process_params(self): 00315 """!Process parameters 00316 """ 00317 for p in self.root.findall('parameter'): 00318 # gisprompt 00319 node_gisprompt = p.find('gisprompt') 00320 gisprompt = False 00321 age = element = prompt = None 00322 if node_gisprompt is not None: 00323 gisprompt = True 00324 age = node_gisprompt.get('age', '') 00325 element = node_gisprompt.get('element', '') 00326 prompt = node_gisprompt.get('prompt', '') 00327 00328 # value(s) 00329 values = [] 00330 values_desc = [] 00331 node_values = p.find('values') 00332 if node_values is not None: 00333 for pv in node_values.findall('value'): 00334 values.append(self._get_node_text(pv, 'name')) 00335 desc = self._get_node_text(pv, 'description') 00336 if desc: 00337 values_desc.append(desc) 00338 00339 # keydesc 00340 key_desc = [] 00341 node_key_desc = p.find('keydesc') 00342 if node_key_desc is not None: 00343 for ki in node_key_desc.findall('item'): 00344 key_desc.append(ki.text) 00345 00346 if p.get('multiple', 'no') == 'yes': 00347 multiple = True 00348 else: 00349 multiple = False 00350 if p.get('required', 'no') == 'yes': 00351 required = True 00352 else: 00353 required = False 00354 00355 if self.task.blackList['enabled'] and \ 00356 self.task.name in self.task.blackList['items'] and \ 00357 p.get('name') in self.task.blackList['items'][self.task.name].get('params', []): 00358 hidden = True 00359 else: 00360 hidden = False 00361 00362 self.task.params.append( { 00363 "name" : p.get('name'), 00364 "type" : p.get('type'), 00365 "required" : required, 00366 "multiple" : multiple, 00367 "label" : self._get_node_text(p, 'label'), 00368 "description" : self._get_node_text(p, 'description'), 00369 'gisprompt' : gisprompt, 00370 'age' : age, 00371 'element' : element, 00372 'prompt' : prompt, 00373 "guisection" : self._get_node_text(p, 'guisection'), 00374 "guidependency" : self._get_node_text(p, 'guidependency'), 00375 "default" : self._get_node_text(p, 'default'), 00376 "values" : values, 00377 "values_desc" : values_desc, 00378 "value" : '', 00379 "key_desc" : key_desc, 00380 "hidden" : hidden 00381 }) 00382 00383 def _process_flags(self): 00384 """!Process flags 00385 """ 00386 for p in self.root.findall('flag'): 00387 if self.task.blackList['enabled'] and \ 00388 self.task.name in self.task.blackList['items'] and \ 00389 p.get('name') in self.task.blackList['items'][self.task.name].get('flags', []): 00390 hidden = True 00391 else: 00392 hidden = False 00393 00394 if p.find('suppress_required') is not None: 00395 suppress_required = True 00396 else: 00397 suppress_required = False 00398 00399 self.task.flags.append( { 00400 "name" : p.get('name'), 00401 "label" : self._get_node_text(p, 'label'), 00402 "description" : self._get_node_text(p, 'description'), 00403 "guisection" : self._get_node_text(p, 'guisection'), 00404 "suppress_required" : suppress_required, 00405 "value" : False, 00406 "hidden" : hidden 00407 } ) 00408 00409 def _get_node_text(self, node, tag, default = ''): 00410 """!Get node text""" 00411 p = node.find(tag) 00412 if p is not None: 00413 return string.join(string.split(p.text), ' ') 00414 00415 return default 00416 00417 def get_task(self): 00418 """!Get grassTask instance""" 00419 return self.task 00420 00421 def get_interface_description(cmd): 00422 """!Returns the XML description for the GRASS cmd. 00423 00424 The DTD must be located in $GISBASE/etc/grass-interface.dtd, 00425 otherwise the parser will not succeed. 00426 00427 @param cmd command (name of GRASS module) 00428 """ 00429 try: 00430 if sys.platform == 'win32' and os.path.splitext(cmd)[1] == '.py': 00431 os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts')) 00432 args = [sys.executable, cmd, '--interface-description'] 00433 else: 00434 args = [cmd, '--interface-description'] 00435 00436 cmdout, cmderr = Popen(args, stdout = PIPE, 00437 stderr = PIPE).communicate() 00438 00439 except OSError, e: 00440 raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'." 00441 "\n\nDetails: %(det)s") % { 'cmd' : cmd, 'det' : e } 00442 00443 # if cmderr and cmderr[:7] != 'WARNING': 00444 # raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'." 00445 # "\n\nDetails: %(det)s") % { 'cmd': cmd, 'det' : decode(cmderr) } 00446 00447 return cmdout.replace('grass-interface.dtd', os.path.join(os.getenv('GISBASE'), 'etc', 'grass-interface.dtd')) 00448 00449 def parse_interface(name, parser = processTask, blackList = None): 00450 """!Parse interface of given GRASS module 00451 00452 @param name name of GRASS module to be parsed 00453 """ 00454 enc = locale.getdefaultlocale()[1] 00455 if enc and enc.lower() == "cp932": 00456 p = re.compile('encoding="' + enc + '"', re.IGNORECASE) 00457 tree = etree.fromstring(p.sub('encoding="utf-8"', 00458 get_interface_description(name).decode(enc).encode("utf-8"))) 00459 else: 00460 tree = etree.fromstring(get_interface_description(name)) 00461 00462 return parser(tree, blackList = blackList).get_task() 00463 00464 def command_info(cmd): 00465 """!Returns meta information for any GRASS command as dictionary 00466 with entries for description, keywords, usage, flags, and 00467 parameters, e.g. 00468 00469 @code 00470 >>> gtask.command_info('g.tempfile') 00471 00472 {'keywords': ['general', 'map management'], 00473 'params': [{'gisprompt': False, 'multiple': False, 'name': 'pid', 'guidependency': '', 00474 'default': '', 'age': None, 'required': True, 'value': '', 00475 'label': '', 'guisection': '', 'key_desc': [], 'values': [], 'values_desc': [], 00476 'prompt': None, 'hidden': False, 'element': None, 'type': 'integer', 00477 'description': 'Process id to use when naming the tempfile'}], 00478 'flags': [{'description': 'Verbose module output', 'value': False, 'label': '', 'guisection': '', 00479 'suppress_required': False, 'hidden': False, 'name': 'verbose'}, {'description': 'Quiet module output', 00480 'value': False, 'label': '', 'guisection': '', 'suppress_required': False, 'hidden': False, 'name': 'quiet'}], 00481 'description': 'Creates a temporary file and prints the file name.', 00482 'usage': 'g.tempfile pid=integer [--verbose] [--quiet]' 00483 } 00484 00485 >>> gtask.command_info('v.buffer')['keywords'] 00486 00487 ['vector', 'geometry', 'buffer'] 00488 @endcode 00489 """ 00490 task = parse_interface(cmd) 00491 cmdinfo = {} 00492 00493 cmdinfo['description'] = task.get_description() 00494 cmdinfo['keywords'] = task.get_keywords() 00495 cmdinfo['flags'] = flags = task.get_options()['flags'] 00496 cmdinfo['params'] = params = task.get_options()['params'] 00497 00498 usage = task.get_name() 00499 flags_short = list() 00500 flags_long = list() 00501 for f in flags: 00502 fname = f.get('name', 'unknown') 00503 if len(fname) > 1: 00504 flags_long.append(fname) 00505 else: 00506 flags_short.append(fname) 00507 00508 if len(flags_short) > 1: 00509 usage += ' [-' + ''.join(flags_short) + ']' 00510 00511 for p in params: 00512 ptype = ','.join(p.get('key_desc', [])) 00513 if not ptype: 00514 ptype = p.get('type', '') 00515 req = p.get('required', False) 00516 if not req: 00517 usage += ' [' 00518 else: 00519 usage += ' ' 00520 usage += p['name'] + '=' + ptype 00521 if p.get('multiple', False): 00522 usage += '[,' + ptype + ',...]' 00523 if not req: 00524 usage += ']' 00525 00526 for key in flags_long: 00527 usage += ' [--' + key + ']' 00528 00529 cmdinfo['usage'] = usage 00530 00531 return cmdinfo