g.search.modules.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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-2016 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. try:
  69. import xml.etree.ElementTree as etree
  70. except ImportError:
  71. import elementtree.ElementTree as etree # Python <= 2.4
  72. COLORIZE = False
  73. def main():
  74. global COLORIZE
  75. AND = flags['a']
  76. NOT = flags['n']
  77. manpages = flags['m']
  78. exact_keywords = flags['k']
  79. out_format = None
  80. if flags['g']:
  81. out_format = 'shell'
  82. elif flags['j']:
  83. out_format = 'json'
  84. else:
  85. COLORIZE = flags['c']
  86. if exact_keywords:
  87. keywords = options['keyword'].split(',')
  88. else:
  89. keywords = options['keyword'].lower().split(',')
  90. modules = _search_module(keywords, AND, NOT, manpages, exact_keywords)
  91. print_results(modules, out_format)
  92. def print_results(data, out_format=None):
  93. """
  94. Print result of the searching method
  95. each data item should have
  96. {
  97. 'name': name of the item,
  98. 'attributes': {
  99. # list of attributes to be shown too
  100. }
  101. }
  102. :param list.<dict> data: input list of found data items
  103. :param str out_format: output format 'shell', 'json', None
  104. """
  105. if not out_format:
  106. _print_results(data)
  107. elif out_format == 'shell':
  108. _print_results_shell(data)
  109. elif out_format == 'json':
  110. _print_results_json(data)
  111. def _print_results_shell(data):
  112. """Print just the name attribute"""
  113. for item in data:
  114. print(item['name'])
  115. def _print_results_json(data):
  116. """Print JSON output"""
  117. import json
  118. print(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
  119. def _print_results(data):
  120. import textwrap
  121. for item in data:
  122. print('\n{0}'.format(colorize(item['name'], attrs=['bold'])))
  123. for attr in item['attributes']:
  124. out = '{0}: {1}'.format(attr, item['attributes'][attr])
  125. out = textwrap.wrap(out, width=79, initial_indent=4 * ' ',
  126. subsequent_indent=4 * ' ' + len(attr) * ' ' + ' ')
  127. for line in out:
  128. print(line)
  129. def colorize(text, attrs=None, pattern=None):
  130. """Colorize given text input
  131. :param string text: input text to be colored
  132. :param list.<string> attrs: list of attributes as defined in termcolor package
  133. :param string pattern: text to be highlighted in input text
  134. :return: colored string
  135. """
  136. if COLORIZE:
  137. try:
  138. from termcolor import colored
  139. except ImportError:
  140. grass.fatal(_("Cannot colorize, python-termcolor is not installed"))
  141. else:
  142. def colored(pattern, attrs):
  143. return pattern
  144. if pattern:
  145. return text.replace(pattern, colored(pattern, attrs=attrs))
  146. else:
  147. return colored(text, attrs=attrs)
  148. def _search_module(keywords, logical_and=False, invert=False, manpages=False,
  149. exact_keywords=False):
  150. """Search modules by given keywords
  151. :param list.<str> keywords: list of keywords
  152. :param boolean logical_and: use AND (default OR)
  153. :param boolean manpages: search in manpages too
  154. :return dict: modules
  155. """
  156. WXGUIDIR = os.path.join(os.getenv("GISBASE"), "gui", "wxpython")
  157. filename = os.path.join(WXGUIDIR, 'xml', 'module_items.xml')
  158. menudata_file = open(filename, 'r')
  159. menudata = etree.parse(menudata_file)
  160. menudata_file.close()
  161. items = menudata.findall('module-item')
  162. # add installed addons to modules list
  163. if os.getenv("GRASS_ADDON_BASE"):
  164. filename_addons = os.path.join(os.getenv("GRASS_ADDON_BASE"), 'modules.xml')
  165. addon_menudata_file = open(filename_addons, 'r')
  166. addon_menudata = etree.parse(addon_menudata_file)
  167. addon_menudata_file.close()
  168. addon_items = addon_menudata.findall('task')
  169. items.extend(addon_items)
  170. found_modules = []
  171. for item in items:
  172. name = item.attrib['name']
  173. description = item.find('description').text
  174. module_keywords = item.find('keywords').text
  175. found = [False]
  176. if logical_and:
  177. found = [False] * len(keywords)
  178. for idx in range(len(keywords)):
  179. keyword = keywords[idx]
  180. keyword_found = False
  181. if exact_keywords:
  182. keyword_found = _exact_search(keyword, module_keywords)
  183. else:
  184. keyword_found = _basic_search(keyword, name, description,
  185. module_keywords)
  186. if not keyword_found and manpages:
  187. keyword_found = _manpage_search(keyword, name)
  188. if keyword_found:
  189. if logical_and:
  190. found[idx] = True
  191. else:
  192. found = [True]
  193. description = colorize(description,
  194. attrs=['underline'],
  195. pattern=keyword)
  196. module_keywords = colorize(module_keywords,
  197. attrs=['underline'],
  198. pattern=keyword)
  199. add = False not in found
  200. if invert:
  201. add = not add
  202. if add:
  203. found_modules.append({
  204. 'name': name,
  205. 'attributes': {
  206. 'keywords': module_keywords,
  207. 'description': description
  208. }
  209. })
  210. return sorted(found_modules, key=lambda k: k['name'])
  211. def _basic_search(pattern, name, description, module_keywords):
  212. """Search for a string in all the provided strings.
  213. This lowercases the strings before searching in them, so the pattern
  214. string should be lowercased too.
  215. """
  216. if (name and description and module_keywords):
  217. if name.lower().find(pattern) > -1 or\
  218. description.lower().find(pattern) > -1 or\
  219. module_keywords.lower().find(pattern) > -1:
  220. return True
  221. else:
  222. return False
  223. else:
  224. return False
  225. def _exact_search(keyword, module_keywords):
  226. """Compare exact keyword with module keywords
  227. :param keyword: exact keyword to find in the list (not lowercased)
  228. :param module_keywords: comma separated list of keywords
  229. """
  230. module_keywords = module_keywords.split(',')
  231. for current in module_keywords:
  232. if keyword == current:
  233. return True
  234. return False
  235. def _manpage_search(pattern, name):
  236. manpage = grass.read_command('g.manual', flags='m', entry=name)
  237. return manpage.lower().find(pattern) > -1
  238. if __name__ == "__main__":
  239. options, flags = grass.parser()
  240. sys.exit(main())