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