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