|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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 get_cmd_error(self): 00173 """!Get error string produced by get_cmd(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 get_cmd(self, ignoreErrors = False, ignoreRequired = False, ignoreDefault = True): 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 @param ignoreDefault True to ignore parameters with default values 00203 """ 00204 cmd = [self.name] 00205 00206 suppress_required = False 00207 for flag in self.flags: 00208 if flag['value']: 00209 if len(flag['name']) > 1: # e.g. overwrite 00210 cmd += [ '--' + flag['name'] ] 00211 else: 00212 cmd += [ '-' + flag['name'] ] 00213 if flag['suppress_required']: 00214 suppress_required = True 00215 for p in self.params: 00216 if p.get('value', '') == '' and p.get('required', False): 00217 if p.get('default', '') != '': 00218 cmd += [ '%s=%s' % (p['name'], p['default']) ] 00219 elif ignoreErrors and not suppress_required and not ignoreRequired: 00220 cmd += [ '%s=%s' % (p['name'], _('<required>')) ] 00221 elif p.get('value', '') == '' and p.get('default', '') != '' and not ignoreDefault: 00222 cmd += [ '%s=%s' % (p['name'], p['default']) ] 00223 elif p.get('value', '') != '' and \ 00224 (p['value'] != p.get('default', '') or not ignoreDefault): 00225 # output only values that have been set, and different from defaults 00226 cmd += [ '%s=%s' % (p['name'], p['value']) ] 00227 00228 errList = self.get_cmd_error() 00229 if ignoreErrors is False and errList: 00230 raise ValueError, '\n'.join(errList) 00231 00232 return cmd 00233 00234 def get_options(self): 00235 """!Get options 00236 """ 00237 return { 'flags' : self.flags, 00238 'params' : self.params } 00239 00240 def has_required(self): 00241 """!Check if command has at least one required paramater 00242 """ 00243 for p in self.params: 00244 if p.get('required', False): 00245 return True 00246 00247 return False 00248 00249 def set_param(self, aParam, aValue, element = 'value'): 00250 """!Set param value/values. 00251 """ 00252 try: 00253 param = self.get_param(aParam) 00254 except ValueError: 00255 return 00256 00257 param[element] = aValue 00258 00259 def set_flag(self, aFlag, aValue, element = 'value'): 00260 """!Enable / disable flag. 00261 """ 00262 try: 00263 param = self.get_flag(aFlag) 00264 except ValueError: 00265 return 00266 00267 param[element] = aValue 00268 00269 def set_options(self, opts): 00270 """!Set flags and parameters 00271 00272 @param opts list of flags and parameters""" 00273 for opt in opts: 00274 if opt[0] == '-': # flag 00275 self.set_flag(opt.lstrip('-'), True) 00276 else: # parameter 00277 key, value = opt.split('=', 1) 00278 self.set_param(key, value) 00279 00280 class processTask: 00281 """!A ElementTree handler for the --interface-description output, 00282 as defined in grass-interface.dtd. Extend or modify this and the 00283 DTD if the XML output of GRASS' parser is extended or modified. 00284 00285 @param tree root tree node 00286 @param task grassTask instance or None 00287 @param blackList list of flags/params to hide 00288 00289 @return grassTask instance 00290 """ 00291 def __init__(self, tree, task = None, blackList = None): 00292 if task: 00293 self.task = task 00294 else: 00295 self.task = grassTask() 00296 if blackList: 00297 self.task.blackList = blackList 00298 00299 self.root = tree 00300 00301 self._process_module() 00302 self._process_params() 00303 self._process_flags() 00304 self.task.define_first() 00305 00306 def _process_module(self): 00307 """!Process module description 00308 """ 00309 self.task.name = self.root.get('name', default = 'unknown') 00310 00311 # keywords 00312 for keyword in self._get_node_text(self.root, 'keywords').split(','): 00313 self.task.keywords.append(keyword.strip()) 00314 00315 self.task.label = self._get_node_text(self.root, 'label') 00316 self.task.description = self._get_node_text(self.root, 'description') 00317 00318 def _process_params(self): 00319 """!Process parameters 00320 """ 00321 for p in self.root.findall('parameter'): 00322 # gisprompt 00323 node_gisprompt = p.find('gisprompt') 00324 gisprompt = False 00325 age = element = prompt = None 00326 if node_gisprompt is not None: 00327 gisprompt = True 00328 age = node_gisprompt.get('age', '') 00329 element = node_gisprompt.get('element', '') 00330 prompt = node_gisprompt.get('prompt', '') 00331 00332 # value(s) 00333 values = [] 00334 values_desc = [] 00335 node_values = p.find('values') 00336 if node_values is not None: 00337 for pv in node_values.findall('value'): 00338 values.append(self._get_node_text(pv, 'name')) 00339 desc = self._get_node_text(pv, 'description') 00340 if desc: 00341 values_desc.append(desc) 00342 00343 # keydesc 00344 key_desc = [] 00345 node_key_desc = p.find('keydesc') 00346 if node_key_desc is not None: 00347 for ki in node_key_desc.findall('item'): 00348 key_desc.append(ki.text) 00349 00350 if p.get('multiple', 'no') == 'yes': 00351 multiple = True 00352 else: 00353 multiple = False 00354 if p.get('required', 'no') == 'yes': 00355 required = True 00356 else: 00357 required = False 00358 00359 if self.task.blackList['enabled'] and \ 00360 self.task.name in self.task.blackList['items'] and \ 00361 p.get('name') in self.task.blackList['items'][self.task.name].get('params', []): 00362 hidden = True 00363 else: 00364 hidden = False 00365 00366 self.task.params.append( { 00367 "name" : p.get('name'), 00368 "type" : p.get('type'), 00369 "required" : required, 00370 "multiple" : multiple, 00371 "label" : self._get_node_text(p, 'label'), 00372 "description" : self._get_node_text(p, 'description'), 00373 'gisprompt' : gisprompt, 00374 'age' : age, 00375 'element' : element, 00376 'prompt' : prompt, 00377 "guisection" : self._get_node_text(p, 'guisection'), 00378 "guidependency" : self._get_node_text(p, 'guidependency'), 00379 "default" : self._get_node_text(p, 'default'), 00380 "values" : values, 00381 "values_desc" : values_desc, 00382 "value" : '', 00383 "key_desc" : key_desc, 00384 "hidden" : hidden 00385 }) 00386 00387 def _process_flags(self): 00388 """!Process flags 00389 """ 00390 for p in self.root.findall('flag'): 00391 if self.task.blackList['enabled'] and \ 00392 self.task.name in self.task.blackList['items'] and \ 00393 p.get('name') in self.task.blackList['items'][self.task.name].get('flags', []): 00394 hidden = True 00395 else: 00396 hidden = False 00397 00398 if p.find('suppress_required') is not None: 00399 suppress_required = True 00400 else: 00401 suppress_required = False 00402 00403 self.task.flags.append( { 00404 "name" : p.get('name'), 00405 "label" : self._get_node_text(p, 'label'), 00406 "description" : self._get_node_text(p, 'description'), 00407 "guisection" : self._get_node_text(p, 'guisection'), 00408 "suppress_required" : suppress_required, 00409 "value" : False, 00410 "hidden" : hidden 00411 } ) 00412 00413 def _get_node_text(self, node, tag, default = ''): 00414 """!Get node text""" 00415 p = node.find(tag) 00416 if p is not None: 00417 return string.join(string.split(p.text), ' ') 00418 00419 return default 00420 00421 def get_task(self): 00422 """!Get grassTask instance""" 00423 return self.task 00424 00425 def get_interface_description(cmd): 00426 """!Returns the XML description for the GRASS cmd. 00427 00428 The DTD must be located in $GISBASE/etc/grass-interface.dtd, 00429 otherwise the parser will not succeed. 00430 00431 @param cmd command (name of GRASS module) 00432 """ 00433 try: 00434 if sys.platform == 'win32' and os.path.splitext(cmd)[1] == '.py': 00435 os.chdir(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'scripts')) 00436 args = [sys.executable, cmd, '--interface-description'] 00437 else: 00438 args = [cmd, '--interface-description'] 00439 00440 cmdout, cmderr = Popen(args, stdout = PIPE, 00441 stderr = PIPE).communicate() 00442 if cmderr: 00443 raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'." 00444 "\n\nDetails: %(det)s") % { 'cmd' : cmd, 'det' : decode(cmderr) } 00445 except OSError, e: 00446 raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'." 00447 "\n\nDetails: %(det)s") % { 'cmd' : cmd, 'det' : e } 00448 00449 # if cmderr and cmderr[:7] != 'WARNING': 00450 # raise ScriptError, _("Unable to fetch interface description for command '%(cmd)s'." 00451 # "\n\nDetails: %(det)s") % { 'cmd': cmd, 'det' : decode(cmderr) } 00452 00453 return cmdout.replace('grass-interface.dtd', os.path.join(os.getenv('GISBASE'), 'etc', 'grass-interface.dtd')) 00454 00455 def parse_interface(name, parser = processTask, blackList = None): 00456 """!Parse interface of given GRASS module 00457 00458 @param name name of GRASS module to be parsed 00459 """ 00460 enc = locale.getdefaultlocale()[1] 00461 if enc and enc.lower() == "cp932": 00462 p = re.compile('encoding="' + enc + '"', re.IGNORECASE) 00463 tree = etree.fromstring(p.sub('encoding="utf-8"', 00464 get_interface_description(name).decode(enc).encode("utf-8"))) 00465 else: 00466 tree = etree.fromstring(get_interface_description(name)) 00467 00468 return parser(tree, blackList = blackList).get_task() 00469 00470 def command_info(cmd): 00471 """!Returns meta information for any GRASS command as dictionary 00472 with entries for description, keywords, usage, flags, and 00473 parameters, e.g. 00474 00475 @code 00476 >>> gtask.command_info('g.tempfile') 00477 00478 {'keywords': ['general', 'map management'], 00479 'params': [{'gisprompt': False, 'multiple': False, 'name': 'pid', 'guidependency': '', 00480 'default': '', 'age': None, 'required': True, 'value': '', 00481 'label': '', 'guisection': '', 'key_desc': [], 'values': [], 'values_desc': [], 00482 'prompt': None, 'hidden': False, 'element': None, 'type': 'integer', 00483 'description': 'Process id to use when naming the tempfile'}], 00484 'flags': [{'description': 'Verbose module output', 'value': False, 'label': '', 'guisection': '', 00485 'suppress_required': False, 'hidden': False, 'name': 'verbose'}, {'description': 'Quiet module output', 00486 'value': False, 'label': '', 'guisection': '', 'suppress_required': False, 'hidden': False, 'name': 'quiet'}], 00487 'description': 'Creates a temporary file and prints the file name.', 00488 'usage': 'g.tempfile pid=integer [--verbose] [--quiet]' 00489 } 00490 00491 >>> gtask.command_info('v.buffer')['keywords'] 00492 00493 ['vector', 'geometry', 'buffer'] 00494 @endcode 00495 """ 00496 task = parse_interface(cmd) 00497 cmdinfo = {} 00498 00499 cmdinfo['description'] = task.get_description() 00500 cmdinfo['keywords'] = task.get_keywords() 00501 cmdinfo['flags'] = flags = task.get_options()['flags'] 00502 cmdinfo['params'] = params = task.get_options()['params'] 00503 00504 usage = task.get_name() 00505 flags_short = list() 00506 flags_long = list() 00507 for f in flags: 00508 fname = f.get('name', 'unknown') 00509 if len(fname) > 1: 00510 flags_long.append(fname) 00511 else: 00512 flags_short.append(fname) 00513 00514 if len(flags_short) > 1: 00515 usage += ' [-' + ''.join(flags_short) + ']' 00516 00517 for p in params: 00518 ptype = ','.join(p.get('key_desc', [])) 00519 if not ptype: 00520 ptype = p.get('type', '') 00521 req = p.get('required', False) 00522 if not req: 00523 usage += ' [' 00524 else: 00525 usage += ' ' 00526 usage += p['name'] + '=' + ptype 00527 if p.get('multiple', False): 00528 usage += '[,' + ptype + ',...]' 00529 if not req: 00530 usage += ']' 00531 00532 for key in flags_long: 00533 usage += ' [--' + key + ']' 00534 00535 cmdinfo['usage'] = usage 00536 00537 return cmdinfo