1# SPDX-FileCopyrightText: 2021 GNOME Foundation
2# SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later
3
4import argparse
5import json
6import os
7import sys
8
9from . import gir, log, utils
10
11
12HELP_MSG = "Search terms in the symbol indices"
13
14
15def _gen_alias_result(symbol, namespace):
16    alias = namespace.find_alias(symbol["name"])
17    if alias.doc is not None:
18        summary = utils.preprocess_docs(alias.doc.content, namespace, summary=True, plain=True)
19    else:
20        summary = "Missing documentation"
21
22    return {
23        "type": "alias",
24        "name": alias.name,
25        "ctype": alias.base_ctype,
26        "summary": summary,
27        "link": f"alias.{alias.name}.html",
28    }
29
30
31def _gen_bitfield_result(symbol, namespace):
32    enum = namespace.find_bitfield(symbol["name"])
33    if enum.doc is not None:
34        summary = utils.preprocess_docs(enum.doc.content, namespace, summary=True, plain=True)
35    else:
36        summary = "Missing documentation"
37
38    return {
39        "type": "flags",
40        "name": enum.name,
41        "ctype": enum.base_ctype,
42        "summary": summary.split("\n"),
43        "link": f"flags.{enum.name}.html",
44    }
45
46
47def _gen_callback_result(symbol, namespace):
48    pass
49
50
51def _gen_class_result(symbol, namespace):
52    cls = namespace.find_class(symbol["name"])
53    if cls.doc is not None:
54        summary = utils.preprocess_docs(cls.doc.content, namespace, summary=True, plain=True)
55    else:
56        summary = "Missing documentation"
57
58    return {
59        "type": "class",
60        "name": cls.name,
61        "ctype": cls.base_ctype,
62        "summary": summary.split("\n"),
63        "link": f"class.{cls.name}.html",
64    }
65
66
67def _gen_class_method_result(symbol, namespace):
68    t = namespace.find_real_type(symbol["type_name"])
69    cls_name = t.struct_for
70    if cls_name is None:
71        return None
72
73    methods = getattr(t, "methods", [])
74    for m in methods:
75        if m.name == symbol["name"]:
76            if m.doc is not None:
77                summary = utils.preprocess_docs(m.doc.content, namespace, summary=True, plain=True)
78            else:
79                summary = "Missing documentation"
80
81            return {
82                "type": "class method",
83                "name": f"{t.name}.{m.name}",
84                "ctype": m.identifier,
85                "summary": summary.split("\n"),
86                "link": f"class_method.{cls_name}.{m.name}.html",
87            }
88
89    return None
90
91
92def _gen_ctor_result(symbol, namespace):
93    t = namespace.find_real_type(symbol["type_name"])
94
95    methods = getattr(t, "constructors", [])
96    for m in methods:
97        if m.name == symbol["name"]:
98            if m.doc is not None:
99                summary = utils.preprocess_docs(m.doc.content, namespace, summary=True, plain=True)
100            else:
101                summary = "Missing documentation"
102
103            return {
104                "type": "constructor",
105                "name": f"{t.name}.{m.name}",
106                "ctype": m.identifier,
107                "summary": summary.split("\n"),
108                "link": f"ctor.{t.name}.{m.name}.html",
109            }
110
111    return None
112
113
114def _gen_domain_result(symbol, namespace):
115    enum = namespace.find_error_domain(symbol["name"])
116    if enum.doc is not None:
117        summary = utils.preprocess_docs(enum.doc.content, namespace, summary=True, plain=True)
118    else:
119        summary = "Missing documentation"
120
121    return {
122        "type": "error",
123        "name": enum.name,
124        "ctype": enum.base_ctype,
125        "summary": summary.split("\n"),
126        "link": f"error.{enum.name}.html",
127    }
128
129
130def _gen_enum_result(symbol, namespace):
131    enum = namespace.find_enumeration(symbol["name"])
132    if enum.doc is not None:
133        summary = utils.preprocess_docs(enum.doc.content, namespace, summary=True, plain=True)
134    else:
135        summary = "Missing documentation"
136
137    return {
138        "type": "enum",
139        "name": enum.name,
140        "ctype": enum.base_ctype,
141        "summary": summary.split("\n"),
142        "link": f"enum.{enum.name}.html",
143    }
144
145
146def _gen_func_result(symbol, namespace):
147    functions = namespace.get_functions()
148    for func in functions:
149        if func.name == symbol["name"]:
150            if func.doc is not None:
151                summary = utils.preprocess_docs(func.doc.content, namespace, summary=True, plain=True)
152            else:
153                summary = "Missing documentation"
154            return {
155                "type": "function",
156                "name": func.name,
157                "ctype": func.identifier,
158                "summary": summary.split("\n"),
159                "link": f"func.{func.name}.html",
160            }
161
162
163def _gen_func_macro_result(symbol, namespace):
164    macros = namespace.get_effective_function_macros()
165    for func in macros:
166        if func.name == symbol["name"]:
167            if func.doc is not None:
168                summary = utils.preprocess_docs(func.doc.content, namespace, summary=True, plain=True)
169            else:
170                summary = "Missing documentation"
171            return {
172                "type": "function macro",
173                "name": func.name,
174                "ctype": func.identifier,
175                "summary": summary.split("\n"),
176                "link": f"func.{func.name}.html",
177            }
178
179
180def _gen_interface_result(symbol, namespace):
181    iface = namespace.find_interface(symbol["name"])
182    if iface.doc is not None:
183        summary = utils.preprocess_docs(iface.doc.content, namespace, summary=True, plain=True)
184    else:
185        summary = "Missing documentation"
186
187    return {
188        "type": "interface",
189        "name": iface.name,
190        "ctype": iface.base_ctype,
191        "summary": summary.split("\n"),
192        "link": f"iface.{iface.name}.html",
193    }
194
195
196def _gen_method_result(symbol, namespace):
197    t = namespace.find_real_type(symbol["type_name"])
198
199    methods = getattr(t, "methods", [])
200    for m in methods:
201        if m.name == symbol["name"]:
202            if m.doc is not None:
203                summary = utils.preprocess_docs(m.doc.content, namespace, summary=True, plain=True)
204            else:
205                summary = "Missing documentation"
206
207            return {
208                "type": "method",
209                "name": f"{t.name}.{m.name}",
210                "ctype": m.identifier,
211                "summary": summary.split("\n"),
212                "link": f"method.{t.name}.{m.name}.html",
213            }
214
215    return None
216
217
218def _gen_property_result(symbol, namespace):
219    t = namespace.find_real_type(symbol["type_name"])
220
221    properties = getattr(t, "properties", {})
222    prop = properties.get(symbol["name"], None)
223    if prop is None:
224        return None
225
226    if prop.doc is not None:
227        summary = utils.preprocess_docs(prop.doc.content, namespace, summary=True, plain=True)
228    else:
229        summary = "Missing documentation"
230
231    return {
232        "type": "property",
233        "name": f"{t.name}:{prop.name}",
234        "ctype": t.base_ctype,
235        "summary": summary.split("\n"),
236        "link": f"property.{t.name}.{prop.name}.html",
237    }
238
239
240def _gen_record_result(symbol, namespace):
241    record = namespace.find_record(symbol["name"])
242    if record.doc is not None:
243        summary = utils.preprocess_docs(record.doc.content, namespace, summary=True, plain=True)
244    else:
245        summary = "Missing documentation"
246
247    return {
248        "type": "struct",
249        "name": record.name,
250        "ctype": record.base_ctype,
251        "summary": summary.split("\n"),
252        "link": f"struct.{record.name}.html",
253    }
254
255
256def _gen_signal_result(symbol, namespace):
257    t = namespace.find_real_type(symbol["type_name"])
258
259    signals = getattr(t, "signals", {})
260    signal = signals.get(symbol["name"], None)
261    if signal is None:
262        return None
263
264    if signal.doc is not None:
265        summary = utils.preprocess_docs(signal.doc.content, namespace, summary=True, plain=True)
266    else:
267        summary = "Missing documentation"
268
269    return {
270        "type": "signal",
271        "name": f"{t.name}::{signal.name}",
272        "ctype": t.base_ctype,
273        "summary": summary.split("\n"),
274        "link": f"signal.{t.name}.{signal.name}.html",
275    }
276
277
278def _gen_type_func_result(symbol, namespace):
279    t = namespace.find_real_type(symbol["type_name"])
280
281    functions = getattr(t, "functions", [])
282    for f in functions:
283        if f.name == symbol["name"]:
284            if f.doc is not None:
285                summary = utils.preprocess_docs(f.doc.content, namespace, summary=True, plain=True)
286            else:
287                summary = "Missing documentation"
288
289            return {
290                "type": "func",
291                "name": f"{t.name}.{f.name}",
292                "ctype": f.identifier,
293                "summary": summary.split("\n"),
294                "link": f"type_func.{t.name}.{f.name}.html",
295            }
296
297    return None
298
299
300def _gen_union_result(symbol, namespace):
301    union = namespace.find_union(symbol["name"])
302    if union.doc is not None:
303        summary = utils.preprocess_docs(union.doc.content, namespace, summary=True, plain=True)
304    else:
305        summary = "Missing documentation"
306
307    return {
308        "type": "union",
309        "name": union.name,
310        "ctype": union.base_ctype,
311        "summary": summary.split("\n"),
312        "link": f"union.{union.name}.html",
313    }
314
315
316def _gen_vfunc_result(symbol, namespace):
317    t = namespace.find_real_type(symbol["type_name"])
318
319    methods = getattr(t, "virtual_methods", [])
320    for m in methods:
321        if m.name == symbol["name"]:
322            if m.doc is not None:
323                summary = utils.preprocess_docs(m.doc.content, namespace, summary=True, plain=True)
324            else:
325                summary = "Missing documentation"
326
327            return {
328                "type": "vfunc",
329                "name": f"{t.name}.{m.name}",
330                "ctype": t.base_ctype,
331                "summary": summary.split("\n"),
332                "link": f"vfunc.{t.name}.{m.name}.html",
333            }
334
335    return None
336
337
338def query(repository, terms, index_file):
339    if index_file is None:
340        index_file = os.path.join(os.getcwd(), "index.json")
341
342    with open(index_file, "r") as f:
343        index = json.load(f)
344
345    namespace = repository.namespace
346
347    index_meta = index["meta"]
348    index_symbols = index["symbols"]
349    index_terms = index["terms"]
350
351    if index_meta["ns"] != namespace.name or index_meta["version"] != namespace.version:
352        log.error("Index file does not match the GIR namespace")
353
354    result_types = {
355        "alias": _gen_alias_result,
356        "bitfield": _gen_bitfield_result,
357        "callback": _gen_callback_result,
358        "class": _gen_class_result,
359        "class_method": _gen_class_method_result,
360        "ctor": _gen_ctor_result,
361        "domain": _gen_domain_result,
362        "enum": _gen_enum_result,
363        "function": _gen_func_result,
364        "function_macro": _gen_func_macro_result,
365        "interface": _gen_interface_result,
366        "method": _gen_method_result,
367        "property": _gen_property_result,
368        "record": _gen_record_result,
369        "signal": _gen_signal_result,
370        "type_func": _gen_type_func_result,
371        "union": _gen_union_result,
372        "vfunc": _gen_vfunc_result,
373    }
374
375    results = []
376
377    for term in terms:
378        docs = index_terms.get(term, [])
379        for doc in docs:
380            symbol = index_symbols[doc]
381
382            gen_result = result_types.get(symbol["type"])
383            if gen_result is None:
384                log.warning(f"Unhandled symbol type {symbol['type']} for '{term}'")
385                continue
386
387            res = gen_result(symbol, namespace)
388            results.append(res)
389
390    prefix = "result:"
391    indent = ''.join([' ' for x in range(len(prefix))])
392
393    n_results = len(results)
394    terms_str = ", ".join(terms)
395    print(f"Found {n_results} results matching '{terms_str}'")
396
397    for res in results:
398        lines = [f"{prefix} [{res['type']}] {res['name']} - {res['ctype']}"]
399        for chunk in res["summary"]:
400            lines.append(f"{indent} {chunk}")
401        lines.append(f"{indent} link: {res['link']}")
402        print("\n".join(lines))
403
404
405def add_args(parser):
406    parser.add_argument("--add-include-path", action="append", dest="include_paths", default=[],
407                        help="include paths for other GIR files")
408    parser.add_argument("--index", help="the index file")
409    parser.add_argument("--term", action="append", dest="terms", default=[],
410                        help="a search term")
411    parser.add_argument("infile", metavar="GIRFILE", type=argparse.FileType('r', encoding='UTF-8'),
412                        default=sys.stdin, help="the GIR file to parse")
413
414
415def run(options):
416    paths = []
417    paths.extend(options.include_paths)
418    paths.append(utils.default_search_paths())
419    log.debug(f"Search paths: {paths}")
420
421    log.info("Parsing GIR file")
422    parser = gir.GirParser(search_paths=paths)
423    parser.parse(options.infile)
424
425    log.checkpoint()
426    query(parser.get_repository(), options.terms, options.index)
427
428    return 0
429