GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
g.extension.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 ############################################################################
4 #
5 # MODULE: g.extension
6 # AUTHOR(S): Markus Neteler
7 # Pythonized by Martin Landa
8 # PURPOSE: Tool to download and install extensions from GRASS Addons SVN into
9 # local GRASS installation
10 # COPYRIGHT: (C) 2009-2011 by Markus Neteler, and the GRASS Development Team
11 #
12 # This program is free software under the GNU General
13 # Public License (>=v2). Read the file COPYING that
14 # comes with GRASS for details.
15 #
16 # TODO: add sudo support where needed (i.e. check first permission to write into
17 # $GISBASE directory)
18 #############################################################################
19 
20 #%module
21 #% label: Maintains GRASS Addons extensions in local GRASS installation.
22 #% description: Downloads, installs extensions from GRASS Addons SVN repository into local GRASS installation or removes installed extensions.
23 #% keywords: general, installation, extensions
24 #%end
25 
26 #%option
27 #% key: extension
28 #% type: string
29 #% key_desc: name
30 #% description: Name of extension to install/remove
31 #% guisection: Required
32 #%end
33 #%option
34 #% key: operation
35 #% type: string
36 #% description: Operation to be performed
37 #% required: yes
38 #% options: add,remove
39 #% answer: add
40 #%end
41 #%option
42 #% key: svnurl
43 #% type: string
44 #% key_desc: url
45 #% description: SVN Addons repository URL
46 #% answer: http://svn.osgeo.org/grass/grass-addons/grass6
47 #%end
48 #%option
49 #% key: prefix
50 #% type: string
51 #% key_desc: path
52 #% description: Prefix where to install extension (ignored when flag -s is given)
53 #% answer: $GRASS_ADDON_PATH
54 #% required: no
55 #%end
56 
57 #%flag
58 #% key: l
59 #% description: List available extensions in the GRASS Addons SVN repository
60 #% guisection: Print
61 #%end
62 #%flag
63 #% key: c
64 #% description: List available extensions in the GRASS Addons SVN repository including module description
65 #% guisection: Print
66 #%end
67 #%flag
68 #% key: g
69 #% description: List available extensions in the GRASS Addons SVN repository (shell script style)
70 #% guisection: Print
71 #%end
72 #%flag
73 #% key: a
74 #% description: List locally installed extension
75 #% guisection: Print
76 #%end
77 #%flag
78 #% key: s
79 #% description: Install system-wide (may need system administrator rights)
80 #% guisection: Install
81 #%end
82 #%flag
83 #% key: d
84 #% description: Download source code and exit
85 #% guisection: Install
86 #%end
87 #%flag
88 #% key: i
89 #% description: Don't install new extension, just compile it
90 #% guisection: Install
91 #%end
92 #%flag
93 #% key: f
94 #% description: Force removal when uninstalling extension (operation=remove)
95 #% guisection: Remove
96 #%end
97 
98 import os
99 import sys
100 import re
101 import atexit
102 import shutil
103 import glob
104 import zipfile
105 import tempfile
106 import shutil
107 import fileinput
108 
109 from urllib2 import urlopen, HTTPError
110 
111 try:
112  import xml.etree.ElementTree as etree
113 except ImportError:
114  import elementtree.ElementTree as etree # Python <= 2.4
115 
116 from grass.script import core as grass
117 
118 # temp dir
119 remove_tmpdir = True
120 
121 # check requirements
123  for prog in ('svn', 'make', 'gcc'):
124  if not grass.find_program(prog, ['--help']):
125  grass.fatal(_("'%s' required. Please install '%s' first.") % (prog, prog))
126 
127 # expand prefix to class name
129  name = { 'd' : 'display',
130  'db' : 'database',
131  'g' : 'general',
132  'i' : 'imagery',
133  'm' : 'misc',
134  'ps' : 'postscript',
135  'p' : 'paint',
136  'r' : 'raster',
137  'r3' : 'raster3d',
138  's' : 'sites',
139  'v' : 'vector',
140  'gui' : 'gui/wxpython' }
141 
142  if name.has_key(c):
143  return name[c]
144 
145  return c
146 
147 
148 # list installed extensions
149 def get_installed_extensions(force = False):
150  fXML = os.path.join(options['prefix'], 'modules.xml')
151  if not os.path.exists(fXML):
152  if force:
153  write_xml_modules(fXML)
154  else:
155  grass.warning(_("No metadata file available"))
156  return []
157 
158  # read XML file
159  fo = open(fXML, 'r')
160  try:
161  tree = etree.fromstring(fo.read())
162  except:
163  os.remove(fXML)
164  write_xml_modules(fXML)
165  return []
166  fo.close()
167 
168  ret = list()
169  for tnode in tree.findall('task'):
170  ret.append(tnode.get('name').strip())
171 
172  return ret
173 
174 # list extensions (read XML file from grass.osgeo.org/addons)
176  mlist = list()
177 
178  # try to download XML metadata file first
179  url = "http://grass.osgeo.org/addons/grass%s/modules.xml" % grass.version()['version'].split('.')[0]
180  grass.debug("url=%s" % url, 1)
181  try:
182  f = urlopen(url)
183  try:
184  tree = etree.fromstring(f.read())
185  except:
186  grass.fatal(_("Unable to parse '%s'") % url)
187 
188  for mnode in tree.findall('task'):
189  name = mnode.get('name')
190  if flags['c'] or flags['g']:
191  desc = mnode.find('description').text
192  if not desc:
193  desc = ''
194  keyw = mnode.find('keywords').text
195  if not keyw:
196  keyw = ''
197 
198  if flags['g']:
199  print 'name=' + name
200  print 'description=' + desc
201  print 'keywords=' + keyw
202  elif flags['c']:
203  print name + ' - ' + desc
204  else:
205  print name
206  except HTTPError:
208 
209  return mlist
210 
211 # list extensions (scan SVN repo)
213  mlist = list()
214  grass.message(_('Fetching list of extensions from GRASS-Addons SVN (be patient)...'))
215  pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
216 
217  if flags['c']:
218  grass.warning(_("Flag 'c' ignored, metadata file not available"))
219  if flags['g']:
220  grass.warning(_("Flag 'g' ignored, metadata file not available"))
221 
222  prefix = ['d', 'db', 'g', 'i', 'm', 'ps',
223  'p', 'r', 'r3', 's', 'v']
224  nprefix = len(prefix)
225  for d in prefix:
226  modclass = expand_module_class_name(d)
227  grass.verbose(_("Checking for '%s' modules...") % modclass)
228 
229  url = '%s/%s' % (options['svnurl'], modclass)
230  grass.debug("url = %s" % url, debug = 2)
231  try:
232  f = urlopen(url)
233  except HTTPError:
234  grass.debug(_("Unable to fetch '%s'") % url, debug = 1)
235  continue
236 
237  for line in f.readlines():
238  # list extensions
239  sline = pattern.search(line)
240  if not sline:
241  continue
242  name = sline.group(2).rstrip('/')
243  if name.split('.', 1)[0] == d:
244  print name
245  mlist.append(name)
246 
247  mlist += list_wxgui_extensions()
248 
249  return mlist
250 
251 # list wxGUI extensions
252 def list_wxgui_extensions(print_module = True):
253  mlist = list()
254  grass.debug('Fetching list of wxGUI extensions from GRASS-Addons SVN (be patient)...')
255  pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
256  grass.verbose(_("Checking for '%s' modules...") % 'gui/wxpython')
257 
258  url = '%s/%s' % (options['svnurl'], 'gui/wxpython')
259  grass.debug("url = %s" % url, debug = 2)
260  f = urlopen(url)
261  if not f:
262  grass.warning(_("Unable to fetch '%s'") % url)
263  return
264 
265  for line in f.readlines():
266  # list extensions
267  sline = pattern.search(line)
268  if not sline:
269  continue
270  name = sline.group(2).rstrip('/')
271  if name not in ('..', 'Makefile'):
272  if print_module:
273  print name
274  mlist.append(name)
275 
276  return mlist
277 
278 def cleanup():
279  if remove_tmpdir:
280  grass.try_rmdir(tmpdir)
281  else:
282  grass.message(_("Path to the source code:"))
283  sys.stderr.write('%s\n' % os.path.join(tmpdir, options['extension']))
284 
285 # write out meta-file
286 def write_xml_modules(name, tree = None):
287  fo = open(name, 'w')
288  version = grass.version()['version'].split('.')[0]
289  fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
290  fo.write('<!DOCTYPE task SYSTEM "grass-addons.dtd">\n')
291  fo.write('<addons version="%s">\n' % version)
292 
293  if tree is not None:
294  for tnode in tree.findall('task'):
295  indent = 4
296  fo.write('%s<task name="%s">\n' % (' ' * indent, tnode.get('name')))
297  indent += 4
298  fo.write('%s<description>%s</description>\n' % \
299  (' ' * indent, tnode.find('description').text))
300  fo.write('%s<keywords>%s</keywords>\n' % \
301  (' ' * indent, tnode.find('keywords').text))
302  bnode = tnode.find('binary')
303  if bnode is not None:
304  fo.write('%s<binary>\n' % (' ' * indent))
305  indent += 4
306  for fnode in bnode.findall('file'):
307  fpath = fnode.text.split(os.path.sep)
308  if not flags['s']: # tidy citizen hacks
309  if fpath[0] in ('bin', 'scripts'):
310  del fpath[0]
311  if fpath[0] == 'man':
312  fpath.insert(0, 'docs')
313 
314  fo.write('%s<file>%s</file>\n' % \
315  (' ' * indent, os.path.join(options['prefix'],
316  os.path.sep.join(fpath))))
317  indent -= 4
318  fo.write('%s</binary>\n' % (' ' * indent))
319  libgisRev = grass.version()['libgis_revision']
320  fo.write('%s<libgis revision="%s" />\n' % \
321  (' ' * indent, libgisRev))
322  indent -= 4
323  fo.write('%s</task>\n' % (' ' * indent))
324 
325  fo.write('</addons>\n')
326  fo.close()
327 
328 # update local meta-file when installing new extension
330  # read metadata from remote server
331  url = "http://grass.osgeo.org/addons/grass%s/modules.xml" % grass.version()['version'].split('.')[0]
332  data = None
333  try:
334  f = urlopen(url)
335  tree = etree.fromstring(f.read())
336  for mnode in tree.findall('task'):
337  name = mnode.get('name')
338  if name != options['extension']:
339  continue
340 
341  fList = list()
342  bnode = mnode.find('binary')
343  windows = sys.platform == 'win32'
344  if bnode is not None:
345  for fnode in bnode.findall('file'):
346  path = fnode.text.split('/')
347  if windows:
348  if path[0] == 'bin':
349  path[-1] += '.exe'
350  if path[0] == 'scripts':
351  path[-1] += '.py'
352  fList.append(os.path.sep.join(path))
353 
354  desc = mnode.find('description').text
355  if not desc:
356  desc = ''
357  keyw = mnode.find('keywords').text
358  if not keyw:
359  keyw = ''
360 
361  data = { 'name' : name,
362  'desc' : desc,
363  'keyw' : keyw,
364  'files' : fList,
365  }
366  except HTTPError:
367  grass.error(_("Unable to read metadata file from the remote server"))
368 
369  if not data:
370  grass.warning(_("No metadata available"))
371  return
372 
373  fXML = os.path.join(options['prefix'], 'modules.xml')
374  # create an empty file if not exists
375  if not os.path.exists(fXML):
376  write_xml_modules(fXML)
377 
378  # read XML file
379  fo = open(fXML, 'r')
380  tree = etree.fromstring(fo.read())
381  fo.close()
382 
383  # update tree
384  tnode = None
385  for node in tree.findall('task'):
386  if node.get('name') == options['extension']:
387  tnode = node
388  break
389 
390  if tnode is not None:
391  # update existing node
392  dnode = tnode.find('description')
393  if dnode is not None:
394  dnode.text = data['desc']
395  knode = tnode.find('keywords')
396  if knode is not None:
397  knode.text = data['keyw']
398  bnode = tnode.find('binary')
399  if bnode is not None:
400  tnode.remove(bnode)
401  bnode = etree.Element('binary')
402  for f in data['files']:
403  fnode = etree.Element('file')
404  fnode.text = f
405  bnode.append(fnode)
406  tnode.append(bnode)
407  else:
408  # create new node for task
409  tnode = etree.Element('task', attrib = { 'name' : data['name'] })
410  dnode = etree.Element('description')
411  dnode.text = data['desc']
412  tnode.append(dnode)
413  knode = etree.Element('keywords')
414  knode.text = data['keyw']
415  tnode.append(knode)
416  bnode = etree.Element('binary')
417  for f in data['files']:
418  fnode = etree.Element('file')
419  fnode.text = f
420  bnode.append(fnode)
421  tnode.append(bnode)
422  tree.append(tnode)
423 
424  write_xml_modules(fXML, tree)
425 
426 # install extension on MS Windows
428  ### TODO: do not use hardcoded url - http://wingrass.fsv.cvut.cz/grassXX/addonsX.X.X
429  version = grass.version()['version'].split('.')
430  grass.message(_("Downloading precompiled GRASS Addons <%s>...") % options['extension'])
431  url = "http://wingrass.fsv.cvut.cz/grass%s%s/addons/grass-%s.%s.%s" % \
432  (version[0], version[1], version[0], version[1], version[2])
433  grass.debug("url=%s" % url, 1)
434 
435  try:
436  f = urlopen(url + '/' + options['extension'] + '.zip')
437 
438  # create addons dir if not exists
439  if not os.path.exists(options['prefix']):
440  os.mkdir(options['prefix'])
441 
442  # download data
443  fo = tempfile.TemporaryFile()
444  fo.write(f.read())
445  zfobj = zipfile.ZipFile(fo)
446  for name in zfobj.namelist():
447  if name.endswith('/'):
448  d = os.path.join(options['prefix'], name)
449  if not os.path.exists(d):
450  os.mkdir(d)
451  else:
452  outfile = open(os.path.join(options['prefix'], name), 'wb')
453  outfile.write(zfobj.read(name))
454  outfile.close()
455 
456  fo.close()
457  except HTTPError:
458  grass.fatal(_("GRASS Addons <%s> not found") % options['extension'])
459 
460  return 0
461 
462 # install extension
464  gisbase = os.getenv('GISBASE')
465  if not gisbase:
466  grass.fatal(_('$GISBASE not defined'))
467 
468  if options['extension'] in get_installed_extensions(force = True):
469  grass.warning(_("Extension <%s> already installed. Re-installing...") % options['extension'])
470 
471  if sys.platform == "win32":
472  ret = install_extension_win()
473  else:
475 
476  if ret != 0:
477  grass.warning(_('Installation failed, sorry. Please check above error messages.'))
478  else:
479  grass.message(_("Updating metadata file..."))
481  grass.message(_("Installation of <%s> successfully finished") % options['extension'])
482 
483  # cleanup build cruft
484  if not flags['s']:
485  tidy_citizen()
486 
487  if not os.environ.has_key('GRASS_ADDON_PATH') or \
488  not os.environ['GRASS_ADDON_PATH']:
489  grass.warning(_('This add-on module will not function until you set the '
490  'GRASS_ADDON_PATH environment variable (see "g.manual variables")'))
491 
492 # install extension on other plaforms
494  gisbase = os.getenv('GISBASE')
495  gui_list = list_wxgui_extensions(print_module = False)
496 
497  if options['extension'] not in gui_list:
498  classchar = options['extension'].split('.', 1)[0]
499  moduleclass = expand_module_class_name(classchar)
500  url = options['svnurl'] + '/' + moduleclass + '/' + options['extension']
501  else:
502  url = options['svnurl'] + '/gui/wxpython/' + options['extension']
503  if not flags['s']:
504  grass.fatal(_("Installation of wxGUI extension requires -%s flag.") % 's')
505 
506  grass.message(_("Fetching <%s> from GRASS-Addons SVN (be patient)...") % options['extension'])
507 
508  os.chdir(tmpdir)
509  if grass.verbosity() <= 2:
510  outdev = open(os.devnull, 'w')
511  else:
512  outdev = sys.stdout
513 
514  if grass.call(['svn', 'checkout',
515  url], stdout = outdev) != 0:
516  grass.fatal(_("GRASS Addons <%s> not found") % options['extension'])
517 
518  dirs = { 'bin' : os.path.join(tmpdir, options['extension'], 'bin'),
519  'docs' : os.path.join(tmpdir, options['extension'], 'docs'),
520  'html' : os.path.join(tmpdir, options['extension'], 'docs', 'html'),
521  'man' : os.path.join(tmpdir, options['extension'], 'man'),
522  'man1' : os.path.join(tmpdir, options['extension'], 'man', 'man1'),
523  'scripts' : os.path.join(tmpdir, options['extension'], 'scripts'),
524  'etc' : os.path.join(tmpdir, options['extension'], 'etc'),
525  }
526 
527  makeCmd = ['make',
528  'MODULE_TOPDIR=%s' % gisbase.replace(' ', '\ '),
529  'BIN=%s' % dirs['bin'],
530  'HTMLDIR=%s' % dirs['html'],
531  'MANDIR=%s' % dirs['man1'],
532  'SCRIPTDIR=%s' % dirs['scripts'],
533  'ETC=%s' % os.path.join(dirs['etc'],options['extension'])
534  ]
535 
536  installCmd = ['make',
537  'MODULE_TOPDIR=%s' % gisbase,
538  'ARCH_DISTDIR=%s' % os.path.join(tmpdir, options['extension']),
539  'INST_DIR=%s' % options['prefix'],
540  'install'
541  ]
542 
543  if flags['d']:
544  grass.message(_("To compile run:"))
545  sys.stderr.write(' '.join(makeCmd) + '\n')
546  grass.message(_("To install run:\n\n"))
547  sys.stderr.write(' '.join(installCmd) + '\n')
548  return
549 
550  os.chdir(os.path.join(tmpdir, options['extension']))
551 
552  grass.message(_("Compiling..."))
553  if options['extension'] not in gui_list:
554  ret = grass.call(makeCmd,
555  stdout = outdev)
556  else:
557  ret = grass.call(['make',
558  'MODULE_TOPDIR=%s' % gisbase.replace(' ', '\ ')],
559  stdout = outdev)
560 
561  if ret != 0:
562  grass.fatal(_('Compilation failed, sorry. Please check above error messages.'))
563 
564  if flags['i'] or options['extension'] in gui_list:
565  return
566 
567  grass.message(_("Installing..."))
568 
569  return grass.call(installCmd,
570  stdout = outdev)
571 
572 # remove dir if empty
574  if os.path.exists(path) and not os.listdir(path):
575  os.removedirs(path)
576 
577 # see http://lists.osgeo.org/pipermail/grass-dev/2011-December/056938.html
579  if sys.platform == 'win32':
580  EXT_BIN = '.exe'
581  EXT_SCT = '.bat'
582  else:
583  EXT_BIN = EXT_SCT = ''
584 
585  # move script/ and bin/ to main dir
586  if os.path.exists(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_BIN):
587  shutil.move(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_BIN,
588  os.path.join(options['prefix'], options['extension']) + EXT_BIN)
589  if os.path.exists(os.path.join(options['prefix'], 'scripts', options['extension'])):
590  shutil.move(os.path.join(options['prefix'], 'scripts', options['extension']),
591  os.path.join(options['prefix'], options['extension']))
592  if os.path.exists(os.path.join(options['prefix'], 'scripts', options['extension'] + '.py')):
593  shutil.move(os.path.join(options['prefix'], 'scripts', options['extension'] + '.py'),
594  os.path.join(options['prefix'], options['extension'] + '.py'))
595  if sys.platform == 'win32' and \
596  os.path.exists(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_SCT):
597  shutil.move(os.path.join(options['prefix'], 'bin', options['extension']) + EXT_SCT,
598  os.path.join(options['prefix'], options['extension']) + EXT_SCT)
599  # fix script path
600  for line in fileinput.FileInput(os.path.join(options['prefix'], options['extension']) + EXT_SCT,
601  inplace = True):
602  line = line.replace("/scripts", "")
603  print line
604 
605  # move man/ into docs/
606  if os.path.exists(os.path.join(options['prefix'], 'man', 'man1', options['extension'] + '.1')):
607  shutil.move(os.path.join(options['prefix'], 'man', 'man1', options['extension'] + '.1'),
608  os.path.join(options['prefix'], 'docs', 'man', 'man1', options['extension'] + '.1'))
609 
610  # if empty, rmdir bin, etc, man, scripts
611  for d in ('bin', 'etc', os.path.join('man', 'man1'), 'man', 'scripts'):
612  remove_empty_dir(os.path.join(options['prefix'], d))
613 
614 # update local meta-file when removing existing extension
616  fXML = os.path.join(options['prefix'], 'modules.xml')
617  if not os.path.exists(fXML):
618  return
619 
620  # read XML file
621  fo = open(fXML, 'r')
622  tree = etree.fromstring(fo.read())
623  fo.close()
624 
625  tnode = None
626  for node in tree.findall('task'):
627  if node.get('name') == options['extension']:
628  tnode = node
629  break
630 
631  if tnode is not None:
632  tree.remove(tnode)
633 
634  write_xml_modules(fXML, tree)
635 
636 # remove existing extension (reading XML file)
637 def remove_extension(force = False):
638  # try to read XML metadata file first
639  fXML = os.path.join(options['prefix'], 'modules.xml')
640  name = options['extension']
641  if name not in get_installed_extensions():
642  grass.warning(_("Extension <%s> not found") % name)
643 
644  if force:
645  grass.verbose(_("List of removed files:"))
646  else:
647  grass.info(_("Files to be removed (use flag 'f' to force removal):"))
648 
649  if os.path.exists(fXML):
650  f = open(fXML, 'r')
651  tree = etree.fromstring(f.read())
652  flist = []
653  for task in tree.findall('task'):
654  if name == task.get('name', default = '') and \
655  task.find('binary') is not None:
656  for f in task.find('binary').findall('file'):
657  flist.append(f.text)
658 
659  if flist:
660  removed = False
661  err = list()
662  for fpath in flist:
663  try:
664  if force:
665  grass.verbose(fpath)
666  os.remove(fpath)
667  removed = True
668  else:
669  print fpath
670  except OSError:
671  err.append((_("Unable to remove file '%s'") % fpath))
672  if force and not removed:
673  grass.fatal(_("Extension <%s> not found") % options['extension'])
674 
675  if err:
676  for e in err:
677  grass.error(e)
678  else:
679  remove_extension_std(force)
680  else:
681  remove_extension_std(force)
682 
683  if force:
684  grass.message(_("Updating metadata file..."))
686  grass.message(_("Extension <%s> successfully uninstalled.") % options['extension'])
687  else:
688  grass.warning(_("Extension <%s> not removed.\n"
689  "Re-run '%s' with 'f' flag to force removal") % (options['extension'], 'g.extension'))
690 
691 # remove exising extension (using standard files layout)
692 def remove_extension_std(force = False):
693  # try even if module does not seem to be available,
694  # as the user may be trying to get rid of left over cruft
695  for fpath in [os.path.join(options['prefix'], options['extension']),
696  os.path.join(options['prefix'], 'bin', options['extension']),
697  os.path.join(options['prefix'], 'scripts', options['extension']),
698  os.path.join(options['prefix'], 'docs', 'html', options['extension'] + '.html'),
699  os.path.join(options['prefix'], 'docs', 'man', 'man1', options['extension'] + '.1'),
700  os.path.join(options['prefix'], 'man', 'man1', options['extension'] + '.1')]:
701  if os.path.isfile(fpath):
702  if force:
703  grass.verbose(fpath)
704  os.remove(fpath)
705  else:
706  print fpath
707 
708 # check links in CSS
710  dist_file = os.path.join(os.getenv('GISBASE'), 'docs', 'html', fil)
711  addons_file = os.path.join(options['prefix'], 'docs', 'html', fil)
712 
713  if os.path.isfile(addons_file):
714  return
715 
716  try:
717  shutil.copyfile(dist_file,addons_file)
718  except OSError, e:
719  grass.fatal(_("Unable to create '%s': %s") % (addons_file, e))
720 
721 def create_dir(path):
722  if os.path.isdir(path):
723  return
724 
725  try:
726  os.makedirs(path)
727  except OSError, e:
728  grass.fatal(_("Unable to create '%s': %s") % (path, e))
729 
730  grass.debug("'%s' created" % path)
731 
733  create_dir(os.path.join(options['prefix'], 'bin'))
734  create_dir(os.path.join(options['prefix'], 'docs', 'html'))
735  check_style_files('grass_logo.png')
736  check_style_files('grassdocs.css')
737  # create_dir(os.path.join(options['prefix'], 'etc'))
738  create_dir(os.path.join(options['prefix'], 'docs', 'man', 'man1'))
739  create_dir(os.path.join(options['prefix'], 'man', 'man1'))
740  create_dir(os.path.join(options['prefix'], 'scripts'))
741 
742 def main():
743  # check dependecies
744  if sys.platform != "win32":
745  check_progs()
746 
747  # define path
748  if flags['s']:
749  options['prefix'] = os.environ['GISBASE']
750  if options['prefix'] == '$GRASS_ADDON_PATH':
751  if not os.environ.has_key('GRASS_ADDON_PATH') or \
752  not os.environ['GRASS_ADDON_PATH']:
753  major_version = int(grass.version()['version'].split('.', 1)[0])
754  grass.warning(_("GRASS_ADDON_PATH is not defined, "
755  "installing to ~/.grass%d/addons/") % major_version)
756  options['prefix'] = os.path.join(os.environ['HOME'], '.grass%d' % major_version, 'addons')
757  else:
758  path_list = os.environ['GRASS_ADDON_PATH'].split(os.pathsep)
759  if len(path_list) < 1:
760  grass.fatal(_("Invalid GRASS_ADDON_PATH value - '%s'") % os.environ['GRASS_ADDON_PATH'])
761  if len(path_list) > 1:
762  grass.warning(_("GRASS_ADDON_PATH has more items, using first defined - '%s'") % path_list[0])
763  options['prefix'] = path_list[0]
764 
765  # list available modules
766  if flags['l'] or flags['c'] or flags['g']:
768  return 0
769  elif flags['a']:
770  elist = get_installed_extensions()
771  if elist:
772  grass.message(_("List of installed extensions:"))
773  sys.stdout.write('\n'.join(elist))
774  sys.stdout.write('\n')
775  else:
776  grass.info(_("No extension installed"))
777  return 0
778  else:
779  if not options['extension']:
780  grass.fatal(_('You need to define an extension name or use -l'))
781 
782  if flags['d']:
783  if options['operation'] != 'add':
784  grass.warning(_("Flag 'd' is relevant only to 'operation=add'. Ignoring this flag."))
785  else:
786  global remove_tmpdir
787  remove_tmpdir = False
788 
789  if options['operation'] == 'add':
790  check_dirs()
792  else: # remove
793  remove_extension(flags['f'])
794 
795  return 0
796 
797 if __name__ == "__main__":
798  options, flags = grass.parser()
799  global tmpdir
800  tmpdir = grass.tempdir()
801  atexit.register(cleanup)
802  sys.exit(main())