GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
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 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