summaryrefslogtreecommitdiff
path: root/codegen/vulkan/scripts/reflow.py
diff options
context:
space:
mode:
authorJason Macnak <natsu@google.com>2023-05-19 14:54:16 -0700
committerJason Macnak <natsu@google.com>2023-05-22 12:03:45 -0700
commitea00ab3f8f6e353fa252dc65cce0ae505d731651 (patch)
tree97952257a570478249f2b04f346ac862949a4d38 /codegen/vulkan/scripts/reflow.py
parentf5d796021d5b76625d706cc9a3580a4aa9f75bb2 (diff)
downloadgfxstream-protocols-ea00ab3f8f6e353fa252dc65cce0ae505d731651.tar.gz
Prepare for move to hardware/google/gfxstream
Move codegen into what will become hardware/google/gfxstream/codegen/vulkan and move generated vulkan headers into what will become hardware/google/gfxstream/common/vulkan. Bug: 271464937 Test: presubmit Test: python android/build/python/cmake.py --gfxstream Change-Id: Ie0465ac72b4a2f7444b595cd1421e2e3cb1d0997
Diffstat (limited to 'codegen/vulkan/scripts/reflow.py')
-rwxr-xr-xcodegen/vulkan/scripts/reflow.py912
1 files changed, 912 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/reflow.py b/codegen/vulkan/scripts/reflow.py
new file mode 100755
index 00000000..1897b05c
--- /dev/null
+++ b/codegen/vulkan/scripts/reflow.py
@@ -0,0 +1,912 @@
+#!/usr/bin/python3
+#
+# Copyright 2016-2021 The Khronos Group Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+"""Used for automatic reflow of spec sources to satisfy the agreed layout to
+minimize git churn. Most of the logic has to do with detecting asciidoc
+markup or block types that *shouldn't* be reflowed (tables, code) and
+ignoring them. It's very likely there are many asciidoc constructs not yet
+accounted for in the script, our usage of asciidoc markup is intentionally
+somewhat limited.
+
+Also used to insert identifying tags on explicit Valid Usage statements.
+
+Usage: `reflow.py [-noflow] [-tagvu] [-nextvu #] [-overwrite] [-out dir] [-suffix str] files`
+
+- `-noflow` acts as a passthrough, instead of reflowing text. Other
+ processing may occur.
+- `-tagvu` generates explicit VUID tag for Valid Usage statements which
+ don't already have them.
+- `-nextvu #` starts VUID tag generation at the specified # instead of
+ the value wired into the `reflow.py` script.
+- `-overwrite` updates in place (can be risky, make sure there are backups)
+- `-check FAIL|WARN` runs some simple sanity checks on markup. If the checks
+ fail and the WARN option is given, the script will simply print a warning
+ message. If the checks fail and the FAIL option is given, the script will
+ exit with an error code. FAIL is for use with continuous integration
+ scripts enforcing the checks.
+- `-out` specifies directory to create output file in, default 'out'
+- `-suffix` specifies suffix to add to output files, default ''
+- `files` are asciidoc source files from the spec to reflow.
+"""
+# For error and file-loading interfaces only
+import argparse
+import os
+import re
+import sys
+from reflib import loadFile, logDiag, logWarn, logErr, setLogFile, getBranch
+
+# Vulkan-specific - will consolidate into scripts/ like OpenXR soon
+sys.path.insert(0, 'xml')
+
+from vkconventions import VulkanConventions as APIConventions
+conventions = APIConventions()
+
+# Markup that always ends a paragraph
+# empty line or whitespace
+# [block options]
+# [[anchor]]
+# // comment
+# <<<< page break
+# :attribute-setting
+# macro-directive::terms
+# + standalone list item continuation
+# label:: labelled list - label must be standalone
+endPara = re.compile(r'^( *|\[.*\]|//.*|<<<<|:.*|[a-z]+::.*|\+|.*::)$')
+
+# Special case of markup ending a paragraph, used to track the current
+# command/structure. This allows for either OpenXR or Vulkan API path
+# conventions. Nominally it should use the file suffix defined by the API
+# conventions (conventions.file_suffix), except that XR uses '.txt' for
+# generated API include files, not '.adoc' like its other includes.
+includePat = re.compile(
+ r'include::(?P<directory_traverse>((../){1,4}|\{INCS-VAR\}/|\{generated\}/)(generated/)?)(?P<generated_type>[\w]+)/(?P<category>\w+)/(?P<entity_name>[^./]+).txt[\[][\]]')
+
+# Find the first pname: or code: pattern in a Valid Usage statement
+pnamePat = re.compile(r'pname:(?P<param>\{?\w+\}?)')
+codePat = re.compile(r'code:(?P<param>\w+)')
+
+# Markup that's OK in a contiguous paragraph but otherwise passed through
+# .anything (except .., which indicates a literal block)
+# === Section Titles
+endParaContinue = re.compile(r'^(\.[^.].*|=+ .*)$')
+
+# Markup for block delimiters whose contents *should* be reformatted
+# -- (exactly two) (open block)
+# **** (4 or more) (sidebar block - why do we have these?!)
+# ==== (4 or more) (example block)
+# ____ (4 or more) (quote block)
+blockReflow = re.compile(r'^(--|[*=_]{4,})$')
+
+# Fake block delimiters for "common" VU statements
+blockCommonReflow = '// Common Valid Usage\n'
+
+# Markup for block delimiters whose contents should *not* be reformatted
+# |=== (3 or more) (table)
+# ++++ (4 or more) (passthrough block)
+# .... (4 or more) (literal block)
+# //// (4 or more) (comment block)
+# ---- (4 or more) (listing block)
+# ``` (3 or more) (listing block)
+# **** (4 or more) (sidebar block)
+blockPassthrough = re.compile(r'^(\|={3,}|[`]{3}|[\-+./~]{4,})$')
+
+# Markup for introducing lists (hanging paragraphs)
+# * bullet
+# ** bullet
+# -- bullet
+# . bullet
+# :: bullet (no longer supported by asciidoctor 2)
+# {empty}:: bullet
+# 1. list item
+beginBullet = re.compile(r'^ *([*\-.]+|\{empty\}::|::|[0-9]+[.]) ')
+
+# Start of an asciidoctor conditional
+# ifdef::
+# ifndef::
+conditionalStart = re.compile(r'^(ifdef|ifndef)::')
+
+# Text that (may) not end sentences
+
+# A single letter followed by a period, typically a middle initial.
+endInitial = re.compile(r'^[A-Z]\.$')
+# An abbreviation, which doesn't (usually) end a line.
+endAbbrev = re.compile(r'(e\.g|i\.e|c\.f|vs)\.$', re.IGNORECASE)
+
+class ReflowState:
+ """State machine for reflowing.
+
+ Represents the state of the reflow operation"""
+ def __init__(self,
+ filename,
+ margin = 76,
+ file = sys.stdout,
+ breakPeriod = True,
+ reflow = True,
+ nextvu = None,
+ maxvu = None):
+
+ self.blockStack = [ None ]
+ """The last element is a line with the asciidoc block delimiter that's currently in effect,
+ such as '--', '----', '****', '======', or '+++++++++'.
+ This affects whether or not the block contents should be formatted."""
+
+ self.reflowStack = [ True ]
+ """The last element is True or False if the current blockStack contents
+ should be reflowed."""
+ self.vuStack = [ False ]
+ """the last element is True or False if the current blockStack contents
+ are an explicit Valid Usage block."""
+
+ self.margin = margin
+ """margin to reflow text to."""
+
+ self.para = []
+ """list of lines in the paragraph being accumulated.
+ When this is non-empty, there is a current paragraph."""
+
+ self.lastTitle = False
+ """true if the previous line was a document title line
+ (e.g. :leveloffset: 0 - no attempt to track changes to this is made)."""
+
+ self.leadIndent = 0
+ """indent level (in spaces) of the first line of a paragraph."""
+
+ self.hangIndent = 0
+ """indent level of the remaining lines of a paragraph."""
+
+ self.file = file
+ """file handle to write to."""
+
+ self.filename = filename
+ """base name of file being read from."""
+
+ self.lineNumber = 0
+ """line number being read from the input file."""
+
+ self.breakPeriod = breakPeriod
+ """True if justification should break to a new line after the end of a sentence."""
+
+ self.breakInitial = True
+ """True if justification should break to a new line after
+ something that appears to be an initial in someone's name. **TBD**"""
+
+ self.reflow = reflow
+ """True if text should be reflowed, False to pass through unchanged."""
+
+ self.vuPrefix = 'VUID'
+ """Prefix of generated Valid Usage tags"""
+
+ self.vuFormat = '{0}-{1}-{2}-{3:0>5d}'
+ """Format string for generating Valid Usage tags.
+ First argument is vuPrefix, second is command/struct name, third is parameter name, fourth is the tag number."""
+
+ self.nextvu = nextvu
+ """Integer to start tagging un-numbered Valid Usage statements with,
+ or None if no tagging should be done."""
+
+ self.maxvu = maxvu
+ """Maximum tag to use for Valid Usage statements, or None if no
+ tagging should be done."""
+
+ self.defaultApiName = '{refpage}'
+ self.apiName = self.defaultApiName
+ """String name of a Vulkan structure or command for VUID tag
+ generation, or {refpage} if one hasn't been included in this file
+ yet."""
+
+ def incrLineNumber(self):
+ self.lineNumber = self.lineNumber + 1
+
+ def printLines(self, lines):
+ """Print an array of lines with newlines already present"""
+ if len(lines) > 0:
+ logDiag(':: printLines:', len(lines), 'lines: ', lines[0], end='')
+
+ if self.file is not None:
+ for line in lines:
+ print(line, file=self.file, end='')
+
+ def endSentence(self, word):
+ """Return True if word ends with a sentence-period, False otherwise.
+
+ Allows for contraction cases which won't end a line:
+
+ - A single letter (if breakInitial is True)
+ - Abbreviations: 'c.f.', 'e.g.', 'i.e.' (or mixed-case versions)"""
+ if (word[-1:] != '.' or
+ endAbbrev.search(word) or
+ (self.breakInitial and endInitial.match(word))):
+ return False
+
+ return True
+
+ def vuidAnchor(self, word):
+ """Return True if word is a Valid Usage ID Tag anchor."""
+ return (word[0:7] == '[[VUID-')
+
+ def isOpenBlockDelimiter(self, line):
+ """Returns True if line is an open block delimiter."""
+ return line[0:2] == '--'
+
+ def reflowPara(self):
+ """Reflow the current paragraph, respecting the paragraph lead and
+ hanging indentation levels.
+
+ The algorithm also respects trailing '+' signs that indicate embedded newlines,
+ and will not reflow a very long word immediately after a bullet point.
+
+ Just return the paragraph unchanged if the -noflow argument was
+ given."""
+ if not self.reflow:
+ return self.para
+
+ logDiag('reflowPara lead indent = ', self.leadIndent,
+ 'hangIndent =', self.hangIndent,
+ 'para:', self.para[0], end='')
+
+ # Total words processed (we care about the *first* word vs. others)
+ wordCount = 0
+
+ # Tracks the *previous* word processed. It must not be empty.
+ prevWord = ' '
+
+ # Track the previous line and paragraph being indented, if any
+ outLine = None
+ outPara = []
+
+ for line in self.para:
+ line = line.rstrip()
+ words = line.split()
+
+ # logDiag('reflowPara: input line =', line)
+ numWords = len(words) - 1
+
+ for i in range(0, numWords + 1):
+ word = words[i]
+ wordLen = len(word)
+ wordCount += 1
+
+ endEscape = False
+ if i == numWords and word == '+':
+ # Trailing ' +' must stay on the same line
+ endEscape = word
+ # logDiag('reflowPara last word of line =', word, 'prevWord =', prevWord, 'endEscape =', endEscape)
+ else:
+ pass
+ # logDiag('reflowPara wordCount =', wordCount, 'word =', word, 'prevWord =', prevWord)
+
+ if wordCount == 1:
+ # The first word of the paragraph is treated specially.
+ # The loop logic becomes trickier if all this code is
+ # done prior to looping over lines and words, so all the
+ # setup logic is done here.
+
+ outPara = []
+ outLine = ''.ljust(self.leadIndent) + word
+ outLineLen = self.leadIndent + wordLen
+
+ # If the paragraph begins with a bullet point, generate
+ # a hanging indent level if there isn't one already.
+ if beginBullet.match(self.para[0]):
+ bulletPoint = True
+ if len(self.para) > 1:
+ logDiag('reflowPara first line matches bullet point',
+ 'but indent already hanging @ input line',
+ self.lineNumber)
+ else:
+ logDiag('reflowPara first line matches bullet point -'
+ 'single line, assuming hangIndent @ input line',
+ self.lineNumber)
+ self.hangIndent = outLineLen + 1
+ else:
+ bulletPoint = False
+ else:
+ # Possible actions to take with this word
+ #
+ # addWord - add word to current line
+ # closeLine - append line and start a new (null) one
+ # startLine - add word to a new line
+
+ # Default behavior if all the tests below fail is to add
+ # this word to the current line, and keep accumulating
+ # that line.
+ (addWord, closeLine, startLine) = (True, False, False)
+
+ # How long would this line be if the word were added?
+ newLen = outLineLen + 1 + wordLen
+
+ # Are we on the first word following a bullet point?
+ firstBullet = (wordCount == 2 and bulletPoint)
+
+ if endEscape:
+ # If the new word ends the input line with ' +',
+ # add it to the current line.
+
+ (addWord, closeLine, startLine) = (True, True, False)
+ elif self.vuidAnchor(word):
+ # If the new word is a Valid Usage anchor, break the
+ # line afterwards. Note that this should only happen
+ # immediately after a bullet point, but we don't
+ # currently check for this.
+ (addWord, closeLine, startLine) = (True, True, False)
+ elif newLen > self.margin:
+ if firstBullet:
+ # If the word follows a bullet point, add it to
+ # the current line no matter its length.
+
+ (addWord, closeLine, startLine) = (True, True, False)
+ elif beginBullet.match(word + ' '):
+ # If the word *is* a bullet point, add it to
+ # the current line no matter its length.
+ # This avoids an innocent inline '-' or '*'
+ # turning into a bogus bullet point.
+
+ (addWord, closeLine, startLine) = (True, True, False)
+ else:
+ # The word overflows, so add it to a new line.
+
+ (addWord, closeLine, startLine) = (False, True, True)
+ elif (self.breakPeriod and
+ (wordCount > 2 or not firstBullet) and
+ self.endSentence(prevWord)):
+ # If the previous word ends a sentence and
+ # breakPeriod is set, start a new line.
+ # The complicated logic allows for leading bullet
+ # points which are periods (implicitly numbered lists).
+ # @@@ But not yet for explicitly numbered lists.
+
+ (addWord, closeLine, startLine) = (False, True, True)
+
+ # Add a word to the current line
+ if addWord:
+ if outLine:
+ outLine += ' ' + word
+ outLineLen = newLen
+ else:
+ # Fall through to startLine case if there's no
+ # current line yet.
+ startLine = True
+
+ # Add current line to the output paragraph. Force
+ # starting a new line, although we don't yet know if it
+ # will ever have contents.
+ if closeLine:
+ if outLine:
+ outPara.append(outLine + '\n')
+ outLine = None
+
+ # Start a new line and add a word to it
+ if startLine:
+ outLine = ''.ljust(self.hangIndent) + word
+ outLineLen = self.hangIndent + wordLen
+
+ # Track the previous word, for use in breaking at end of
+ # a sentence
+ prevWord = word
+
+ # Add this line to the output paragraph.
+ if outLine:
+ outPara.append(outLine + '\n')
+
+ return outPara
+
+ def emitPara(self):
+ """Emit a paragraph, possibly reflowing it depending on the block context.
+
+ Resets the paragraph accumulator."""
+ if self.para != []:
+ if self.vuStack[-1] and self.nextvu is not None:
+ # If:
+ # - this paragraph is in a Valid Usage block,
+ # - VUID tags are being assigned,
+ # Try to assign VUIDs
+
+ if nestedVuPat.search(self.para[0]):
+ # Check for nested bullet points. These should not be
+ # assigned VUIDs, nor present at all, because they break
+ # the VU extractor.
+ logWarn(self.filename + ': Invalid nested bullet point in VU block:', self.para[0])
+ elif self.vuPrefix not in self.para[0]:
+ # If:
+ # - a tag is not already present, and
+ # - the paragraph is a properly marked-up list item
+ # Then add a VUID tag starting with the next free ID.
+
+ # Split the first line after the bullet point
+ matches = vuPat.search(self.para[0])
+ if matches is not None:
+ logDiag('findRefs: Matched vuPat on line:', self.para[0], end='')
+ head = matches.group('head')
+ tail = matches.group('tail')
+
+ # Use the first pname: or code: tag in the paragraph as
+ # the parameter name in the VUID tag. This won't always
+ # be correct, but should be highly reliable.
+ for vuLine in self.para:
+ matches = pnamePat.search(vuLine)
+ if matches is not None:
+ break
+ matches = codePat.search(vuLine)
+ if matches is not None:
+ break
+
+ if matches is not None:
+ paramName = matches.group('param')
+ else:
+ paramName = 'None'
+ logWarn(self.filename,
+ 'No param name found for VUID tag on line:',
+ self.para[0])
+
+ newline = (head + ' [[' +
+ self.vuFormat.format(self.vuPrefix,
+ self.apiName,
+ paramName,
+ self.nextvu) + ']] ' + tail)
+
+ logDiag('Assigning', self.vuPrefix, self.apiName, self.nextvu,
+ ' on line:', self.para[0], '->', newline, 'END')
+
+ # Don't actually assign the VUID unless it's in the reserved range
+ if self.nextvu <= self.maxvu:
+ if self.nextvu == self.maxvu:
+ logWarn('Skipping VUID assignment, no more VUIDs available')
+ self.para[0] = newline
+ self.nextvu = self.nextvu + 1
+ # else:
+ # There are only a few cases of this, and they're all
+ # legitimate. Leave detecting this case to another tool
+ # or hand inspection.
+ # logWarn(self.filename + ': Unexpected non-bullet item in VU block (harmless if following an ifdef):',
+ # self.para[0])
+
+ if self.reflowStack[-1]:
+ self.printLines(self.reflowPara())
+ else:
+ self.printLines(self.para)
+
+ # Reset the paragraph, including its indentation level
+ self.para = []
+ self.leadIndent = 0
+ self.hangIndent = 0
+
+ def endPara(self, line):
+ """'line' ends a paragraph and should itself be emitted.
+ line may be None to indicate EOF or other exception."""
+ logDiag('endPara line', self.lineNumber, ': emitting paragraph')
+
+ # Emit current paragraph, this line, and reset tracker
+ self.emitPara()
+
+ if line:
+ self.printLines( [ line ] )
+
+ def endParaContinue(self, line):
+ """'line' ends a paragraph (unless there's already a paragraph being
+ accumulated, e.g. len(para) > 0 - currently not implemented)"""
+ self.endPara(line)
+
+ def endBlock(self, line, reflow = False, vuBlock = False):
+ """'line' begins or ends a block.
+
+ If beginning a block, tag whether or not to reflow the contents.
+
+ vuBlock is True if the previous line indicates this is a Valid Usage block."""
+ self.endPara(line)
+
+ if self.blockStack[-1] == line:
+ logDiag('endBlock line', self.lineNumber,
+ ': popping block end depth:', len(self.blockStack),
+ ':', line, end='')
+
+ # Reset apiName at the end of an open block.
+ # Open blocks cannot be nested (at present), so this is safe.
+ if self.isOpenBlockDelimiter(line):
+ logDiag('reset apiName to empty at line', self.lineNumber)
+ self.apiName = self.defaultApiName
+ else:
+ logDiag('NOT resetting apiName to default at line', self.lineNumber)
+
+ self.blockStack.pop()
+ self.reflowStack.pop()
+ self.vuStack.pop()
+ else:
+ # Start a block
+ self.blockStack.append(line)
+ self.reflowStack.append(reflow)
+ self.vuStack.append(vuBlock)
+
+ logDiag('endBlock reflow =', reflow, ' line', self.lineNumber,
+ ': pushing block start depth', len(self.blockStack),
+ ':', line, end='')
+
+ def endParaBlockReflow(self, line, vuBlock):
+ """'line' begins or ends a block. The paragraphs in the block *should* be
+ reformatted (e.g. a NOTE)."""
+ self.endBlock(line, reflow = True, vuBlock = vuBlock)
+
+ def endParaBlockPassthrough(self, line):
+ """'line' begins or ends a block. The paragraphs in the block should
+ *not* be reformatted (e.g. a code listing)."""
+ self.endBlock(line, reflow = False)
+
+ def addLine(self, line):
+ """'line' starts or continues a paragraph.
+
+ Paragraphs may have "hanging indent", e.g.
+
+ ```
+ * Bullet point...
+ ... continued
+ ```
+
+ In this case, when the higher indentation level ends, so does the
+ paragraph."""
+ logDiag('addLine line', self.lineNumber, ':', line, end='')
+
+ # See https://stackoverflow.com/questions/13648813/what-is-the-pythonic-way-to-count-the-leading-spaces-in-a-string
+ indent = len(line) - len(line.lstrip())
+
+ # A hanging paragraph ends due to a less-indented line.
+ if self.para != [] and indent < self.hangIndent:
+ logDiag('addLine: line reduces indentation, emit paragraph')
+ self.emitPara()
+
+ # A bullet point (or something that looks like one) always ends the
+ # current paragraph.
+ if beginBullet.match(line):
+ logDiag('addLine: line matches beginBullet, emit paragraph')
+ self.emitPara()
+
+ if self.para == []:
+ # Begin a new paragraph
+ self.para = [ line ]
+ self.leadIndent = indent
+ self.hangIndent = indent
+ else:
+ # Add a line to a paragraph. Increase the hanging indentation
+ # level - once.
+ if self.hangIndent == self.leadIndent:
+ self.hangIndent = indent
+ self.para.append(line)
+
+def apiMatch(oldname, newname):
+ """Returns whether oldname and newname match, up to an API suffix.
+ This should use the API map instead of this heuristic, since aliases
+ like VkPhysicalDeviceVariablePointerFeatures ->
+ VkPhysicalDeviceVariablePointersFeatures are not recognized."""
+ upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ return oldname.rstrip(upper) == newname.rstrip(upper)
+
+def reflowFile(filename, args):
+ logDiag('reflow: filename', filename)
+
+ lines = loadFile(filename)
+ if lines is None:
+ return
+
+ # Output file handle and reflow object for this file. There are no race
+ # conditions on overwriting the input, but it's not recommended unless
+ # you have backing store such as git.
+
+ if args.overwrite:
+ outFilename = filename
+ else:
+ outFilename = args.outDir + '/' + os.path.basename(filename) + args.suffix
+
+ if args.nowrite:
+ fp = None
+ else:
+ try:
+ fp = open(outFilename, 'w', encoding='utf8')
+ except:
+ logWarn('Cannot open output file', outFilename, ':', sys.exc_info()[0])
+ return
+
+ state = ReflowState(filename,
+ margin = args.margin,
+ file = fp,
+ reflow = not args.noflow,
+ nextvu = args.nextvu,
+ maxvu = args.maxvu)
+
+ for line in lines:
+ state.incrLineNumber()
+
+ # Is this a title line (leading '= ' followed by text)?
+ thisTitle = False
+
+ matches = vuidPat.search(line)
+ if matches is not None:
+ # If we found a VUID pattern, add the (filename,line) it was
+ # found at to a list for that VUID, to find duplicates.
+ vuid = matches.group('vuid')
+ if vuid not in args.vuidDict:
+ args.vuidDict[vuid] = []
+ args.vuidDict[vuid].append([filename, line])
+
+ # The logic here is broken. If we're in a non-reflowable block and
+ # this line *doesn't* end the block, it should always be
+ # accumulated.
+
+ # Test for a blockCommonReflow delimiter comment first, to avoid
+ # treating it solely as a end-Paragraph marker comment.
+ if line == blockCommonReflow:
+ # Starting or ending a pseudo-block for "common" VU statements.
+ state.endParaBlockReflow(line, vuBlock = True)
+
+ elif blockReflow.match(line):
+ # Starting or ending a block whose contents may be reflowed.
+ # Blocks cannot be nested.
+
+ # Is this is an explicit Valid Usage block?
+ vuBlock = (state.lineNumber > 1 and
+ lines[state.lineNumber-2] == '.Valid Usage\n')
+
+ state.endParaBlockReflow(line, vuBlock)
+
+ elif endPara.match(line):
+ # Ending a paragraph. Emit the current paragraph, if any, and
+ # prepare to begin a new paragraph.
+
+ state.endPara(line)
+
+ # If this is an include:: line starting the definition of a
+ # structure or command, track that for use in VUID generation.
+
+ matches = includePat.search(line)
+ if matches is not None:
+ generated_type = matches.group('generated_type')
+ include_type = matches.group('category')
+ if generated_type == 'api' and include_type in ('protos', 'structs', 'funcpointers'):
+ apiName = matches.group('entity_name')
+ if state.apiName != state.defaultApiName:
+ # This happens when there are multiple API include
+ # lines in a single block. The style guideline is to
+ # always place the API which others are promoted to
+ # first. In virtually all cases, the promoted API
+ # will differ solely in the vendor suffix (or
+ # absence of it), which is benign.
+ if not apiMatch(state.apiName, apiName):
+ logDiag(f'Promoted API name mismatch at line {state.lineNumber}: {apiName} does not match state.apiName (this is OK if it is just a spelling alias)')
+ else:
+ state.apiName = apiName
+
+ elif endParaContinue.match(line):
+ # For now, always just end the paragraph.
+ # Could check see if len(para) > 0 to accumulate.
+
+ state.endParaContinue(line)
+
+ # If it's a title line, track that
+ if line[0:2] == '= ':
+ thisTitle = True
+
+ elif blockPassthrough.match(line):
+ # Starting or ending a block whose contents must not be reflowed.
+ # These are tables, etc. Blocks cannot be nested.
+
+ state.endParaBlockPassthrough(line)
+ elif state.lastTitle:
+ # The previous line was a document title line. This line
+ # is the author / credits line and must not be reflowed.
+
+ state.endPara(line)
+ else:
+ # Just accumulate a line to the current paragraph. Watch out for
+ # hanging indents / bullet-points and track that indent level.
+
+ state.addLine(line)
+
+ # This test looks for disallowed conditionals inside Valid Usage
+ # blocks, by checking if (a) this line does not start a new VU
+ # (bullet point) and (b) the previous line starts an asciidoctor
+ # conditional (ifdef:: or ifndef::).
+
+ if (args.check
+ and state.vuStack[-1]
+ and not beginBullet.match(line)
+ and conditionalStart.match(lines[state.lineNumber-2])):
+
+ logWarn('Detected embedded Valid Usage conditional: {}:{}'.format(
+ filename, state.lineNumber - 1))
+ # Keep track of warning check count
+ args.warnCount = args.warnCount + 1
+
+ state.lastTitle = thisTitle
+
+ # Cleanup at end of file
+ state.endPara(None)
+
+ # Check for sensible block nesting
+ if len(state.blockStack) > 1:
+ logWarn('file', filename,
+ 'mismatched asciidoc block delimiters at EOF:',
+ state.blockStack[-1])
+
+ if fp is not None:
+ fp.close()
+
+ # Update the 'nextvu' value
+ if args.nextvu != state.nextvu:
+ logWarn('Updated nextvu to', state.nextvu, 'after file', filename)
+ args.nextvu = state.nextvu
+
+def reflowAllAdocFiles(folder_to_reflow, args):
+ for root, subdirs, files in os.walk(folder_to_reflow):
+ for file in files:
+ if file.endswith(conventions.file_suffix):
+ file_path = os.path.join(root, file)
+ reflowFile(file_path, args)
+ for subdir in subdirs:
+ sub_folder = os.path.join(root, subdir)
+ print('Sub-folder = %s' % sub_folder)
+ if subdir.lower() not in conventions.spec_no_reflow_dirs:
+ print(' Parsing = %s' % sub_folder)
+ reflowAllAdocFiles(sub_folder, args)
+ else:
+ print(' Skipping = %s' % sub_folder)
+
+# Patterns used to recognize interesting lines in an asciidoc source file.
+# These patterns are only compiled once.
+
+# Explicit Valid Usage list item with one or more leading asterisks
+# The re.DOTALL is needed to prevent vuPat.search() from stripping
+# the trailing newline.
+vuPat = re.compile(r'^(?P<head> [*]+)( *)(?P<tail>.*)', re.DOTALL)
+
+# VUID with the numeric portion captured in the match object
+vuidPat = re.compile(r'VUID-[^-]+-[^-]+-(?P<vuid>[0-9]+)')
+
+# Pattern matching leading nested bullet points
+global nestedVuPat
+nestedVuPat = re.compile(r'^ \*\*')
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('-diag', action='store', dest='diagFile',
+ help='Set the diagnostic file')
+ parser.add_argument('-warn', action='store', dest='warnFile',
+ help='Set the warning file')
+ parser.add_argument('-log', action='store', dest='logFile',
+ help='Set the log file for both diagnostics and warnings')
+ parser.add_argument('-overwrite', action='store_true',
+ help='Overwrite input filenames instead of writing different output filenames')
+ parser.add_argument('-out', action='store', dest='outDir',
+ default='out',
+ help='Set the output directory in which updated files are generated (default: out)')
+ parser.add_argument('-nowrite', action='store_true',
+ help='Do not write output files, for use with -check')
+ parser.add_argument('-check', action='store', dest='check',
+ help='Run markup checks and warn if WARN option is given, error exit if FAIL option is given')
+ parser.add_argument('-checkVUID', action='store', dest='checkVUID',
+ help='Detect duplicated VUID numbers and warn if WARN option is given, error exit if FAIL option is given')
+ parser.add_argument('-tagvu', action='store_true',
+ help='Tag un-tagged Valid Usage statements starting at the value wired into reflow.py')
+ parser.add_argument('-nextvu', action='store', dest='nextvu', type=int,
+ default=None,
+ help='Specify start VUID to use instead of the value wired into vuidCounts.py')
+ parser.add_argument('-maxvu', action='store', dest='maxvu', type=int,
+ default=None,
+ help='Specify maximum VUID instead of the value wired into vuidCounts.py')
+ parser.add_argument('-branch', action='store', dest='branch',
+ help='Specify branch to assign VUIDs for')
+ parser.add_argument('-noflow', action='store_true', dest='noflow',
+ help='Do not reflow text. Other actions may apply')
+ parser.add_argument('-margin', action='store', type=int, dest='margin',
+ default='76',
+ help='Width to reflow text, defaults to 76 characters')
+ parser.add_argument('-suffix', action='store', dest='suffix',
+ default='',
+ help='Set the suffix added to updated file names (default: none)')
+ parser.add_argument('files', metavar='filename', nargs='*',
+ help='a filename to reflow text in')
+ parser.add_argument('--version', action='version', version='%(prog)s 1.0')
+
+ args = parser.parse_args()
+
+ setLogFile(True, True, args.logFile)
+ setLogFile(True, False, args.diagFile)
+ setLogFile(False, True, args.warnFile)
+
+ print('args.margin = ', args.margin)
+
+ if args.overwrite:
+ logWarn("reflow.py: will overwrite all input files")
+
+ errors = ''
+ if args.branch is None:
+ (args.branch, errors) = getBranch()
+ if args.branch is None:
+ # This is not fatal unless VUID assignment is required
+ if args.tagvu:
+ logErr('Cannot determine current git branch, so cannot assign VUIDs:', errors)
+
+ if args.tagvu and args.nextvu is None:
+ # Moved here since vuidCounts is only needed in the internal
+ # repository
+ from vuidCounts import vuidCounts
+
+ if args.branch not in vuidCounts:
+ logErr('Branch', args.branch, 'not in vuidCounts, cannot continue')
+ maxVUID = vuidCounts[args.branch][1]
+ startVUID = vuidCounts[args.branch][2]
+ args.nextvu = startVUID
+ args.maxvu = maxVUID
+
+ if args.nextvu is not None:
+ logWarn('Tagging untagged Valid Usage statements starting at', args.nextvu)
+
+ # Count of markup check warnings encountered
+ # This is added to the argparse structure
+ args.warnCount = 0
+
+ # Dictionary of VUID numbers found, containing a list of (file, line) on
+ # which that number was found
+ # This is added to the argparse structure
+ args.vuidDict = {}
+
+ # If no files are specified, reflow the entire specification chapters folder
+ if not args.files:
+ folder_to_reflow = conventions.spec_reflow_path
+ logWarn('Reflowing all asciidoc files under', folder_to_reflow)
+ reflowAllAdocFiles(folder_to_reflow, args)
+ else:
+ for file in args.files:
+ reflowFile(file, args)
+
+ if args.warnCount > 0:
+ if args.check == 'FAIL':
+ logErr('Failed with', args.warnCount, 'markup errors detected.\n' +
+ 'To fix these, you can take actions such as:\n' +
+ ' * Moving conditionals outside VU start / end without changing VU meaning\n' +
+ ' * Refactor conditional text using terminology defined conditionally outside the VU itself\n' +
+ ' * Remove the conditional (allowable when this just affects command / structure / enum names)\n')
+ else:
+ logWarn('Total warning count for markup issues is', args.warnCount)
+
+ # Look for duplicated VUID numbers
+ if args.checkVUID:
+ dupVUIDs = 0
+ for vuid in sorted(args.vuidDict):
+ found = args.vuidDict[vuid]
+ if len(found) > 1:
+ logWarn('Duplicate VUID number {} found in files:'.format(vuid))
+ for (file, line) in found:
+ logWarn(' {}: {}'.format(file, line))
+ dupVUIDs = dupVUIDs + 1
+
+ if dupVUIDs > 0:
+ if args.checkVUID == 'FAIL':
+ logErr('Failed with', dupVUIDs, 'duplicated VUID numbers found.\n' +
+ 'To fix this, either convert these to commonvalidity VUs if possible, or strip\n' +
+ 'the VUIDs from all but one of the duplicates and regenerate new ones.')
+ else:
+ logWarn('Total number of duplicated VUID numbers is', dupVUIDs)
+
+ if args.nextvu is not None and args.nextvu != startVUID:
+ # Update next free VUID to assign
+ vuidCounts[args.branch][2] = args.nextvu
+ try:
+ reflow_count_file_path = os.path.dirname(os.path.realpath(__file__))
+ reflow_count_file_path += '/vuidCounts.py'
+ reflow_count_file = open(reflow_count_file_path, 'w', encoding='utf8')
+ print('# Do not edit this file!', file=reflow_count_file)
+ print('# VUID ranges reserved for branches', file=reflow_count_file)
+ print('# Key is branch name, value is [ start, end, nextfree ]', file=reflow_count_file)
+ print('vuidCounts = {', file=reflow_count_file)
+ for key in sorted(vuidCounts):
+ print(" '{}': [ {}, {}, {} ],".format(
+ key,
+ vuidCounts[key][0],
+ vuidCounts[key][1],
+ vuidCounts[key][2]),
+ file=reflow_count_file)
+ print('}', file=reflow_count_file)
+ reflow_count_file.close()
+ except:
+ logWarn('Cannot open output count file vuidCounts.py', ':', sys.exc_info()[0])