blob: 7d109e9da3db44bc84c2dadcdbf78605ff8cf054 [file] [log] [blame]
Igor Murashkinda1c3142012-11-21 17:11:37 -08001#
2# Copyright (C) 2012 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18A set of helpers for rendering Mako templates with a Metadata model.
19"""
20
21import metadata_model
Igor Murashkinaa133d32013-06-28 17:27:49 -070022import re
Igor Murashkin586c8612012-11-29 17:08:36 -080023from collections import OrderedDict
Igor Murashkinda1c3142012-11-21 17:11:37 -080024
25_context_buf = None
26
27def _is_sec_or_ins(x):
28 return isinstance(x, metadata_model.Section) or \
29 isinstance(x, metadata_model.InnerNamespace)
30
31##
32## Metadata Helpers
33##
34
35def find_all_sections(root):
36 """
37 Find all descendants that are Section or InnerNamespace instances.
38
39 Args:
40 root: a Metadata instance
41
42 Returns:
43 A list of Section/InnerNamespace instances
44
45 Remarks:
46 These are known as "sections" in the generated C code.
47 """
48 return root.find_all(_is_sec_or_ins)
49
50def find_parent_section(entry):
51 """
52 Find the closest ancestor that is either a Section or InnerNamespace.
53
54 Args:
55 entry: an Entry or Clone node
56
57 Returns:
58 An instance of Section or InnerNamespace
59 """
60 return entry.find_parent_first(_is_sec_or_ins)
61
62# find uniquely named entries (w/o recursing through inner namespaces)
63def find_unique_entries(node):
64 """
65 Find all uniquely named entries, without recursing through inner namespaces.
66
67 Args:
68 node: a Section or InnerNamespace instance
69
70 Yields:
71 A sequence of MergedEntry nodes representing an entry
72
73 Remarks:
74 This collapses multiple entries with the same fully qualified name into
75 one entry (e.g. if there are multiple entries in different kinds).
76 """
77 if not isinstance(node, metadata_model.Section) and \
78 not isinstance(node, metadata_model.InnerNamespace):
79 raise TypeError("expected node to be a Section or InnerNamespace")
80
Igor Murashkin586c8612012-11-29 17:08:36 -080081 d = OrderedDict()
Igor Murashkinda1c3142012-11-21 17:11:37 -080082 # remove the 'kinds' from the path between sec and the closest entries
83 # then search the immediate children of the search path
84 search_path = isinstance(node, metadata_model.Section) and node.kinds \
85 or [node]
86 for i in search_path:
87 for entry in i.entries:
88 d[entry.name] = entry
89
90 for k,v in d.iteritems():
91 yield v.merge()
92
93def path_name(node):
94 """
95 Calculate a period-separated string path from the root to this element,
96 by joining the names of each node and excluding the Metadata/Kind nodes
97 from the path.
98
99 Args:
100 node: a Node instance
101
102 Returns:
103 A string path
104 """
105
106 isa = lambda x,y: isinstance(x, y)
107 fltr = lambda x: not isa(x, metadata_model.Metadata) and \
108 not isa(x, metadata_model.Kind)
109
110 path = node.find_parents(fltr)
111 path = list(path)
112 path.reverse()
113 path.append(node)
114
115 return ".".join((i.name for i in path))
116
Igor Murashkinaa133d32013-06-28 17:27:49 -0700117def has_descendants_with_enums(node):
118 """
119 Determine whether or not the current node is or has any descendants with an
120 Enum node.
121
122 Args:
123 node: a Node instance
124
125 Returns:
126 True if it finds an Enum node in the subtree, False otherwise
127 """
128 return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum)))
129
130def get_children_by_throwing_away_kind(node, member='entries'):
131 """
132 Get the children of this node by compressing the subtree together by removing
133 the kind and then combining any children nodes with the same name together.
134
135 Args:
136 node: An instance of Section, InnerNamespace, or Kind
137
138 Returns:
139 An iterable over the combined children of the subtree of node,
140 as if the Kinds never existed.
141
142 Remarks:
143 Not recursive. Call this function repeatedly on each child.
144 """
145
146 if isinstance(node, metadata_model.Section):
147 # Note that this makes jump from Section to Kind,
148 # skipping the Kind entirely in the tree.
149 node_to_combine = node.combine_kinds_into_single_node()
150 else:
151 node_to_combine = node
152
153 combined_kind = node_to_combine.combine_children_by_name()
154
155 return (i for i in getattr(combined_kind, member))
156
157def get_children_by_filtering_kind(section, kind_name, member='entries'):
158 """
159 Takes a section and yields the children of the kind under this section.
160
161 Args:
162 section: An instance of Section
163 kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
164
165 Returns:
166 An iterable over the children of the specified kind.
167 """
168
169# TODO: test/use this function
170 matched_kind = next((i for i in section.kinds if i.name == kind_name), None)
171
172 if matched_kind:
173 return getattr(matched_kind, member)
174 else:
175 return ()
176
Igor Murashkinda1c3142012-11-21 17:11:37 -0800177##
178## Filters
179##
180
181# abcDef.xyz -> ABC_DEF_XYZ
182def csym(name):
183 """
184 Convert an entry name string into an uppercase C symbol.
185
186 Returns:
187 A string
188
189 Example:
190 csym('abcDef.xyz') == 'ABC_DEF_XYZ'
191 """
192 newstr = name
193 newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper()
194 newstr = newstr.replace(".", "_")
195 return newstr
196
197# abcDef.xyz -> abc_def_xyz
198def csyml(name):
199 """
200 Convert an entry name string into a lowercase C symbol.
201
202 Returns:
203 A string
204
205 Example:
206 csyml('abcDef.xyz') == 'abc_def_xyz'
207 """
208 return csym(name).lower()
209
210# pad with spaces to make string len == size. add new line if too big
211def ljust(size, indent=4):
212 """
213 Creates a function that given a string will pad it with spaces to make
214 the string length == size. Adds a new line if the string was too big.
215
216 Args:
217 size: an integer representing how much spacing should be added
218 indent: an integer representing the initial indendation level
219
220 Returns:
221 A function that takes a string and returns a string.
222
223 Example:
224 ljust(8)("hello") == 'hello '
225
226 Remarks:
227 Deprecated. Use pad instead since it works for non-first items in a
228 Mako template.
229 """
230 def inner(what):
231 newstr = what.ljust(size)
232 if len(newstr) > size:
233 return what + "\n" + "".ljust(indent + size)
234 else:
235 return newstr
236 return inner
237
238def _find_new_line():
239
240 if _context_buf is None:
241 raise ValueError("Context buffer was not set")
242
243 buf = _context_buf
244 x = -1 # since the first read is always ''
245 cur_pos = buf.tell()
246 while buf.tell() > 0 and buf.read(1) != '\n':
247 buf.seek(cur_pos - x)
248 x = x + 1
249
250 buf.seek(cur_pos)
251
252 return int(x)
253
254# Pad the string until the buffer reaches the desired column.
255# If string is too long, insert a new line with 'col' spaces instead
256def pad(col):
257 """
258 Create a function that given a string will pad it to the specified column col.
259 If the string overflows the column, put the string on a new line and pad it.
260
261 Args:
262 col: an integer specifying the column number
263
264 Returns:
265 A function that given a string will produce a padded string.
266
267 Example:
268 pad(8)("hello") == 'hello '
269
270 Remarks:
271 This keeps track of the line written by Mako so far, so it will always
272 align to the column number correctly.
273 """
274 def inner(what):
275 wut = int(col)
276 current_col = _find_new_line()
277
278 if len(what) > wut - current_col:
279 return what + "\n".ljust(col)
280 else:
281 return what.ljust(wut - current_col)
282 return inner
283
284# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32
285def ctype_enum(what):
286 """
287 Generate a camera_metadata_type_t symbol from a type string.
288
289 Args:
290 what: a type string
291
292 Returns:
293 A string representing the camera_metadata_type_t
294
295 Example:
296 ctype_enum('int32') == 'TYPE_INT32'
297 ctype_enum('int64') == 'TYPE_INT64'
298 ctype_enum('float') == 'TYPE_FLOAT'
Igor Murashkinda1c3142012-11-21 17:11:37 -0800299
300 Remarks:
Igor Murashkine6b66462012-11-26 15:26:38 -0800301 An enum is coerced to a byte since the rest of the camera_metadata
Igor Murashkinda1c3142012-11-21 17:11:37 -0800302 code doesn't support enums directly yet.
303 """
Igor Murashkinda1c3142012-11-21 17:11:37 -0800304 return 'TYPE_%s' %(what.upper())
Igor Murashkinaa133d32013-06-28 17:27:49 -0700305
306def jtype(entry):
307 """
308 Calculate the Java type from an entry type string, to be used as a generic
309 type argument in Java. The type is guaranteed to inherit from Object.
310
311 Remarks:
312 Since Java generics cannot be instantiated with primitives, this version
313 will use boxed types when absolutely required.
314
315 Returns:
316 The string representing the Java type.
317 """
318
319 if not isinstance(entry, metadata_model.Entry):
320 raise ValueError("Expected entry to be an instance of Entry")
321
322 primitive_type = entry.type
323
324 if entry.enum:
325 name = entry.name
326
327 name_without_ons = entry.get_name_as_list()[1:]
328 base_type = ".".join([pascal_case(i) for i in name_without_ons]) + \
329 "Key.Enum"
330 else:
331 mapping = {
332 'int32': 'Integer',
333 'int64': 'Long',
334 'float': 'Float',
335 'double': 'Double',
336 'byte': 'Byte',
337 'rational': 'Rational'
338 }
339
340 base_type = mapping[primitive_type]
341
342 if entry.container == 'array':
343 additional = '[]'
344
345 #unbox if it makes sense
346 if primitive_type != 'rational' and not entry.enum:
347 base_type = jtype_primitive(primitive_type)
348 else:
349 additional = ''
350
351 return "%s%s" %(base_type, additional)
352
353def jtype_primitive(what):
354 """
355 Calculate the Java type from an entry type string.
356
357 Remarks:
358 Makes a special exception for Rational, since it's a primitive in terms of
359 the C-library camera_metadata type system.
360
361 Returns:
362 The string representing the primitive type
363 """
364 mapping = {
365 'int32': 'int',
366 'int64': 'long',
367 'float': 'float',
368 'double': 'double',
369 'byte': 'byte',
370 'rational': 'Rational'
371 }
372
373 try:
374 return mapping[what]
375 except KeyError as e:
376 raise ValueError("Can't map '%s' to a primitive, not supported" %what)
377
378def jclass(entry):
379 """
380 Calculate the java Class reference string for an entry.
381
382 Args:
383 entry: an Entry node
384
385 Example:
386 <entry name="some_int" type="int32"/>
387 <entry name="some_int_array" type="int32" container='array'/>
388
389 jclass(some_int) == 'int.class'
390 jclass(some_int_array) == 'int[].class'
391
392 Returns:
393 The ClassName.class string
394 """
395 the_type = entry.type
396 try:
397 class_name = jtype_primitive(the_type)
398 except ValueError as e:
399 class_name = the_type
400
401 if entry.container == 'array':
402 class_name += "[]"
403
404 return "%s.class" %class_name
405
406def jidentifier(what):
407 """
408 Convert the input string into a valid Java identifier.
409
410 Args:
411 what: any identifier string
412
413 Returns:
414 String with added underscores if necessary.
415 """
416 if re.match("\d", what):
417 return "_%s" %what
418 else:
419 return what
420
421def enum_calculate_value_string(enum_value):
422 """
423 Calculate the value of the enum, even if it does not have one explicitly
424 defined.
425
426 This looks back for the first enum value that has a predefined value and then
427 applies addition until we get the right value, using C-enum semantics.
428
429 Args:
430 enum_value: an EnumValue node with a valid Enum parent
431
432 Example:
433 <enum>
434 <value>X</value>
435 <value id="5">Y</value>
436 <value>Z</value>
437 </enum>
438
439 enum_calculate_value_string(X) == '0'
440 enum_calculate_Value_string(Y) == '5'
441 enum_calculate_value_string(Z) == '6'
442
443 Returns:
444 String that represents the enum value as an integer literal.
445 """
446
447 enum_value_siblings = list(enum_value.parent.values)
448 this_index = enum_value_siblings.index(enum_value)
449
450 def is_hex_string(instr):
451 return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE))
452
453 base_value = 0
454 base_offset = 0
455 emit_as_hex = False
456
457 this_id = enum_value_siblings[this_index].id
458 while this_index != 0 and not this_id:
459 this_index -= 1
460 base_offset += 1
461 this_id = enum_value_siblings[this_index].id
462
463 if this_id:
464 base_value = int(this_id, 0) # guess base
465 emit_as_hex = is_hex_string(this_id)
466
467 if emit_as_hex:
468 return "0x%X" %(base_value + base_offset)
469 else:
470 return "%d" %(base_value + base_offset)
471
472def enumerate_with_last(iterable):
473 """
474 Enumerate a sequence of iterable, while knowing if this element is the last in
475 the sequence or not.
476
477 Args:
478 iterable: an Iterable of some sequence
479
480 Yields:
481 (element, bool) where the bool is True iff the element is last in the seq.
482 """
483 it = (i for i in iterable)
484
485 first = next(it) # OK: raises exception if it is empty
486
487 second = first # for when we have only 1 element in iterable
488
489 try:
490 while True:
491 second = next(it)
492 # more elements remaining.
493 yield (first, False)
494 first = second
495 except StopIteration:
496 # last element. no more elements left
497 yield (second, True)
498
499def pascal_case(what):
500 """
501 Convert the first letter of a string to uppercase, to make the identifier
502 conform to PascalCase.
503
504 Args:
505 what: a string representing some identifier
506
507 Returns:
508 String with first letter capitalized
509
510 Example:
511 pascal_case("helloWorld") == "HelloWorld"
512 pascal_case("foo") == "Foo"
513 """
514 return what[0:1].upper() + what[1:]
515
516def jenum(enum):
517 """
518 Calculate the Java symbol referencing an enum value (in Java).
519
520 Args:
521 enum: An Enum node
522
523 Returns:
524 String representing the Java symbol
525 """
526
527 entry = enum.parent
528 name = entry.name
529
530 name_without_ons = entry.get_name_as_list()[1:]
531 jenum_name = ".".join([pascal_case(i) for i in name_without_ons]) + "Key.Enum"
532
533 return jenum_name
534