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) # 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 == '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_see_filter(node_set): node_set = (x for x in node_set if x.applied_visibility == '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_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 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=""):
"""
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'] # make with ASCII |_| tables
# render with markdown
text = markdown.markdown(text, 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)
for outer_namespace in metadata.outer_namespaces:
tag_match = outer_namespace.name + \
r"\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)(\.[a-zA-Z0-9\n]+)?([/]?)"
def filter_sub(match):
whole_match = match.group(0)
section1 = match.group(1)
section2 = match.group(2)
section3 = match.group(3)
end_slash = match.group(4)
# Don't linkify things ending in slash (urls, for example)
if end_slash:
return whole_match
candidate = ""
# First try a two-level match
candidate2 = "%s.%s.%s" % (outer_namespace.name, section1, section2)
got_two_level = False
node = metadata.find_first(name_match(candidate2.replace('\n','')))
if not node and '\n' in section2:
# Linefeeds are ambiguous - was the intent to add a space,
# or continue a lengthy name? Try the former now.
candidate2b = "%s.%s.%s" % (outer_namespace.name, section1, section2[:section2.find('\n')])
node = metadata.find_first(name_match(candidate2b))
if node:
candidate2 = candidate2b
if node:
# Have two-level match
got_two_level = True
candidate = candidate2
elif section3:
# Try three-level match
candidate3 = "%s%s" % (candidate2, section3)
node = metadata.find_first(name_match(candidate3.replace('\n','')))
if not node and '\n' in section3:
# Linefeeds are ambiguous - was the intent to add a space,
# or continue a lengthy name? Try the former now.
candidate3b = "%s%s" % (candidate2, section3[:section3.find('\n')])
node = metadata.find_first(name_match(candidate3b))
if node:
candidate3 = candidate3b
if node:
# Have 3-level match
candidate = candidate3
# Replace match with crossref or complain if a likely match couldn't be matched
if node:
tag_set.add(node)
return whole_match.replace(candidate,filter_function(node))
else:
print >> sys.stderr,\
" WARNING: Could not crossref likely reference {%s}" % (match.group(0))
return whole_match
text = re.sub(tag_match, filter_sub, text)
if summary_function is not None:
return text + summary_function(sorted(tag_set, key=lambda x: x.name))
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 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()