GRASS Programmer's Manual  6.4.2(2012)
g.extension.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 ############################################################################
00004 #
00005 # MODULE:       g.extension
00006 # AUTHOR(S):    Markus Neteler
00007 #               Pythonized by Martin Landa
00008 # PURPOSE:      Tool to download and install extensions from GRASS Addons SVN into 
00009 #               local GRASS installation
00010 # COPYRIGHT:    (C) 2009-2011 by Markus Neteler, and the GRASS Development Team
00011 #
00012 #               This program is free software under the GNU General
00013 #               Public License (>=v2). Read the file COPYING that
00014 #               comes with GRASS for details.
00015 #
00016 # TODO: add sudo support where needed (i.e. check first permission to write into
00017 #       $GISBASE directory)
00018 #############################################################################
00019 
00020 #%module
00021 #% label: Maintains GRASS Addons extensions in local GRASS installation.
00022 #% description: Downloads, installs extensions from GRASS Addons SVN repository into local GRASS installation or removes installed extensions.
00023 #% keywords: general, installation, extensions
00024 #%end
00025 
00026 #%option
00027 #% key: extension
00028 #% type: string
00029 #% key_desc: name
00030 #% description: Name of extension to install/remove
00031 #% guisection: Required
00032 #%end
00033 #%option
00034 #% key: operation
00035 #% type: string
00036 #% description: Operation to be performed
00037 #% required: yes
00038 #% options: add,remove
00039 #% answer: add
00040 #%end
00041 #%option
00042 #% key: svnurl
00043 #% type: string
00044 #% key_desc: url
00045 #% description: SVN Addons repository URL
00046 #% answer: http://svn.osgeo.org/grass/grass-addons/grass6
00047 #%end
00048 #%option
00049 #% key: prefix
00050 #% type: string
00051 #% key_desc: path
00052 #% description: Prefix where to install extension (ignored when flag -s is given)
00053 #% answer: $GRASS_ADDON_PATH
00054 #% required: no
00055 #%end
00056 
00057 #%flag
00058 #% key: l
00059 #% description: List available extensions in the GRASS Addons SVN repository
00060 #% guisection: Print
00061 #%end
00062 #%flag
00063 #% key: c
00064 #% description: List available extensions in the GRASS Addons SVN repository including module description
00065 #% guisection: Print
00066 #%end
00067 #%flag
00068 #% key: g
00069 #% description: List available extensions in the GRASS Addons SVN repository (shell script style)
00070 #% guisection: Print
00071 #%end
00072 #%flag
00073 #% key: a
00074 #% description: List locally installed extension
00075 #% guisection: Print
00076 #%end
00077 #%flag
00078 #% key: s
00079 #% description: Install system-wide (may need system administrator rights)
00080 #% guisection: Install
00081 #%end
00082 #%flag
00083 #% key: d
00084 #% description: Download source code and exit
00085 #% guisection: Install
00086 #%end
00087 #%flag
00088 #% key: i
00089 #% description: Don't install new extension, just compile it
00090 #% guisection: Install
00091 #%end
00092 #%flag
00093 #% key: f
00094 #% description: Force removal when uninstalling extension (operation=remove)
00095 #% guisection: Remove
00096 #%end
00097 
00098 import os
00099 import sys
00100 import re
00101 import atexit
00102 import shutil
00103 import glob
00104 import zipfile
00105 import tempfile
00106 import shutil
00107 import fileinput
00108 
00109 from urllib2 import urlopen, HTTPError
00110 
00111 try:
00112     import xml.etree.ElementTree as etree
00113 except ImportError:
00114     import elementtree.ElementTree as etree # Python <= 2.4
00115 
00116 from grass.script import core as grass
00117 
00118 # temp dir
00119 remove_tmpdir = True
00120 
00121 # check requirements
00122 def check_progs():
00123     for prog in ('svn', 'make', 'gcc'):
00124         if not grass.find_program(prog, ['--help']):
00125             grass.fatal(_("'%s' required. Please install '%s' first.") % (prog, prog))
00126     
00127 # expand prefix to class name
00128 def expand_module_class_name(c):
00129     name = { 'd'   : 'display',
00130              'db'  : 'database',
00131              'g'   : 'general',
00132              'i'   : 'imagery',
00133              'm'   : 'misc',
00134              'ps'  : 'postscript',
00135              'p'   : 'paint',
00136              'r'   : 'raster',
00137              'r3'  : 'raster3d',
00138              's'   : 'sites',
00139              'v'   : 'vector',
00140              'gui' : 'gui/wxpython' }
00141     
00142     if name.has_key(c):
00143         return name[c]
00144     
00145     return c
00146 
00147 
00148 # list installed extensions
00149 def get_installed_extensions(force = False):
00150     fXML = os.path.join(options['prefix'], 'modules.xml')
00151     if not os.path.exists(fXML):
00152         if force:
00153             write_xml_modules(fXML)
00154         else:
00155             grass.warning(_("No metadata file available"))
00156         return []
00157     
00158     # read XML file
00159     fo = open(fXML, 'r')
00160     try:
00161         tree = etree.fromstring(fo.read())
00162     except:
00163         os.remove(fXML)
00164         write_xml_modules(fXML)
00165         return []
00166     fo.close()
00167     
00168     ret = list()
00169     for tnode in tree.findall('task'):
00170         ret.append(tnode.get('name').strip())
00171     
00172     return ret
00173 
00174 # list extensions (read XML file from grass.osgeo.org/addons)
00175 def list_available_extensions():
00176     mlist = list()
00177 
00178     # try to download XML metadata file first
00179     url = "http://grass.osgeo.org/addons/grass%s/modules.xml" % grass.version()['version'].split('.')[0]
00180     grass.debug("url=%s" % url, 1)
00181     try:
00182         f = urlopen(url)
00183         try:
00184             tree = etree.fromstring(f.read())
00185         except:
00186             grass.fatal(_("Unable to parse '%s'") % url)
00187         
00188         for mnode in tree.findall('task'):
00189             name = mnode.get('name')
00190             if flags['c'] or flags['g']:
00191                 desc = mnode.find('description').text
00192                 if not desc:
00193                     desc = ''
00194                 keyw = mnode.find('keywords').text
00195                 if not keyw:
00196                     keyw = ''
00197             
00198             if flags['g']:
00199                 print 'name=' + name
00200                 print 'description=' + desc
00201                 print 'keywords=' + keyw
00202             elif flags['c']:
00203                 print name + ' - ' + desc
00204             else:
00205                 print name
00206     except HTTPError:
00207         return list_available_extensions_svn()
00208     
00209     return mlist
00210 
00211 # list extensions (scan SVN repo)
00212 def list_available_extensions_svn():
00213     mlist = list()
00214     grass.message(_('Fetching list of extensions from GRASS-Addons SVN (be patient)...'))
00215     pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
00216 
00217     if flags['c']:
00218         grass.warning(_("Flag 'c' ignored, metadata file not available"))
00219     if flags['g']:
00220         grass.warning(_("Flag 'g' ignored, metadata file not available"))
00221         
00222     prefix = ['d', 'db', 'g', 'i', 'm', 'ps',
00223               'p', 'r', 'r3', 's', 'v']
00224     nprefix = len(prefix)
00225     for d in prefix:
00226         modclass = expand_module_class_name(d)
00227         grass.verbose(_("Checking for '%s' modules...") % modclass)
00228         
00229         url = '%s/%s' % (options['svnurl'], modclass)
00230         grass.debug("url = %s" % url, debug = 2)
00231         try:
00232             f = urlopen(url)
00233         except HTTPError:
00234             grass.debug(_("Unable to fetch '%s'") % url, debug = 1)
00235             continue
00236         
00237         for line in f.readlines():
00238             # list extensions
00239             sline = pattern.search(line)
00240             if not sline:
00241                 continue
00242             name = sline.group(2).rstrip('/')
00243             if name.split('.', 1)[0] == d:
00244                 print name
00245                 mlist.append(name)
00246     
00247     mlist += list_wxgui_extensions()
00248         
00249     return mlist
00250 
00251 # list wxGUI extensions
00252 def list_wxgui_extensions(print_module = True):
00253     mlist = list()
00254     grass.debug('Fetching list of wxGUI extensions from GRASS-Addons SVN (be patient)...')
00255     pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
00256     grass.verbose(_("Checking for '%s' modules...") % 'gui/wxpython')
00257     
00258     url = '%s/%s' % (options['svnurl'], 'gui/wxpython')
00259     grass.debug("url = %s" % url, debug = 2)
00260     f = urlopen(url)
00261     if not f:
00262         grass.warning(_("Unable to fetch '%s'") % url)
00263         return
00264         
00265     for line in f.readlines():
00266         # list extensions
00267         sline = pattern.search(line)
00268         if not sline:
00269             continue
00270         name = sline.group(2).rstrip('/')
00271         if name not in ('..', 'Makefile'):
00272             if print_module:
00273                 print name
00274             mlist.append(name)
00275     
00276     return mlist
00277 
00278 def cleanup():
00279     if remove_tmpdir:
00280         grass.try_rmdir(tmpdir)
00281     else:
00282         grass.message(_("Path to the source code:"))
00283         sys.stderr.write('%s\n' % os.path.join(tmpdir, options['extension']))
00284 
00285 # write out meta-file
00286 def write_xml_modules(name, tree = None):
00287     fo = open(name, 'w')
00288     version = grass.version()['version'].split('.')[0]
00289     fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
00290     fo.write('<!DOCTYPE task SYSTEM "grass-addons.dtd">\n')
00291     fo.write('<addons version="%s">\n' % version)
00292     
00293     if tree is not None:
00294         for tnode in tree.findall('task'):
00295             indent = 4
00296             fo.write('%s<task name="%s">\n' % (' ' * indent, tnode.get('name')))
00297             indent += 4
00298             fo.write('%s<description>%s</description>\n' % \
00299                          (' ' * indent, tnode.find('description').text))
00300             fo.write('%s<keywords>%s</keywords>\n' % \
00301                          (' ' * indent, tnode.find('keywords').text))
00302             bnode = tnode.find('binary')
00303             if bnode is not None:
00304                 fo.write('%s<binary>\n' % (' ' * indent))
00305                 indent += 4
00306                 for fnode in bnode.findall('file'):
00307                     fpath = fnode.text.split(os.path.sep)
00308                     if not flags['s']: # tidy citizen hacks
00309                         if fpath[0] in ('bin', 'scripts'):
00310                             del fpath[0]
00311                         if fpath[0] == 'man':
00312                             fpath.insert(0, 'docs')
00313                     
00314                     fo.write('%s<file>%s</file>\n' % \
00315                                  (' ' * indent, os.path.join(options['prefix'],
00316                                                              os.path.sep.join(fpath))))
00317                 indent -= 4 
00318                 fo.write('%s</binary>\n' % (' ' * indent))
00319             libgisRev = grass.version()['libgis_revision']
00320             fo.write('%s<libgis revision="%s" />\n' % \
00321                          (' ' * indent, libgisRev))
00322             indent -= 4
00323             fo.write('%s</task>\n' % (' ' * indent))
00324     
00325     fo.write('</addons>\n')
00326     fo.close()
00327     
00328 # update local meta-file when installing new extension
00329 def install_extension_xml():
00330     # read metadata from remote server
00331     url = "http://grass.osgeo.org/addons/grass%s/modules.xml" % grass.version()['version'].split('.')[0]
00332     data = None
00333     try:
00334         f = urlopen(url)
00335         tree = etree.fromstring(f.read())
00336         for mnode in tree.findall('task'):
00337             name = mnode.get('name')
00338             if name != options['extension']:
00339                 continue
00340             
00341             fList = list()
00342             bnode = mnode.find('binary')
00343             windows = sys.platform == 'win32'
00344             if bnode is not None:
00345                 for fnode in bnode.findall('file'):
00346                     path = fnode.text.split('/')
00347                     if windows:
00348                         if path[0] == 'bin':
00349                             path[-1] += '.exe'
00350                         if path[0] == 'scripts':
00351                             path[-1] += '.py'
00352                     fList.append(os.path.sep.join(path))
00353             
00354             desc = mnode.find('description').text
00355             if not desc:
00356                 desc = ''
00357             keyw = mnode.find('keywords').text
00358             if not keyw:
00359                 keyw = ''
00360             
00361             data = { 'name'  : name,
00362                      'desc'  : desc,
00363                      'keyw'  : keyw,
00364                      'files' : fList,
00365                      }
00366     except HTTPError:
00367         grass.error(_("Unable to read metadata file from the remote server"))
00368     
00369     if not data:
00370         grass.warning(_("No metadata available"))
00371         return
00372 
00373     fXML = os.path.join(options['prefix'], 'modules.xml')
00374     # create an empty file if not exists
00375     if not os.path.exists(fXML):
00376         write_xml_modules(fXML)
00377     
00378     # read XML file
00379     fo = open(fXML, 'r')
00380     tree = etree.fromstring(fo.read())
00381     fo.close()
00382     
00383     # update tree
00384     tnode = None
00385     for node in tree.findall('task'):
00386         if node.get('name') == options['extension']:
00387             tnode = node
00388             break
00389     
00390     if tnode is not None:
00391         # update existing node
00392         dnode = tnode.find('description')
00393         if dnode is not None:
00394             dnode.text = data['desc']
00395         knode = tnode.find('keywords')
00396         if knode is not None:
00397             knode.text = data['keyw']
00398         bnode = tnode.find('binary')
00399         if bnode is not None:
00400             tnode.remove(bnode)
00401         bnode = etree.Element('binary')
00402         for f in data['files']:
00403             fnode = etree.Element('file')
00404             fnode.text = f
00405             bnode.append(fnode)
00406         tnode.append(bnode)
00407     else:
00408         # create new node for task
00409         tnode = etree.Element('task', attrib = { 'name' : data['name'] })
00410         dnode = etree.Element('description')
00411         dnode.text = data['desc']
00412         tnode.append(dnode)
00413         knode = etree.Element('keywords')
00414         knode.text = data['keyw']
00415         tnode.append(knode)
00416         bnode = etree.Element('binary')
00417         for f in data['files']:
00418             fnode = etree.Element('file')
00419             fnode.text = f
00420             bnode.append(fnode)
00421         tnode.append(bnode)
00422         tree.append(tnode)
00423     
00424     write_xml_modules(fXML, tree)
00425 
00426 # install extension on MS Windows
00427 def install_extension_win():
00428     ### TODO: do not use hardcoded url - http://wingrass.fsv.cvut.cz/grassXX/addonsX.X.X
00429     version = grass.version()['version'].split('.')
00430     grass.message(_("Downloading precompiled GRASS Addons <%s>...") % options['extension'])
00431     url = "http://wingrass.fsv.cvut.cz/grass%s%s/addons/grass-%s.%s.%s" % \
00432         (version[0], version[1], version[0], version[1], version[2])
00433     grass.debug("url=%s" % url, 1)
00434     
00435     try:
00436         f = urlopen(url + '/' + options['extension'] + '.zip')
00437         
00438         # create addons dir if not exists
00439         if not os.path.exists(options['prefix']):
00440             os.mkdir(options['prefix'])
00441         
00442         # download data
00443         fo = tempfile.TemporaryFile()
00444         fo.write(f.read())
00445         zfobj = zipfile.ZipFile(fo)
00446         for name in zfobj.namelist():
00447             if name.endswith('/'):
00448                 d = os.path.join(options['prefix'], name)
00449                 if not os.path.exists(d):
00450                     os.mkdir(d)
00451             else:
00452                 outfile = open(os.path.join(options['prefix'], name), 'wb')
00453                 outfile.write(zfobj.read(name))
00454                 outfile.close()
00455         
00456         fo.close()
00457     except HTTPError:
00458         grass.fatal(_("GRASS Addons <%s> not found") % options['extension'])
00459     
00460     return 0
00461 
00462 # install extension
00463 def install_extension():
00464     gisbase = os.getenv('GISBASE')
00465     if not gisbase:
00466         grass.fatal(_('$GISBASE not defined'))
00467     
00468     if options['extension'] in get_installed_extensions(force = True):
00469         grass.warning(_("Extension <%s> already installed. Re-installing...") % options['extension'])
00470     
00471     if sys.platform == "win32":
00472         ret = install_extension_win()
00473     else:
00474         ret = install_extension_other()
00475     
00476     if ret != 0:
00477         grass.warning(_('Installation failed, sorry. Please check above error messages.'))
00478     else:
00479         grass.message(_("Updating metadata file..."))
00480         install_extension_xml()
00481         grass.message(_("Installation of <%s> successfully finished") % options['extension'])
00482     
00483     # cleanup build cruft
00484     if not flags['s']:
00485         tidy_citizen()
00486     
00487     if not os.environ.has_key('GRASS_ADDON_PATH') or \
00488             not os.environ['GRASS_ADDON_PATH']:
00489         grass.warning(_('This add-on module will not function until you set the '
00490                         'GRASS_ADDON_PATH environment variable (see "g.manual variables")'))
00491 
00492 # install extension on other plaforms
00493 def install_extension_other():
00494     gisbase = os.getenv('GISBASE')
00495     gui_list = list_wxgui_extensions(print_module = False)
00496 
00497     if options['extension'] not in gui_list:
00498         classchar = options['extension'].split('.', 1)[0]
00499         moduleclass = expand_module_class_name(classchar)
00500         url = options['svnurl'] + '/' + moduleclass + '/' + options['extension']
00501     else:
00502         url = options['svnurl'] + '/gui/wxpython/' + options['extension']
00503         if not flags['s']:
00504             grass.fatal(_("Installation of wxGUI extension requires -%s flag.") % 's')
00505         
00506     grass.message(_("Fetching <%s> from GRASS-Addons SVN (be patient)...") % options['extension'])
00507     
00508     os.chdir(tmpdir)
00509     if grass.verbosity() <= 2:
00510         outdev = open(os.devnull, 'w')
00511     else:
00512         outdev = sys.stdout
00513     
00514     if grass.call(['svn', 'checkout',
00515                    url], stdout = outdev) != 0:
00516         grass.fatal(_("GRASS Addons <%s> not found") % options['extension'])
00517     
00518     dirs = { 'bin' : os.path.join(tmpdir, options['extension'], 'bin'),
00519              'docs' : os.path.join(tmpdir, options['extension'], 'docs'),
00520              'html' : os.path.join(tmpdir, options['extension'], 'docs', 'html'),
00521              'man' : os.path.join(tmpdir, options['extension'], 'man'),
00522              'man1' : os.path.join(tmpdir, options['extension'], 'man', 'man1'),
00523              'scripts' : os.path.join(tmpdir, options['extension'], 'scripts'),
00524              'etc' : os.path.join(tmpdir, options['extension'], 'etc'),
00525              }
00526     
00527     makeCmd = ['make',
00528                'MODULE_TOPDIR=%s' % gisbase.replace(' ', '\ '),
00529                'BIN=%s' % dirs['bin'],
00530                'HTMLDIR=%s' % dirs['html'],
00531                'MANDIR=%s' % dirs['man1'],
00532                'SCRIPTDIR=%s' % dirs['scripts'],
00533                'ETC=%s' % os.path.join(dirs['etc'],options['extension'])
00534                ]
00535     
00536     installCmd = ['make',
00537                   'MODULE_TOPDIR=%s' % gisbase,
00538                   'ARCH_DISTDIR=%s' % os.path.join(tmpdir, options['extension']),
00539                   'INST_DIR=%s' % options['prefix'],
00540                   'install'
00541                   ]
00542     
00543     if flags['d']:
00544         grass.message(_("To compile run:"))
00545         sys.stderr.write(' '.join(makeCmd) + '\n')
00546         grass.message(_("To install run:\n\n"))
00547         sys.stderr.write(' '.join(installCmd) + '\n')
00548         return
00549     
00550     os.chdir(os.path.join(tmpdir, options['extension']))
00551     
00552     grass.message(_("Compiling..."))
00553     if options['extension'] not in gui_list:
00554         ret = grass.call(makeCmd,
00555                          stdout = outdev)
00556     else:
00557         ret = grass.call(['make',
00558                           'MODULE_TOPDIR=%s' % gisbase.replace(' ', '\ ')],
00559                          stdout = outdev)
00560     
00561     if ret != 0:
00562         grass.fatal(_('Compilation failed, sorry. Please check above error messages.'))
00563 
00564     if flags['i'] or options['extension'] in gui_list:
00565         return
00566     
00567     grass.message(_("Installing..."))
00568     
00569     return grass.call(installCmd,
00570                       stdout = outdev)
00571 
00572 # remove dir if empty
00573 def remove_empty_dir(path):
00574     if os.path.exists(path) and not os.listdir(path):
00575         os.removedirs(path)
00576 
00577 # see http://lists.osgeo.org/pipermail/grass-dev/2011-December/056938.html
00578 def tidy_citizen():
00579     if sys.platform == 'win32':
00580         EXT_BIN = '.exe'
00581         EXT_SCT = '.bat'
00582     else:
00583         EXT_BIN = EXT_SCT = ''
00584     
00585     # move script/ and bin/ to main dir
00586     if os.path.exists(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_BIN):
00587         shutil.move(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_BIN,
00588                     os.path.join(options['prefix'], options['extension']) + EXT_BIN)
00589     if os.path.exists(os.path.join(options['prefix'], 'scripts', options['extension'])):
00590         shutil.move(os.path.join(options['prefix'], 'scripts', options['extension']),
00591                     os.path.join(options['prefix'], options['extension']))
00592     if os.path.exists(os.path.join(options['prefix'], 'scripts', options['extension'] + '.py')):
00593         shutil.move(os.path.join(options['prefix'], 'scripts', options['extension'] + '.py'),
00594                     os.path.join(options['prefix'], options['extension'] + '.py'))
00595     if sys.platform == 'win32' and \
00596             os.path.exists(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_SCT):
00597         shutil.move(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_SCT,
00598                     os.path.join(options['prefix'], options['extension']) + EXT_SCT)
00599         # fix script path
00600         for line in fileinput.FileInput(os.path.join(options['prefix'], options['extension']) + EXT_SCT,
00601                                         inplace = True):
00602             line = line.replace("/scripts", "")
00603             print line
00604     
00605     # move man/ into docs/
00606     if os.path.exists(os.path.join(options['prefix'], 'man', 'man1', options['extension'] + '.1')):
00607         shutil.move(os.path.join(options['prefix'], 'man', 'man1', options['extension'] + '.1'),
00608                     os.path.join(options['prefix'], 'docs', 'man', 'man1', options['extension'] + '.1'))
00609         
00610     # if empty, rmdir bin, etc, man, scripts
00611     for d in ('bin', 'etc', os.path.join('man', 'man1'), 'man', 'scripts'):
00612         remove_empty_dir(os.path.join(options['prefix'], d))
00613 
00614 # update local meta-file when removing existing extension
00615 def remove_extension_xml():
00616     fXML = os.path.join(options['prefix'], 'modules.xml')
00617     if not os.path.exists(fXML):
00618         return
00619     
00620     # read XML file
00621     fo = open(fXML, 'r')
00622     tree = etree.fromstring(fo.read())
00623     fo.close()
00624 
00625     tnode = None
00626     for node in tree.findall('task'):
00627         if node.get('name') == options['extension']:
00628             tnode = node
00629             break
00630 
00631     if tnode is not None:
00632         tree.remove(tnode)
00633         
00634     write_xml_modules(fXML, tree)
00635     
00636 # remove existing extension (reading XML file)
00637 def remove_extension(force = False):
00638     # try to read XML metadata file first
00639     fXML = os.path.join(options['prefix'], 'modules.xml')
00640     name = options['extension']
00641     if name not in get_installed_extensions():
00642         grass.warning(_("Extension <%s> not found") % name)
00643     
00644     if force:
00645         grass.verbose(_("List of removed files:"))
00646     else:
00647         grass.info(_("Files to be removed (use flag 'f' to force removal):"))
00648     
00649     if os.path.exists(fXML):
00650         f = open(fXML, 'r')
00651         tree = etree.fromstring(f.read())
00652         flist = []
00653         for task in tree.findall('task'):
00654             if name == task.get('name', default = '') and \
00655                     task.find('binary') is not None:
00656                 for f in task.find('binary').findall('file'):
00657                     flist.append(f.text)
00658         
00659         if flist:
00660             removed = False
00661             err = list()
00662             for fpath in flist:
00663                 try:
00664                     if force:
00665                         grass.verbose(fpath)
00666                         os.remove(fpath)
00667                         removed = True
00668                     else:
00669                         print fpath
00670                 except OSError:
00671                     err.append((_("Unable to remove file '%s'") % fpath))
00672             if force and not removed:
00673                 grass.fatal(_("Extension <%s> not found") % options['extension'])
00674             
00675             if err:
00676                 for e in err:
00677                     grass.error(e)
00678         else:
00679             remove_extension_std(force)
00680     else:
00681         remove_extension_std(force)
00682 
00683     if force:
00684         grass.message(_("Updating metadata file..."))
00685         remove_extension_xml()
00686         grass.message(_("Extension <%s> successfully uninstalled.") % options['extension'])
00687     else:
00688         grass.warning(_("Extension <%s> not removed.\n"
00689                         "Re-run '%s' with 'f' flag to force removal") % (options['extension'], 'g.extension'))
00690     
00691 # remove exising extension (using standard files layout)
00692 def remove_extension_std(force = False):
00693     # try even if module does not seem to be available,
00694     # as the user may be trying to get rid of left over cruft
00695     for fpath in [os.path.join(options['prefix'], options['extension']),
00696                   os.path.join(options['prefix'], 'bin', options['extension']),
00697                   os.path.join(options['prefix'], 'scripts', options['extension']),
00698                   os.path.join(options['prefix'], 'docs', 'html', options['extension'] + '.html'),
00699                   os.path.join(options['prefix'], 'docs', 'man', 'man1', options['extension'] + '.1'),
00700                   os.path.join(options['prefix'], 'man', 'man1', options['extension'] + '.1')]:
00701         if os.path.isfile(fpath):
00702             if force:
00703                 grass.verbose(fpath)
00704                 os.remove(fpath)
00705             else:
00706                 print fpath
00707     
00708 # check links in CSS
00709 def check_style_files(fil):
00710     dist_file   = os.path.join(os.getenv('GISBASE'), 'docs', 'html', fil)
00711     addons_file = os.path.join(options['prefix'], 'docs', 'html', fil)
00712     
00713     if os.path.isfile(addons_file):
00714         return
00715     
00716     try:
00717         shutil.copyfile(dist_file,addons_file)
00718     except OSError, e:
00719         grass.fatal(_("Unable to create '%s': %s") % (addons_file, e))
00720     
00721 def create_dir(path):
00722     if os.path.isdir(path):
00723         return
00724 
00725     try:
00726         os.makedirs(path)
00727     except OSError, e:
00728         grass.fatal(_("Unable to create '%s': %s") % (path, e))
00729     
00730     grass.debug("'%s' created" % path)
00731     
00732 def check_dirs():
00733     create_dir(os.path.join(options['prefix'], 'bin'))
00734     create_dir(os.path.join(options['prefix'], 'docs', 'html'))
00735     check_style_files('grass_logo.png')
00736     check_style_files('grassdocs.css')
00737     # create_dir(os.path.join(options['prefix'], 'etc'))
00738     create_dir(os.path.join(options['prefix'], 'docs', 'man', 'man1'))
00739     create_dir(os.path.join(options['prefix'], 'man', 'man1'))
00740     create_dir(os.path.join(options['prefix'], 'scripts'))
00741 
00742 def main():
00743     # check dependecies
00744     if sys.platform != "win32":
00745         check_progs()
00746     
00747     # define path
00748     if flags['s']:
00749         options['prefix'] = os.environ['GISBASE']
00750     if options['prefix'] == '$GRASS_ADDON_PATH':
00751         if not os.environ.has_key('GRASS_ADDON_PATH') or \
00752                 not os.environ['GRASS_ADDON_PATH']:
00753             major_version = int(grass.version()['version'].split('.', 1)[0])
00754             grass.warning(_("GRASS_ADDON_PATH is not defined, "
00755                             "installing to ~/.grass%d/addons/") % major_version)
00756             options['prefix'] = os.path.join(os.environ['HOME'], '.grass%d' % major_version, 'addons')
00757         else:
00758             path_list = os.environ['GRASS_ADDON_PATH'].split(os.pathsep)
00759             if len(path_list) < 1:
00760                 grass.fatal(_("Invalid GRASS_ADDON_PATH value - '%s'") % os.environ['GRASS_ADDON_PATH'])
00761             if len(path_list) > 1:
00762                 grass.warning(_("GRASS_ADDON_PATH has more items, using first defined - '%s'") % path_list[0])
00763             options['prefix'] = path_list[0]
00764     
00765     # list available modules
00766     if flags['l'] or flags['c'] or flags['g']:
00767         list_available_extensions()
00768         return 0
00769     elif flags['a']:
00770         elist = get_installed_extensions()
00771         if elist:
00772             grass.message(_("List of installed extensions:"))
00773             sys.stdout.write('\n'.join(elist))
00774             sys.stdout.write('\n')
00775         else:
00776             grass.info(_("No extension installed"))
00777         return 0
00778     else:
00779         if not options['extension']:
00780             grass.fatal(_('You need to define an extension name or use -l'))
00781 
00782     if flags['d']:
00783         if options['operation'] != 'add':
00784             grass.warning(_("Flag 'd' is relevant only to 'operation=add'. Ignoring this flag."))
00785         else:
00786             global remove_tmpdir
00787             remove_tmpdir = False
00788     
00789     if options['operation'] == 'add':
00790         check_dirs()
00791         install_extension()
00792     else: # remove
00793         remove_extension(flags['f'])
00794     
00795     return 0
00796 
00797 if __name__ == "__main__":
00798     options, flags = grass.parser()
00799     global tmpdir
00800     tmpdir = grass.tempdir()
00801     atexit.register(cleanup)
00802     sys.exit(main())
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines