#!/usr/bin/env python ############################################################################ # # MODULE: g.extension # AUTHOR(S): Markus Neteler # Pythonized by Martin Landa # PURPOSE: Tool to download and install extensions from GRASS Addons SVN into # local GRASS installation # COPYRIGHT: (C) 2009-2011 by Markus Neteler, and the GRASS Development Team # # This program is free software under the GNU General # Public License (>=v2). Read the file COPYING that # comes with GRASS for details. # # TODO: add sudo support where needed (i.e. check first permission to write into # $GISBASE directory) ############################################################################# #%module #% label: Tool to maintain the extensions in local GRASS installation. #% description: Downloads, installs extensions from GRASS Addons SVN repository into local GRASS installation or removes installed extensions. #% keywords: general #% keywords: installation #% keywords: extensions #%end #%option #% key: extension #% type: string #% key_desc: name #% description: Name of extension to install/remove #% required: no #%end #%option #% key: operation #% type: string #% description: Operation to be performed #% required: no #% options: add,remove #% answer: add #%end #%option #% key: svnurl #% type: string #% key_desc: url #% description: SVN Addons repository URL #% required: yes #% answer: https://svn.osgeo.org/grass/grass-addons/grass7 #%end #%option #% key: prefix #% type: string #% key_desc: path #% description: Prefix where to install extension (ignored when flag -s is given) #% answer: $GRASS_ADDON_PATH #% required: yes #%end #%flag #% key: l #% description: List available modules in the GRASS Addons SVN repository #% guisection: Print #%end #%flag #% key: f #% description: List available modules in the GRASS Addons SVN repository including modules description #% guisection: Print #%end #%flag #% key: g #% description: List available modules in the GRASS Addons SVN repository (shell script style) #% guisection: Print #%end #%flag #% key: s #% description: Install system-wide (may need system administrator rights) #%end #%flag #% key: d #% description: Download source code and exit #%end #%flag #% key: i #% description: Don't install new extension, just compile it #%end import os import sys import re import atexit import urllib from grass.script import core as grass # temp dir remove_tmpdir = True tmpdir = grass.tempdir() def check(): for prog in ('svn', 'make', 'install', 'gcc'): if not grass.find_program(prog, ['--help']): grass.fatal(_("%s required. Please install '%s' first.") % (prog, prog)) def expand_module_class_name(c): name = { 'd' : 'display', 'db' : 'database', 'g' : 'general', 'i' : 'imagery', 'm' : 'misc', 'ps' : 'postscript', 'p' : 'paint', 'r' : 'raster', 'r3' : 'raster3D', 's' : 'sites', 'v' : 'vector', 'gui' : 'gui/wxpython' } if name.has_key(c): return name[c] return c def list_available_modules(): mlist = list() grass.message(_('Fetching list of modules from GRASS-Addons SVN (be patient)...')) pattern = re.compile(r'(
  • )(.+)(
  • )', re.IGNORECASE) i = 0 prefix = ['d', 'db', 'g', 'i', 'm', 'ps', 'p', 'r', 'r3', 's', 'v'] nprefix = len(prefix) for d in prefix: if flags['g']: grass.percent(i, nprefix, 1) i += 1 modclass = expand_module_class_name(d) grass.verbose(_("Checking for '%s' modules...") % modclass) url = '%s/%s' % (options['svnurl'], modclass) grass.debug("url = %s" % url, debug = 2) f = urllib.urlopen(url) if not f: grass.warning(_("Unable to fetch '%s'") % url) continue for line in f.readlines(): # list modules sline = pattern.search(line) if not sline: continue name = sline.group(2).rstrip('/') if name.split('.', 1)[0] == d: print_module_desc(name, url) mlist.append(name) mlist += list_wxgui_extensions() if flags['g']: grass.percent(1, 1, 1) return mlist def list_wxgui_extensions(print_module = True): mlist = list() grass.debug('Fetching list of wxGUI extensions from GRASS-Addons SVN (be patient)...') pattern = re.compile(r'(
  • )(.+)(
  • )', re.IGNORECASE) grass.verbose(_("Checking for '%s' modules...") % 'gui/wxpython') url = '%s/%s' % (options['svnurl'], 'gui/wxpython') grass.debug("url = %s" % url, debug = 2) f = urllib.urlopen(url) if not f: grass.warning(_("Unable to fetch '%s'") % url) return for line in f.readlines(): # list modules sline = pattern.search(line) if not sline: continue name = sline.group(2).rstrip('/') if name not in ('..', 'Makefile'): if print_module: print_module_desc(name, url) mlist.append(name) return mlist def print_module_desc(name, url): if not flags['f'] and not flags['g']: print name return if flags['g']: print 'name=' + name # check main.c first desc = get_module_desc(url + '/' + name + '/' + name) if not desc: desc = get_module_desc(url + '/' + name + '/main.c', script = False) if not desc: if not flags['g']: print name + '-' return if flags['g']: print 'description=' + desc.get('description', '') print 'keywords=' + ','.join(desc.get('keywords', list())) else: print name + ' - ' + desc.get('description', '') def get_module_desc(url, script = True): grass.debug('url=%s' % url) f = urllib.urlopen(url) if script: ret = get_module_script(f) else: ret = get_module_main(f) return ret def get_module_main(f): if not f: return dict() ret = { 'keyword' : list() } pattern = re.compile(r'(module.*->)(.+)(=)(.*)', re.IGNORECASE) keyword = re.compile(r'(G_add_keyword\()(.+)(\);)', re.IGNORECASE) key = '' value = '' for line in f.readlines(): line = line.strip() find = pattern.search(line) if find: key = find.group(2).strip() line = find.group(4).strip() else: find = keyword.search(line) if find: ret['keyword'].append(find.group(2).replace('"', '').replace('_(', '').replace(')', '')) if key: value += line if line[-2:] == ');': value = value.replace('"', '').replace('_(', '').replace(');', '') if key == 'keywords': ret[key] = map(lambda x: x.strip(), value.split(',')) else: ret[key] = value key = value = '' return ret def get_module_script(f): ret = dict() if not f: return ret begin = re.compile(r'#%.*module', re.IGNORECASE) end = re.compile(r'#%.*end', re.IGNORECASE) mline = None for line in f.readlines(): if not mline: mline = begin.search(line) if mline: if end.search(line): break try: key, value = line.split(':', 1) key = key.replace('#%', '').strip() value = value.strip() if key == 'keywords': ret[key] = map(lambda x: x.strip(), value.split(',')) else: ret[key] = value except ValueError: pass return ret def cleanup(): global tmpdir, remove_tmpdir if remove_tmpdir: grass.try_rmdir(tmpdir) else: grass.info(_("Path to the source code: '%s'") % tmpdir) def install_extension(): gisbase = os.getenv('GISBASE') if not gisbase: grass.fatal(_('$GISBASE not defined')) if grass.find_program(options['extension']): grass.warning(_("Extension '%s' already installed. Will be updated...") % options['extension']) gui_list = list_wxgui_extensions(print_module = False) if options['extension'] not in gui_list: classchar = options['extension'].split('.', 1)[0] moduleclass = expand_module_class_name(classchar) url = options['svnurl'] + '/' + moduleclass + '/' + options['extension'] else: url = options['svnurl'] + '/gui/wxpython/' + options['extension'] if not flags['s']: grass.fatal(_("Installation of wxGUI extension requires -%s flag.") % 's') grass.message(_("Fetching '%s' from GRASS-Addons SVN (be patient)...") % options['extension']) global tmpdir os.chdir(tmpdir) if grass.verbosity() == 0: outdev = open(os.devnull, 'w') else: outdev = sys.stdout if grass.call(['svn', 'checkout', url], stdout = outdev) != 0: grass.fatal(_("GRASS Addons '%s' not found in repository") % options['extension']) if flags['d']: return os.chdir(os.path.join(tmpdir, options['extension'])) grass.message(_("Compiling '%s'...") % options['extension']) if options['extension'] not in gui_list: bin_dir = os.path.join(tmpdir, options['extension'], 'bin') docs_dir = os.path.join(tmpdir, options['extension'], 'docs') html_dir = os.path.join(docs_dir, 'html') man_dir = os.path.join(tmpdir, options['extension'], 'man') man1_dir = os.path.join(man_dir, 'man1') for d in (bin_dir, docs_dir, html_dir, man_dir, man1_dir): os.mkdir(d) ret = grass.call(['make', 'MODULE_TOPDIR=%s' % gisbase.replace(' ', '\ '), 'BIN=%s' % bin_dir, 'HTMLDIR=%s' % html_dir, 'MANDIR=%s' % man1_dir], stdout = outdev) else: ret = grass.call(['make', 'MODULE_TOPDIR=%s' % gisbase.replace(' ', '\ ')], stdout = outdev) if ret != 0: grass.fatal(_('Compilation failed, sorry. Please check above error messages.')) if flags['i'] or options['extension'] in gui_list: return grass.message(_("Installing '%s'...") % options['extension']) ret = grass.call(['make', 'MODULE_TOPDIR=%s' % gisbase, 'ARCH_DISTDIR=%s' % os.path.join(tmpdir, options['extension']), 'INST_DIR=%s' % options['prefix'], 'install'], stdout = outdev) if ret != 0: grass.warning(_('Installation failed, sorry. Please check above error messages.')) else: grass.message(_("Installation of '%s' successfully finished.") % options['extension']) def remove_extension(): # is module available? if not os.path.exists(os.path.join(options['prefix'], 'bin', options['extension'])): grass.fatal(_("Module '%s' not found") % options['extension']) for file in [os.path.join(options['prefix'], 'bin', options['extension']), os.path.join(options['prefix'], 'scripts', options['extension']), os.path.join(options['prefix'], 'docs', 'html', options['extension'] + '.html')]: if os.path.isfile(file): os.remove(file) grass.message(_("'%s' successfully uninstalled.") % options['extension']) def create_dir(path): if os.path.isdir(path): return try: os.makedirs(path) except OSError, e: grass.fatal(_("Unable to create '%s': %s") % (path, e)) grass.debug("'%s' created" % path) def check_dirs(): create_dir(os.path.join(options['prefix'], 'bin')) create_dir(os.path.join(options['prefix'], 'docs', 'html')) create_dir(os.path.join(options['prefix'], 'man', 'man1')) def main(): # check dependecies check() # list available modules if flags['l'] or flags['f'] or flags['g']: list_available_modules() return 0 else: if not options['extension']: grass.fatal(_('You need to define an extension name or use -l')) # define path if flags['s']: options['prefix'] = os.environ['GISBASE'] if options['prefix'] == '$GRASS_ADDON_PATH': if not os.environ.has_key('GRASS_ADDON_PATH') or \ not os.environ['GRASS_ADDON_PATH']: grass.warning(_("GRASS_ADDON_PATH is not defined, installing to ~/.grass7/addons/")) options['prefix'] = os.path.join(os.environ['HOME'], '.grass7', 'addons') # check dirs check_dirs() if flags['d']: if options['operation'] != 'add': grass.warning(_("Flag 'd' is relevant only to 'operation=add'. Ignoring this flag.")) else: global remove_tmpdir remove_tmpdir = False if options['operation'] == 'add': install_extension() else: # remove remove_extension() return 0 if __name__ == "__main__": options, flags = grass.parser() atexit.register(cleanup) sys.exit(main())