From 0bcc6fa3ebedc3e7e19b5ce2b0bb9f53f1a5c991 Mon Sep 17 00:00:00 2001 From: Mark Gillard Date: Sat, 29 May 2021 13:06:25 +0300 Subject: [PATCH] added config option `theme` also: - fixed images being center-aligned in detail tables - fixed config schema errors showing full stack traces - added lib version to css and js filenames - added magic macro `POXY_IGNORE` - added CHANGELOG.md - migrated to conf.py for m.css - restructured temp files --- CHANGELOG.md | 55 ++++ poxy/data/poxy-dark.css | 126 +++++++++ poxy/data/poxy-github-black.svg | 1 + poxy/data/poxy-github-icon.png | Bin 2077 -> 0 bytes poxy/data/poxy-github-white.svg | 1 + poxy/data/poxy-light.css | 139 ++++++++++ poxy/data/poxy.css | 185 ++++--------- poxy/data/version.txt | 2 +- poxy/fixers.py | 9 +- poxy/project.py | 113 +++++--- poxy/run.py | 443 ++++++++++++++++---------------- setup.py | 10 +- 12 files changed, 684 insertions(+), 400 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 poxy/data/poxy-dark.css create mode 100644 poxy/data/poxy-github-black.svg delete mode 100644 poxy/data/poxy-github-icon.png create mode 100644 poxy/data/poxy-github-white.svg create mode 100644 poxy/data/poxy-light.css 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 @@ +poxy-github-logo \ 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 6e62b6c3085ffebc4403b1e8c53781817278135e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2077 zcmbtVc~nzp7LRPQh#=y~HuNP#OGlEt1PDn;fP@eh4Mvi(R1`w;LLwwDBo7jBDI|8Z zQ-okoN2@T%qKJZQ(s3LF;z$`cU<8$|6&c*dj>1euXi@s2VEf0M{xk2K_rCkx^ZWho z{qDWrd7ld6qn#bS95EP-b8HMZ0bMDU*WM2O`i38DK^IRXDj5;NGDIiVKp3_Z&VYbe zr8pBxfW*@LoU0HMgRzcKh?0?H{uYJ=RuaV)3{kICqihU@8Ln50C0P&xWI&k;6$|(L zuU$AmA!Xr`g83j{9SO-5F$Ee(SP(Cg6l6*0Qe1c#z|=EP0VRZp0lhL?rDf<@xHq~C z)V8=uIN%Kg$ztK&CY8(=0Fkf;0)mNDf`lAI0Yd0RG8Lp#f&u^vNDd)^AtZ7j0VFd( zDkG2%EIl|hnno&PByf34vCth0Cr1!9gGAElbVOYs5!Pgq$aFfL1X4&83IRnBwD~GT ztS6|ne#;76NGs7O)QAFB0TxAZ2AqqqaA>A)kDyfZ`R{~P+NDCFB_rv@Y7&_Ul9Woz zx!$0)NCNb88vhfm73HfTQUaufb2SpQA2PpXGFrP|4`czN*)X`UBv%QkkXSAYhkA%o zg_J>#ilWfy97-^S%qEkg*z72fObe!i?7+xi8k@>l=EkbDh*%|ombr?bx#VBvG9opQ z7=bk+7|v$lc-i?1RVKhkT>x7GYgA|hgNYPNQI~RrG>SY(%G1C~;7wH+ieE&2I~7t` zf<{-%VFcF7VKoB;Wr0ETU>Q0HQVAhM9F#$zNixI)DP1O`QXv{043XlN)KKob8vGw> zBs5`?rR~2MgJl;wTNd-(P@|i7*8)YlymehxhxhdUSl{LOzW0?Fp|fk(}`E3G5K2fUG~{^REN z1y0`X#=8?2Rx-REW>lkF|`R#$l9o=s;_(nQz=t7nF^G6c8L;OS|VSeOdZE}KuU zSl_*7`bwe`o?8~Ac3p60z=tMmJkE`h)@2^M^kQN+_yf;)e%+N&0)AcU>Wi?>98auW0p~W>}{Aw@=ZLX@K>_dY@U4F>) zHL>2O_2@{6>0^1Rp!Q;XbICtGU!W{&eN_~VT|M^i{8on!l_ zoV+Wis!a}@t_@WzH%z_teGV_lRydgcONK@v}QC z0_L{7KKI^*^w#m6g_P#C#cs$x*WuMA#`nu>zmh-&BXjN*DGr)Pd&(o)MmN8geEtg) zrs>}?#WKnHZ7Je5bL9NO-itjBN9vkuWzqh%Lk(evF=r6p>h|MT!sru6*v>Ug1{2qN zH%>}ohn(f~dl)DCj)9e{n!QsWTxvV;Qdm66YidpT{!46QGU2XbftT6wsAvB8 z?N?8NG;3Go2x9(Heb72D4wmjVd)>@@QGTj^a$M-t)o~g`L@oBBz7*Z36*kvbJ>P2B zHiq+QWK?(^5C$e>v@hbz9<1(j-u^17Hp~&b+3)U-J2`In>d?>;2QaG1-ZSc~%^4u*GfE7AH 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 @@ +poxy-github-logo \ 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('(?