Skip to content

Commit

Permalink
fixed sorting of namespace and group members
Browse files Browse the repository at this point in the history
also:
- fixed build failure on new versions of doxygen due to `Doxyfile.xml`
- added google structured data to `\pages`
- added exception types `Error` and `WarningTreatedAsError`
  • Loading branch information
marzer committed May 23, 2021
1 parent b818a1a commit 543bc10
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 43 deletions.
2 changes: 1 addition & 1 deletion poxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
# SPDX-License-Identifier: MIT

from .run import run, main
from .utils import lib_version
from .utils import lib_version, Error, WarningTreatedAsError

__version__ = r'.'.join(lib_version())
2 changes: 1 addition & 1 deletion poxy/data/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.2
0.3.3
3 changes: 1 addition & 2 deletions poxy/doxygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,12 @@ def __init__(self, doxyfile_path, cwd=None, logger=None, doxygen_path=None, flus
# read in doxyfile
if self.path.exists():
if not self.path.is_file():
raise Exception(f'{self.path} was not a file')
raise Error(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(
[str(self.__doxygen), r'-s', r'-g', r'-'],
check=True,
Expand Down
20 changes: 16 additions & 4 deletions poxy/fixers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import soup

import html
import json



Expand Down Expand Up @@ -748,8 +749,9 @@ class HeadTags(object):
@classmethod
def __append(cls, doc, name, attrs):
doc.head.append(' ')
doc.new_tag(name, parent=doc.head, attrs=attrs)
tag = doc.new_tag(name, parent=doc.head, attrs=attrs)
doc.head.append('\n')
return tag

def __call__(self, doc, context):

Expand Down Expand Up @@ -802,8 +804,18 @@ def __call__(self, doc, context):
self.__append(doc, r'meta', tag)

# stylesheets and scripts
self.__append(doc, r'link', {r'href' : r'poxy.css', r'rel' : r'stylesheet'})
self.__append(doc, r'script', {r'src' : r'poxy.js' })
self.__append(doc, r'script', {r'src' : context.jquery.name })
self.__append(doc, r'link', { r'href' : r'poxy.css', r'rel' : r'stylesheet' })
self.__append(doc, r'script', { r'src' : r'poxy.js' })
self.__append(doc, r'script', { r'src' : context.jquery.name })

# google structured data
if doc.path.name in context.compound_pages and context.compound_pages[doc.path.name][r'kind'] == r'page':
tag = self.__append(doc, r'script', { r'type' : r'application/ld+json' })
data = {}
data[r'@context'] = r'https://schema.org'
data[r'@type'] = r'Article'
data[r'dateModified'] = context.now.isoformat()
data[r'headline'] = doc.head.title.string
tag.string = json.dumps(data, indent=" ")

return True
62 changes: 33 additions & 29 deletions poxy/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,16 +623,16 @@ def _extract_kvps(config, table,
if strip_keys:
key = key.strip()
if not allow_blank_keys and not key:
raise Exception(rf'{table}: keys cannot be blank')
raise Error(rf'{table}: keys cannot be blank')
if key in out:
raise Exception(rf'{table}.{key}: cannot be specified more than once')
raise Error(rf'{table}.{key}: cannot be specified more than once')

value = value_getter(v)
if isinstance(value, str):
if strip_values and isinstance(value, str):
value = value.strip()
if not allow_blank_values and not value:
raise Exception(rf'{table}.{key}: values cannot be blank')
raise Error(rf'{table}.{key}: values cannot be blank')

if value_type is not None:
value = value_type(value)
Expand All @@ -646,7 +646,7 @@ def _extract_kvps(config, table,
def _assert_no_unexpected_keys(raw, validated, prefix=''):
for key in raw:
if key not in validated:
raise Exception(rf"Unknown config property '{prefix}{key}'")
raise Error(rf"Unknown config property '{prefix}{key}'")
if isinstance(validated[key], dict):
_assert_no_unexpected_keys(raw[key], validated[key], prefix=rf'{prefix}{key}.')
return validated
Expand All @@ -665,18 +665,19 @@ def __init__(self, config):
self.treat_as_errors = None
self.undocumented = None

if 'warnings' not in config:
return
config = config['warnings']
if config is not None:
if 'warnings' not in config:
return
config = config['warnings']

if 'enabled' in config:
self.enabled = bool(config['enabled'])
if 'enabled' in config:
self.enabled = bool(config['enabled'])

if 'treat_as_errors' in config:
self.treat_as_errors = bool(config['treat_as_errors'])
if 'treat_as_errors' in config:
self.treat_as_errors = bool(config['treat_as_errors'])

if 'undocumented' in config:
self.undocumented = bool(config['undocumented'])
if 'undocumented' in config:
self.undocumented = bool(config['undocumented'])



Expand Down Expand Up @@ -773,9 +774,9 @@ def __init__(self, config, key, input_dir):
path = Path(input_dir, path)
path = path.resolve()
if not path.exists():
raise Exception(rf"{key}: '{path}' does not exist")
raise Error(rf"{key}: '{path}' does not exist")
if not (path.is_file() or path.is_dir()):
raise Exception(rf"{key}: '{path}' was not a directory or file")
raise Error(rf"{key}: '{path}' was not a directory or file")
paths.add(str(path))
if recursive and path.is_dir():
for subdir in enum_subdirs(path):
Expand Down Expand Up @@ -917,11 +918,11 @@ def verbose(self, msg, indent=None):
def info(self, msg, indent=None):
self.__log(logging.INFO, msg, indent=indent)

def warning(self, msg, indent=None, prefix=r'Warning: '):
if prefix:
self.__log(logging.WARNING, rf'{prefix}{msg}', indent=indent)
def warning(self, msg, indent=None):
if self.warnings.treat_as_errors:
raise WarningTreatedAsError(msg)
else:
self.__log(logging.WARNING, msg, indent=indent)
self.__log(logging.WARNING, rf'Warning: {msg}', indent=indent)

def verbose_value(self, name, val):
if not self.__verbose:
Expand Down Expand Up @@ -1016,9 +1017,12 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,

self.fixers = None
self.tagfile_path = None
self.warnings = None
self.warnings = _Warnings(None) # overwritten after reading config; this is for correct 'treat_as_errors' behaviour if we add any pre-config warnings
if treat_warnings_as_errors:
self.warnings.treat_as_errors = True

now = datetime.datetime.utcnow()
now = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc)
self.now = now

# resolve paths
if 1:
Expand Down Expand Up @@ -1080,7 +1084,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
assert self.doxyfile_path is not None
self.doxyfile_path = self.doxyfile_path.resolve()
if self.doxyfile_path.exists() and not self.doxyfile_path.is_file():
raise Exception(rf'{doxyfile_path} was not a file')
raise Error(rf'{doxyfile_path} was not a file')
if self.config_path is not None:
self.config_path = self.config_path.resolve()
self.verbose_value(r'Context.config_path', self.config_path)
Expand All @@ -1102,16 +1106,16 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
if not p.exists() or not p.is_file() or not os.access(str(p), os.X_OK):
p = Path(doxygen_path, 'doxygen')
if not p.exists() or not p.is_file() or not os.access(str(p), os.X_OK):
raise Exception(rf'Could not find Doxygen executable in {doxygen_path}')
raise Error(rf'Could not find Doxygen executable in {doxygen_path}')
doxygen_path = p
assert_existing_file(doxygen_path)
self.doxygen_path = doxygen_path
else:
self.doxygen_path = shutil.which(r'doxygen')
if self.doxygen_path is None:
raise Exception(rf'Could not find Doxygen on system path')
raise Error(rf'Could not find Doxygen on system path')
if not os.access(str(self.doxygen_path), os.X_OK):
raise Exception(rf'{doxygen_path} was not an executable file')
raise Error(rf'{doxygen_path} was not an executable file')
self.verbose_value(r'Context.doxygen_path', self.doxygen_path)

# m.css
Expand Down Expand Up @@ -1209,7 +1213,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
if self.cpp in (3, 11, 14, 17, 20, 23, 26, 29):
self.cpp = self.cpp + 2000
else:
raise Exception(rf"cpp: '{config['cpp']}' is not a valid cpp standard version")
raise Error(rf"cpp: '{config['cpp']}' is not a valid cpp standard version")
self.verbose_value(r'Context.cpp', self.cpp)
badge = rf'poxy-badge-c++{str(self.cpp)[2:]}.svg'
badges.append((rf'C++{str(self.cpp)[2:]}', badge, r'https://en.cppreference.com/w/cpp/compiler_support'))
Expand Down Expand Up @@ -1393,7 +1397,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
if not alias:
continue
if alias in self.aliases:
raise Exception(rf'aliases.{k}: cannot override a built-in alias')
raise Error(rf'aliases.{k}: cannot override a built-in alias')
self.aliases[alias] = v
self.verbose_value(r'Context.aliases', self.aliases)

Expand Down Expand Up @@ -1432,13 +1436,13 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
extra_files[i] = Path(self.input_dir, extra_files[i])
extra_files[i] = extra_files[i].resolve()
if not extra_files[i].exists() or not extra_files[i].is_file():
raise Exception(rf'extra_files: {extra_files[i]} did not exist or was not a file')
raise Error(rf'extra_files: {extra_files[i]} did not exist or was not a file')
self.extra_files = set(extra_files)
self.verbose_value(r'Context.extra_files', self.extra_files)
extra_filenames = set()
for f in self.extra_files:
if f.name in extra_filenames:
raise Exception(rf'extra_files: Multiple source files with the name {f.name}')
raise Error(rf'extra_files: Multiple source files with the name {f.name}')
extra_filenames.add(f.name)

self.code_blocks = _CodeBlocks(config, non_cpp_def_macros) # printed in run.py post-xml
Expand Down
45 changes: 39 additions & 6 deletions poxy/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ def _postprocess_xml(context):
assert context is not None
assert isinstance(context, project.Context)

# delete the new Doxyfile.xml (https://github.com/doxygen/doxygen/pull/8463)
# (it breaks m.css otherwise)
delete_file(Path(context.xml_dir, r'Doxyfile.xml'), logger=context.verbose_logger)

xml_files = get_all_files(context.xml_dir, any=(r'*.xml'))
if not xml_files:
return
Expand Down Expand Up @@ -479,10 +483,24 @@ def _postprocess_xml(context):
for typedef in typedefs:
cpp_tree.add_type(rf'{scope_name}::{typedef.find("name").text}')

# enumerate all compound pages and their types for use later in the HTML post-process
pages = {}
for tag in root.findall(r'compound'):
refid = tag.get(r'refid')
filename = refid
if filename == r'indexpage':
filename = r'index'
filename = filename + r'.html'
pages[filename] = { r'kind' : tag.get(r'kind'), r'name' : tag.find(r'name').text, r'refid' : refid }
context.__dict__[r'compound_pages'] = pages
context.verbose_value(r'Context.compound_pages', pages)

# some other compound definition
else:
compounddef = root.find(r'compounddef')
assert compounddef is not None
if compounddef is None:
context.warning(rf'{xml_file} did not contain a <compounddef>!')
continue
compoundname = compounddef.find(r'compoundname')
assert compoundname is not None
assert compoundname.text
Expand Down Expand Up @@ -619,6 +637,19 @@ def _postprocess_xml(context):
compounddef.remove(tag)
changed = True

# groups and namespaces
if compounddef.get(r'kind') in (r'group', r'namespace'):

# fix inner(class|namespace|group) sorting
inners = [tag for tag in compounddef.iterchildren() if tag.tag.startswith(r'inner')]
if inners:
changed = True
for tag in inners:
compounddef.remove(tag)
inners.sort(key=lambda tag: tag.text)
for tag in inners:
compounddef.append(tag)

if changed:
write_xml_to_file(xml, xml_file)

Expand Down Expand Up @@ -947,8 +978,6 @@ def run(config_path='.',
if context.warnings.enabled:
warnings = _extract_warnings(outputs)
for w in warnings:
if context.warnings.treat_as_errors:
raise Exception(rf'{w} (warning treated as error)')
context.warning(w)

# remove the local paths from the tagfile since they're meaningless (and a privacy breach)
Expand Down Expand Up @@ -1000,8 +1029,6 @@ def run(config_path='.',
if context.warnings.enabled:
warnings = _extract_warnings(outputs)
for w in warnings:
if context.warnings.treat_as_errors:
raise Exception(rf'{w} (warning treated as error)')
context.warning(w)

# copy extra_files
Expand Down Expand Up @@ -1114,8 +1141,14 @@ def main():
sys.exit(0)
else:
sys.exit(1)
except WarningTreatedAsError as err:
print(rf'Error: {err} (warning treated as error)', file=sys.stderr)
sys.exit(1)
except Error as err:
print(rf'Error: {err}', file=sys.stderr)
sys.exit(1)
except Exception as err:
print_exception(err, include_type=verbose, include_traceback=verbose, skip_frames=1)
print_exception(err, include_type=True, include_traceback=True, skip_frames=1)
sys.exit(-1)


Expand Down
22 changes: 22 additions & 0 deletions poxy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,25 @@ def add_enum_value(self, val):

def matcher(self, type_):
return self.root.regex_matcher(type_)



#=======================================================================================================================
# Custom exceptions
#=======================================================================================================================

class Error(Exception):
"""Base class for other exceptions."""

def __init__(self, *message):
self.__message = r' '.join([str(m) for m in message])
super().__init__(*message)

def __str__(self):
return self.__message



class WarningTreatedAsError(Error):
"""Raised when a warning is generated and the user has chosen to treat warnings as errors."""
pass

0 comments on commit 543bc10

Please sign in to comment.