1# Copyright 2020 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import argparse
6import os.path
7import subprocess
8import sys
9
10import wayland_protocol_c_arg_handling
11import wayland_protocol_construction
12import wayland_protocol_data_classes
13import wayland_protocol_externals
14import wayland_protocol_identifiers
15
16
17def expand_template(template, context):
18    # type: (str, Mapping[str, Any]) -> str
19    """Expands the template using context, and returns the result"""
20
21    # Loaded from third_party/jinja2 after a sys.path modification
22    import jinja2
23
24    env = jinja2.Environment(
25        loader=jinja2.FileSystemLoader(os.path.dirname(template)),
26        keep_trailing_newline=True,  # newline-terminate generated files
27        lstrip_blocks=True,
28        trim_blocks=True)  # so don't need {%- -%} everywhere
29
30    # Additional global functions
31    env.globals['get_c_arg_for_server_request_arg'] = (
32        wayland_protocol_c_arg_handling.get_c_arg_for_server_request_arg)
33    env.globals['get_c_arg_for_server_event_arg'] = (
34        wayland_protocol_c_arg_handling.get_c_arg_for_server_event_arg)
35    env.globals['get_c_return_type_for_client_request'] = (
36        wayland_protocol_c_arg_handling.get_c_return_type_for_client_request)
37    env.globals['get_c_arg_for_client_request_arg'] = (
38        wayland_protocol_c_arg_handling.get_c_arg_for_client_request_arg)
39    env.globals['get_c_arg_for_client_event_arg'] = (
40        wayland_protocol_c_arg_handling.get_c_arg_for_client_event_arg)
41    env.globals['get_construction_steps'] = (
42        wayland_protocol_construction.get_construction_steps)
43    env.globals['get_destructor'] = (
44        wayland_protocol_construction.get_destructor)
45    env.globals['get_external_interfaces_for_protocol'] = (
46        wayland_protocol_externals.get_external_interfaces_for_protocol)
47    env.globals['get_minimum_version_to_construct'] = (
48        wayland_protocol_construction.get_minimum_version_to_construct)
49    env.globals['get_versions_to_test_for_event_delivery'] = (
50        wayland_protocol_construction.get_versions_to_test_for_event_delivery)
51    env.globals['is_global_interface'] = (
52        wayland_protocol_construction.is_global_interface)
53
54    # Additional filters for transforming text
55    env.filters['kebab'] = wayland_protocol_identifiers.kebab_case
56    env.filters['pascal'] = wayland_protocol_identifiers.pascal_case
57
58    return env.get_template(os.path.basename(template)).render(context)
59
60
61def clang_format_source_text(source_text, clang_format_path,
62                             effective_filename):
63    # type: (str, str, str) -> str
64    """Runs clang-format on source_text and returns the result."""
65    # clang-format the output, for better readability and for
66    # -Wmisleading-indentation.
67    proc = subprocess.Popen(
68        [clang_format_path, '--assume-filename', effective_filename],
69        stdin=subprocess.PIPE,
70        stdout=subprocess.PIPE)
71    stdout_output, stderr_output = proc.communicate(input=source_text)
72    retcode = proc.wait()
73    if retcode != 0:
74        raise CalledProcessError(retcode,
75                                 'clang-format error: ' + stderr_output)
76    return stdout_output
77
78
79def write_if_changed(source_text, output):
80    # type: (str, str) -> None
81    """Writes source_text to output, but only if different."""
82    if os.path.exists(output):
83        with open(output, 'rt') as infile:
84            if infile.read() == source_text:
85                return
86
87    with open(output, 'wt') as outfile:
88        outfile.write(source_text)
89
90
91def main():
92    parser = argparse.ArgumentParser()
93    parser.add_argument('third_party_path')
94    parser.add_argument('clang_format_path')
95    parser.add_argument('template')
96    parser.add_argument('output')
97    parser.add_argument('protocols', nargs='+')
98    args = parser.parse_args()
99
100    # Allows us to import jinga2
101    sys.path.append(args.third_party_path)
102
103    protocols = wayland_protocol_data_classes.Protocols.parse_xml_files(
104        args.protocols)
105
106    if len(protocols.protocols) == 1:
107        expanded = expand_template(args.template,
108                                   {'protocol': protocols.protocols[0]})
109    else:
110        expanded = expand_template(args.template,
111                                   {'protocols': protocols.protocols})
112
113    formatted = clang_format_source_text(expanded, args.clang_format_path,
114                                         args.output)
115    write_if_changed(formatted, args.output)
116
117
118if __name__ == '__main__':
119    main()
120