g.search.modules.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # MODULE: g.search.modules
  5. # AUTHOR(S): Jachym Cepicky <jachym.cepicky gmail.com>
  6. # PURPOSE: g.search.modules in grass modules using keywords
  7. # COPYRIGHT: (C) 2015-2019 by the GRASS Development Team
  8. #
  9. # This program is free software under the GNU General
  10. # Public License (>=v2). Read the file COPYING that
  11. # comes with GRASS for details.
  12. #
  13. #############################################################################
  14. #%module
  15. #% description: Search in GRASS modules using keywords
  16. #% keyword: general
  17. #% keyword: modules
  18. #% keyword: search
  19. #%end
  20. #%option
  21. #% key: keyword
  22. #% multiple: yes
  23. #% type: string
  24. #% description: Keyword to be searched
  25. #% required : yes
  26. #%end
  27. #%flag
  28. #% key: a
  29. #% description: Display only modules where all keywords are available (AND), default: OR
  30. #% guisection: Output
  31. #%end
  32. #%flag
  33. #% key: n
  34. #% description: Invert selection (logical NOT)
  35. #% guisection: Output
  36. #%end
  37. #%flag
  38. #% key: m
  39. #% description: Search in manual pages too (can be slow)
  40. #% guisection: Output
  41. #%end
  42. #%flag
  43. #% key: k
  44. #% label: Search only for the exact keyword in module keyword list
  45. #% description: Instead of full text search, search only in actual keywords
  46. #% guisection: Output
  47. #%end
  48. #%flag
  49. #% key: c
  50. #% description: Use colorized (more readable) output to terminal
  51. #% guisection: Output
  52. #%end
  53. #%flag
  54. #% key: g
  55. #% description: Shell script format
  56. #% guisection: Output
  57. #%end
  58. #%flag
  59. #% key: j
  60. #% description: JSON format
  61. #% guisection: Output
  62. #%end
  63. from __future__ import print_function
  64. import os
  65. import sys
  66. from grass.script.utils import diff_files, try_rmdir
  67. from grass.script import core as grass
  68. from grass.exceptions import CalledModuleError
  69. try:
  70. import xml.etree.ElementTree as etree
  71. except ImportError:
  72. import elementtree.ElementTree as etree # Python <= 2.4
  73. COLORIZE = False
  74. def main():
  75. global COLORIZE
  76. AND = flags['a']
  77. NOT = flags['n']
  78. manpages = flags['m']
  79. exact_keywords = flags['k']
  80. out_format = None
  81. if flags['g']:
  82. out_format = 'shell'
  83. elif flags['j']:
  84. out_format = 'json'
  85. else:
  86. COLORIZE = flags['c']
  87. if exact_keywords:
  88. keywords = options['keyword'].split(',')
  89. else:
  90. keywords = options['keyword'].lower().split(',')
  91. modules = _search_module(keywords, AND, NOT, manpages, exact_keywords)
  92. print_results(modules, out_format)
  93. def print_results(data, out_format=None):
  94. """
  95. Print result of the searching method
  96. each data item should have
  97. {
  98. 'name': name of the item,
  99. 'attributes': {
  100. # list of attributes to be shown too
  101. }
  102. }
  103. :param list.<dict> data: input list of found data items
  104. :param str out_format: output format 'shell', 'json', None
  105. """
  106. if not out_format:
  107. _print_results(data)
  108. elif out_format == 'shell':
  109. _print_results_shell(data)
  110. elif out_format == 'json':
  111. _print_results_json(data)
  112. def _print_results_shell(data):
  113. """Print just the name attribute"""
  114. for item in data:
  115. print(item['name'])
  116. def _print_results_json(data):
  117. """Print JSON output"""
  118. import json
  119. print(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
  120. def _print_results(data):
  121. import textwrap
  122. for item in data:
  123. print('\n{0}'.format(colorize(item['name'], attrs=['bold'])))
  124. for attr in item['attributes']:
  125. out = '{0}: {1}'.format(attr, item['attributes'][attr])
  126. out = textwrap.wrap(out, width=79, initial_indent=4 * ' ',
  127. subsequent_indent=4 * ' ' + len(attr) * ' ' + ' ')
  128. for line in out:
  129. print(line)
  130. def colorize(text, attrs=None, pattern=None):
  131. """Colorize given text input
  132. :param string text: input text to be colored
  133. :param list.<string> attrs: list of attributes as defined in termcolor package
  134. :param string pattern: text to be highlighted in input text
  135. :return: colored string
  136. """
  137. if COLORIZE:
  138. try:
  139. from termcolor import colored
  140. except ImportError:
  141. grass.fatal(_("Cannot colorize, python-termcolor is not installed"))
  142. else:
  143. def colored(pattern, attrs):
  144. return pattern
  145. if pattern:
  146. return text.replace(pattern, colored(pattern, attrs=attrs))
  147. else:
  148. return colored(text, attrs=attrs)
  149. def _search_module(keywords, logical_and=False, invert=False, manpages=False,
  150. exact_keywords=False):
  151. """Search modules by given keywords
  152. :param list.<str> keywords: list of keywords
  153. :param boolean logical_and: use AND (default OR)
  154. :param boolean manpages: search in manpages too
  155. :return dict: modules
  156. """
  157. WXGUIDIR = os.path.join(os.getenv("GISBASE"), "gui", "wxpython")
  158. filename = os.path.join(WXGUIDIR, 'xml', 'module_items.xml')
  159. menudata_file = open(filename, 'r')
  160. menudata = etree.parse(menudata_file)
  161. menudata_file.close()
  162. items = menudata.findall('module-item')
  163. # add installed addons to modules list
  164. if os.getenv("GRASS_ADDON_BASE"):
  165. filename_addons = os.path.join(os.getenv("GRASS_ADDON_BASE"), 'modules.xml')
  166. if os.path.isfile(filename_addons):
  167. addon_menudata_file = open(filename_addons, 'r')
  168. addon_menudata = etree.parse(addon_menudata_file)
  169. addon_menudata_file.close()
  170. addon_items = addon_menudata.findall('task')
  171. items.extend(addon_items)
  172. # add system-wide installed addons to modules list
  173. filename_addons_s = os.path.join(os.getenv("GISBASE"), 'modules.xml')
  174. if os.path.isfile(filename_addons_s):
  175. addon_menudata_file_s = open(filename_addons_s, 'r')
  176. addon_menudata_s = etree.parse(addon_menudata_file_s)
  177. addon_menudata_file_s.close()
  178. addon_items_s = addon_menudata_s.findall('task')
  179. items.extend(addon_items_s)
  180. found_modules = []
  181. for item in items:
  182. name = item.attrib['name']
  183. description = item.find('description').text
  184. module_keywords = item.find('keywords').text
  185. found = [False]
  186. if logical_and:
  187. found = [False] * len(keywords)
  188. for idx in range(len(keywords)):
  189. keyword = keywords[idx]
  190. keyword_found = False
  191. if exact_keywords:
  192. keyword_found = _exact_search(keyword, module_keywords)
  193. else:
  194. keyword_found = _basic_search(keyword, name, description,
  195. module_keywords)
  196. # meta-modules (i.sentinel, r.modis, ...) do not have descriptions
  197. # and keywords, but they have a manpage
  198. # TODO change the handling of meta-modules
  199. if (description and module_keywords) and not keyword_found and manpages:
  200. keyword_found = _manpage_search(keyword, name)
  201. if keyword_found:
  202. if logical_and:
  203. found[idx] = True
  204. else:
  205. found = [True]
  206. description = colorize(description,
  207. attrs=['underline'],
  208. pattern=keyword)
  209. module_keywords = colorize(module_keywords,
  210. attrs=['underline'],
  211. pattern=keyword)
  212. add = False not in found
  213. if invert:
  214. add = not add
  215. if add:
  216. found_modules.append({
  217. 'name': name,
  218. 'attributes': {
  219. 'keywords': module_keywords,
  220. 'description': description
  221. }
  222. })
  223. return sorted(found_modules, key=lambda k: k['name'])
  224. def _basic_search(pattern, name, description, module_keywords):
  225. """Search for a string in all the provided strings.
  226. This lowercases the strings before searching in them, so the pattern
  227. string should be lowercased too.
  228. """
  229. if (name and description and module_keywords):
  230. if name.lower().find(pattern) > -1 or\
  231. description.lower().find(pattern) > -1 or\
  232. module_keywords.lower().find(pattern) > -1:
  233. return True
  234. else:
  235. return False
  236. else:
  237. return False
  238. def _exact_search(keyword, module_keywords):
  239. """Compare exact keyword with module keywords
  240. :param keyword: exact keyword to find in the list (not lowercased)
  241. :param module_keywords: comma separated list of keywords
  242. """
  243. module_keywords = module_keywords.split(',')
  244. for current in module_keywords:
  245. if keyword == current:
  246. return True
  247. return False
  248. def _manpage_search(pattern, name):
  249. try:
  250. manpage = grass.read_command('g.manual', flags='m', entry=name)
  251. except CalledModuleError:
  252. # in case man page is missing
  253. return False
  254. return manpage.lower().find(pattern) > -1
  255. if __name__ == "__main__":
  256. options, flags = grass.parser()
  257. sys.exit(main())