diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..a599bb4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,55 @@
+# Changelog
+
+## v0.4.0 - 2020-05-29
+- Added config option `theme`
+- Added version number to CSS and javascript filenames to prevent browser cache issues
+- Added `POXY_IMPLEMENTATION_DETAIL(...)` magic macro
+- Added `POXY_IGNORE(...)` magic macro
+- Fixed alignment of nested images inside detail blocks
+
+## v0.3.4 - 2020-05-28
+- Added basic `using` alias detection to syntax highlighter
+- Added missing badges for C++23, 26 and 29
+
+## v0.3.3 - 2020-05-23
+- Fixed sorting of namespace and group members
+- Fixed m.css failing with new versions of doxygen due to `Doxyfile.xml`
+- Added google structured data to `\pages`
+
+## v0.3.2 - 2020-05-19
+- Fixed formatting of `` tags
+- Added config option `author`
+- Added config option `robots`
+- Added markup tag `[p]`
+- Added markup tag `[center]`
+
+## v0.3.1 - 2020-05-13
+- Added config option `macros`
+- Added command-line option `--version`
+
+## v0.3.0 - 2020-05-09
+- Improved handling of m.css and Doxygen warnings and errors
+- Added command-line option `--doxygen`
+- Added command-line option `--werror`
+- Added markup tag `[set_parent_class]`
+- Added markup tag `[add_parent_class]`
+- Added markup tag `[remove_parent_class]`
+- Added config option `images`
+- Added config option `examples`
+- Added ability to specify tagfiles as URIs
+
+## v0.2.1 - 2020-05-07
+- Fixed some minor autolinking issues
+
+## v0.2.0 - 2020-05-06
+- Added config option `source_patterns`
+
+## v0.1.2 - 2020-05-02
+- Fixed the Z-order of the nav bar being higher than the search overlay
+- Added `NDEBUG` to the default set of defines
+
+## v0.1.1 - 2020-04-26
+- Added an additional cleanup step to the HTML postprocessor
+
+## v0.1.0 - 2020-04-26
+First public release :tada:
diff --git a/poxy/data/poxy-dark.css b/poxy/data/poxy-dark.css
new file mode 100644
index 0000000..a9e663e
--- /dev/null
+++ b/poxy/data/poxy-dark.css
@@ -0,0 +1,126 @@
+/* detail sections */
+article section.m-doc-details > div
+{
+ background-color: rgba(0,0,0,0.07)
+}
+
+/* detail section headers */
+article section.m-doc-details > div > h3:first-child
+{
+ background-color: #22272e;
+}
+
+/* namespace/class/struct etc prefixes in treeviews */
+ul.m-doc > li.m-doc-collapsible,
+ul.m-doc > li.m-doc-collapsible > a:first-child,
+ul.m-doc > li.m-doc-collapsible li
+{
+ color: #CCCCCC !important;
+}
+
+/* "Parameters", "Returns", subheadings inside description blocks, etc */
+.m-doc-details div > h4,
+.m-doc-details div > h5,
+.m-doc-details div > h6,
+.m-doc-details div > table.m-table th,
+.m-doc-details div > table.m-table td > h3,
+.m-doc-details div > table.m-table td > h4,
+.m-doc-details div > table.m-table td > h5,
+.m-doc-details div > table.m-table td > h6,
+.m-doc-details div > table.m-table td > strong > em,
+.m-doc-details div > table.m-table td > p > strong > em
+{
+ color: #a5c9ea;
+}
+
+/* 'dim' blocks and notes */
+.m-block.m-dim code,
+.m-note.m-dim code
+{
+ color: #acacac;
+}
+.m-block.m-dim pre.m-code,
+.m-note.m-dim pre.m-code
+{
+ opacity: 0.85;
+}
+
+/* ====================================================
+ code blocks
+==================================================== */
+
+/* keywords */
+.m-code .k
+{
+ color: rgb(86, 156, 214);
+}
+
+/* identifier names */
+.m-code .n
+{
+ color: rgb(220,220,220);
+}
+
+/* punctuators (brackets etc) */
+.m-code .p
+{
+ color: rgb(120,120,120);
+}
+
+/* user types and typedefs */
+.m-code .ut,
+.m-code .nc
+{
+ color: rgb(78,201,176);
+}
+
+/* int and float literals, enum values */
+.m-code .mb,
+.m-code .mi,
+.m-code .mf,
+.m-code .mh,
+.m-code .ne
+{
+ color: rgb(181,206,168);
+}
+
+/* string literals, "includes" */
+.m-code .s,
+.m-code .sa,
+.m-code .dl,
+.m-code .cpf
+{
+ color: rgb(214,157,133);
+}
+
+/* macros */
+.m-code .m
+{
+ color: rgb(190,183,255);
+}
+
+/* comments */
+.m-code .c1,
+.m-code .cm
+{
+ color: rgb(87,166,74) !important;
+}
+
+/* preprocessor directives */
+.m-code .cp
+{
+ color: rgb(120,120,120);
+}
+
+/* namespace::scopes:: */
+.m-code .ns
+{
+ color: rgb(140,140,140);
+}
+
+/* implementation detail blocks */
+pre.poxy-impl,
+code.poxy-impl
+{
+ color: rgb(87,166,74) !important;
+}
diff --git a/poxy/data/poxy-github-black.svg b/poxy/data/poxy-github-black.svg
new file mode 100644
index 0000000..ddd7deb
--- /dev/null
+++ b/poxy/data/poxy-github-black.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/poxy/data/poxy-github-icon.png b/poxy/data/poxy-github-icon.png
deleted file mode 100644
index 6e62b6c..0000000
Binary files a/poxy/data/poxy-github-icon.png and /dev/null differ
diff --git a/poxy/data/poxy-github-white.svg b/poxy/data/poxy-github-white.svg
new file mode 100644
index 0000000..53ebceb
--- /dev/null
+++ b/poxy/data/poxy-github-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/poxy/data/poxy-light.css b/poxy/data/poxy-light.css
new file mode 100644
index 0000000..95fcb48
--- /dev/null
+++ b/poxy/data/poxy-light.css
@@ -0,0 +1,139 @@
+/* detail section headers */
+article section.m-doc-details > div > h3:first-child
+{
+ background-color: #ffe1d6;
+}
+
+/* 'light' caption text */
+.m-doc-template,
+dl.m-doc dd,
+ul.m-doc li > span.m-doc
+{
+ color: #939393;
+}
+.m-doc-template a,
+dl.m-doc dd a,
+ul.m-doc li > span.m-doc a
+{
+ color: #666;
+}
+
+/* "Parameters", "Returns", subheadings inside description blocks, etc */
+.m-doc-details div > h4,
+.m-doc-details div > h5,
+.m-doc-details div > h6,
+.m-doc-details div > table.m-table th,
+.m-doc-details div > table.m-table td > h3,
+.m-doc-details div > table.m-table td > h4,
+.m-doc-details div > table.m-table td > h5,
+.m-doc-details div > table.m-table td > h6,
+.m-doc-details div > table.m-table td > strong > em,
+.m-doc-details div > table.m-table td > p > strong > em
+{
+ color: #ea7944;
+}
+
+/* 'dim' blocks and notes */
+.m-block.m-dim,
+.m-text.m-dim,
+.m-label.m-flat.m-dim
+{
+ color: #939393;
+}
+.m-block.m-dim a,
+.m-text.m-dim a,
+.m-label.m-flat.m-dim a
+{
+ color: #666;
+}
+.m-block.m-dim pre.m-code,
+.m-note.m-dim pre.m-code
+{
+ opacity: 0.75;
+}
+
+/* ====================================================
+ code blocks
+==================================================== */
+
+pre.m-code
+{
+ background-color: #fdfdfd;
+ border: 1px solid #cccccc;
+}
+
+/* keywords */
+.m-code .k
+{
+ color: rgb(0, 0, 255);
+}
+
+/* identifier names */
+.m-code .n
+{
+ color: #111111;
+}
+
+/* punctuators (brackets etc) */
+.m-code .p
+{
+ color: rgb(120,120,120);
+}
+
+/* user types and typedefs */
+.m-code .ut,
+.m-code .nc
+{
+ color: rgb(43, 145, 175);
+}
+
+/* int and float literals, enum values */
+.m-code .mb,
+.m-code .mi,
+.m-code .mf,
+.m-code .mh,
+.m-code .ne
+{
+ color: rgb(47, 79, 79);
+}
+
+/* string literals, "includes" */
+.m-code .s,
+.m-code .sa,
+.m-code .dl,
+.m-code .cpf
+{
+ color: rgb(163, 21, 21);
+}
+
+/* macros */
+.m-code .m
+{
+ color: rgb(138, 27, 255);
+}
+
+/* comments */
+.m-code .c1,
+.m-code .cm
+{
+ color: rgb(0, 128, 0) !important;
+}
+
+/* preprocessor directives */
+.m-code .cp
+{
+ color: rgb(128, 128, 128);
+}
+
+/* namespace::scopes:: */
+.m-code .ns
+{
+ color: rgb(140,140,140);
+}
+
+/* implementation detail blocks */
+pre.poxy-impl,
+code.poxy-impl
+{
+ color: rgb(0, 128, 0) !important;
+}
diff --git a/poxy/data/poxy.css b/poxy/data/poxy.css
index aa1b992..c9a4125 100644
--- a/poxy/data/poxy.css
+++ b/poxy/data/poxy.css
@@ -51,21 +51,6 @@ article div > section > section
margin-bottom: 2.25rem;
}
-article section.m-doc-details > div
-{
- background-color: rgba(0,0,0,0.07)
-}
-
-article section.m-doc-details > div > h3:first-child
-{
- background-color: #22272e;
-}
-
-pre, code
-{
- font-family: 'Consolas', 'Source Code Pro', monospace;
-}
-
a.poxy-external
{
font-weight: normal;
@@ -82,24 +67,20 @@ pre.m-console a:hover
text-decoration: underline !important;
}
-@media screen and (min-width: 576px)
+/* nav bar */
+nav .m-thin
{
- nav .m-thin
- {
- margin-left: 0.5em;
- }
-
- nav .github
- {
- padding-left: 44px !important;
- background-image: url("poxy-github-icon.png");
- background-repeat: no-repeat;
- background-size: 25px 25px;
- background-position: -30px center;
- background-origin: content-box;
- }
+ margin-left: 0.5em;
+}
+nav .github
+{
+ padding-left: 44px !important;
+ background-image: url("poxy-github.svg");
+ background-repeat: no-repeat;
+ background-size: 25px 25px;
+ background-position: -30px center;
+ background-origin: content-box;
}
-
@media screen and (max-width: 576px)
{
nav .m-thin, nav .github
@@ -108,19 +89,6 @@ pre.m-console a:hover
}
}
-/* code blocks w/ output examples */
-pre.m-code + pre.m-console
-{
- margin-top: -1.0rem;
- border-top: 1px solid #444444;
- font-size: 0.8rem;
- background-color: #1a1c1d !important;
-}
-pre.m-code + pre.m-console span
-{
- color: #bababa; /* is yououou */
-}
-
/* "Parameters", "Returns", subheadings inside description blocks, etc */
.m-doc-details div > h4,
.m-doc-details div > h5,
@@ -133,7 +101,6 @@ pre.m-code + pre.m-console span
.m-doc-details div > table.m-table td > strong > em,
.m-doc-details div > table.m-table td > p > strong > em
{
- color: #a5c9ea;
font-style: normal;
}
.m-doc-details div > table.m-table td > strong > em,
@@ -147,81 +114,6 @@ pre.m-code + pre.m-console span
margin-top: 1.0rem;
}
-/* comments */
-.m-code .c1,
-.m-code .cm
-{
- color: rgb(87,166,74) !important;
-}
-
-/* int and float literals, enum values */
-.m-code .mb,
-.m-code .mi,
-.m-code .mf,
-.m-code .mh,
-.m-code .ne
-{
- color: rgb(181,206,168);
-}
-
-/* keywords */
-.m-code .k
-{
- color: rgb(86,156,214);
-}
-.m-code .kt,
-.m-code .k,
-.m-code .nc
-{
- font-weight: normal;
-}
-
-/* identifier names */
-.m-code .n
-{
- color: rgb(220,220,220);
-}
-
-/* punctuators (brackets etc) */
-.m-code .p
-{
- color: rgb(120,120,120);
-}
-
-/* preprocessor directives */
-.m-code .cp
-{
- color: rgb(120,120,120);
-}
-
-/* macros */
-.m-code .m
-{
- color: rgb(190,183,255);
-}
-
-/* string literals, "includes" */
-.m-code .s,
-.m-code .sa,
-.m-code .dl,
-.m-code .cpf
-{
- color: rgb(214,157,133);
-}
-
-/* user types and typedefs */
-.m-code .ut,
-.m-code .nc
-{
- color: rgb(78,201,176);
-}
-
-/* namespace::scopes:: */
-.m-code .ns
-{
- color: rgb(140,140,140);
-}
-
/* github badges (index.html) */
.gh-badges
{
@@ -362,18 +254,6 @@ figure.m-figure::before
margin-bottom: initial;
}
-/* 'dim' blocks and notes */
-.m-block.m-dim code,
-.m-note.m-dim code
-{
- color: #acacac;
-}
-.m-block.m-dim pre.m-code,
-.m-note.m-dim pre.m-code
-{
- opacity: 0.85;
-}
-
/* m-special (purple) */
.m-note.m-special,
table.m-table tr.m-special td,
@@ -453,19 +333,46 @@ h6:last-child
margin-bottom: 0.0rem;
}
-/* namespace/class/struct etc prefixes in treeviews */
-ul.m-doc > li.m-doc-collapsible,
-ul.m-doc > li.m-doc-collapsible > a:first-child,
-ul.m-doc > li.m-doc-collapsible li
+/* images in detail sections */
+.m-doc-details table img.m-image
{
- color: #CCCCCC !important;
+ margin-left: inherit;
+}
+
+/* ====================================================
+ code blocks
+==================================================== */
+
+pre, code
+{
+ font-family: 'Consolas', 'Source Code Pro', monospace;
+}
+
+/* code blocks w/ output examples */
+pre.m-code + pre.m-console
+{
+ margin-top: -1.0rem;
+ font-size: 0.8rem;
+ /* border-top: 1px solid #444444; */
+}
+pre.m-code + pre.m-console span
+{
+ color: #bababa; /* is yououou */
+}
+
+/* keywords */
+.m-code .kt,
+.m-code .k,
+.m-code .nc
+{
+ font-weight: normal;
}
/* implementation detail blocks */
-.poxy-impl
+pre.poxy-impl,
+code.poxy-impl
{
padding: 0rem !important;
margin: 0rem 0.2rem !important;
font-weight: normal;
- color: rgb(87,166,74) !important;
}
diff --git a/poxy/data/version.txt b/poxy/data/version.txt
index 42045ac..1d0ba9e 100644
--- a/poxy/data/version.txt
+++ b/poxy/data/version.txt
@@ -1 +1 @@
-0.3.4
+0.4.0
diff --git a/poxy/fixers.py b/poxy/fixers.py
index 4e02527..872eaf3 100644
--- a/poxy/fixers.py
+++ b/poxy/fixers.py
@@ -308,7 +308,7 @@ class ImplementationDetails(PlainTextFixer):
Replaces implementation details with appropriate shorthands.
'''
__shorthands = (
- (r'POXY_IMPLEMENTATION_DETAIL', r'/* ... */
'),
+ (r'POXY_IMPLEMENTATION_DETAIL_IMPL', r'/* ... */
'),
)
def __call__(self, doc, context):
changed = False
@@ -842,7 +842,7 @@ def __call__(self, doc, context):
if r'format-detection' not in context.meta_tags:
meta.append({ r'name' : r'format-detection', r'content' : r'telephone=no'})
if r'generator' not in context.meta_tags:
- meta.append({ r'name' : r'generator', r'content' : rf'Poxy v{".".join(context.version)}'})
+ meta.append({ r'name' : r'generator', r'content' : rf'Poxy v{context.version_string}'})
if r'referrer' not in context.meta_tags:
meta.append({ r'name' : r'referrer', r'content' : r'no-referrer-when-downgrade'})
@@ -854,8 +854,9 @@ 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'link', { r'href' : rf'poxy-{context.version_string}.css', r'rel' : r'stylesheet' })
+ self.__append(doc, r'link', { r'href' : rf'poxy-{context.version_string}-{context.theme}.css', r'rel' : r'stylesheet' })
+ self.__append(doc, r'script', { r'src' : rf'poxy-{context.version_string}.js' })
self.__append(doc, r'script', { r'src' : context.jquery.name })
# google structured data
diff --git a/poxy/project.py b/poxy/project.py
index 70401d7..6e4036c 100644
--- a/poxy/project.py
+++ b/poxy/project.py
@@ -72,15 +72,17 @@ class _Defaults(object):
r'std::(?:literals::)?(?:chrono|complex|string|string_view)_literals'
}
macros = {
- r'NDEBUG' : 1,
- r'DOXYGEN' : 1,
- r'__DOXYGEN__' : 1,
- r'__doxygen__' : 1,
- r'__POXY__' : 1,
- r'__poxy__' : 1,
- r'__has_include(...)' : 0,
- r'__has_attribute(...)' : 0,
- r'__has_cpp_attribute(...)' : 999999,
+ r'NDEBUG' : 1,
+ r'DOXYGEN' : 1,
+ r'__DOXYGEN__' : 1,
+ r'__doxygen__' : 1,
+ r'__POXY__' : 1,
+ r'__poxy__' : 1,
+ r'__has_include(...)' : 0,
+ r'__has_attribute(...)' : 0,
+ r'__has_cpp_attribute(...)' : 999999,
+ r'POXY_IMPLEMENTATION_DETAIL(...)' : r'POXY_IMPLEMENTATION_DETAIL_IMPL',
+ r'POXY_IGNORE(...)' : r'',
}
cpp_builtin_macros = {
1998 : {
@@ -532,6 +534,8 @@ class _Defaults(object):
r'__has_(?:(?:cpp_)?attribute|include)',
r'assert',
r'offsetof',
+ # poxy:
+ r'POXY_[a-zA-Z_]+',
# msvc:
r'__(?:'
+ r'FILE|LINE|DATE|TIME|COUNTER'
@@ -851,7 +855,9 @@ def __init__(self, config, key, input_dir):
#=======================================================================================================================
class Context(object):
-
+ """
+ The context object passed around during one invocation.
+ """
__emoji = None
__emoji_codepoints = None
__emoji_uri = re.compile(r".+unicode/([0-9a-fA-F]+)[.]png.*", re.I)
@@ -886,6 +892,7 @@ class Context(object):
Optional(r'show_includes') : bool,
Optional(r'sources') : _Sources.schema,
Optional(r'tagfiles') : {str : str},
+ Optional(r'theme') : Or(r'dark', r'light'),
Optional(r'warnings') : _Warnings.schema,
},
ignore_extra_keys=True
@@ -1003,8 +1010,9 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
self.verbose_logger = logger if self.__verbose else None
self.version = lib_version()
+ self.version_string = r'.'.join(self.version)
if not self.dry_run or self.__verbose:
- self.info(rf'Poxy v{".".join(self.version)}')
+ self.info(rf'Poxy v{self.version_string}')
self.verbose_value(r'Context.dry_run', self.dry_run)
self.verbose_value(r'Context.cleanup', self.cleanup)
@@ -1037,12 +1045,13 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
self.output_dir = coerce_path(output_dir).resolve()
self.verbose_value(r'Context.output_dir', self.output_dir)
assert self.output_dir.is_absolute()
+ self.case_sensitive_paths = not (Path(str(self.data_dir).upper()).exists() and Path(str(self.data_dir).lower()).exists())
+ self.verbose_value(r'Context.case_sensitive_paths', self.case_sensitive_paths)
# config + doxyfile
input_dir = None
self.config_path = None
self.doxyfile_path = None
- self.temp_doxyfile_path = None
if config_path is None:
config_path = self.output_dir
else:
@@ -1084,17 +1093,30 @@ 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 Error(rf'{doxyfile_path} was not a file')
+ raise Error(rf'{self.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)
self.verbose_value(r'Context.doxyfile_path', self.doxyfile_path)
- # output folders
- self.xml_dir = Path(self.output_dir, 'xml')
- self.html_dir = Path(self.output_dir, 'html')
+ # temp dirs
+ self.global_temp_dir = Path(tempfile.gettempdir(), r'poxy')
+ self.verbose_value(r'Context.global_temp_dir', self.global_temp_dir)
+ self.global_temp_dir.mkdir(exist_ok=True)
+ temp_dir_hash_source = str(self.input_dir)
+ if not self.case_sensitive_paths:
+ temp_dir_hash_source = temp_dir_hash_source.upper()
+ self.temp_dir = Path(self.global_temp_dir, sha1(temp_dir_hash_source))
+ self.temp_dir.mkdir(exist_ok=True)
+ self.verbose_value(r'Context.temp_dir', self.temp_dir)
+
+ # output paths
+ self.xml_dir = Path(self.temp_dir, 'xml')
self.verbose_value(r'Context.xml_dir', self.xml_dir)
+ self.html_dir = Path(self.output_dir, 'html')
self.verbose_value(r'Context.html_dir', self.html_dir)
+ self.mcss_conf_path = Path(self.temp_dir, 'conf.py')
+ self.verbose_value(r'Context.mcss_conf_path', self.mcss_conf_path)
# doxygen
if doxygen_path is not None:
@@ -1123,7 +1145,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
mcss_dir = Path(self.data_dir, r'mcss')
mcss_dir = coerce_path(mcss_dir).resolve()
assert_existing_directory(mcss_dir)
- assert_existing_file(Path(mcss_dir, 'documentation/doxygen.py'))
+ assert_existing_file(Path(mcss_dir, r'documentation/doxygen.py'))
self.mcss_dir = mcss_dir
self.verbose_value(r'Context.mcss_dir', self.mcss_dir)
@@ -1221,7 +1243,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
# project logo
self.logo = None
- if 'logo' in config:
+ if r'logo' in config:
if config['logo']:
file = config['logo'].strip()
if file:
@@ -1231,6 +1253,12 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
self.logo = file.resolve()
self.verbose_value(r'Context.logo', self.logo)
+ # theme (M_THEME_COLOR)
+ self.theme = r'dark'
+ if r'theme' in config:
+ self.theme = str(config[r'theme'])
+ self.verbose_value(r'Context.theme', self.theme)
+
# sources (INPUT, FILE_PATTERNS, STRIP_FROM_PATH, STRIP_FROM_INC_PATH, EXTRACT_ALL)
self.sources = _Sources(config, 'sources', self.input_dir)
self.verbose_object(r'Context.sources', self.sources)
@@ -1253,7 +1281,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
dest = str(v)
if source and dest:
if is_uri(source):
- file = str(Path(tempfile.gettempdir(), rf'poxy.tagfile.{sha1(source)}.{now.year}-{now.isocalendar().week}.xml'))
+ file = str(Path(self.global_temp_dir, rf'tagfile_{sha1(source)}_{now.year}_{now.isocalendar().week}.xml'))
self.tagfiles[source] = (file, dest)
self.unresolved_tagfiles = True
else:
@@ -1268,7 +1296,7 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
self.verbose_value(r'Context.tagfiles', self.tagfiles)
# m.css navbar
- if 'navbar' in config:
+ if r'navbar' in config:
self.navbar = []
for v in coerce_collection(config['navbar']):
val = v.strip().lower()
@@ -1422,29 +1450,34 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
extra_files.append(Path(file))
# add built-ins to extra files
- extra_files.append(Path(self.data_dir, r'poxy.css'))
- extra_files.append(Path(self.data_dir, r'poxy.js'))
- extra_files.append(Path(self.data_dir, r'poxy-github-icon.png'))
+ extra_files.append((rf'poxy-{self.version_string}.css', Path(self.data_dir, r'poxy.css')))
+ extra_files.append((rf'poxy-{self.version_string}-{self.theme}.css', Path(self.data_dir, rf'poxy-{self.theme}.css')))
+ extra_files.append((rf'poxy-{self.version_string}.js', Path(self.data_dir, r'poxy.js')))
+ extra_files.append((r'poxy-github.svg', Path(self.data_dir, rf'poxy-github-{"black" if self.theme == "light" else "white"}.svg')))
# add jquery
- self.jquery = Path(self.data_dir, r'jquery-3.6.0.slim.min.js')
+ self.jquery = get_all_files(self.data_dir, any=(r'jquery*.js'))[0]
+ self.verbose_value(r'Context.jquery', self.jquery)
extra_files.append(self.jquery)
- # check extra files
+ # finalize extra_files
+ self.extra_files = {}
for i in range(len(extra_files)):
- if not extra_files[i].is_absolute():
- 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 Error(rf'extra_files: {extra_files[i]} did not exist or was not a file')
- self.extra_files = set(extra_files)
+ file = extra_files[i]
+ if not isinstance(file, tuple):
+ path = coerce_path(file)
+ file = (path.name, path)
+ if not file[1].is_absolute():
+ file = (file[0], Path(self.input_dir, file[1]))
+ file = (file[0], file[1].resolve())
+ if not file[1].exists() or not file[1].is_file():
+ raise Error(rf'extra_files: {file[1]} did not exist or was not a file')
+ if file[0] in extra_files:
+ raise Error(rf'extra_files: Multiple files with the name {file[0]}')
+ self.extra_files[file[0]] = file[1]
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 Error(rf'extra_files: Multiple source files with the name {f.name}')
- extra_filenames.add(f.name)
+ # code_blocks
self.code_blocks = _CodeBlocks(config, non_cpp_def_macros) # printed in run.py post-xml
# initialize other data from files on disk
@@ -1452,5 +1485,13 @@ def __init__(self, config_path, output_dir, threads, cleanup, verbose, mcss_dir,
self.emoji = self.__emoji
self.emoji_codepoints = self.__emoji_codepoints
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if self.cleanup and self.temp_dir is not None:
+ delete_directory(self.temp_dir, logger=self.verbose_logger)
+
def __bool__(self):
return True
diff --git a/poxy/run.py b/poxy/run.py
index bf564e1..ee49ee3 100644
--- a/poxy/run.py
+++ b/poxy/run.py
@@ -24,8 +24,8 @@
import tempfile
import requests
from lxml import etree
-from io import BytesIO
-
+from io import BytesIO, StringIO
+from schema import SchemaError
#=======================================================================================================================
@@ -44,6 +44,8 @@
(r'CREATE_SUBDIRS', False),
(r'DISTRIBUTE_GROUP_DOC', False),
(r'DOXYFILE_ENCODING', r'UTF-8'),
+ (r'DOT_FONTNAME', r'Source Sans Pro'),
+ (r'DOT_FONTSIZE', 16),
(r'ENABLE_PREPROCESSING', True),
(r'EXAMPLE_RECURSIVE', False),
(r'EXCLUDE_SYMLINKS', False),
@@ -89,6 +91,7 @@
(r'HIDE_SCOPE_NAMES', False),
(r'HIDE_UNDOC_CLASSES', True),
(r'HIDE_UNDOC_MEMBERS', True),
+ (r'HTML_EXTRA_STYLESHEET', None),
(r'HTML_FILE_EXTENSION', r'.html'),
(r'HTML_OUTPUT', r'html'),
(r'IDL_PROPERTY_SUPPORT', False),
@@ -137,7 +140,6 @@
(r'WARN_IF_INCOMPLETE_DOC', True),
(r'WARN_LOGFILE', None),
(r'XML_NS_MEMB_FILE_SCOPE', True),
- (r'XML_OUTPUT', r'xml'),
(r'XML_PROGRAMLISTING', False),
)
@@ -151,7 +153,12 @@ def _preprocess_doxyfile(context):
logger = context.verbose_logger,
doxygen_path = context.doxygen_path,
flush_at_exit = not context.dry_run
- ) as df:
+ ) as df, StringIO(newline='\n') as conf_py:
+
+ # redirect to temp dir
+ df.path = Path(context.temp_dir, rf'Doxyfile')
+ context.doxyfile_path = df.path
+ context.verbose_value(r'Context.doxyfile_path', context.doxyfile_path)
df.append()
df.append(r'#---------------------------------------------------------------------------')
@@ -171,6 +178,7 @@ def _preprocess_doxyfile(context):
df.append(r'# general config', end='\n\n') # ---------------------------------------------------
df.set_value(r'OUTPUT_DIRECTORY', context.output_dir)
+ df.set_value(r'XML_OUTPUT', context.xml_dir)
if not context.name:
context.name = df.get_value(r'PROJECT_NAME', fallback='')
df.set_value(r'PROJECT_NAME', context.name)
@@ -204,7 +212,7 @@ def _preprocess_doxyfile(context):
context.generate_tagfile = not (context.private_repo or context.internal_docs)
context.verbose_value(r'Context.generate_tagfile', context.generate_tagfile)
if context.generate_tagfile:
- context.tagfile_path = coerce_path(context.output_dir, rf'{context.name.replace(" ","_")}.tagfile.xml' if context.name else r'tagfile.xml')
+ context.tagfile_path = Path(context.output_dir, rf'{context.name.replace(" ","_")}.tagfile.xml' if context.name else r'tagfile.xml')
df.set_value(r'GENERATE_TAGFILE', context.tagfile_path.name)
else:
df.set_value(r'GENERATE_TAGFILE', None)
@@ -213,6 +221,10 @@ def _preprocess_doxyfile(context):
df.add_value(r'CLANG_OPTIONS', rf'-std=c++{context.cpp%100}')
df.add_value(r'CLANG_OPTIONS', r'-Wno-everything')
+ if context.theme == r'light':
+ df.add_value(r'HTML_EXTRA_STYLESHEET', r'https://fonts.googleapis.com/css?family=Libre+Baskerville:400,400i,700,700i%7CSource+Code+Pro:400,400i,600')
+ df.add_value(r'HTML_EXTRA_STYLESHEET', r'../css/m-light+documentation.compiled.css')
+
df.append()
df.append(r'# context.warnings', end='\n\n') # ---------------------------------------------------
@@ -236,7 +248,7 @@ def _preprocess_doxyfile(context):
df.add_value(r'INPUT', context.sources.paths)
df.set_value(r'FILE_PATTERNS', context.sources.patterns)
- df.add_value(r'EXCLUDE', { context.html_dir, context.xml_dir })
+ df.add_value(r'EXCLUDE', context.html_dir)
df.add_value(r'STRIP_FROM_PATH', context.sources.strip_paths)
if context.sources.extract_all is None:
@@ -270,87 +282,94 @@ def _preprocess_doxyfile(context):
df.append(r'# context.macros', end='\n\n')
df.add_value(r'PREDEFINED', [rf'{k}={v}' for k,v in context.macros.items()])
- # apply m.css stuff
+ # build m.css conf.py
if 1:
- df.append()
- df.append(r'# m.css', end='\n\n')
-
- df.append(r'##!')
- df.append(rf'##! M_SHOW_UNDOCUMENTED = {"YES" if context.sources.extract_all else "NO"}')
- df.append(r'##!')
- df.append(rf'##! M_FAVICON = "{context.favicon if context.favicon is not None else ""}"')
- df.append(r'##!')
+ conf = lambda s='', end='\n': print(s, file=conf_py, end=end)
+ conf(rf"DOXYFILE = r'{context.doxyfile_path}'")
+ conf(rf"""THEME_COLOR = r'{"#cb4b16" if context.theme == "light" else "#22272e"}'""")
+ if not df.contains(r'M_FAVICON') and context.favicon:
+ conf(rf'FAVICON = {context.favicon}')
+ if not df.contains(r'M_SHOW_UNDOCUMENTED'):
+ conf(rf'SHOW_UNDOCUMENTED = {context.sources.extract_all}')
if not df.contains(r'M_CLASS_TREE_EXPAND_LEVELS'):
- df.append(r'##! M_CLASS_TREE_EXPAND_LEVELS = 3')
- df.append(r'##!')
+ conf(r'CLASS_INDEX_EXPAND_LEVELS = 3')
if not df.contains(r'M_FILE_TREE_EXPAND_LEVELS'):
- df.append(r'##! M_FILE_TREE_EXPAND_LEVELS = 3')
- df.append(r'##!')
+ conf(r'FILE_INDEX_EXPAND_LEVELS = 3')
if not df.contains(r'M_EXPAND_INNER_TYPES'):
- df.append(r'##! M_EXPAND_INNER_TYPES = YES')
- df.append(r'##!')
+ conf(r'CLASS_INDEX_EXPAND_INNER = True')
if not df.contains(r'M_SEARCH_DOWNLOAD_BINARY'):
- df.append(r'##! M_SEARCH_DOWNLOAD_BINARY = NO')
- df.append(r'##!')
+ conf(r'SEARCH_DOWNLOAD_BINARY = False')
if not df.contains(r'M_SEARCH_DISABLED'):
- df.append(r'##! M_SEARCH_DISABLED = NO')
- df.append(r'##!')
+ conf(r'SEARCH_DISABLED = False')
if not df.contains(r'M_LINKS_NAVBAR1') and not df.contains(r'M_LINKS_NAVBAR2'):
+ navbars = ([],[])
if context.navbar:
bar = [v for v in context.navbar]
for i in range(len(bar)):
- if bar[i] == 'github':
- bar[i] = rf'"Github"'
+ if bar[i] == r'github':
+ if context.github:
+ bar[i] = (rf'Github' , [])
+ else:
+ bar[i] = None
+ bar = [b for b in bar if b is not None]
split = min(max(int(len(bar)/2) + len(bar)%2, 2), len(bar))
- for b, i in ((bar[:split], 1), (bar[split:], 2)):
- if b:
- df.append(rf'##! M_LINKS_NAVBAR{i} = ''\\')
- for j in range(len(b)):
- df.append(rf'##! {b[j]}' + (' \\' if j+1 < len(b) else ''))
- else:
- df.append(rf'##! M_LINKS_NAVBAR{i} = ')
- df.append(r'##!')
- else:
- df.append(r'##! M_LINKS_NAVBAR1 = ')
- df.append(r'##! M_LINKS_NAVBAR2 = ')
- df.append(r'##!')
+ for b, i in ((bar[:split], 0), (bar[split:], 1)):
+ for j in range(len(b)):
+ if isinstance(b[j], tuple):
+ navbars[i].append(b[j])
+ else:
+ navbars[i].append((None, b[j], []))
+ for i in (0, 1):
+ if navbars[i]:
+ conf(f'LINKS_NAVBAR{i+1} = [\n ', end='')
+ conf(',\n '.join([rf'{b}' for b in navbars[i]]))
+ conf(r']')
+ else:
+ conf(rf'LINKS_NAVBAR{i+1} = []')
if not df.contains(r'M_PAGE_FINE_PRINT'):
- df.append(r'##! M_PAGE_FINE_PRINT = ''\\')
- top_row = []
+ conf(r"FINE_PRINT = r'''")
+ footer = []
if context.github:
- top_row.append(rf'Github')
- top_row.append(rf'Report an issue')
+ footer.append(rf'Github')
+ footer.append(rf'Report an issue')
if context.license and context.license[r'uri']:
- top_row.append(rf'License')
+ footer.append(rf'License')
if context.generate_tagfile:
- top_row.append(rf'Doxygen tagfile')
- if top_row:
- for i in range(len(top_row)):
- df.append(rf'##! {" • " if i else ""}{top_row[i]} ''\\')
- df.append(r'##!
''\\')
- df.append(r'##! Documentation created using ''\\')
- df.append(r'##! Doxygen ''\\')
- df.append(r'##! + mosra/m.css ''\\')
- df.append(r'##! + marzer/poxy')
- df.append(r'##!')
-
- # move to a temp file path
- df.path = coerce_path(tempfile.gettempdir(), rf'poxy.{df.hash()}.Doxyfile')
- context.temp_doxyfile_path = df.path
- context.verbose_value(r'Context.temp_doxyfile_path', context.temp_doxyfile_path)
-
- # debug dump final doxyfile
- if not context.is_verbose() or 1:
- df.cleanup()
+ footer.append(rf'Doxygen tagfile')
+ if footer:
+ for i in range(1, len(footer)):
+ footer[i] = r' • ' + footer[i]
+ footer.append(r'
')
+ footer.append(r'Site generated using Poxy')
+ for i in range(len(footer)):
+ conf(rf" {footer[i]}")
+ conf(r"'''")
+
+ if not context.dry_run:
+ context.verbose(rf'Writing {context.mcss_conf_path}')
+ with open(context.mcss_conf_path, r'w', encoding=r'utf-8', newline='\n') as f:
+ f.write(conf_py.getvalue())
+
+ # clean and debug dump final doxyfile
+ df.cleanup()
if context.dry_run:
context.info(r'#====================================================================================')
- context.info(rf'# generated by Poxy v{".".join(context.version)}')
+ context.info(rf'# generated by Poxy v{context.version_string}')
context.info(r'#====================================================================================')
context.info(df.get_text())
+ context.info(r'##! ---------------------------------------------------------------------------------')
+ context.info(r'##! m.css conf.py:')
+ context.info(r'##! ---------------------------------------------------------------------------------')
+ context.info(conf_py.getvalue(), indent=r'##! ')
context.info(r'#====================================================================================')
else:
context.verbose(r'Effective Doxyfile:')
- context.verbose(df.get_text(), indent=' ')
+ context.verbose(df.get_text(), indent=r' ')
+ context.verbose(r' ##! --------------------------------------------------------------------------')
+ context.verbose(r' ##! m.css conf.py:')
+ context.verbose(r' ##! --------------------------------------------------------------------------')
+ context.verbose(conf_py.getvalue(), indent=r' ##! ')
+
@@ -663,7 +682,7 @@ def _postprocess_xml(context):
# merge extracted implementations
if extracted_implementation:
for (hp, hfn, hid, impl) in implementation_header_data:
- xml_file = coerce_path(context.xml_dir, rf'{hid}.xml')
+ xml_file = Path(context.xml_dir, rf'{hid}.xml')
context.verbose(rf'Merging implementation nodes into {xml_file}')
xml = etree.parse(str(xml_file), parser=xml_parser)
compounddef = xml.getroot().find(r'compounddef')
@@ -716,7 +735,7 @@ def _postprocess_xml(context):
if 1 and context.implementation_headers:
for hdata in implementation_header_data:
for (ip, ifn, iid) in hdata[3]:
- delete_file(coerce_path(context.xml_dir, rf'{iid}.xml'), logger=context.verbose_logger)
+ delete_file(Path(context.xml_dir, rf'{iid}.xml'), logger=context.verbose_logger)
# scan through the files and substitute impl header ids and paths as appropriate
if 1 and context.implementation_headers:
@@ -775,6 +794,7 @@ def _postprocess_html_file(path, context=None):
if fix(doc, context):
plain_text_changed = True
if plain_text_changed:
+ context.verbose(rf'Writing {path}')
with open(path, 'w', encoding='utf-8', newline='\n') as f:
f.write(doc[0])
@@ -922,7 +942,7 @@ def run(config_path='.',
treat_warnings_as_errors=None
):
- context = project.Context(
+ with project.Context(
config_path = config_path,
output_dir = output_dir,
threads = threads,
@@ -933,144 +953,135 @@ def run(config_path='.',
logger = logger,
dry_run = dry_run,
treat_warnings_as_errors = treat_warnings_as_errors
- )
-
- with ScopeTimer(r'All tasks', print_start=False, print_end=context.verbose if dry_run else context.info) as all_tasks_timer:
+ ) as context:
# preprocess the doxyfile
_preprocess_doxyfile(context)
context.verbose_object(r'Context.warnings', context.warnings)
- # preprocessing the doxyfile creates a temp copy; this is the cleanup block.
- try:
- if not dry_run:
-
- # delete any leftovers from the previous run
- if 1:
- delete_directory(context.xml_dir, logger=context.verbose_logger)
- delete_directory(context.html_dir, logger=context.verbose_logger)
-
- # resolve any uri tagfiles
- if context.unresolved_tagfiles:
- with ScopeTimer(r'Resolving remote tagfiles', print_start=True, print_end=context.verbose_logger) as t:
- for source, v in context.tagfiles.items():
- if isinstance(v, str):
- continue
- file = Path(v[0])
- if file.exists():
- continue
- context.verbose(rf'Downloading {source} => {file}')
- response = requests.get(
- source,
- allow_redirects=True,
- stream=False,
- timeout=30
- )
- with open(file, 'w', encoding='utf-8', newline='\n') as f:
- f.write(response.text)
-
- make_temp_file = lambda: tempfile.SpooledTemporaryFile(mode='w+', newline='\n', encoding='utf-8')
-
- # run doxygen to generate the xml
- if 1:
- with ScopeTimer(r'Generating XML files with Doxygen', print_start=True, print_end=context.verbose_logger) as t:
- with make_temp_file() as stdout, make_temp_file() as stderr:
- try:
- subprocess.run(
- [str(context.doxygen_path), str(context.temp_doxyfile_path)],
- check=True,
- stdout=stdout,
- stderr=stderr,
- cwd=context.input_dir
- )
- except:
- context.info(r'Doxygen failed!')
- _dump_output_streams(context, _read_output_streams(stdout, stderr), source=r'Doxygen')
- raise
- if context.is_verbose() or context.warnings.enabled:
- outputs = _read_output_streams(stdout, stderr)
- if context.is_verbose():
- _dump_output_streams(context, outputs, source=r'Doxygen')
- if context.warnings.enabled:
- warnings = _extract_warnings(outputs)
- for w in warnings:
- context.warning(w)
-
- # remove the local paths from the tagfile since they're meaningless (and a privacy breach)
- if context.tagfile_path is not None and context.tagfile_path.exists():
- text = read_all_text_from_file(context.tagfile_path, logger=context.verbose_logger)
- text = re.sub(r'\n\s*?.+?\s*?\n', '\n', text, re.S)
- with open(context.tagfile_path, 'w', encoding='utf-8', newline='\n') as f:
- f.write(text)
-
- # post-process xml files
- if 1:
- _postprocess_xml(context)
-
- context.verbose_object(r'Context.code_blocks', context.code_blocks)
-
- # compile regexes
- # (done here because doxygen and xml preprocessing adds additional values to these lists)
- context.code_blocks.namespaces = regex_or(context.code_blocks.namespaces, pattern_prefix='(?:::)?', pattern_suffix='(?:::)?')
- context.code_blocks.types = regex_or(context.code_blocks.types, pattern_prefix='(?:::)?', pattern_suffix='(?:::)?')
- context.code_blocks.enums = regex_or(context.code_blocks.enums, pattern_prefix='(?:::)?')
- context.code_blocks.string_literals = regex_or(context.code_blocks.string_literals)
- context.code_blocks.numeric_literals = regex_or(context.code_blocks.numeric_literals)
- context.code_blocks.macros = regex_or(context.code_blocks.macros)
- context.autolinks = tuple([(re.compile('(? {file}')
+ response = requests.get(
+ source,
+ allow_redirects=True,
+ stream=False,
+ timeout=30
+ )
+ context.verbose(rf'Writing {file}')
+ with open(file, 'w', encoding='utf-8', newline='\n') as f:
+ f.write(response.text)
+
+ make_temp_file = lambda: tempfile.SpooledTemporaryFile(mode='w+', newline='\n', encoding='utf-8')
+
+ # run doxygen to generate the xml
+ if 1:
+ with ScopeTimer(r'Generating XML files with Doxygen', print_start=True, print_end=context.verbose_logger) as t:
+ with make_temp_file() as stdout, make_temp_file() as stderr:
+ try:
+ subprocess.run(
+ [str(context.doxygen_path), str(context.doxyfile_path)],
+ check=True,
+ stdout=stdout,
+ stderr=stderr,
+ cwd=context.input_dir
+ )
+ except:
+ context.info(r'Doxygen failed!')
+ _dump_output_streams(context, _read_output_streams(stdout, stderr), source=r'Doxygen')
+ raise
+ if context.is_verbose() or context.warnings.enabled:
+ outputs = _read_output_streams(stdout, stderr)
+ if context.is_verbose():
+ _dump_output_streams(context, outputs, source=r'Doxygen')
+ if context.warnings.enabled:
+ warnings = _extract_warnings(outputs)
+ for w in warnings:
+ context.warning(w)
+
+ # remove the local paths from the tagfile since they're meaningless (and a privacy breach)
+ if context.tagfile_path is not None and context.tagfile_path.exists():
+ text = read_all_text_from_file(context.tagfile_path, logger=context.verbose_logger)
+ text = re.sub(r'\n\s*?.+?\s*?\n', '\n', text, re.S)
+ context.verbose(rf'Writing {context.tagfile_path}')
+ with open(context.tagfile_path, 'w', encoding='utf-8', newline='\n') as f:
+ f.write(text)
+
+ # post-process xml files
+ if 1:
+ _postprocess_xml(context)
- # post-process html files
- if 1:
- _postprocess_html(context)
+ context.verbose_object(r'Context.code_blocks', context.code_blocks)
+
+ # compile regexes
+ # (done here because doxygen and xml preprocessing adds additional values to these lists)
+ context.code_blocks.namespaces = regex_or(context.code_blocks.namespaces, pattern_prefix='(?:::)?', pattern_suffix='(?:::)?')
+ context.code_blocks.types = regex_or(context.code_blocks.types, pattern_prefix='(?:::)?', pattern_suffix='(?:::)?')
+ context.code_blocks.enums = regex_or(context.code_blocks.enums, pattern_prefix='(?:::)?')
+ context.code_blocks.string_literals = regex_or(context.code_blocks.string_literals)
+ context.code_blocks.numeric_literals = regex_or(context.code_blocks.numeric_literals)
+ context.code_blocks.macros = regex_or(context.code_blocks.macros)
+ context.autolinks = tuple([(re.compile('(?