1# 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 22import re 23import markdown 24import textwrap 25import sys 26import bs4 27# Monkey-patch BS4. WBR element must not have an end tag. 28bs4.builder.HTMLTreeBuilder.empty_element_tags.add("wbr") 29 30from collections import OrderedDict 31 32# Relative path from HTML file to the base directory used by <img> tags 33IMAGE_SRC_METADATA="images/camera2/metadata/" 34 35# Prepend this path to each <img src="foo"> in javadocs 36JAVADOC_IMAGE_SRC_METADATA="/reference/" + IMAGE_SRC_METADATA 37NDKDOC_IMAGE_SRC_METADATA="../" + IMAGE_SRC_METADATA 38 39#Corresponds to Android Q, where the camera VNDK was added (minor version 4 and vndk version 29). 40# Minor version and vndk version must correspond to the same release 41FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION = 4 42FRAMEWORK_CAMERA_VNDK_STARTING_VERSION = 29 43 44_context_buf = None 45_enum = None 46 47def _is_sec_or_ins(x): 48 return isinstance(x, metadata_model.Section) or \ 49 isinstance(x, metadata_model.InnerNamespace) 50 51## 52## Metadata Helpers 53## 54 55def find_all_sections(root): 56 """ 57 Find all descendants that are Section or InnerNamespace instances. 58 59 Args: 60 root: a Metadata instance 61 62 Returns: 63 A list of Section/InnerNamespace instances 64 65 Remarks: 66 These are known as "sections" in the generated C code. 67 """ 68 return root.find_all(_is_sec_or_ins) 69 70def find_parent_section(entry): 71 """ 72 Find the closest ancestor that is either a Section or InnerNamespace. 73 74 Args: 75 entry: an Entry or Clone node 76 77 Returns: 78 An instance of Section or InnerNamespace 79 """ 80 return entry.find_parent_first(_is_sec_or_ins) 81 82# find uniquely named entries (w/o recursing through inner namespaces) 83def find_unique_entries(node): 84 """ 85 Find all uniquely named entries, without recursing through inner namespaces. 86 87 Args: 88 node: a Section or InnerNamespace instance 89 90 Yields: 91 A sequence of MergedEntry nodes representing an entry 92 93 Remarks: 94 This collapses multiple entries with the same fully qualified name into 95 one entry (e.g. if there are multiple entries in different kinds). 96 """ 97 if not isinstance(node, metadata_model.Section) and \ 98 not isinstance(node, metadata_model.InnerNamespace): 99 raise TypeError("expected node to be a Section or InnerNamespace") 100 101 d = OrderedDict() 102 # remove the 'kinds' from the path between sec and the closest entries 103 # then search the immediate children of the search path 104 search_path = isinstance(node, metadata_model.Section) and node.kinds \ 105 or [node] 106 for i in search_path: 107 for entry in i.entries: 108 d[entry.name] = entry 109 110 for k,v in d.items(): 111 yield v.merge() 112 113def path_name(node): 114 """ 115 Calculate a period-separated string path from the root to this element, 116 by joining the names of each node and excluding the Metadata/Kind nodes 117 from the path. 118 119 Args: 120 node: a Node instance 121 122 Returns: 123 A string path 124 """ 125 126 isa = lambda x,y: isinstance(x, y) 127 fltr = lambda x: not isa(x, metadata_model.Metadata) and \ 128 not isa(x, metadata_model.Kind) 129 130 path = node.find_parents(fltr) 131 path = list(path) 132 path.reverse() 133 path.append(node) 134 135 return ".".join((i.name for i in path)) 136 137def ndk(name): 138 """ 139 Return the NDK version of given name, which replace 140 the leading "android" to "acamera" 141 142 Args: 143 name: name string of an entry 144 145 Returns: 146 A NDK version name string of the input name 147 """ 148 name_list = name.split(".") 149 if name_list[0] == "android": 150 name_list[0] = "acamera" 151 return ".".join(name_list) 152 153def protobuf_type(entry): 154 """ 155 Return the protocol buffer message type for input metadata entry. 156 Only support types used by static metadata right now 157 158 Returns: 159 A string of protocol buffer type. Ex: "optional int32" or "repeated RangeInt" 160 """ 161 typeName = None 162 if entry.typedef is None: 163 typeName = entry.type 164 else: 165 typeName = entry.typedef.name 166 167 typename_to_protobuftype = { 168 "rational" : "Rational", 169 "size" : "Size", 170 "sizeF" : "SizeF", 171 "rectangle" : "Rect", 172 "streamConfigurationMap" : "StreamConfigurations", 173 "mandatoryStreamCombination" : "MandatoryStreamCombination", 174 "rangeInt" : "RangeInt", 175 "rangeLong" : "RangeLong", 176 "rangeFloat" : "RangeFloat", 177 "colorSpaceTransform" : "ColorSpaceTransform", 178 "blackLevelPattern" : "BlackLevelPattern", 179 "byte" : "int32", # protocol buffer don't support byte 180 "boolean" : "bool", 181 "float" : "float", 182 "double" : "double", 183 "int32" : "int32", 184 "int64" : "int64", 185 "enumList" : "int32", 186 "string" : "string", 187 "capability" : "Capability", 188 "multiResolutionStreamConfigurationMap" : "MultiResolutionStreamConfigurations", 189 "deviceStateSensorOrientationMap" : "DeviceStateSensorOrientationMap", 190 "dynamicRangeProfiles" : "DynamicRangeProfiles", 191 "colorSpaceProfiles" : "ColorSpaceProfiles", 192 } 193 194 if typeName not in typename_to_protobuftype: 195 print(" ERROR: Could not find protocol buffer type for {%s} type {%s} typedef {%s}" % \ 196 (entry.name, entry.type, entry.typedef), file=sys.stderr) 197 198 proto_type = typename_to_protobuftype[typeName] 199 200 prefix = "optional" 201 if entry.container == 'array': 202 has_variable_size = False 203 for size in entry.container_sizes: 204 try: 205 size_int = int(size) 206 except ValueError: 207 has_variable_size = True 208 209 if has_variable_size: 210 prefix = "repeated" 211 212 return "%s %s" %(prefix, proto_type) 213 214 215def protobuf_name(entry): 216 """ 217 Return the protocol buffer field name for input metadata entry 218 219 Returns: 220 A string. Ex: "android_colorCorrection_availableAberrationModes" 221 """ 222 return entry.name.replace(".", "_") 223 224def has_descendants_with_enums(node): 225 """ 226 Determine whether or not the current node is or has any descendants with an 227 Enum node. 228 229 Args: 230 node: a Node instance 231 232 Returns: 233 True if it finds an Enum node in the subtree, False otherwise 234 """ 235 return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum))) 236 237def get_children_by_throwing_away_kind(node, member='entries'): 238 """ 239 Get the children of this node by compressing the subtree together by removing 240 the kind and then combining any children nodes with the same name together. 241 242 Args: 243 node: An instance of Section, InnerNamespace, or Kind 244 245 Returns: 246 An iterable over the combined children of the subtree of node, 247 as if the Kinds never existed. 248 249 Remarks: 250 Not recursive. Call this function repeatedly on each child. 251 """ 252 253 if isinstance(node, metadata_model.Section): 254 # Note that this makes jump from Section to Kind, 255 # skipping the Kind entirely in the tree. 256 node_to_combine = node.combine_kinds_into_single_node() 257 else: 258 node_to_combine = node 259 260 combined_kind = node_to_combine.combine_children_by_name() 261 262 return (i for i in getattr(combined_kind, member)) 263 264def get_children_by_filtering_kind(section, kind_name, member='entries'): 265 """ 266 Takes a section and yields the children of the merged kind under this section. 267 268 Args: 269 section: An instance of Section 270 kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls' 271 272 Returns: 273 An iterable over the children of the specified merged kind. 274 """ 275 276 matched_kind = next((i for i in section.merged_kinds if i.name == kind_name), None) 277 278 if matched_kind: 279 return getattr(matched_kind, member) 280 else: 281 return () 282 283## 284## Filters 285## 286 287# abcDef.xyz -> ABC_DEF_XYZ 288def csym(name): 289 """ 290 Convert an entry name string into an uppercase C symbol. 291 292 Returns: 293 A string 294 295 Example: 296 csym('abcDef.xyz') == 'ABC_DEF_XYZ' 297 """ 298 newstr = name 299 newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper() 300 newstr = newstr.replace(".", "_") 301 return newstr 302 303# abcDef.xyz -> abc_def_xyz 304def csyml(name): 305 """ 306 Convert an entry name string into a lowercase C symbol. 307 308 Returns: 309 A string 310 311 Example: 312 csyml('abcDef.xyz') == 'abc_def_xyz' 313 """ 314 return csym(name).lower() 315 316# pad with spaces to make string len == size. add new line if too big 317def ljust(size, indent=4): 318 """ 319 Creates a function that given a string will pad it with spaces to make 320 the string length == size. Adds a new line if the string was too big. 321 322 Args: 323 size: an integer representing how much spacing should be added 324 indent: an integer representing the initial indendation level 325 326 Returns: 327 A function that takes a string and returns a string. 328 329 Example: 330 ljust(8)("hello") == 'hello ' 331 332 Remarks: 333 Deprecated. Use pad instead since it works for non-first items in a 334 Mako template. 335 """ 336 def inner(what): 337 newstr = what.ljust(size) 338 if len(newstr) > size: 339 return what + "\n" + "".ljust(indent + size) 340 else: 341 return newstr 342 return inner 343 344def _find_new_line(): 345 346 if _context_buf is None: 347 raise ValueError("Context buffer was not set") 348 349 buf = _context_buf 350 x = -1 # since the first read is always '' 351 cur_pos = buf.tell() 352 while buf.tell() > 0 and buf.read(1) != '\n': 353 buf.seek(cur_pos - x) 354 x = x + 1 355 356 buf.seek(cur_pos) 357 358 return int(x) 359 360# Pad the string until the buffer reaches the desired column. 361# If string is too long, insert a new line with 'col' spaces instead 362def pad(col): 363 """ 364 Create a function that given a string will pad it to the specified column col. 365 If the string overflows the column, put the string on a new line and pad it. 366 367 Args: 368 col: an integer specifying the column number 369 370 Returns: 371 A function that given a string will produce a padded string. 372 373 Example: 374 pad(8)("hello") == 'hello ' 375 376 Remarks: 377 This keeps track of the line written by Mako so far, so it will always 378 align to the column number correctly. 379 """ 380 def inner(what): 381 wut = int(col) 382 current_col = _find_new_line() 383 384 if len(what) > wut - current_col: 385 return what + "\n".ljust(col) 386 else: 387 return what.ljust(wut - current_col) 388 return inner 389 390# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32 391def ctype_enum(what): 392 """ 393 Generate a camera_metadata_type_t symbol from a type string. 394 395 Args: 396 what: a type string 397 398 Returns: 399 A string representing the camera_metadata_type_t 400 401 Example: 402 ctype_enum('int32') == 'TYPE_INT32' 403 ctype_enum('int64') == 'TYPE_INT64' 404 ctype_enum('float') == 'TYPE_FLOAT' 405 406 Remarks: 407 An enum is coerced to a byte since the rest of the camera_metadata 408 code doesn't support enums directly yet. 409 """ 410 return 'TYPE_%s' %(what.upper()) 411 412 413# Calculate a java type name from an entry with a Typedef node 414def _jtypedef_type(entry): 415 typedef = entry.typedef 416 additional = '' 417 418 # Hacky way to deal with arrays. Assume that if we have 419 # size 'Constant x N' the Constant is part of the Typedef size. 420 # So something sized just 'Constant', 'Constant1 x Constant2', etc 421 # is not treated as a real java array. 422 if entry.container == 'array': 423 has_variable_size = False 424 for size in entry.container_sizes: 425 try: 426 size_int = int(size) 427 except ValueError: 428 has_variable_size = True 429 430 if has_variable_size: 431 additional = '[]' 432 433 try: 434 name = typedef.languages['java'] 435 436 return "%s%s" %(name, additional) 437 except KeyError: 438 return None 439 440# Box if primitive. Otherwise leave unboxed. 441def _jtype_box(type_name): 442 mapping = { 443 'boolean': 'Boolean', 444 'byte': 'Byte', 445 'int': 'Integer', 446 'float': 'Float', 447 'double': 'Double', 448 'long': 'Long' 449 } 450 451 return mapping.get(type_name, type_name) 452 453def jtype_unboxed(entry): 454 """ 455 Calculate the Java type from an entry type string, to be used whenever we 456 need the regular type in Java. It's not boxed, so it can't be used as a 457 generic type argument when the entry type happens to resolve to a primitive. 458 459 Remarks: 460 Since Java generics cannot be instantiated with primitives, this version 461 is not applicable in that case. Use jtype_boxed instead for that. 462 463 Returns: 464 The string representing the Java type. 465 """ 466 if not isinstance(entry, metadata_model.Entry): 467 raise ValueError("Expected entry to be an instance of Entry") 468 469 metadata_type = entry.type 470 471 java_type = None 472 473 if entry.typedef: 474 typedef_name = _jtypedef_type(entry) 475 if typedef_name: 476 java_type = typedef_name # already takes into account arrays 477 478 if not java_type: 479 if not java_type and entry.enum and metadata_type == 'byte': 480 # Always map byte enums to Java ints, unless there's a typedef override 481 base_type = 'int' 482 483 else: 484 mapping = { 485 'int32': 'int', 486 'int64': 'long', 487 'float': 'float', 488 'double': 'double', 489 'byte': 'byte', 490 'rational': 'Rational' 491 } 492 493 base_type = mapping[metadata_type] 494 495 # Convert to array (enums, basic types) 496 if entry.container == 'array': 497 additional = '[]' 498 else: 499 additional = '' 500 501 java_type = '%s%s' %(base_type, additional) 502 503 # Now box this sucker. 504 return java_type 505 506def jtype_boxed(entry): 507 """ 508 Calculate the Java type from an entry type string, to be used as a generic 509 type argument in Java. The type is guaranteed to inherit from Object. 510 511 It will only box when absolutely necessary, i.e. int -> Integer[], but 512 int[] -> int[]. 513 514 Remarks: 515 Since Java generics cannot be instantiated with primitives, this version 516 will use boxed types when absolutely required. 517 518 Returns: 519 The string representing the boxed Java type. 520 """ 521 unboxed_type = jtype_unboxed(entry) 522 return _jtype_box(unboxed_type) 523 524def _is_jtype_generic(entry): 525 """ 526 Determine whether or not the Java type represented by the entry type 527 string and/or typedef is a Java generic. 528 529 For example, "Range<Integer>" would be considered a generic, whereas 530 a "MeteringRectangle" or a plain "Integer" would not be considered a generic. 531 532 Args: 533 entry: An instance of an Entry node 534 535 Returns: 536 True if it's a java generic, False otherwise. 537 """ 538 if entry.typedef: 539 local_typedef = _jtypedef_type(entry) 540 if local_typedef: 541 match = re.search(r'<.*>', local_typedef) 542 return bool(match) 543 return False 544 545def _jtype_primitive(what): 546 """ 547 Calculate the Java type from an entry type string. 548 549 Remarks: 550 Makes a special exception for Rational, since it's a primitive in terms of 551 the C-library camera_metadata type system. 552 553 Returns: 554 The string representing the primitive type 555 """ 556 mapping = { 557 'int32': 'int', 558 'int64': 'long', 559 'float': 'float', 560 'double': 'double', 561 'byte': 'byte', 562 'rational': 'Rational' 563 } 564 565 try: 566 return mapping[what] 567 except KeyError as e: 568 raise ValueError("Can't map '%s' to a primitive, not supported" %what) 569 570def jclass(entry): 571 """ 572 Calculate the java Class reference string for an entry. 573 574 Args: 575 entry: an Entry node 576 577 Example: 578 <entry name="some_int" type="int32"/> 579 <entry name="some_int_array" type="int32" container='array'/> 580 581 jclass(some_int) == 'int.class' 582 jclass(some_int_array) == 'int[].class' 583 584 Returns: 585 The ClassName.class string 586 """ 587 588 return "%s.class" %jtype_unboxed(entry) 589 590def jkey_type_token(entry): 591 """ 592 Calculate the java type token compatible with a Key constructor. 593 This will be the Java Class<T> for non-generic classes, and a 594 TypeReference<T> for generic classes. 595 596 Args: 597 entry: An entry node 598 599 Returns: 600 The ClassName.class string, or 'new TypeReference<ClassName>() {{ }}' string 601 """ 602 if _is_jtype_generic(entry): 603 return "new TypeReference<%s>() {{ }}" %(jtype_boxed(entry)) 604 else: 605 return jclass(entry) 606 607def jidentifier(what): 608 """ 609 Convert the input string into a valid Java identifier. 610 611 Args: 612 what: any identifier string 613 614 Returns: 615 String with added underscores if necessary. 616 """ 617 if re.match("\d", what): 618 return "_%s" %what 619 else: 620 return what 621 622def enum_calculate_value_string(enum_value): 623 """ 624 Calculate the value of the enum, even if it does not have one explicitly 625 defined. 626 627 This looks back for the first enum value that has a predefined value and then 628 applies addition until we get the right value, using C-enum semantics. 629 630 Args: 631 enum_value: an EnumValue node with a valid Enum parent 632 633 Example: 634 <enum> 635 <value>X</value> 636 <value id="5">Y</value> 637 <value>Z</value> 638 </enum> 639 640 enum_calculate_value_string(X) == '0' 641 enum_calculate_Value_string(Y) == '5' 642 enum_calculate_value_string(Z) == '6' 643 644 Returns: 645 String that represents the enum value as an integer literal. 646 """ 647 648 enum_value_siblings = list(enum_value.parent.values) 649 this_index = enum_value_siblings.index(enum_value) 650 651 def is_hex_string(instr): 652 return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE)) 653 654 base_value = 0 655 base_offset = 0 656 emit_as_hex = False 657 658 this_id = enum_value_siblings[this_index].id 659 while this_index != 0 and not this_id: 660 this_index -= 1 661 base_offset += 1 662 this_id = enum_value_siblings[this_index].id 663 664 if this_id: 665 base_value = int(this_id, 0) # guess base 666 emit_as_hex = is_hex_string(this_id) 667 668 if emit_as_hex: 669 return "0x%X" %(base_value + base_offset) 670 else: 671 return "%d" %(base_value + base_offset) 672 673def enumerate_with_last(iterable): 674 """ 675 Enumerate a sequence of iterable, while knowing if this element is the last in 676 the sequence or not. 677 678 Args: 679 iterable: an Iterable of some sequence 680 681 Yields: 682 (element, bool) where the bool is True iff the element is last in the seq. 683 """ 684 it = (i for i in iterable) 685 686 try: 687 first = next(it) # OK: raises exception if it is empty 688 except StopIteration: 689 return 690 691 second = first # for when we have only 1 element in iterable 692 693 try: 694 while True: 695 second = next(it) 696 # more elements remaining. 697 yield (first, False) 698 first = second 699 except StopIteration: 700 # last element. no more elements left 701 yield (second, True) 702 703def pascal_case(what): 704 """ 705 Convert the first letter of a string to uppercase, to make the identifier 706 conform to PascalCase. 707 708 If there are dots, remove the dots, and capitalize the letter following 709 where the dot was. Letters that weren't following dots are left unchanged, 710 except for the first letter of the string (which is made upper-case). 711 712 Args: 713 what: a string representing some identifier 714 715 Returns: 716 String with first letter capitalized 717 718 Example: 719 pascal_case("helloWorld") == "HelloWorld" 720 pascal_case("foo") == "Foo" 721 pascal_case("hello.world") = "HelloWorld" 722 pascal_case("fooBar.fooBar") = "FooBarFooBar" 723 """ 724 return "".join([s[0:1].upper() + s[1:] for s in what.split('.')]) 725 726def jkey_identifier(what): 727 """ 728 Return a Java identifier from a property name. 729 730 Args: 731 what: a string representing a property name. 732 733 Returns: 734 Java identifier corresponding to the property name. May need to be 735 prepended with the appropriate Java class name by the caller of this 736 function. Note that the outer namespace is stripped from the property 737 name. 738 739 Example: 740 jkey_identifier("android.lens.facing") == "LENS_FACING" 741 """ 742 return csym(what[what.find('.') + 1:]) 743 744def jenum_value(enum_entry, enum_value): 745 """ 746 Calculate the Java name for an integer enum value 747 748 Args: 749 enum: An enum-typed Entry node 750 value: An EnumValue node for the enum 751 752 Returns: 753 String representing the Java symbol 754 """ 755 756 cname = csym(enum_entry.name) 757 return cname[cname.find('_') + 1:] + '_' + enum_value.name 758 759def generate_extra_javadoc_detail(entry): 760 """ 761 Returns a function to add extra details for an entry into a string for inclusion into 762 javadoc. Adds information about units, the list of enum values for this key, and the valid 763 range. 764 """ 765 def inner(text): 766 if entry.units and not (entry.typedef and entry.typedef.name == 'string'): 767 text += '\n\n<b>Units</b>: %s\n' % (dedent(entry.units)) 768 if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')): 769 text += '\n\n<b>Possible values:</b>\n<ul>\n' 770 for value in entry.enum.values: 771 if not value.hidden: 772 text += ' <li>{@link #%s %s}</li>\n' % ( jenum_value(entry, value ), value.name ) 773 text += '</ul>\n' 774 if entry.range: 775 if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')): 776 text += '\n\n<b>Available values for this device:</b><br>\n' 777 else: 778 text += '\n\n<b>Range of valid values:</b><br>\n' 779 text += '%s\n' % (dedent(entry.range)) 780 if entry.hwlevel != 'legacy': # covers any of (None, 'limited', 'full') 781 text += '\n\n<b>Optional</b> - The value for this key may be {@code null} on some devices.\n' 782 if entry.hwlevel == 'full': 783 text += \ 784 '\n<b>Full capability</b> - \n' + \ 785 'Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the\n' + \ 786 'android.info.supportedHardwareLevel key\n' 787 if entry.hwlevel == 'limited': 788 text += \ 789 '\n<b>Limited capability</b> - \n' + \ 790 'Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the\n' + \ 791 'android.info.supportedHardwareLevel key\n' 792 if entry.hwlevel == 'legacy': 793 text += "\nThis key is available on all devices." 794 if entry.permission_needed == "true": 795 text += "\n\n<b>Permission {@link android.Manifest.permission#CAMERA} is needed to access this property</b>\n\n" 796 797 return text 798 return inner 799 800 801def javadoc(metadata, indent = 4): 802 """ 803 Returns a function to format a markdown syntax text block as a 804 javadoc comment section, given a set of metadata 805 806 Args: 807 metadata: A Metadata instance, representing the top-level root 808 of the metadata for cross-referencing 809 indent: baseline level of indentation for javadoc block 810 Returns: 811 A function that transforms a String text block as follows: 812 - Indent and * for insertion into a Javadoc comment block 813 - Trailing whitespace removed 814 - Entire body rendered via markdown to generate HTML 815 - All tag names converted to appropriate Javadoc {@link} with @see 816 for each tag 817 818 Example: 819 "This is a comment for Javadoc\n" + 820 " with multiple lines, that should be \n" + 821 " formatted better\n" + 822 "\n" + 823 " That covers multiple lines as well\n" 824 " And references android.control.mode\n" 825 826 transforms to 827 " * <p>This is a comment for Javadoc\n" + 828 " * with multiple lines, that should be\n" + 829 " * formatted better</p>\n" + 830 " * <p>That covers multiple lines as well</p>\n" + 831 " * and references {@link CaptureRequest#CONTROL_MODE android.control.mode}\n" + 832 " *\n" + 833 " * @see CaptureRequest#CONTROL_MODE\n" 834 """ 835 def javadoc_formatter(text): 836 comment_prefix = " " * indent + " * " 837 838 # render with markdown => HTML 839 javatext = md(text, JAVADOC_IMAGE_SRC_METADATA) 840 841 # Identity transform for javadoc links 842 def javadoc_link_filter(target, target_ndk, shortname): 843 return '{@link %s %s}' % (target, shortname) 844 845 javatext = filter_links(javatext, javadoc_link_filter) 846 847 # Crossref tag names 848 kind_mapping = { 849 'static': 'CameraCharacteristics', 850 'dynamic': 'CaptureResult', 851 'controls': 'CaptureRequest' } 852 853 # Convert metadata entry "android.x.y.z" to form 854 # "{@link CaptureRequest#X_Y_Z android.x.y.z}" 855 def javadoc_crossref_filter(node): 856 if node.applied_visibility in ('public', 'java_public', 'fwk_java_public'): 857 return '{@link %s#%s %s}' % (kind_mapping[node.kind], 858 jkey_identifier(node.name), 859 node.name) 860 else: 861 return node.name 862 863 # For each public tag "android.x.y.z" referenced, add a 864 # "@see CaptureRequest#X_Y_Z" 865 def javadoc_crossref_see_filter(node_set): 866 node_set = (x for x in node_set if x.applied_visibility in \ 867 ('public', 'java_public', 'fwk_java_public')) 868 869 text = '\n' 870 for node in node_set: 871 text = text + '\n@see %s#%s' % (kind_mapping[node.kind], 872 jkey_identifier(node.name)) 873 874 return text if text != '\n' else '' 875 876 javatext = filter_tags(javatext, metadata, javadoc_crossref_filter, javadoc_crossref_see_filter) 877 878 def line_filter(line): 879 # Indent each line 880 # Add ' * ' to it for stylistic reasons 881 # Strip right side of trailing whitespace 882 return (comment_prefix + line).rstrip() 883 884 # Process each line with above filter 885 javatext = "\n".join(line_filter(i) for i in javatext.split("\n")) + "\n" 886 887 return javatext 888 889 return javadoc_formatter 890 891def ndkdoc(metadata, indent = 4): 892 """ 893 Returns a function to format a markdown syntax text block as a 894 NDK camera API C/C++ comment section, given a set of metadata 895 896 Args: 897 metadata: A Metadata instance, representing the top-level root 898 of the metadata for cross-referencing 899 indent: baseline level of indentation for comment block 900 Returns: 901 A function that transforms a String text block as follows: 902 - Indent and * for insertion into a comment block 903 - Trailing whitespace removed 904 - Entire body rendered via markdown 905 - All tag names converted to appropriate NDK tag name for each tag 906 907 Example: 908 "This is a comment for NDK\n" + 909 " with multiple lines, that should be \n" + 910 " formatted better\n" + 911 "\n" + 912 " That covers multiple lines as well\n" 913 " And references android.control.mode\n" 914 915 transforms to 916 " * This is a comment for NDK\n" + 917 " * with multiple lines, that should be\n" + 918 " * formatted better\n" + 919 " * That covers multiple lines as well\n" + 920 " * and references ACAMERA_CONTROL_MODE\n" + 921 " *\n" + 922 " * @see ACAMERA_CONTROL_MODE\n" 923 """ 924 def ndkdoc_formatter(text): 925 # render with markdown => HTML 926 # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags 927 ndktext = md(text, NDKDOC_IMAGE_SRC_METADATA, False) 928 929 # Simple transform for ndk doc links 930 def ndkdoc_link_filter(target, target_ndk, shortname): 931 if target_ndk is not None: 932 return '{@link %s %s}' % (target_ndk, shortname) 933 934 # Create HTML link to Javadoc 935 if shortname == '': 936 lastdot = target.rfind('.') 937 if lastdot == -1: 938 shortname = target 939 else: 940 shortname = target[lastdot + 1:] 941 942 target = target.replace('.','/') 943 if target.find('#') != -1: 944 target = target.replace('#','.html#') 945 else: 946 target = target + '.html' 947 948 # Work around html links with inner classes. 949 target = target.replace('CaptureRequest/Builder', 'CaptureRequest.Builder') 950 target = target.replace('Build/VERSION', 'Build.VERSION') 951 952 return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname) 953 954 ndktext = filter_links(ndktext, ndkdoc_link_filter) 955 956 # Convert metadata entry "android.x.y.z" to form 957 # NDK tag format of "ACAMERA_X_Y_Z" 958 def ndkdoc_crossref_filter(node): 959 if node.applied_ndk_visible == 'true': 960 return csym(ndk(node.name)) 961 else: 962 return node.name 963 964 # For each public tag "android.x.y.z" referenced, add a 965 # "@see ACAMERA_X_Y_Z" 966 def ndkdoc_crossref_see_filter(node_set): 967 node_set = (x for x in node_set if x.applied_ndk_visible == 'true') 968 969 text = '\n' 970 for node in node_set: 971 text = text + '\n@see %s' % (csym(ndk(node.name))) 972 973 return text if text != '\n' else '' 974 975 ndktext = filter_tags(ndktext, metadata, ndkdoc_crossref_filter, ndkdoc_crossref_see_filter) 976 977 ndktext = ndk_replace_tag_wildcards(ndktext, metadata) 978 979 comment_prefix = " " * indent + " * "; 980 981 def line_filter(line): 982 # Indent each line 983 # Add ' * ' to it for stylistic reasons 984 # Strip right side of trailing whitespace 985 return (comment_prefix + line).rstrip() 986 987 # Process each line with above filter 988 ndktext = "\n".join(line_filter(i) for i in ndktext.split("\n")) + "\n" 989 990 return ndktext 991 992 return ndkdoc_formatter 993 994def hidldoc(metadata, indent = 4): 995 """ 996 Returns a function to format a markdown syntax text block as a 997 HIDL camera HAL module C/C++ comment section, given a set of metadata 998 999 Args: 1000 metadata: A Metadata instance, representing the top-level root 1001 of the metadata for cross-referencing 1002 indent: baseline level of indentation for comment block 1003 Returns: 1004 A function that transforms a String text block as follows: 1005 - Indent and * for insertion into a comment block 1006 - Trailing whitespace removed 1007 - Entire body rendered via markdown 1008 - All tag names converted to appropriate HIDL tag name for each tag 1009 1010 Example: 1011 "This is a comment for NDK\n" + 1012 " with multiple lines, that should be \n" + 1013 " formatted better\n" + 1014 "\n" + 1015 " That covers multiple lines as well\n" 1016 " And references android.control.mode\n" 1017 1018 transforms to 1019 " * This is a comment for NDK\n" + 1020 " * with multiple lines, that should be\n" + 1021 " * formatted better\n" + 1022 " * That covers multiple lines as well\n" + 1023 " * and references ANDROID_CONTROL_MODE\n" + 1024 " *\n" + 1025 " * @see ANDROID_CONTROL_MODE\n" 1026 """ 1027 def hidldoc_formatter(text): 1028 # render with markdown => HTML 1029 # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags 1030 hidltext = md(text, NDKDOC_IMAGE_SRC_METADATA, False) 1031 1032 # Simple transform for hidl doc links 1033 def hidldoc_link_filter(target, target_ndk, shortname): 1034 if target_ndk is not None: 1035 return '{@link %s %s}' % (target_ndk, shortname) 1036 1037 # Create HTML link to Javadoc 1038 if shortname == '': 1039 lastdot = target.rfind('.') 1040 if lastdot == -1: 1041 shortname = target 1042 else: 1043 shortname = target[lastdot + 1:] 1044 1045 target = target.replace('.','/') 1046 if target.find('#') != -1: 1047 target = target.replace('#','.html#') 1048 else: 1049 target = target + '.html' 1050 1051 return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname) 1052 1053 hidltext = filter_links(hidltext, hidldoc_link_filter) 1054 1055 # Convert metadata entry "android.x.y.z" to form 1056 # HIDL tag format of "ANDROID_X_Y_Z" 1057 def hidldoc_crossref_filter(node): 1058 return csym(node.name) 1059 1060 # For each public tag "android.x.y.z" referenced, add a 1061 # "@see ANDROID_X_Y_Z" 1062 def hidldoc_crossref_see_filter(node_set): 1063 text = '\n' 1064 for node in node_set: 1065 text = text + '\n@see %s' % (csym(node.name)) 1066 1067 return text if text != '\n' else '' 1068 1069 hidltext = filter_tags(hidltext, metadata, hidldoc_crossref_filter, hidldoc_crossref_see_filter) 1070 1071 comment_prefix = " " * indent + " * "; 1072 1073 def line_filter(line): 1074 # Indent each line 1075 # Add ' * ' to it for stylistic reasons 1076 # Strip right side of trailing whitespace 1077 return (comment_prefix + line).rstrip() 1078 1079 # Process each line with above filter 1080 hidltext = "\n".join(line_filter(i) for i in hidltext.split("\n")) + "\n" 1081 1082 return hidltext 1083 1084 return hidldoc_formatter 1085 1086def dedent(text): 1087 """ 1088 Remove all common indentation from every line but the 0th. 1089 This will avoid getting <code> blocks when rendering text via markdown. 1090 Ignoring the 0th line will also allow the 0th line not to be aligned. 1091 1092 Args: 1093 text: A string of text to dedent. 1094 1095 Returns: 1096 String dedented by above rules. 1097 1098 For example: 1099 assertEquals("bar\nline1\nline2", dedent("bar\n line1\n line2")) 1100 assertEquals("bar\nline1\nline2", dedent(" bar\n line1\n line2")) 1101 assertEquals("bar\n line1\nline2", dedent(" bar\n line1\n line2")) 1102 """ 1103 text = textwrap.dedent(text) 1104 text_lines = text.split('\n') 1105 text_not_first = "\n".join(text_lines[1:]) 1106 text_not_first = textwrap.dedent(text_not_first) 1107 text = text_lines[0] + "\n" + text_not_first 1108 1109 return text 1110 1111def md(text, img_src_prefix="", table_ext=True): 1112 """ 1113 Run text through markdown to produce HTML. 1114 1115 This also removes all common indentation from every line but the 0th. 1116 This will avoid getting <code> blocks in markdown. 1117 Ignoring the 0th line will also allow the 0th line not to be aligned. 1118 1119 Args: 1120 text: A markdown-syntax using block of text to format. 1121 img_src_prefix: An optional string to prepend to each <img src="target"/> 1122 1123 Returns: 1124 String rendered by markdown and other rules applied (see above). 1125 1126 For example, this avoids the following situation: 1127 1128 <!-- Input --> 1129 1130 <!--- can't use dedent directly since 'foo' has no indent --> 1131 <notes>foo 1132 bar 1133 bar 1134 </notes> 1135 1136 <!-- Bad Output -- > 1137 <!-- if no dedent is done generated code looks like --> 1138 <p>foo 1139 <code><pre> 1140 bar 1141 bar</pre></code> 1142 </p> 1143 1144 Instead we get the more natural expected result: 1145 1146 <!-- Good Output --> 1147 <p>foo 1148 bar 1149 bar</p> 1150 1151 """ 1152 text = dedent(text) 1153 1154 # full list of extensions at http://pythonhosted.org/Markdown/extensions/ 1155 md_extensions = ['tables'] if table_ext else []# make <table> with ASCII |_| tables 1156 # render with markdown 1157 text = markdown.markdown(text, extensions=md_extensions) 1158 1159 # prepend a prefix to each <img src="foo"> -> <img src="${prefix}foo"> 1160 text = re.sub(r'src="([^"]*)"', 'src="' + img_src_prefix + r'\1"', text) 1161 return text 1162 1163def filter_tags(text, metadata, filter_function, summary_function = None): 1164 """ 1165 Find all references to tags in the form outer_namespace.xxx.yyy[.zzz] in 1166 the provided text, and pass them through filter_function and summary_function. 1167 1168 Used to linkify entry names in HMTL, javadoc output. 1169 1170 Args: 1171 text: A string representing a block of text destined for output 1172 metadata: A Metadata instance, the root of the metadata properties tree 1173 filter_function: A Node->string function to apply to each node 1174 when found in text; the string returned replaces the tag name in text. 1175 summary_function: A Node list->string function that is provided the list of 1176 unique tag nodes found in text, and which must return a string that is 1177 then appended to the end of the text. The list is sorted alphabetically 1178 by node name. 1179 """ 1180 1181 tag_set = set() 1182 def name_match(name): 1183 return lambda node: node.name == name 1184 1185 # Match outer_namespace.x.y or outer_namespace.x.y.z, making sure 1186 # to grab .z and not just outer_namespace.x.y. (sloppy, but since we 1187 # check for validity, a few false positives don't hurt). 1188 # Try to ignore items of the form {@link <outer_namespace>... 1189 for outer_namespace in metadata.outer_namespaces: 1190 1191 tag_match = r"(?<!\{@link\s)" + outer_namespace.name + \ 1192 r"\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)(\.[a-zA-Z0-9\n]+)?([/]?)" 1193 1194 def filter_sub(match): 1195 whole_match = match.group(0) 1196 section1 = match.group(1) 1197 section2 = match.group(2) 1198 section3 = match.group(3) 1199 end_slash = match.group(4) 1200 1201 # Don't linkify things ending in slash (urls, for example) 1202 if end_slash: 1203 return whole_match 1204 1205 candidate = "" 1206 1207 # First try a two-level match 1208 candidate2 = "%s.%s.%s" % (outer_namespace.name, section1, section2) 1209 got_two_level = False 1210 1211 node = metadata.find_first(name_match(candidate2.replace('\n',''))) 1212 if not node and '\n' in section2: 1213 # Linefeeds are ambiguous - was the intent to add a space, 1214 # or continue a lengthy name? Try the former now. 1215 candidate2b = "%s.%s.%s" % (outer_namespace.name, section1, section2[:section2.find('\n')]) 1216 node = metadata.find_first(name_match(candidate2b)) 1217 if node: 1218 candidate2 = candidate2b 1219 1220 if node: 1221 # Have two-level match 1222 got_two_level = True 1223 candidate = candidate2 1224 elif section3: 1225 # Try three-level match 1226 candidate3 = "%s%s" % (candidate2, section3) 1227 node = metadata.find_first(name_match(candidate3.replace('\n',''))) 1228 1229 if not node and '\n' in section3: 1230 # Linefeeds are ambiguous - was the intent to add a space, 1231 # or continue a lengthy name? Try the former now. 1232 candidate3b = "%s%s" % (candidate2, section3[:section3.find('\n')]) 1233 node = metadata.find_first(name_match(candidate3b)) 1234 if node: 1235 candidate3 = candidate3b 1236 1237 if node: 1238 # Have 3-level match 1239 candidate = candidate3 1240 1241 # Replace match with crossref or complain if a likely match couldn't be matched 1242 1243 if node: 1244 tag_set.add(node) 1245 return whole_match.replace(candidate,filter_function(node)) 1246 else: 1247 print(" WARNING: Could not crossref likely reference {%s}" % (match.group(0)), 1248 file=sys.stderr) 1249 return whole_match 1250 1251 text = re.sub(tag_match, filter_sub, text) 1252 1253 if summary_function is not None: 1254 return text + summary_function(sorted(tag_set, key=lambda x: x.name)) 1255 else: 1256 return text 1257 1258def ndk_replace_tag_wildcards(text, metadata): 1259 """ 1260 Find all references to tags in the form android.xxx.* or android.xxx.yyy.* 1261 in the provided text, and replace them by NDK format of "ACAMERA_XXX_*" or 1262 "ACAMERA_XXX_YYY_*" 1263 1264 Args: 1265 text: A string representing a block of text destined for output 1266 metadata: A Metadata instance, the root of the metadata properties tree 1267 """ 1268 tag_match = r"android\.([a-zA-Z0-9\n]+)\.\*" 1269 tag_match_2 = r"android\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)\*" 1270 1271 def filter_sub(match): 1272 return "ACAMERA_" + match.group(1).upper() + "_*" 1273 def filter_sub_2(match): 1274 return "ACAMERA_" + match.group(1).upper() + match.group(2).upper() + "_*" 1275 1276 text = re.sub(tag_match, filter_sub, text) 1277 text = re.sub(tag_match_2, filter_sub_2, text) 1278 return text 1279 1280def filter_links(text, filter_function, summary_function = None): 1281 """ 1282 Find all references to tags in the form {@link xxx#yyy [zzz]} in the 1283 provided text, and pass them through filter_function and 1284 summary_function. 1285 1286 Used to linkify documentation cross-references in HMTL, javadoc output. 1287 1288 Args: 1289 text: A string representing a block of text destined for output 1290 metadata: A Metadata instance, the root of the metadata properties tree 1291 filter_function: A (string, string)->string function to apply to each 'xxx#yyy', 1292 zzz pair when found in text; the string returned replaces the tag name in text. 1293 summary_function: A string list->string function that is provided the list of 1294 unique targets found in text, and which must return a string that is 1295 then appended to the end of the text. The list is sorted alphabetically 1296 by node name. 1297 1298 """ 1299 1300 target_set = set() 1301 def name_match(name): 1302 return lambda node: node.name == name 1303 1304 tag_match = r"\{@link\s+([^\s\}\|]+)(?:\|([^\s\}]+))*([^\}]*)\}" 1305 1306 def filter_sub(match): 1307 whole_match = match.group(0) 1308 target = match.group(1) 1309 target_ndk = match.group(2) 1310 shortname = match.group(3).strip() 1311 1312 #print("Found link '%s' ndk '%s' as '%s' -> '%s'" % (target, target_ndk, shortname, filter_function(target, target_ndk, shortname))) 1313 1314 # Replace match with crossref 1315 target_set.add(target) 1316 return filter_function(target, target_ndk, shortname) 1317 1318 text = re.sub(tag_match, filter_sub, text) 1319 1320 if summary_function is not None: 1321 return text + summary_function(sorted(target_set)) 1322 else: 1323 return text 1324 1325def any_visible(section, kind_name, visibilities): 1326 """ 1327 Determine if entries in this section have an applied visibility that's in 1328 the list of given visibilities. 1329 1330 Args: 1331 section: A section of metadata 1332 kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls' 1333 visibilities: An iterable of visibilities to match against 1334 1335 Returns: 1336 True if the section has any entries with any of the given visibilities. False otherwise. 1337 """ 1338 1339 for inner_namespace in get_children_by_filtering_kind(section, kind_name, 1340 'namespaces'): 1341 if any(filter_visibility(inner_namespace.merged_entries, visibilities)): 1342 return True 1343 1344 return any(filter_visibility(get_children_by_filtering_kind(section, kind_name, 1345 'merged_entries'), 1346 visibilities)) 1347 1348 1349def filter_visibility(entries, visibilities): 1350 """ 1351 Remove entries whose applied visibility is not in the supplied visibilities. 1352 1353 Args: 1354 entries: An iterable of Entry nodes 1355 visibilities: An iterable of visibilities to filter against 1356 1357 Yields: 1358 An iterable of Entry nodes 1359 """ 1360 return (e for e in entries if e.applied_visibility in visibilities) 1361 1362def remove_hal_non_visible(entries): 1363 """ 1364 Filter the given entries by removing those that are not HAL visible: 1365 synthetic, fwk_only, or fwk_java_public. 1366 1367 Args: 1368 entries: An iterable of Entry nodes 1369 1370 Yields: 1371 An iterable of Entry nodes 1372 """ 1373 return (e for e in entries if not (e.synthetic or e.visibility == 'fwk_only' 1374 or e.visibility == 'fwk_java_public')) 1375 1376""" 1377 Return the vndk version for a given hal minor version. The major version is assumed to be 3 1378 1379 Args: 1380 hal_minor_version : minor version to retrieve the vndk version for 1381 1382 Yields: 1383 int representing the vndk version 1384 """ 1385def get_vndk_version(hal_minor_version): 1386 if hal_minor_version <= FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION: 1387 return 0 1388 return hal_minor_version - FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION \ 1389 + FRAMEWORK_CAMERA_VNDK_STARTING_VERSION 1390 1391""" 1392 Returns an api level -> dict of metadata tags corresponding to the api level 1393 1394 Args: 1395 sections : metadata sections to create the mapping for 1396 metadata: the metadata structure to be used to create the mapping 1397 kind : kind of entries to create a mapping for : 'static' or 'dynamic' 1398 1399 Yields: 1400 A dictionary mapping api level to a dictionary of metadata tags for the particular key (api level) 1401 """ 1402def get_api_level_to_keys(sections, metadata, kind): 1403 api_level_to_keys = {} 1404 for sec in sections: 1405 for idx,entry in enumerate(remove_synthetic(find_unique_entries(sec))): 1406 if entry._hal_minor_version > FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION and \ 1407 metadata.is_entry_this_kind(entry, kind): 1408 api_level = get_vndk_version(entry._hal_minor_version) 1409 try: 1410 api_level_to_keys[api_level].add(entry.name) 1411 except KeyError: 1412 api_level_to_keys[api_level] = {entry.name} 1413 #Insert the keys in sorted order since dicts in python (< 3.7, even OrderedDicts don't actually 1414 # sort keys) 1415 api_level_to_keys_ordered = OrderedDict() 1416 for api_level_ordered in sorted(api_level_to_keys.keys()): 1417 api_level_to_keys_ordered[api_level_ordered] = sorted(api_level_to_keys[api_level_ordered]) 1418 return api_level_to_keys_ordered 1419 1420def remove_synthetic(entries): 1421 """ 1422 Filter the given entries by removing those that are synthetic. 1423 1424 Args: 1425 entries: An iterable of Entry nodes 1426 1427 Yields: 1428 An iterable of Entry nodes 1429 """ 1430 return (e for e in entries if not e.synthetic) 1431 1432def filter_added_in_hal_version(entries, hal_major_version, hal_minor_version): 1433 """ 1434 Filter the given entries to those added in the given HIDL HAL version 1435 1436 Args: 1437 entries: An iterable of Entry nodes 1438 hal_major_version: Major HIDL version to filter for 1439 hal_minor_version: Minor HIDL version to filter for 1440 1441 Yields: 1442 An iterable of Entry nodes 1443 """ 1444 return (e for e in entries if e.hal_major_version == hal_major_version and e.hal_minor_version == hal_minor_version) 1445 1446def filter_has_enum_values_added_in_hal_version(entries, hal_major_version, hal_minor_version): 1447 """ 1448 Filter the given entries to those that have a new enum value added in the given HIDL HAL version 1449 1450 Args: 1451 entries: An iterable of Entry nodes 1452 hal_major_version: Major HIDL version to filter for 1453 hal_minor_version: Minor HIDL version to filter for 1454 1455 Yields: 1456 An iterable of Entry nodes 1457 """ 1458 return (e for e in entries if e.has_new_values_added_in_hal_version(hal_major_version, hal_minor_version)) 1459 1460def permission_needed_count(root): 1461 """ 1462 Return the number entries that need camera permission. 1463 1464 Args: 1465 root: a Metadata instance 1466 1467 Returns: 1468 The number of entires that need camera permission. 1469 1470 """ 1471 ret = 0 1472 for sec in find_all_sections(root): 1473 ret += len(list(filter_has_permission_needed(remove_hal_non_visible(find_unique_entries(sec))))) 1474 1475 return ret 1476 1477def filter_has_permission_needed(entries): 1478 """ 1479 Filter the given entries by removing those that don't need camera permission. 1480 1481 Args: 1482 entries: An iterable of Entry nodes 1483 1484 Yields: 1485 An iterable of Entry nodes 1486 """ 1487 return (e for e in entries if e.permission_needed == 'true') 1488 1489def filter_ndk_visible(entries): 1490 """ 1491 Filter the given entries by removing those that are not NDK visible. 1492 1493 Args: 1494 entries: An iterable of Entry nodes 1495 1496 Yields: 1497 An iterable of Entry nodes 1498 """ 1499 return (e for e in entries if e.applied_ndk_visible == 'true') 1500 1501def wbr(text): 1502 """ 1503 Insert word break hints for the browser in the form of <wbr> HTML tags. 1504 1505 Word breaks are inserted inside an HTML node only, so the nodes themselves 1506 will not be changed. Attributes are also left unchanged. 1507 1508 The following rules apply to insert word breaks: 1509 - For characters in [ '.', '/', '_' ] 1510 - For uppercase letters inside a multi-word X.Y.Z (at least 3 parts) 1511 1512 Args: 1513 text: A string of text containing HTML content. 1514 1515 Returns: 1516 A string with <wbr> inserted by the above rules. 1517 """ 1518 SPLIT_CHARS_LIST = ['.', '_', '/'] 1519 SPLIT_CHARS = r'([.|/|_/,]+)' # split by these characters 1520 CAP_LETTER_MIN = 3 # at least 3 components split by above chars, i.e. x.y.z 1521 def wbr_filter(text): 1522 new_txt = text 1523 1524 # for johnyOrange.appleCider.redGuardian also insert wbr before the caps 1525 # => johny<wbr>Orange.apple<wbr>Cider.red<wbr>Guardian 1526 for words in text.split(" "): 1527 for char in SPLIT_CHARS_LIST: 1528 # match at least x.y.z, don't match x or x.y 1529 if len(words.split(char)) >= CAP_LETTER_MIN: 1530 new_word = re.sub(r"([a-z])([A-Z])", r"\1<wbr>\2", words) 1531 new_txt = new_txt.replace(words, new_word) 1532 1533 # e.g. X/Y/Z -> X/<wbr>Y/<wbr>/Z. also for X.Y.Z, X_Y_Z. 1534 new_txt = re.sub(SPLIT_CHARS, r"\1<wbr>", new_txt) 1535 1536 return new_txt 1537 1538 # Do not mangle HTML when doing the replace by using BeatifulSoup 1539 # - Use the 'html.parser' to avoid inserting <html><body> when decoding 1540 soup = bs4.BeautifulSoup(text, features='html.parser') 1541 wbr_tag = lambda: soup.new_tag('wbr') # must generate new tag every time 1542 1543 for navigable_string in soup.findAll(text=True): 1544 parent = navigable_string.parent 1545 1546 # Insert each '$text<wbr>$foo' before the old '$text$foo' 1547 split_by_wbr_list = wbr_filter(navigable_string).split("<wbr>") 1548 for (split_string, last) in enumerate_with_last(split_by_wbr_list): 1549 navigable_string.insert_before(split_string) 1550 1551 if not last: 1552 # Note that 'insert' will move existing tags to this spot 1553 # so make a new tag instead 1554 navigable_string.insert_before(wbr_tag()) 1555 1556 # Remove the old unmodified text 1557 navigable_string.extract() 1558 1559 return soup.decode() 1560 1561def copyright_year(): 1562 return _copyright_year 1563 1564def enum(): 1565 return _enum 1566 1567def first_hal_minor_version(hal_major_version): 1568 return 2 if hal_major_version == 3 else 0 1569 1570def find_all_sections_added_in_hal(root, hal_major_version, hal_minor_version): 1571 """ 1572 Find all descendants that are Section or InnerNamespace instances, which 1573 were added in HIDL HAL version major.minor. The section is defined to be 1574 added in a HAL version iff the lowest HAL version number of its entries is 1575 that HAL version. 1576 1577 Args: 1578 root: a Metadata instance 1579 hal_major/minor_version: HAL version numbers 1580 1581 Returns: 1582 A list of Section/InnerNamespace instances 1583 1584 Remarks: 1585 These are known as "sections" in the generated C code. 1586 """ 1587 all_sections = find_all_sections(root) 1588 new_sections = [] 1589 for section in all_sections: 1590 min_major_version = None 1591 min_minor_version = None 1592 for entry in remove_hal_non_visible(find_unique_entries(section)): 1593 min_major_version = (min_major_version or entry.hal_major_version) 1594 min_minor_version = (min_minor_version or entry.hal_minor_version) 1595 if entry.hal_major_version < min_major_version or \ 1596 (entry.hal_major_version == min_major_version and entry.hal_minor_version < min_minor_version): 1597 min_minor_version = entry.hal_minor_version 1598 min_major_version = entry.hal_major_version 1599 if min_major_version == hal_major_version and min_minor_version == hal_minor_version: 1600 new_sections.append(section) 1601 return new_sections 1602 1603def find_first_older_used_hal_version(section, hal_major_version, hal_minor_version): 1604 hal_version = (0, 0) 1605 for v in section.hal_versions: 1606 if (v[0] > hal_version[0] or (v[0] == hal_version[0] and v[1] > hal_version[1])) and \ 1607 (v[0] < hal_major_version or (v[0] == hal_major_version and v[1] < hal_minor_version)): 1608 hal_version = v 1609 return hal_version 1610 1611# Some exceptions need to be made regarding enum value identifiers in AIDL. 1612# Process them here. 1613def aidl_enum_value_name(name): 1614 if name == 'ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5': 1615 name = 'ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_AIDL_DEVICE' 1616 return name 1617 1618def aidl_enum_values(entry): 1619 ignoreList = [ 1620 'ANDROID_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END', 1621 'ANDROID_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END_3_8' 1622 ] 1623 return [ 1624 val for val in entry.enum.values if '%s_%s'%(csym(entry.name), val.name) not in ignoreList 1625 ]