Skip to content

Commit

Permalink
added config sources
Browse files Browse the repository at this point in the history
also:
- added config `recursive_sources`
- added internal config schema validation
- added support for doxygen's `@INCLUDE_PATHS`
- added config `strip_paths`
- added config `strip_includes`
- added config `extract_all`
- added config `internal_docs`
- added more internal doxygen overrides
- renamed config `meta` to `meta_tags`
- improved dead link detection
- minor style fixes
  • Loading branch information
marzer committed Apr 25, 2021
1 parent e471b33 commit 408df59
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 312 deletions.
6 changes: 3 additions & 3 deletions dox/data/dox.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pre.m-console a:hover
}
}

@media screen and (max-width: 575px)
@media screen and (max-width: 576px)
{
nav .m-thin, nav .github
{
Expand Down Expand Up @@ -277,7 +277,7 @@ pre.m-code + pre.m-console span
display: none;
}
}
@media screen and (max-width: 991px)
@media screen and (max-width: 992px)
{
.gh-badges
{
Expand Down Expand Up @@ -353,7 +353,7 @@ pre > p.godbolt
{
float: right;
}
@media screen and (max-width: 767px)
@media screen and (max-width: 768px)
{
.godbolt
{
Expand Down
59 changes: 26 additions & 33 deletions dox/doxygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ def _format_for_doxyfile(val):

class Doxyfile(object):

__include = re.compile(r'^\s*@INCLUDE\s*=\s*(.+?)\s*$', re.M)

def __init__(self, doxyfile_path, cwd=None, logger=None):
self.__logger=logger
self.__dirty=True
Expand All @@ -89,12 +87,16 @@ def __init__(self, doxyfile_path, cwd=None, logger=None):
self.__cwd = Path.cwd() if cwd is None else coerce_path(cwd).resolve()
assert_existing_directory(self.__cwd)

# read in doxyfile (or generate one)
self.__text = ''

# read in doxyfile
if self.path.exists():
if not self.path.is_file():
raise Exception(f'{self.path} was not a file')
self.__text = read_all_text_from_file(self.path, logger=self.__logger).strip()
self.cleanup() # expands includes

# ...or generate one
else:
log(self.__logger, rf'Warning: doxyfile {self.path} not found! A default one will be generated in-memory.', level=logging.WARNING)
result = subprocess.run(
Expand All @@ -106,37 +108,25 @@ def __init__(self, doxyfile_path, cwd=None, logger=None):
)
self.__text = result.stdout.strip()

# expand includes
m = self.__include.search(self.__text)
while m:
inc = m[1].strip(' "')
sub = ''
if inc:
inc = Path(inc)
if not inc.is_absolute():
inc = Path(self.__cwd, inc)
inc = inc.resolve()
sub = f'\n\n{read_all_text_from_file(inc, logger=self.__logger).strip()}\n\n'
self.__text = self.__text[:m.start()].strip() + sub + self.__text[m.end():].strip()
m = self.__include.search(self.__text)

# simplify regex searches by ensuring there's always leading and trailing newlines
self.__text = f'\n{self.__text}\n'

def cleanup(self):
if not self.__dirty:
return
log(self.__logger, rf'Invoking doxygen to clean doxyfile')
result = subprocess.run(
r'doxygen -s -u -'.split(),
check=True,
capture_output=True,
cwd=self.__cwd,
encoding='utf-8',
input=self.__text
)
if 1:
log(self.__logger, rf'Invoking doxygen to clean doxyfile')
result = subprocess.run(
r'doxygen -s -u -'.split(),
check=True,
capture_output=True,
cwd=self.__cwd,
encoding='utf-8',
input=self.__text
)
self.__text = result.stdout.strip()
self.__dirty = False
self.__text = result.stdout.strip()

def flush(self):
self.cleanup()
Expand Down Expand Up @@ -192,13 +182,16 @@ def add_value(self, key, value=None):

def set_value(self, key, value=None):
if isinstance(value, (list, tuple, set)):
first = True
for v in value:
if first:
self.append(rf'{key:<23}= {_format_for_doxyfile(v)}')
else:
self.add_value(key, v)
first = False
if not value:
self.append(rf'{key:<23}=')
else:
first = True
for v in value:
if first:
self.append(rf'{key:<23}= {_format_for_doxyfile(v)}')
else:
self.add_value(key, v)
first = False
else:
self.append(rf'{key:<23}= {_format_for_doxyfile(value)}')
self.__dirty = True
Expand Down
126 changes: 73 additions & 53 deletions dox/fixers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# custom tags
#=======================================================================================================================

class CustomTagsFix(object):
class CustomTags(object):
'''
Modifies HTML using custom square-bracket [tags].
'''
Expand Down Expand Up @@ -132,7 +132,7 @@ def __call__(self, doc, context):
# C++
#=======================================================================================================================

class _ModifiersFixBase(object):
class _ModifiersBase(object):
'''
Base type for modifier parsing fixers.
'''
Expand All @@ -152,11 +152,11 @@ class _ModifiersFixBase(object):



class ModifiersFix1(_ModifiersFixBase):
class Modifiers1(_ModifiersBase):
'''
Fixes improperly-parsed modifiers on function signatures in the various 'detail view' sections.
'''
__expression = re.compile(rf'(\s+)({_ModifiersFixBase._modifierRegex})(\s+)')
__expression = re.compile(rf'(\s+)({_ModifiersBase._modifierRegex})(\s+)')
__sections = ('pub-static-methods', 'pub-methods', 'friends', 'func-members')

@classmethod
Expand All @@ -178,11 +178,11 @@ def __call__(self, doc, context):



class ModifiersFix2(_ModifiersFixBase):
class Modifiers2(_ModifiersBase):
'''
Fixes improperly-parsed modifiers on function signatures in the 'Function documentation' section.
'''
__expression = re.compile(rf'\s+({_ModifiersFixBase._modifierRegex})\s+')
__expression = re.compile(rf'\s+({_ModifiersBase._modifierRegex})\s+')

@classmethod
def __substitute(cls, m, matches):
Expand Down Expand Up @@ -224,7 +224,7 @@ def __call__(self, doc, context):



class TemplateTemplateFix(object):
class TemplateTemplate(object):
'''
Spreads consecutive template <> declarations out over multiple lines.
'''
Expand All @@ -245,11 +245,40 @@ def __call__(self, doc, context):



class StripIncludes(object):
'''
Strips #include <paths/to/headers.h> based on context.strip_includes.
'''
def __call__(self, doc, context):
if doc.article is None or not context.strip_includes:
return False
changed = False
for include_div in doc.article.find_all(r'div', class_=r'm-doc-include'):
anchor = include_div.find('a', href=True, class_=r'cpf')
if anchor is None:
continue
text = anchor.get_text()
if not (text.startswith('<') and text.endswith('>')):
continue
text = text[1:-1].strip()
for strip in context.strip_includes:
if len(text) < len(strip) or not text.startswith(strip):
continue
if len(text) == len(strip):
soup.destroy_node(include_div)
else:
anchor.contents.clear()
anchor.contents.append(soup.NavigableString(rf'<{text[len(strip):]}>'))
changed = True
break
return changed


#=======================================================================================================================
# index.html
#=======================================================================================================================

class IndexPageFix(object):
class IndexPage(object):
'''
Applies some basic fixes to index.html
'''
Expand Down Expand Up @@ -279,7 +308,7 @@ def __call__(self, doc, context):
# <code> blocks
#=======================================================================================================================

class CodeBlockFix(object):
class CodeBlocks(object):
'''
Fixes various issues and improves syntax highlighting in <code> blocks.
'''
Expand Down Expand Up @@ -534,7 +563,7 @@ def _m_doc_anchor_tags(tag):



class AutoDocLinksFix(object):
class AutoDocLinks(object):
'''
Adds links to additional sources where appropriate.
'''
Expand Down Expand Up @@ -597,22 +626,21 @@ def __call__(self, doc, context):



class LinksFix(object):
class Links(object):
'''
Fixes various minor issues with anchor tags.
'''
__external_href = re.compile(r'^(?:https?|s?ftp|mailto)[:].+$', re.I)
__internal_doc_id = re.compile(r'^[a-fA-F0-9]+$')
__godbolt = re.compile(r'^\s*https[:]//godbolt.org/z/.+?$', re.I)
__local_href = re.compile(r'^([-/_a-zA-Z0-9]+\.[a-zA-Z]+)(?:#(.*))?$')

def __call__(self, doc, context):
changed = False
for anchor in doc.body('a', recursive=True):
if 'href' not in anchor.attrs:
continue
for anchor in doc.body('a', recursive=True, href=True):
href = anchor['href']

# make sure links to certain external sources are correctly marked as such
# make sure links to external sources are correctly marked as such
if self.__external_href.fullmatch(href) is not None:
if 'target' not in anchor.attrs or anchor['target'] != '_blank':
anchor['target'] = '_blank'
Expand All @@ -622,18 +650,38 @@ def __call__(self, doc, context):
# do magic with godbolt.org links
if self.__godbolt.fullmatch(href):
changed = soup.add_class(anchor, 'godbolt') or changed
if anchor.parent.name == 'p' and len(anchor.parent.contents) == 1:
changed = soup.add_class(anchor.parent, ('m-note', 'm-success', 'godbolt')) or changed
if anchor.parent.next_sibling is not None and anchor.parent.next_sibling.name == 'pre':
code_block = anchor.parent.next_sibling
code_block.insert(0, anchor.parent.extract())
if (anchor.parent.name == 'p'
and len(anchor.parent.contents) == 1
and anchor.parent.next_sibling is not None
and anchor.parent.next_sibling.name == 'pre'):
soup.add_class(anchor.parent, ('m-note', 'm-success', 'godbolt'))
code_block = anchor.parent.next_sibling
code_block.insert(0, anchor.parent.extract())
changed = True
continue

# make sure internal documentation links actually have somewhere to go
if ('class' in anchor.attrs
is_mdoc = r'class' in anchor.attrs and (r'm-doc' in anchor['class'] or r'm-doc-self' in anchor['class'])

# make sure links to local files point to actual existing files
match = self.__local_href.fullmatch(href)
if match and not coerce_path(doc.path.parent, match[1]).exists():
changed = True
if is_mdoc:
href = r'#'
anchor[r'href'] = r'#' # will by fixed by the next step
else:
for attr in (r'download', r'href', r'hreflang', r'media', r'ping', r'referrerpolicy', r'rel', r'target', r'type'):
if attr in anchor.attrs:
del anchor[attr]
anchor.name = r'span'
continue


# make sure internal documentation #id links actually have somewhere to go
if (is_mdoc
and href.startswith(r'#')
and (r'm-doc' in anchor['class'] or r'm-doc-self' in anchor['class'])
and (href == r'#' or doc.body.find(id=href[1:], recursive=True) is None)):
and (len(href) == 1 or doc.body.find(id=href[1:], recursive=True) is None)):
changed = True
soup.remove_class(anchor, 'm-doc')
soup.add_class(anchor, 'm-doc-self')
anchor['href'] = '#'
Expand All @@ -646,34 +694,6 @@ def __call__(self, doc, context):
parent_with_id = anchor.find_parent(id=True)
if parent_with_id is not None:
anchor['href'] = '#' + parent_with_id['id']
continue

return changed



class DeadLinksFix(object):
'''
Fixes dead links to non-existent local files.
'''
__href = re.compile(r'^([-_a-zA-Z0-9]+\.html?)(?:#(.*))?$')

def __call__(self, doc, context):
changed = False
for anchor in doc.body('a', recursive=True):
match = self.__href.fullmatch(anchor['href'])
if match and not coerce_path(doc.path.parent, match[1]).exists():
soup.remove_class(anchor, 'm-doc')
if anchor.parent is not None and anchor.parent.name in ('dt', 'div'):
soup.add_class(anchor, 'm-doc-self')
id = None
if 'id' in anchor.parent.attrs:
id = anchor.parent['id']
else:
id = match[2]
if not id:
id = f'{sha1(match[1], anchor.string)}'
anchor.parent['id'] = id
anchor['href'] = f'#{id}'
changed = True
return changed

Loading

0 comments on commit 408df59

Please sign in to comment.