This is a comment for Javadoc\n" + " * with multiple lines, that should be\n" + " * formatted better
\n" + " *That covers multiple lines as well
\n" + " * and references {@link CaptureRequest#CONTROL_MODE android.control.mode}\n" + " *\n" + " * @see CaptureRequest#CONTROL_MODE\n" """ def javadoc_formatter(text): comment_prefix = " " * indent + " * " # render with markdown => HTML javatext = md(text, JAVADOC_IMAGE_SRC_METADATA) # Identity transform for javadoc links def javadoc_link_filter(target, target_ndk, shortname): return '{@link %s %s}' % (target, shortname) javatext = filter_links(javatext, javadoc_link_filter) # Crossref tag names kind_mapping = { 'static': 'CameraCharacteristics', 'dynamic': 'CaptureResult', 'controls': 'CaptureRequest' } # Convert metadata entry "android.x.y.z" to form # "{@link CaptureRequest#X_Y_Z android.x.y.z}" def javadoc_crossref_filter(node): if node.applied_visibility in ('public', 'java_public'): return '{@link %s#%s %s}' % (kind_mapping[node.kind], jkey_identifier(node.name), node.name) else: return node.name # For each public tag "android.x.y.z" referenced, add a # "@see CaptureRequest#X_Y_Z" def javadoc_crossref_see_filter(node_set): node_set = (x for x in node_set if x.applied_visibility in ('public', 'java_public')) text = '\n' for node in node_set: text = text + '\n@see %s#%s' % (kind_mapping[node.kind], jkey_identifier(node.name)) return text if text != '\n' else '' javatext = filter_tags(javatext, metadata, javadoc_crossref_filter, javadoc_crossref_see_filter) def line_filter(line): # Indent each line # Add ' * ' to it for stylistic reasons # Strip right side of trailing whitespace return (comment_prefix + line).rstrip() # Process each line with above filter javatext = "\n".join(line_filter(i) for i in javatext.split("\n")) + "\n" return javatext return javadoc_formatter def ndkdoc(metadata, indent = 4): """ Returns a function to format a markdown syntax text block as a NDK camera API C/C++ comment section, given a set of metadata Args: metadata: A Metadata instance, representing the top-level root of the metadata for cross-referencing indent: baseline level of indentation for comment block Returns: A function that transforms a String text block as follows: - Indent and * for insertion into a comment block - Trailing whitespace removed - Entire body rendered via markdown - All tag names converted to appropriate NDK tag name for each tag Example: "This is a comment for NDK\n" + " with multiple lines, that should be \n" + " formatted better\n" + "\n" + " That covers multiple lines as well\n" " And references android.control.mode\n" transforms to " * This is a comment for NDK\n" + " * with multiple lines, that should be\n" + " * formatted better\n" + " * That covers multiple lines as well\n" + " * and references ACAMERA_CONTROL_MODE\n" + " *\n" + " * @see ACAMERA_CONTROL_MODE\n" """ def ndkdoc_formatter(text): # render with markdown => HTML # Turn off the table plugin since doxygen doesn't recognize generated tags ndktext = md(text, NDKDOC_IMAGE_SRC_METADATA, False) # Simple transform for ndk doc links def ndkdoc_link_filter(target, target_ndk, shortname): if target_ndk is not None: return '{@link %s %s}' % (target_ndk, shortname) # Create HTML link to Javadoc if shortname == '': lastdot = target.rfind('.') if lastdot == -1: shortname = target else: shortname = target[lastdot + 1:] target = target.replace('.','/') if target.find('#') != -1: target = target.replace('#','.html#') else: target = target + '.html' return '%s' % (target, shortname) ndktext = filter_links(ndktext, ndkdoc_link_filter) # Convert metadata entry "android.x.y.z" to form # NDK tag format of "ACAMERA_X_Y_Z" def ndkdoc_crossref_filter(node): if node.applied_ndk_visible == 'true': return csym(ndk(node.name)) else: return node.name # For each public tag "android.x.y.z" referenced, add a # "@see ACAMERA_X_Y_Z" def ndkdoc_crossref_see_filter(node_set): node_set = (x for x in node_set if x.applied_ndk_visible == 'true') text = '\n' for node in node_set: text = text + '\n@see %s' % (csym(ndk(node.name))) return text if text != '\n' else '' ndktext = filter_tags(ndktext, metadata, ndkdoc_crossref_filter, ndkdoc_crossref_see_filter) ndktext = ndk_replace_tag_wildcards(ndktext, metadata) comment_prefix = " " * indent + " * "; def line_filter(line): # Indent each line # Add ' * ' to it for stylistic reasons # Strip right side of trailing whitespace return (comment_prefix + line).rstrip() # Process each line with above filter ndktext = "\n".join(line_filter(i) for i in ndktext.split("\n")) + "\n" return ndktext return ndkdoc_formatter def hidldoc(metadata, indent = 4): """ Returns a function to format a markdown syntax text block as a HIDL camera HAL module C/C++ comment section, given a set of metadata Args: metadata: A Metadata instance, representing the top-level root of the metadata for cross-referencing indent: baseline level of indentation for comment block Returns: A function that transforms a String text block as follows: - Indent and * for insertion into a comment block - Trailing whitespace removed - Entire body rendered via markdown - All tag names converted to appropriate HIDL tag name for each tag Example: "This is a comment for NDK\n" + " with multiple lines, that should be \n" + " formatted better\n" + "\n" + " That covers multiple lines as well\n" " And references android.control.mode\n" transforms to " * This is a comment for NDK\n" + " * with multiple lines, that should be\n" + " * formatted better\n" + " * That covers multiple lines as well\n" + " * and references ANDROID_CONTROL_MODE\n" + " *\n" + " * @see ANDROID_CONTROL_MODE\n" """ def hidldoc_formatter(text): # render with markdown => HTML # Turn off the table plugin since doxygen doesn't recognize generated tags hidltext = md(text, NDKDOC_IMAGE_SRC_METADATA, False) # Simple transform for hidl doc links def hidldoc_link_filter(target, target_ndk, shortname): if target_ndk is not None: return '{@link %s %s}' % (target_ndk, shortname) # Create HTML link to Javadoc if shortname == '': lastdot = target.rfind('.') if lastdot == -1: shortname = target else: shortname = target[lastdot + 1:] target = target.replace('.','/') if target.find('#') != -1: target = target.replace('#','.html#') else: target = target + '.html' return '%s' % (target, shortname) hidltext = filter_links(hidltext, hidldoc_link_filter) # Convert metadata entry "android.x.y.z" to form # HIDL tag format of "ANDROID_X_Y_Z" def hidldoc_crossref_filter(node): return csym(node.name) # For each public tag "android.x.y.z" referenced, add a # "@see ANDROID_X_Y_Z" def hidldoc_crossref_see_filter(node_set): text = '\n' for node in node_set: text = text + '\n@see %s' % (csym(node.name)) return text if text != '\n' else '' hidltext = filter_tags(hidltext, metadata, hidldoc_crossref_filter, hidldoc_crossref_see_filter) comment_prefix = " " * indent + " * "; def line_filter(line): # Indent each line # Add ' * ' to it for stylistic reasons # Strip right side of trailing whitespace return (comment_prefix + line).rstrip() # Process each line with above filter hidltext = "\n".join(line_filter(i) for i in hidltext.split("\n")) + "\n" return hidltext return hidldoc_formatter def dedent(text): """ Remove all common indentation from every line but the 0th. This will avoid getting blocks when rendering text via markdown.
Ignoring the 0th line will also allow the 0th line not to be aligned.
Args:
text: A string of text to dedent.
Returns:
String dedented by above rules.
For example:
assertEquals("bar\nline1\nline2", dedent("bar\n line1\n line2"))
assertEquals("bar\nline1\nline2", dedent(" bar\n line1\n line2"))
assertEquals("bar\n line1\nline2", dedent(" bar\n line1\n line2"))
"""
text = textwrap.dedent(text)
text_lines = text.split('\n')
text_not_first = "\n".join(text_lines[1:])
text_not_first = textwrap.dedent(text_not_first)
text = text_lines[0] + "\n" + text_not_first
return text
def md(text, img_src_prefix="", table_ext=True):
"""
Run text through markdown to produce HTML.
This also removes all common indentation from every line but the 0th.
This will avoid getting blocks in markdown.
Ignoring the 0th line will also allow the 0th line not to be aligned.
Args:
text: A markdown-syntax using block of text to format.
img_src_prefix: An optional string to prepend to each
Returns:
String rendered by markdown and other rules applied (see above).
For example, this avoids the following situation:
foo
bar
bar
foo
bar
bar
Instead we get the more natural expected result:
foo
bar
bar
"""
text = dedent(text)
# full list of extensions at http://pythonhosted.org/Markdown/extensions/
md_extensions = ['tables'] if table_ext else []# make with ASCII |_| tables
# render with markdown
text = markdown.markdown(text, extensions=md_extensions)
# prepend a prefix to each ->
text = re.sub(r'src="([^"]*)"', 'src="' + img_src_prefix + r'\1"', text)
return text
def filter_tags(text, metadata, filter_function, summary_function = None):
"""
Find all references to tags in the form outer_namespace.xxx.yyy[.zzz] in
the provided text, and pass them through filter_function and summary_function.
Used to linkify entry names in HMTL, javadoc output.
Args:
text: A string representing a block of text destined for output
metadata: A Metadata instance, the root of the metadata properties tree
filter_function: A Node->string function to apply to each node
when found in text; the string returned replaces the tag name in text.
summary_function: A Node list->string function that is provided the list of
unique tag nodes found in text, and which must return a string that is
then appended to the end of the text. The list is sorted alphabetically
by node name.
"""
tag_set = set()
def name_match(name):
return lambda node: node.name == name
# Match outer_namespace.x.y or outer_namespace.x.y.z, making sure
# to grab .z and not just outer_namespace.x.y. (sloppy, but since we
# check for validity, a few false positives don't hurt).
# Try to ignore items of the form {@link ...
for outer_namespace in metadata.outer_namespaces:
tag_match = r"(?string function to apply to each 'xxx#yyy',
zzz pair when found in text; the string returned replaces the tag name in text.
summary_function: A string list->string function that is provided the list of
unique targets found in text, and which must return a string that is
then appended to the end of the text. The list is sorted alphabetically
by node name.
"""
target_set = set()
def name_match(name):
return lambda node: node.name == name
tag_match = r"\{@link\s+([^\s\}\|]+)(?:\|([^\s\}]+))*([^\}]*)\}"
def filter_sub(match):
whole_match = match.group(0)
target = match.group(1)
target_ndk = match.group(2)
shortname = match.group(3).strip()
#print("Found link '%s' ndk '%s' as '%s' -> '%s'" % (target, target_ndk, shortname, filter_function(target, target_ndk, shortname)))
# Replace match with crossref
target_set.add(target)
return filter_function(target, target_ndk, shortname)
text = re.sub(tag_match, filter_sub, text)
if summary_function is not None:
return text + summary_function(sorted(target_set))
else:
return text
def any_visible(section, kind_name, visibilities):
"""
Determine if entries in this section have an applied visibility that's in
the list of given visibilities.
Args:
section: A section of metadata
kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
visibilities: An iterable of visibilities to match against
Returns:
True if the section has any entries with any of the given visibilities. False otherwise.
"""
for inner_namespace in get_children_by_filtering_kind(section, kind_name,
'namespaces'):
if any(filter_visibility(inner_namespace.merged_entries, visibilities)):
return True
return any(filter_visibility(get_children_by_filtering_kind(section, kind_name,
'merged_entries'),
visibilities))
def filter_visibility(entries, visibilities):
"""
Remove entries whose applied visibility is not in the supplied visibilities.
Args:
entries: An iterable of Entry nodes
visibilities: An iterable of visibilities to filter against
Yields:
An iterable of Entry nodes
"""
return (e for e in entries if e.applied_visibility in visibilities)
def remove_synthetic(entries):
"""
Filter the given entries by removing those that are synthetic.
Args:
entries: An iterable of Entry nodes
Yields:
An iterable of Entry nodes
"""
return (e for e in entries if not e.synthetic)
def filter_added_in_hal_version(entries, hal_major_version, hal_minor_version):
"""
Filter the given entries to those added in the given HIDL HAL version
Args:
entries: An iterable of Entry nodes
hal_major_version: Major HIDL version to filter for
hal_minor_version: Minor HIDL version to filter for
Yields:
An iterable of Entry nodes
"""
return (e for e in entries if e.hal_major_version == hal_major_version and e.hal_minor_version == hal_minor_version)
def filter_has_enum_values_added_in_hal_version(entries, hal_major_version, hal_minor_version):
"""
Filter the given entries to those that have a new enum value added in the given HIDL HAL version
Args:
entries: An iterable of Entry nodes
hal_major_version: Major HIDL version to filter for
hal_minor_version: Minor HIDL version to filter for
Yields:
An iterable of Entry nodes
"""
return (e for e in entries if e.has_new_values_added_in_hal_version(hal_major_version, hal_minor_version))
def permission_needed_count(root):
"""
Return the number entries that need camera permission.
Args:
root: a Metadata instance
Returns:
The number of entires that need camera permission.
"""
ret = 0
for sec in find_all_sections(root):
ret += len(list(filter_has_permission_needed(remove_synthetic(find_unique_entries(sec)))))
return ret
def filter_has_permission_needed(entries):
"""
Filter the given entries by removing those that don't need camera permission.
Args:
entries: An iterable of Entry nodes
Yields:
An iterable of Entry nodes
"""
return (e for e in entries if e.permission_needed == 'true')
def filter_ndk_visible(entries):
"""
Filter the given entries by removing those that are not NDK visible.
Args:
entries: An iterable of Entry nodes
Yields:
An iterable of Entry nodes
"""
return (e for e in entries if e.applied_ndk_visible == 'true')
def wbr(text):
"""
Insert word break hints for the browser in the form of HTML tags.
Word breaks are inserted inside an HTML node only, so the nodes themselves
will not be changed. Attributes are also left unchanged.
The following rules apply to insert word breaks:
- For characters in [ '.', '/', '_' ]
- For uppercase letters inside a multi-word X.Y.Z (at least 3 parts)
Args:
text: A string of text containing HTML content.
Returns:
A string with inserted by the above rules.
"""
SPLIT_CHARS_LIST = ['.', '_', '/']
SPLIT_CHARS = r'([.|/|_/,]+)' # split by these characters
CAP_LETTER_MIN = 3 # at least 3 components split by above chars, i.e. x.y.z
def wbr_filter(text):
new_txt = text
# for johnyOrange.appleCider.redGuardian also insert wbr before the caps
# => johnyOrange.appleCider.redGuardian
for words in text.split(" "):
for char in SPLIT_CHARS_LIST:
# match at least x.y.z, don't match x or x.y
if len(words.split(char)) >= CAP_LETTER_MIN:
new_word = re.sub(r"([a-z])([A-Z])", r"\1\2", words)
new_txt = new_txt.replace(words, new_word)
# e.g. X/Y/Z -> X/Y//Z. also for X.Y.Z, X_Y_Z.
new_txt = re.sub(SPLIT_CHARS, r"\1", new_txt)
return new_txt
# Do not mangle HTML when doing the replace by using BeatifulSoup
# - Use the 'html.parser' to avoid inserting when decoding
soup = bs4.BeautifulSoup(text, features='html.parser')
wbr_tag = lambda: soup.new_tag('wbr') # must generate new tag every time
for navigable_string in soup.findAll(text=True):
parent = navigable_string.parent
# Insert each '$text$foo' before the old '$text$foo'
split_by_wbr_list = wbr_filter(navigable_string).split("")
for (split_string, last) in enumerate_with_last(split_by_wbr_list):
navigable_string.insert_before(split_string)
if not last:
# Note that 'insert' will move existing tags to this spot
# so make a new tag instead
navigable_string.insert_before(wbr_tag())
# Remove the old unmodified text
navigable_string.extract()
return soup.decode()
def hal_major_version():
return _hal_major_version
def hal_minor_version():
return _hal_minor_version
def first_hal_minor_version(hal_major_version):
return 2 if hal_major_version == 3 else 0
def find_all_sections_added_in_hal(root, hal_major_version, hal_minor_version):
"""
Find all descendants that are Section or InnerNamespace instances, which
were added in HIDL HAL version major.minor. The section is defined to be
added in a HAL version iff the lowest HAL version number of its entries is
that HAL version.
Args:
root: a Metadata instance
hal_major/minor_version: HAL version numbers
Returns:
A list of Section/InnerNamespace instances
Remarks:
These are known as "sections" in the generated C code.
"""
all_sections = find_all_sections(root)
new_sections = []
for section in all_sections:
min_major_version = None
min_minor_version = None
for entry in remove_synthetic(find_unique_entries(section)):
min_major_version = (min_major_version or entry.hal_major_version)
min_minor_version = (min_minor_version or entry.hal_minor_version)
if entry.hal_major_version < min_major_version or \
(entry.hal_major_version == min_major_version and entry.hal_minor_version < min_minor_version):
min_minor_version = entry.hal_minor_version
min_major_version = entry.hal_major_version
if min_major_version == hal_major_version and min_minor_version == hal_minor_version:
new_sections.append(section)
return new_sections
def find_first_older_used_hal_version(section, hal_major_version, hal_minor_version):
hal_version = (0, 0)
for v in section.hal_versions:
if (v[0] > hal_version[0] or (v[0] == hal_version[0] and v[1] > hal_version[1])) and \
(v[0] < hal_major_version or (v[0] == hal_major_version and v[1] < hal_minor_version)):
hal_version = v
return hal_version