source: grass/trunk/scripts/g.search.modules/g.search.modules.py

Last change on this file was 74490, checked in by annakrat, 5 years ago

g.search.modules: handle cases when man page is missing

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:mime-type set to text/x-python
File size: 8.9 KB
Line 
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
15#%module
16#% description: Search in GRASS modules using keywords
17#% keyword: general
18#% keyword: modules
19#% keyword: search
20#%end
21#%option
22#% key: keyword
23#% multiple: yes
24#% type: string
25#% description: Keyword to be searched
26#% required : yes
27#%end
28#%flag
29#% key: a
30#% description: Display only modules where all keywords are available (AND), default: OR
31#% guisection: Output
32#%end
33#%flag
34#% key: n
35#% description: Invert selection (logical NOT)
36#% guisection: Output
37#%end
38#%flag
39#% key: m
40#% description: Search in manual pages too (can be slow)
41#% guisection: Output
42#%end
43#%flag
44#% key: k
45#% label: Search only for the exact keyword in module keyword list
46#% description: Instead of full text search, search only in actual keywords
47#% guisection: Output
48#%end
49#%flag
50#% key: c
51#% description: Use colorized (more readable) output to terminal
52#% guisection: Output
53#%end
54#%flag
55#% key: g
56#% description: Shell script format
57#% guisection: Output
58#%end
59#%flag
60#% key: j
61#% description: JSON format
62#% guisection: Output
63#%end
64
65from __future__ import print_function
66import os
67import sys
68
69from grass.script.utils import diff_files, try_rmdir
70from grass.script import core as grass
71from grass.exceptions import CalledModuleError
72
73try:
74 import xml.etree.ElementTree as etree
75except ImportError:
76 import elementtree.ElementTree as etree # Python <= 2.4
77
78COLORIZE = False
79
80
81def main():
82 global COLORIZE
83 AND = flags['a']
84 NOT = flags['n']
85 manpages = flags['m']
86 exact_keywords = flags['k']
87 out_format = None
88 if flags['g']:
89 out_format = 'shell'
90 elif flags['j']:
91 out_format = 'json'
92 else:
93 COLORIZE = flags['c']
94
95 if exact_keywords:
96 keywords = options['keyword'].split(',')
97 else:
98 keywords = options['keyword'].lower().split(',')
99
100 modules = _search_module(keywords, AND, NOT, manpages, exact_keywords)
101
102 print_results(modules, out_format)
103
104
105def print_results(data, out_format=None):
106 """
107 Print result of the searching method
108
109 each data item should have
110
111 {
112 'name': name of the item,
113 'attributes': {
114 # list of attributes to be shown too
115 }
116 }
117
118 :param list.<dict> data: input list of found data items
119 :param str out_format: output format 'shell', 'json', None
120 """
121
122 if not out_format:
123 _print_results(data)
124
125 elif out_format == 'shell':
126 _print_results_shell(data)
127
128 elif out_format == 'json':
129 _print_results_json(data)
130
131
132def _print_results_shell(data):
133 """Print just the name attribute"""
134
135 for item in data:
136 print(item['name'])
137
138
139def _print_results_json(data):
140 """Print JSON output"""
141
142 import json
143 print(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
144
145
146def _print_results(data):
147
148 import textwrap
149
150 for item in data:
151 print('\n{0}'.format(colorize(item['name'], attrs=['bold'])))
152 for attr in item['attributes']:
153 out = '{0}: {1}'.format(attr, item['attributes'][attr])
154 out = textwrap.wrap(out, width=79, initial_indent=4 * ' ',
155 subsequent_indent=4 * ' ' + len(attr) * ' ' + ' ')
156 for line in out:
157 print(line)
158
159
160def colorize(text, attrs=None, pattern=None):
161 """Colorize given text input
162
163 :param string text: input text to be colored
164 :param list.<string> attrs: list of attributes as defined in termcolor package
165 :param string pattern: text to be highlighted in input text
166 :return: colored string
167 """
168
169 if COLORIZE:
170 try:
171 from termcolor import colored
172 except ImportError:
173 grass.fatal(_("Cannot colorize, python-termcolor is not installed"))
174 else:
175 def colored(pattern, attrs):
176 return pattern
177
178 if pattern:
179 return text.replace(pattern, colored(pattern, attrs=attrs))
180 else:
181 return colored(text, attrs=attrs)
182
183
184def _search_module(keywords, logical_and=False, invert=False, manpages=False,
185 exact_keywords=False):
186 """Search modules by given keywords
187
188 :param list.<str> keywords: list of keywords
189 :param boolean logical_and: use AND (default OR)
190 :param boolean manpages: search in manpages too
191 :return dict: modules
192 """
193
194 WXGUIDIR = os.path.join(os.getenv("GISBASE"), "gui", "wxpython")
195 filename = os.path.join(WXGUIDIR, 'xml', 'module_items.xml')
196 menudata_file = open(filename, 'r')
197
198 menudata = etree.parse(menudata_file)
199 menudata_file.close()
200
201 items = menudata.findall('module-item')
202
203 # add installed addons to modules list
204 if os.getenv("GRASS_ADDON_BASE"):
205 filename_addons = os.path.join(os.getenv("GRASS_ADDON_BASE"), 'modules.xml')
206 if os.path.isfile(filename_addons):
207 addon_menudata_file = open(filename_addons, 'r')
208 addon_menudata = etree.parse(addon_menudata_file)
209 addon_menudata_file.close()
210 addon_items = addon_menudata.findall('task')
211 items.extend(addon_items)
212
213 # add system-wide installed addons to modules list
214 filename_addons_s = os.path.join(os.getenv("GISBASE"), 'modules.xml')
215 if os.path.isfile(filename_addons_s):
216 addon_menudata_file_s = open(filename_addons_s, 'r')
217 addon_menudata_s = etree.parse(addon_menudata_file_s)
218 addon_menudata_file_s.close()
219 addon_items_s = addon_menudata_s.findall('task')
220 items.extend(addon_items_s)
221
222 found_modules = []
223 for item in items:
224 name = item.attrib['name']
225 description = item.find('description').text
226 module_keywords = item.find('keywords').text
227
228 found = [False]
229 if logical_and:
230 found = [False] * len(keywords)
231
232 for idx in range(len(keywords)):
233 keyword = keywords[idx]
234 keyword_found = False
235
236 if exact_keywords:
237 keyword_found = _exact_search(keyword, module_keywords)
238 else:
239 keyword_found = _basic_search(keyword, name, description,
240 module_keywords)
241
242 # meta-modules (i.sentinel, r.modis, ...) do not have descriptions
243 # and keywords, but they have a manpage
244 # TODO change the handling of meta-modules
245 if (description and module_keywords) and not keyword_found and manpages:
246 keyword_found = _manpage_search(keyword, name)
247
248 if keyword_found:
249 if logical_and:
250 found[idx] = True
251 else:
252 found = [True]
253
254 description = colorize(description,
255 attrs=['underline'],
256 pattern=keyword)
257 module_keywords = colorize(module_keywords,
258 attrs=['underline'],
259 pattern=keyword)
260
261 add = False not in found
262 if invert:
263 add = not add
264 if add:
265 found_modules.append({
266 'name': name,
267 'attributes': {
268 'keywords': module_keywords,
269 'description': description
270 }
271 })
272
273 return sorted(found_modules, key=lambda k: k['name'])
274
275
276def _basic_search(pattern, name, description, module_keywords):
277 """Search for a string in all the provided strings.
278
279 This lowercases the strings before searching in them, so the pattern
280 string should be lowercased too.
281 """
282 if (name and description and module_keywords):
283 if name.lower().find(pattern) > -1 or\
284 description.lower().find(pattern) > -1 or\
285 module_keywords.lower().find(pattern) > -1:
286 return True
287 else:
288 return False
289 else:
290 return False
291
292
293def _exact_search(keyword, module_keywords):
294 """Compare exact keyword with module keywords
295
296 :param keyword: exact keyword to find in the list (not lowercased)
297 :param module_keywords: comma separated list of keywords
298 """
299 module_keywords = module_keywords.split(',')
300 for current in module_keywords:
301 if keyword == current:
302 return True
303 return False
304
305
306def _manpage_search(pattern, name):
307 try:
308 manpage = grass.read_command('g.manual', flags='m', entry=name)
309 except CalledModuleError:
310 # in case man page is missing
311 return False
312
313 return manpage.lower().find(pattern) > -1
314
315if __name__ == "__main__":
316 options, flags = grass.parser()
317 sys.exit(main())
Note: See TracBrowser for help on using the repository browser.