g.extension.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # MODULE: g.extension
  5. # AUTHOR(S): Markus Neteler
  6. # Pythonized by Martin Landa
  7. # PURPOSE: Tool to download and install extensions from GRASS Addons SVN into
  8. # local GRASS installation
  9. # COPYRIGHT: (C) 2009-2010 by Markus Neteler, and the GRASS Development Team
  10. #
  11. # This program is free software under the GNU General
  12. # Public License (>=v2). Read the file COPYING that
  13. # comes with GRASS for details.
  14. #
  15. # TODO: add sudo support where needed (i.e. check first permission to write into
  16. # $GISBASE directory)
  17. #############################################################################
  18. #%module
  19. #% label: Tool to maintain the extensions in local GRASS installation.
  20. #% description: Downloads, installs extensions from GRASS Addons SVN repository into local GRASS installation or removes installed extensions.
  21. #% keywords: general
  22. #% keywords: installation
  23. #% keywords: extensions
  24. #%end
  25. #%option
  26. #% key: extension
  27. #% type: string
  28. #% key_desc: name
  29. #% description: Name of extension to install/remove
  30. #% required: no
  31. #%end
  32. #%option
  33. #% key: operation
  34. #% type: string
  35. #% description: Operation to be performed
  36. #% required: no
  37. #% options: add,remove
  38. #% answer: add
  39. #%end
  40. #%option
  41. #% key: svnurl
  42. #% type: string
  43. #% key_desc: url
  44. #% description: SVN Addons repository URL
  45. #% required: yes
  46. #% answer: https://svn.osgeo.org/grass/grass-addons
  47. #%end
  48. #%option
  49. #% key: prefix
  50. #% type: string
  51. #% key_desc: path
  52. #% description: Prefix where to install extension
  53. #% answer: $(HOME)/.grass7/addons
  54. #% required: yes
  55. #%end
  56. #%option
  57. #% key: menuitem
  58. #% type: string
  59. #% label: Menu item in wxGUI
  60. #% description: Given as string, e.g. 'Imagery;Filter image'
  61. #% required: no
  62. #%end
  63. #%flag
  64. #% key: l
  65. #% description: List available modules in the add-ons repository
  66. #% guisection: Print
  67. #%end
  68. #%flag
  69. #% key: f
  70. #% description: List available modules in the add-ons repository including modules description
  71. #% guisection: Print
  72. #%end
  73. #%flag
  74. #% key: g
  75. #% description: List available modules in the add-ons repository in shell script style
  76. #% guisection: Print
  77. #%end
  78. import os
  79. import sys
  80. import re
  81. import atexit
  82. import urllib
  83. from grass.script import core as grass
  84. # temp dir
  85. tmpdir = grass.tempfile()
  86. grass.try_remove(tmpdir)
  87. os.mkdir(tmpdir)
  88. def check():
  89. # check if we have the svn client
  90. if not grass.find_program('svn'):
  91. grass.fatal(_('svn client required. Please install subversion first.'))
  92. def expand_module_class_name(c):
  93. name = { 'd' : 'display',
  94. 'db' : 'database',
  95. 'g' : 'general',
  96. 'i' : 'imagery',
  97. 'm' : 'misc',
  98. 'ps' : 'postscript',
  99. 'p' : 'paint',
  100. 'r' : 'raster',
  101. 'r3' : 'raster3D',
  102. 's' : 'sites',
  103. 'v' : 'vector' }
  104. if name.has_key(c):
  105. return name[c]
  106. return c
  107. def list_available_modules(svnurl, full = False, shell = False):
  108. grass.message(_('Fetching list of modules from GRASS-Addons SVN (be patient)...'))
  109. pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
  110. i = 0
  111. prefix = ['d', 'db', 'g', 'i', 'ps',
  112. 'p', 'r', 'r3', 'v']
  113. nprefix = len(prefix)
  114. for d in prefix:
  115. if shell:
  116. grass.percent(i, nprefix, 1)
  117. i += 1
  118. modclass = expand_module_class_name(d)
  119. url = svnurl + '/' + modclass
  120. f = urllib.urlopen(url)
  121. if not f:
  122. grass.warning(_("Unable to fetch '%s'") % url)
  123. continue
  124. for line in f.readlines():
  125. # list modules
  126. sline = pattern.search(line)
  127. if sline and sline.group(2).split('.', 1)[0] == d:
  128. name = sline.group(2).rstrip('/')
  129. print_module_desc(name, url, full, shell)
  130. if shell:
  131. grass.percent(1, 1, 1)
  132. def print_module_desc(name, url, full = False, shell = False):
  133. if not full and not shell:
  134. print name
  135. return
  136. if shell:
  137. print 'name=' + name
  138. # check main.c first
  139. desc = get_module_desc(url + '/' + name + '/' + name)
  140. if not desc:
  141. desc = get_module_desc(url + '/' + name + '/main.c', script = False)
  142. if not desc:
  143. if not shell:
  144. print name + '-'
  145. return
  146. if shell:
  147. print 'description=' + desc.get('description', '')
  148. print 'keywords=' + ','.join(desc.get('keywords', list()))
  149. else:
  150. print name + ' - ' + desc.get('description', '')
  151. def get_module_desc(url, script = True):
  152. grass.debug('url=%s' % url)
  153. f = urllib.urlopen(url)
  154. if script:
  155. ret = get_module_script(f)
  156. else:
  157. ret = get_module_main(f)
  158. return ret
  159. def get_module_main(f):
  160. if not f:
  161. return dict()
  162. ret = { 'keyword' : list() }
  163. pattern = re.compile(r'(module.*->)(.+)(=)(.*)', re.IGNORECASE)
  164. keyword = re.compile(r'(G_add_keyword\()(.+)(\);)', re.IGNORECASE)
  165. key = ''
  166. value = ''
  167. for line in f.readlines():
  168. line = line.strip()
  169. find = pattern.search(line)
  170. if find:
  171. key = find.group(2).strip()
  172. line = find.group(4).strip()
  173. else:
  174. find = keyword.search(line)
  175. if find:
  176. ret['keyword'].append(find.group(2).replace('"', '').replace('_(', '').replace(')', ''))
  177. if key:
  178. value += line
  179. if line[-2:] == ');':
  180. value = value.replace('"', '').replace('_(', '').replace(');', '')
  181. if key == 'keywords':
  182. ret[key] = map(lambda x: x.strip(), value.split(','))
  183. else:
  184. ret[key] = value
  185. key = value = ''
  186. return ret
  187. def get_module_script(f):
  188. ret = dict()
  189. if not f:
  190. return ret
  191. begin = re.compile(r'#%.*module', re.IGNORECASE)
  192. end = re.compile(r'#%.*end', re.IGNORECASE)
  193. mline = None
  194. for line in f.readlines():
  195. if not mline:
  196. mline = begin.search(line)
  197. if mline:
  198. if end.search(line):
  199. break
  200. try:
  201. key, value = line.split(':', 1)
  202. key = key.replace('#%', '').strip()
  203. value = value.strip()
  204. if key == 'keywords':
  205. ret[key] = map(lambda x: x.strip(), value.split(','))
  206. else:
  207. ret[key] = value
  208. except ValueError:
  209. pass
  210. return ret
  211. def cleanup():
  212. global tmpdir
  213. grass.try_rmdir(tmpdir)
  214. def install_extension(svnurl, prefix, module):
  215. gisbase = os.getenv('GISBASE')
  216. if not gisbase:
  217. grass.fatal(_('$GISBASE not defined'))
  218. if grass.find_program(module):
  219. grass.warning(_("Extension '%s' already installed. Will be updated...") % module)
  220. classchar = module.split('.', 1)[0]
  221. moduleclass = expand_module_class_name(classchar)
  222. url = svnurl + '/' + moduleclass + '/' + module
  223. grass.message(_("Fetching '%s' from GRASS-Addons SVN (be patient)...") % module)
  224. global tmpdir
  225. os.chdir(tmpdir)
  226. if grass.call(['svn', 'checkout',
  227. url]) != 0:
  228. grass.fatal(_("GRASS Addons '%s' not found in repository") % module)
  229. os.chdir(os.path.join(tmpdir, module))
  230. grass.message(_("Compiling '%s'...") % module)
  231. if grass.call(['make',
  232. 'MODULE_TOPDIR=%s' % gisbase]) != 0:
  233. grass.fatal(_('Compilation failed, sorry. Please check above error messages.'))
  234. grass.message(_("Installing '%s'...") % module)
  235. # can we write ?
  236. try:
  237. # replace with something better
  238. file = os.path.join(prefix, 'test')
  239. f = open(file, "w")
  240. f.close()
  241. os.remove(file)
  242. ret = grass.call(['make',
  243. 'MODULE_TOPDIR=%s' % gisbase,
  244. 'INST_DIR=%s' % prefix,
  245. 'install'])
  246. except IOError:
  247. ret = grass.call(['sudo', 'make',
  248. 'MODULE_TOPDIR=%s' % gisbase,
  249. 'INST_DIR=%s' % prefix,
  250. 'install'])
  251. if ret != 0:
  252. grass.warning(_('Installation failed, sorry. Please check above error messages.'))
  253. else:
  254. grass.message(_("Installation of '%s' successfully finished.") % module)
  255. def remove_extension(prefix, module):
  256. # is module available?
  257. if not grass.find_program(module):
  258. grass.fatal(_("'%s' not found") % module)
  259. for file in [os.path.join(prefix, 'bin', module),
  260. os.path.join(prefix, 'scripts', module),
  261. os.path.join(prefix, 'docs', 'html', module + '.html')]:
  262. if os.path.isfile(file):
  263. os.remove(file)
  264. grass.message(_("'%s' successfully uninstalled.") % module)
  265. def update_menu(menuitem, module, operation):
  266. grass.warning(_('Not implemented'))
  267. if operation == 'add':
  268. pass
  269. else: # remove
  270. pass
  271. def main():
  272. # check dependecies
  273. check()
  274. # list available modules
  275. if flags['l'] or flags['f'] or flags['g']:
  276. list_available_modules(options['svnurl'], full = flags['f'], shell = flags['g'])
  277. return 0
  278. else:
  279. if not options['extension']:
  280. grass.fatal(_('You need to define an extension name or use -l'))
  281. # TODO: check more variable
  282. if '$(HOME)' in options['prefix']:
  283. options['prefix'] = options['prefix'].replace('$(HOME)', os.environ['HOME'])
  284. if not os.path.isdir(options['prefix']):
  285. try:
  286. os.makedirs(options['prefix'])
  287. except OSError, e:
  288. grass.fatal(_("Unable to create '%s'\n%s") % (options['prefix'], e))
  289. grass.warning(_("'%s' created") % options['prefix'])
  290. if options['operation'] == 'add':
  291. install_extension(options['svnurl'], options['prefix'], options['extension'])
  292. else: # remove
  293. remove_extension(options['prefix'], options['extension'])
  294. if options['menuitem']:
  295. update_menu(options['menuitem'], options['extension'], options['operation'])
  296. return 0
  297. if __name__ == "__main__":
  298. options, flags = grass.parser()
  299. atexit.register(cleanup)
  300. sys.exit(main())