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