g.extension.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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: $GRASS_ADDON_PATH
  54. #% required: yes
  55. #%end
  56. #%flag
  57. #% key: l
  58. #% description: List available modules in the GRASS Addons SVN repository
  59. #% guisection: Print
  60. #%end
  61. #%flag
  62. #% key: f
  63. #% description: List available modules in the GRASS Addons SVN repository including modules description
  64. #% guisection: Print
  65. #%end
  66. #%flag
  67. #% key: g
  68. #% description: List available modules in the GRASS Addons SVN repository (shell script style)
  69. #% guisection: Print
  70. #%end
  71. #%flag
  72. #% key: d
  73. #% description: Don't delete downloaded source code when installing new extension
  74. #%end
  75. #%flag
  76. #% key: i
  77. #% description: Don't install new extension, just compile it
  78. #%end
  79. import os
  80. import sys
  81. import re
  82. import atexit
  83. import urllib
  84. from grass.script import core as grass
  85. # temp dir
  86. remove_tmpdir = True
  87. tmpdir = grass.tempfile()
  88. grass.try_remove(tmpdir)
  89. os.mkdir(tmpdir)
  90. def check():
  91. # check if we have the svn client
  92. if not grass.find_program('svn'):
  93. grass.fatal(_('svn client required. Please install subversion first.'))
  94. # probably test here if we have "make" and "install" programs as well. how about gcc?
  95. def expand_module_class_name(c):
  96. name = { 'd' : 'display',
  97. 'db' : 'database',
  98. 'g' : 'general',
  99. 'i' : 'imagery',
  100. 'm' : 'misc',
  101. 'ps' : 'postscript',
  102. 'p' : 'paint',
  103. 'r' : 'raster',
  104. 'r3' : 'raster3D',
  105. 's' : 'sites',
  106. 'v' : 'vector' }
  107. if name.has_key(c):
  108. return name[c]
  109. return c
  110. def list_available_modules(svnurl, full = False, shell = False):
  111. grass.message(_('Fetching list of modules from GRASS-Addons SVN (be patient)...'))
  112. pattern = re.compile(r'(<li><a href=".+">)(.+)(</a></li>)', re.IGNORECASE)
  113. i = 0
  114. prefix = ['d', 'db', 'g', 'i', 'ps',
  115. 'p', 'r', 'r3', 'v']
  116. nprefix = len(prefix)
  117. for d in prefix:
  118. if shell:
  119. grass.percent(i, nprefix, 1)
  120. i += 1
  121. modclass = expand_module_class_name(d)
  122. url = svnurl + '/' + modclass
  123. f = urllib.urlopen(url)
  124. if not f:
  125. grass.warning(_("Unable to fetch '%s'") % url)
  126. continue
  127. for line in f.readlines():
  128. # list modules
  129. sline = pattern.search(line)
  130. if sline and sline.group(2).split('.', 1)[0] == d:
  131. name = sline.group(2).rstrip('/')
  132. print_module_desc(name, url, full, shell)
  133. if shell:
  134. grass.percent(1, 1, 1)
  135. def print_module_desc(name, url, full = False, shell = False):
  136. if not full and not shell:
  137. print name
  138. return
  139. if shell:
  140. print 'name=' + name
  141. # check main.c first
  142. desc = get_module_desc(url + '/' + name + '/' + name)
  143. if not desc:
  144. desc = get_module_desc(url + '/' + name + '/main.c', script = False)
  145. if not desc:
  146. if not shell:
  147. print name + '-'
  148. return
  149. if shell:
  150. print 'description=' + desc.get('description', '')
  151. print 'keywords=' + ','.join(desc.get('keywords', list()))
  152. else:
  153. print name + ' - ' + desc.get('description', '')
  154. def get_module_desc(url, script = True):
  155. grass.debug('url=%s' % url)
  156. f = urllib.urlopen(url)
  157. if script:
  158. ret = get_module_script(f)
  159. else:
  160. ret = get_module_main(f)
  161. return ret
  162. def get_module_main(f):
  163. if not f:
  164. return dict()
  165. ret = { 'keyword' : list() }
  166. pattern = re.compile(r'(module.*->)(.+)(=)(.*)', re.IGNORECASE)
  167. keyword = re.compile(r'(G_add_keyword\()(.+)(\);)', re.IGNORECASE)
  168. key = ''
  169. value = ''
  170. for line in f.readlines():
  171. line = line.strip()
  172. find = pattern.search(line)
  173. if find:
  174. key = find.group(2).strip()
  175. line = find.group(4).strip()
  176. else:
  177. find = keyword.search(line)
  178. if find:
  179. ret['keyword'].append(find.group(2).replace('"', '').replace('_(', '').replace(')', ''))
  180. if key:
  181. value += line
  182. if line[-2:] == ');':
  183. value = value.replace('"', '').replace('_(', '').replace(');', '')
  184. if key == 'keywords':
  185. ret[key] = map(lambda x: x.strip(), value.split(','))
  186. else:
  187. ret[key] = value
  188. key = value = ''
  189. return ret
  190. def get_module_script(f):
  191. ret = dict()
  192. if not f:
  193. return ret
  194. begin = re.compile(r'#%.*module', re.IGNORECASE)
  195. end = re.compile(r'#%.*end', re.IGNORECASE)
  196. mline = None
  197. for line in f.readlines():
  198. if not mline:
  199. mline = begin.search(line)
  200. if mline:
  201. if end.search(line):
  202. break
  203. try:
  204. key, value = line.split(':', 1)
  205. key = key.replace('#%', '').strip()
  206. value = value.strip()
  207. if key == 'keywords':
  208. ret[key] = map(lambda x: x.strip(), value.split(','))
  209. else:
  210. ret[key] = value
  211. except ValueError:
  212. pass
  213. return ret
  214. def cleanup():
  215. global tmpdir, remove_tmpdir
  216. if remove_tmpdir:
  217. grass.try_rmdir(tmpdir)
  218. else:
  219. grass.info(_("Path to the source code: '%s'") % tmpdir)
  220. def install_extension(svnurl, prefix, module, no_install):
  221. gisbase = os.getenv('GISBASE')
  222. if not gisbase:
  223. grass.fatal(_('$GISBASE not defined'))
  224. if grass.find_program(module):
  225. grass.warning(_("Extension '%s' already installed. Will be updated...") % module)
  226. classchar = module.split('.', 1)[0]
  227. moduleclass = expand_module_class_name(classchar)
  228. url = svnurl + '/' + moduleclass + '/' + module
  229. grass.message(_("Fetching '%s' from GRASS-Addons SVN (be patient)...") % module)
  230. global tmpdir
  231. os.chdir(tmpdir)
  232. if grass.verbosity() == 0:
  233. outdev = open(os.devnull, 'w')
  234. else:
  235. outdev = sys.stdout
  236. if grass.call(['svn', 'checkout',
  237. url], stdout = outdev) != 0:
  238. grass.fatal(_("GRASS Addons '%s' not found in repository") % module)
  239. os.chdir(os.path.join(tmpdir, module))
  240. if os.path.exists(os.path.join(tmpdir, module, 'grass7.patch')):
  241. grass.message(_("Patch for GRASS 7 detected. Applying..."))
  242. if not grass.find_program('patch'):
  243. grass.fatal(_("Program 'patch' required. Exiting."))
  244. stdin = open(os.path.join(tmpdir, module, 'grass7.patch'))
  245. grass.call(['patch',
  246. '-p0'],
  247. stdin = stdin,
  248. stdout = outdev)
  249. # rename manual page
  250. os.rename('description.html', module + '.html')
  251. grass.verbose(_("Manual renamed from 'description.html' to '%s.html'") % module)
  252. grass.message(_("Compiling '%s'...") % module)
  253. if grass.call(['make',
  254. 'MODULE_TOPDIR=%s' % gisbase],
  255. stdout = outdev) != 0:
  256. grass.fatal(_('Compilation failed, sorry. Please check above error messages.'))
  257. if no_install:
  258. return
  259. grass.message(_("Installing '%s'...") % module)
  260. # can we write ?
  261. try:
  262. # replace with something better
  263. file = os.path.join(prefix, 'test')
  264. f = open(file, "w")
  265. f.close()
  266. os.remove(file)
  267. ret = grass.call(['make',
  268. 'MODULE_TOPDIR=%s' % gisbase,
  269. 'INST_DIR=%s' % prefix,
  270. 'install'],
  271. stdout = outdev)
  272. except IOError:
  273. ret = grass.call(['sudo', 'make',
  274. 'MODULE_TOPDIR=%s' % gisbase,
  275. 'INST_DIR=%s' % prefix,
  276. 'install'],
  277. stdout = outdev)
  278. if ret != 0:
  279. grass.warning(_('Installation failed, sorry. Please check above error messages.'))
  280. else:
  281. grass.message(_("Installation of '%s' successfully finished.") % module)
  282. def remove_extension(prefix, module):
  283. # is module available?
  284. if not os.path.exists(os.path.join(prefix, 'bin', module)):
  285. grass.fatal(_("Module '%s' not found") % module)
  286. for file in [os.path.join(prefix, 'bin', module),
  287. os.path.join(prefix, 'scripts', module),
  288. os.path.join(prefix, 'docs', 'html', module + '.html')]:
  289. if os.path.isfile(file):
  290. os.remove(file)
  291. grass.message(_("'%s' successfully uninstalled.") % module)
  292. def create_dir(path):
  293. if os.path.isdir(path):
  294. return
  295. try:
  296. os.makedirs(path)
  297. except OSError, e:
  298. grass.fatal(_("Unable to create '%s': %s") % (path, e))
  299. grass.debug("'%s' created" % path)
  300. def check_dirs():
  301. create_dir(os.path.join(options['prefix'], 'bin'))
  302. create_dir(os.path.join(options['prefix'], 'docs', 'html'))
  303. create_dir(os.path.join(options['prefix'], 'man', 'man1'))
  304. def main():
  305. # check dependecies
  306. check()
  307. # list available modules
  308. if flags['l'] or flags['f'] or flags['g']:
  309. list_available_modules(options['svnurl'], full = flags['f'], shell = flags['g'])
  310. return 0
  311. else:
  312. if not options['extension']:
  313. grass.fatal(_('You need to define an extension name or use -l'))
  314. # define path
  315. if options['prefix'] == '$GRASS_ADDON_PATH':
  316. if not os.environ.has_key('GRASS_ADDON_PATH') or \
  317. not os.environ['GRASS_ADDON_PATH']:
  318. grass.warning(_("GRASS_ADDON_PATH is not defined, installing to ~/.grass7/addons/"))
  319. options['prefix'] = os.path.join(os.environ['HOME'], '.grass7', 'addons')
  320. # check dirs
  321. check_dirs()
  322. if flags['d']:
  323. if options['operation'] != 'add':
  324. grass.warning(_("Flag 'd' is relevant only to 'operation=add'. Ignoring this flag."))
  325. else:
  326. global remove_tmpdir
  327. remove_tmpdir = False
  328. if options['operation'] == 'add':
  329. install_extension(options['svnurl'], options['prefix'], options['extension'], flags['i'])
  330. else: # remove
  331. remove_extension(options['prefix'], options['extension'])
  332. return 0
  333. if __name__ == "__main__":
  334. options, flags = grass.parser()
  335. atexit.register(cleanup)
  336. sys.exit(main())