GRASS Programmer's Manual  6.4.2(2012)
task.py
Go to the documentation of this file.
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
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines