1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Outputter to generate C++ code for metrics.
"""
import json
import jinja2
from glean_parser import util
from util import generate_metric_ids, generate_ping_ids, get_metrics
def cpp_datatypes_filter(value):
"""
A Jinja2 filter that renders C++ literals.
Based on Python's JSONEncoder, but overrides:
- lists to array literals {}
- strings to "value"
"""
class CppEncoder(json.JSONEncoder):
def iterencode(self, value):
if isinstance(value, list):
yield "{"
first = True
for subvalue in list(value):
if not first:
yield ", "
yield from self.iterencode(subvalue)
first = False
yield "}"
elif isinstance(value, str):
yield '"' + value + '"'
else:
yield from super().iterencode(value)
return "".join(CppEncoder().iterencode(value))
def type_name(obj):
"""
Returns the C++ type to use for a given metric object.
"""
if getattr(obj, "labeled", False):
class_name = util.Camelize(obj.type[8:]) # strips "labeled_" off the front.
return "Labeled<impl::{}Metric>".format(class_name)
generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
if len(generate_enums):
for name, suffix in generate_enums:
if not len(getattr(obj, name)) and suffix == "Keys":
return util.Camelize(obj.type) + "Metric<NoExtraKeys>"
else:
# we always use the `extra` suffix,
# because we only expose the new event API
suffix = "Extra"
return "{}Metric<{}>".format(
util.Camelize(obj.type), util.Camelize(obj.name) + suffix
)
return util.Camelize(obj.type) + "Metric"
def extra_type_name(typ: str) -> str:
"""
Returns the corresponding Rust type for event's extra key types.
"""
if typ == "boolean":
return "bool"
elif typ == "string":
return "nsCString"
elif typ == "quantity":
return "uint32_t"
else:
return "UNSUPPORTED"
def output_cpp(objs, output_fd, options={}):
"""
Given a tree of objects, output C++ code to the file-like object `output_fd`.
:param objs: A tree of objects (metrics and pings) as returned from
`parser.parse_objects`.
:param output_fd: Writeable file to write the output to.
:param options: options dictionary.
"""
# Monkeypatch a util.snake_case function for the templates to use
util.snake_case = lambda value: value.replace(".", "_").replace("-", "_")
# Monkeypatch util.get_jinja2_template to find templates nearby
def get_local_template(template_name, filters=()):
env = jinja2.Environment(
loader=jinja2.PackageLoader("cpp", "templates"),
trim_blocks=True,
lstrip_blocks=True,
)
env.filters["camelize"] = util.camelize
env.filters["Camelize"] = util.Camelize
for filter_name, filter_func in filters:
env.filters[filter_name] = filter_func
return env.get_template(template_name)
util.get_jinja2_template = get_local_template
get_metric_id = generate_metric_ids(objs)
get_ping_id = generate_ping_ids(objs)
if "pings" in objs:
template_filename = "cpp_pings.jinja2"
if objs.get("tags"):
del objs["tags"]
else:
template_filename = "cpp.jinja2"
objs = get_metrics(objs)
template = util.get_jinja2_template(
template_filename,
filters=(
("cpp", cpp_datatypes_filter),
("snake_case", util.snake_case),
("type_name", type_name),
("extra_type_name", extra_type_name),
("metric_id", get_metric_id),
("ping_id", get_ping_id),
("Camelize", util.Camelize),
),
)
output_fd.write(template.render(all_objs=objs))
output_fd.write("\n")
|