├── .editorconfig ├── .gitignore ├── Dockerfile ├── LICENSE.TXT ├── README.md ├── common.py ├── cparser.py ├── generator.py ├── generator_markdown.py ├── run.sh ├── run_examples.py └── test ├── cxxdox.yml ├── docs ├── index.md └── js │ └── math.js ├── mkdocs.yml ├── template ├── CMakeLists.txt ├── install.sh └── main.cpp └── test.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | 6 | charset = utf-8 7 | 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [.sh] 12 | end_of_line = lf 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | clang 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20.3 2 | 3 | RUN apk update 4 | RUN apk add --no-cache python3 mkdocs-material py3-regex py3-requests py3-colorama cmake make g++ git 5 | RUN apk add --no-cache 'clang19-libs' 'clang19' 'clang19-extra-tools' --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 6 | 7 | WORKDIR /opt/clang 8 | 9 | RUN wget https://raw.githubusercontent.com/llvm/llvm-project/refs/tags/llvmorg-19.1.3/clang/bindings/python/clang/__init__.py 10 | RUN wget https://raw.githubusercontent.com/llvm/llvm-project/refs/tags/llvmorg-19.1.3/clang/bindings/python/clang/cindex.py 11 | 12 | WORKDIR /opt 13 | 14 | ADD cparser.py . 15 | ADD generator_markdown.py . 16 | ADD generator.py . 17 | ADD run_examples.py . 18 | ADD common.py . 19 | 20 | ADD run.sh . 21 | 22 | RUN chmod +x run.sh 23 | 24 | RUN ls -la /usr/lib/llvm19/lib 25 | 26 | ENTRYPOINT ["/bin/sh", "/opt/run.sh"] 27 | 28 | CMD [""] 29 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CxxDox - Documentation Generator for C++ with Minimal Configuration 2 | 3 | CxxDox is a documentation generator for C++ projects that creates markdown files compatible with `mkdocs` or directly rendered HTML files. This tool is designed to be highly compatible with DOXYGEN syntax and is built with Python and libclang to run in a Docker environment. 4 | 5 | Using `libclang` allows CxxDox to handle complex, template-heavy C++ projects and support the latest C++ standards. This integration ensures reliable parsing of advanced templates, metaprogramming, and new language features, allowing CxxDox to generate accurate documentation even for projects using cutting-edge C++ constructs. 6 | 7 | Current libclang version: `19` (C++20 and c++23 support) 8 | 9 | ## Features 10 | 11 | - Parses C++ headers, extracting functions, variables, and types, and generates `mkdocs`-compatible Markdown files. 12 | - Autolinks to the repository (including commit hash). 13 | - Supports math rendering and syntax highlighting. 14 | - Generates code snippets, optionally runs them, and includes output in the documentation. 15 | - Automatically generates an alphabetical index. 16 | 17 | ### Requirements 18 | 19 | To use CxxDox, you’ll only need Docker. 20 | 21 | If you want to render md files manually you’ll need mkdocs or mkdocs-material with the following plugins: 22 | - `codehilite` 23 | - `pymdownx.arithmatex` (uses KaTeX for fast math rendering) 24 | 25 | ### Example Projects 26 | 27 | CxxDox is created and used for projects [KFR - C++ DSP library](https://kfr.dev) and [Brisk GUI framework](https://github.com/brisklib/brisk). 28 | 29 | ## Getting Started 30 | 31 | ### Building and Running the Docker Container 32 | 33 | To build and run the Docker container for CxxDox: 34 | 35 | 1. Clone your project and place it in the desired directory structure, as required by `run.sh`. 36 | 2. Use the following Docker commands to build the image and run the container, specifying the necessary arguments to generate HTML documentation. 37 | 38 | #### Build Docker Image 39 | 40 | ```bash 41 | docker build -t cxxdox . 42 | ``` 43 | 44 | #### Run the Docker Container 45 | 46 | ```bash 47 | docker run --rm -v $(pwd)/src:/src -v $(pwd)/out:/out -v $(pwd)/data:/data cxxdox 48 | ``` 49 | 50 | - **``**: Replace this with the path to your documentation directory inside `/src`, which contains the files to be documented along with the `cxxdox.yml` and `mkdocs.yml` config files. 51 | 52 | ### Configuring `cxxdox.yml` 53 | 54 | The `cxxdox.yml` file is the main configuration file used to customize the behavior of CxxDox for your project. Below is a guide on how to set up `cxxdox.yml`: 55 | 56 | #### Key Configuration Options 57 | 58 | - **title**: The title of the documentation. 59 | ```yaml 60 | title: Project Name 61 | ``` 62 | 63 | - **postprocessor.ignore**: A list of macros or qualifiers to ignore while parsing, ensuring specific elements aren't included in the documentation. 64 | ```yaml 65 | postprocessor: 66 | ignore: 67 | - ALWAYS_INLINE 68 | - PUBLIC_EXPORT 69 | - ... 70 | ``` 71 | 72 | - **clang.arguments**: Compiler arguments passed to `libclang` to configure parsing. This can include flags for specific standards or preprocessor definitions. 73 | ```yaml 74 | clang: 75 | arguments: 76 | - '-std=gnu++20' 77 | - '-DENABLE_AWESOME_FEATURE=1' 78 | - ... 79 | ``` 80 | 81 | - **input_directory**: Specifies the directory with the source files to document, relative to the `cxxdox.yml` location. 82 | ```yaml 83 | input_directory: ../include/your_project 84 | ``` 85 | 86 | - **masks**: Patterns to specify file types or names to be included in parsing (e.g., only header files). 87 | ```yaml 88 | masks: ['**/*.hpp'] 89 | ``` 90 | 91 | - **repository**: Format for linking source files to the repository, enabling auto-linking to specific lines of code. 92 | ```yaml 93 | repository: https://github.com/your_project/repo/blob/{TAG}/path/to/file/{FILE}#L{LINE} 94 | ``` 95 | 96 | - **groups**: Defines categorized groups for organizing functions or classes within documentation. 97 | ```yaml 98 | groups: 99 | filter: "Filter API" 100 | array: "Array functions" 101 | ... 102 | ``` 103 | 104 | For a full example of a `cxxdox.yml`, refer to this configuration file: https://github.com/kfrlib/kfr/blob/main/docs/cxxdox.yml 105 | 106 | ### Usage Examples 107 | 108 | #### Generating HTML Documentation for a Sample C++ Project 109 | 110 | Assuming your project is in a folder named `my_cpp_project`, the following commands will generate the documentation and output it to `./out/site`: 111 | 112 | ```bash 113 | docker run --rm -v $(pwd)/src:/src -v $(pwd)/out:/out -v $(pwd)/data:/data cxxdox my_cpp_project 114 | ``` 115 | 116 | This command will: 117 | - Copy files from `/src/my_cpp_project/` to `/out`. 118 | - Parse headers, generate Markdown files in `/out/docs`, and then use `mkdocs` to build the site in `/out/site`. 119 | 120 | ## Demo 121 | 122 | View a live demo of the generated documentation [here](https://www.kfrlib.com/newdocs/index.html). 123 | 124 | ## Version 125 | 126 | 0.2-beta 127 | 128 | ## License 129 | 130 | Licensed under the Apache License. See `LICENSE.TXT` for details. 131 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | 2 | def remove_padding(s): 3 | lines = s.splitlines() 4 | minpadding = 100 5 | for l in lines: 6 | if len(l) > 0: 7 | minpadding = min(minpadding, len(l) - len(l.lstrip(' '))) 8 | if minpadding == 100: 9 | return s 10 | 11 | lines = [l[minpadding:] for l in lines] 12 | return '\n'.join(lines) 13 | -------------------------------------------------------------------------------- /cparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import glob 4 | import yaml 5 | import sys 6 | import argparse 7 | import cparser 8 | import generator 9 | import json 10 | from clang.cindex import Index, CursorKind, Config 11 | import codecs 12 | import re 13 | import os 14 | from typing import List, Dict 15 | from common import remove_padding 16 | 17 | file_cache = {} 18 | 19 | 20 | rules = [ 21 | [r'[@\\]c\s+(\S+)', 'inlinecode', 1], 22 | [r'[@\\]ref\s+(\S+)', 'ref', 1], 23 | [r'\s*[@\\]code(.*?)\s+[@\\]endcode\s*', 'blockcode', 1], 24 | [r'\\f\\\((.*?)\\f\\\)', 'inlinemath', 1], 25 | [r'\\f\$(.*?)\\f\$', 'inlinemath', 1], 26 | [r'\$(.*?)\$', 'inlinemath', 1], 27 | [r'\s*\\f\[(.*?)\\f\]\s*', 'blockmath', 1], 28 | 29 | [r'[@\\]concept\s+', 'concept', 0], 30 | [r'[@\\]class\s+', 'class', 0], 31 | [r'[@\\]struct\s+', 'struct', 0], 32 | [r'[@\\]fn\s+', 'function', 0], 33 | [r'[@\\]enum\s+', 'enum', 0], 34 | [r'[@\\]typedef\s+', 'typedef', 0], 35 | 36 | [r'[@\\]param\s+(\S+)', 'param', 1], 37 | [r'[@\\]tparam\s+(\S+)', 'tparam', 1], 38 | 39 | [r'[@\\](?:throws?|exceptions?)\s+(\S+)', 'exceptions', 1], 40 | [r'[@\\](?:details|remark)\s+', 'details', 0], 41 | [r'[@\\](?:threadsafe|threadsafety)\s+', 'threadsafety', 0], 42 | [r'[@\\]note\s+', 'note', 0], 43 | [r'[@\\](?:see|sa)\s+', 'note', 0], 44 | [r'[@\\]returns?\s+', 'return', 0], 45 | ] 46 | 47 | def parse_description(s): 48 | if isinstance(s, str): 49 | for rule in rules: 50 | m = re.search(rule[0], s, re.MULTILINE | re.DOTALL) 51 | if m is not None: 52 | prefix = s[:m.start()] 53 | match = remove_padding(m.group(1)).strip() if rule[2] > 0 else '' 54 | postfix = s[m.end():] 55 | return parse_description([prefix, {rule[1]: match}, postfix]) 56 | 57 | return s 58 | elif isinstance(s, List): 59 | r = [] 60 | for ss in s: 61 | if isinstance(ss, str): 62 | rr = parse_description(ss) 63 | if isinstance(rr, str): 64 | if len(rr) > 0: 65 | r.append(rr.strip()) 66 | else: 67 | r.extend(rr) 68 | else: 69 | r.append(ss) 70 | return r 71 | else: 72 | return s 73 | 74 | 75 | def clean_text(str): 76 | str = str.replace('\t', ' ') 77 | str = str.replace('\r', '') 78 | return str 79 | 80 | 81 | 82 | def get_location(node): 83 | if node is None: 84 | return '' 85 | if node.location is None: 86 | return '' 87 | if node.location.file is None: 88 | return '' 89 | return node.location.file.name 90 | 91 | 92 | def get_location_line(node): 93 | if node is None: 94 | return -1 95 | if node.location is None: 96 | return -1 97 | return node.location.line 98 | 99 | 100 | def get_source(cursor): 101 | assert cursor.extent.start.file.name == cursor.extent.end.file.name 102 | filename = cursor.extent.start.file.name 103 | if filename not in file_cache: 104 | file_cache[filename] = codecs.open( 105 | filename, 'r', encoding="utf-8").read() 106 | 107 | file_content = file_cache[filename].encode('utf-8') 108 | bytes = ' ' * (cursor.extent.start.column - 1) + clean_text( 109 | file_content[cursor.extent.start.offset:cursor.extent.end.offset].decode('utf-8')) 110 | return remove_padding(bytes) 111 | 112 | 113 | def clean_comment(s): 114 | s = s.strip() 115 | if s.startswith('///<'): 116 | return remove_padding(s[4:]) 117 | elif s.startswith('/**<'): 118 | return remove_padding(s[4::-2]) 119 | elif s.startswith('///'): 120 | return remove_padding(re.sub(r'^\s*///', '', s, flags=re.MULTILINE)) 121 | elif s.startswith('/**'): 122 | return remove_padding(re.sub(r'^\s*\*( |$)', '', s[3:-2], flags=re.MULTILINE)) 123 | return s 124 | 125 | 126 | def replace_macros(s: str, macros: Dict): 127 | for key, value in macros.items(): 128 | s = re.sub(r'\b'+key+r'\b', value, s) 129 | return s 130 | 131 | 132 | def same_location(x, y): 133 | return x == y 134 | 135 | 136 | def class_name(node): 137 | 138 | template = [] 139 | for c in node.get_children(): 140 | if c.kind in [CursorKind.TEMPLATE_TYPE_PARAMETER, CursorKind.TEMPLATE_NON_TYPE_PARAMETER]: 141 | template.append(get_source(c)) 142 | if template: 143 | template = 'template <' + ', '.join(template) + '>' 144 | else: 145 | template = '' 146 | 147 | return template + node.spelling 148 | 149 | 150 | def source_to_definition(source): 151 | source = re.sub(r'^(.*?)\{.*', r'\1', source, flags=re.DOTALL).strip() 152 | return source 153 | 154 | 155 | def parse_index(root_path, index: List[Dict], node, root_location, group: str, ns: str = '', macros={}, include_source=False): 156 | 157 | source = '' 158 | if node.brief_comment is not None: 159 | source = get_source(node) 160 | definition = source_to_definition(replace_macros(source, macros)) 161 | 162 | entity: Dict = {} 163 | 164 | if node.kind in [CursorKind.FUNCTION_TEMPLATE, CursorKind.FUNCTION_DECL, CursorKind.CXX_METHOD, CursorKind.CONSTRUCTOR, CursorKind.DESTRUCTOR, CursorKind.CONVERSION_FUNCTION, CursorKind.USING_DECLARATION]: 165 | entity['type'] = 'function' 166 | entity['name'] = node.spelling 167 | entity['definition'] = definition 168 | elif node.kind in [CursorKind.CLASS_TEMPLATE, CursorKind.CLASS_DECL, CursorKind.STRUCT_DECL, CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION]: 169 | entity['type'] = 'class' 170 | entity['name'] = node.spelling 171 | entity['definition'] = class_name(node) 172 | entity['content'] = [] 173 | elif node.kind in [CursorKind.ENUM_DECL]: 174 | entity['type'] = 'enum' 175 | entity['name'] = node.spelling 176 | entity['definition'] = definition 177 | entity['content'] = [] 178 | elif node.kind in [CursorKind.ENUM_CONSTANT_DECL]: 179 | entity['type'] = 'enumerator' 180 | entity['name'] = node.spelling 181 | entity['definition'] = definition 182 | elif node.kind in [CursorKind.TYPEDEF_DECL, CursorKind.TYPE_ALIAS_DECL, CursorKind.TYPE_ALIAS_TEMPLATE_DECL]: 183 | entity['type'] = 'typedef' 184 | entity['name'] = node.spelling 185 | entity['definition'] = re.sub(r'(^|\s+)using\s+', r'', definition) 186 | elif node.kind in [CursorKind.VAR_DECL, CursorKind.UNEXPOSED_DECL, CursorKind.FIELD_DECL]: 187 | entity['type'] = 'variable' 188 | entity['name'] = node.spelling 189 | entity['definition'] = definition 190 | elif node.kind in [CursorKind.NAMESPACE]: 191 | entity['type'] = 'namespace' 192 | entity['name'] = node.displayname 193 | entity['definition'] = definition 194 | entity['source'] = definition + ' { ... }' 195 | elif node.kind in [CursorKind.CONCEPT_DECL]: 196 | entity['type'] = 'concept' 197 | entity['name'] = node.displayname 198 | entity['definition'] = definition 199 | entity['source'] = definition + ' { ... }' 200 | elif node.kind in [CursorKind.FRIEND_DECL]: 201 | print("ignored: {}".format(node.kind)) 202 | return 203 | else: 204 | print('warning: Unknown cursor kind: {} for {}'.format( 205 | node.kind, node.displayname)) 206 | return 207 | 208 | entity['qualifiedname'] = re.sub('^::', '', ns + '::' + entity['name']) 209 | if 'source' not in entity: 210 | entity['source'] = source 211 | 212 | if not include_source: 213 | entity['source'] = "" 214 | entity['file'] = os.path.relpath( 215 | get_location(node), root_path).replace('\\', '/') 216 | entity['line'] = get_location_line(node) 217 | description = clean_comment(clean_text(node.raw_comment)) 218 | 219 | m = re.match(r'[@\\]copybrief\s+([a-zA-Z0-9:\._-]+)', 220 | description.strip()) 221 | if m: 222 | copyFrom = m.group(1) 223 | description = {"copy": copyFrom} 224 | else: 225 | description = re.sub(r'\s*@brief\s*', '', description) 226 | description = parse_description(description) 227 | 228 | entity['description'] = description 229 | 230 | index.append(entity) 231 | 232 | entity['group'] = group 233 | 234 | if 'content' in entity: 235 | index = entity['content'] 236 | 237 | if node.kind == CursorKind.NAMESPACE: 238 | ns += ns+'::'+node.spelling 239 | 240 | if node.kind in [CursorKind.CLASS_TEMPLATE, CursorKind.CLASS_DECL, CursorKind.STRUCT_DECL, CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION]: 241 | ns += ns+'::'+node.spelling 242 | 243 | if node.kind in [CursorKind.ENUM_DECL]: 244 | ns += ns+'::'+node.spelling 245 | 246 | for c in node.get_children(): 247 | if same_location(get_location(c), root_location): 248 | parse_index(root_path, index, c, root_location, group, ns, macros) 249 | 250 | 251 | def parse(index, root_path, filenames: List[str], clang_args: List[str], macros={}, include_source=False): 252 | 253 | for filename in filenames: 254 | print('Parsing ' + filename) 255 | 256 | group = '' 257 | with open(filename, 'r', encoding='utf-8') as strm: 258 | text = strm.read() 259 | m = re.search(r'@addtogroup\s+([a-zA-Z0-9_-]+)', text) 260 | if m: 261 | group = m.group(1) 262 | else: 263 | group = os.path.basename(os.path.dirname(filename)) 264 | 265 | clangIndex = Index.create() 266 | cargs = [filename.replace('\\', '/')] + clang_args 267 | tu = clangIndex.parse(None, cargs) 268 | if not tu: 269 | print('Unable to load input') 270 | exit(1) 271 | 272 | if len(tu.diagnostics): 273 | print('------------DIAGNOSTICS---------------') 274 | for diag in tu.diagnostics: 275 | print(diag) 276 | print('------------/DIAGNOSTICS---------------') 277 | 278 | count = len(index) 279 | parse_index(root_path, index, tu.cursor, 280 | tu.cursor.displayname, group, '', macros, include_source) 281 | print(' Found {} entities'.format(len(index) - count)) 282 | 283 | def convert_config(c, config_path): 284 | if 'masks' in c: 285 | return {**c, 'input': [{ 286 | 'include': [ os.path.normpath(os.path.join(os.path.dirname(config_path), c['input_directory'], x)) for x in c['masks'] ], 287 | 'hide_tokens': c['postprocessor']['ignore'], 288 | 'compile_options': c['clang']['arguments']}]} 289 | return c 290 | 291 | if __name__ == '__main__': 292 | 293 | import subprocess 294 | 295 | parser = argparse.ArgumentParser( 296 | description='Parse C++ sources to generate index') 297 | parser.add_argument('config_path', help='path to configuration file (YML)') 298 | parser.add_argument('source_path', help='path to source directory') 299 | parser.add_argument('--output', 300 | help='path where generated index will be written (JSON)') 301 | parser.add_argument('--libclang', help='libclang path (.dll or .so)') 302 | parser.add_argument( 303 | '--git', help='Retrieve commit hash and branch', action='store_true') 304 | 305 | args = parser.parse_args() 306 | 307 | if args.libclang: 308 | Config.set_library_file(args.libclang) 309 | 310 | clang_args = [] 311 | 312 | config = None 313 | 314 | defaults = { 315 | 'inputs': [ 316 | { 317 | 'include': ['**/*.hpp', 318 | '**/*.cpp', 319 | '**/*.cxx', 320 | '**/*.hxx', 321 | '**/*.h'], 322 | 'hide_tokens': [], 323 | 'compile_options': [], 324 | } 325 | ], 326 | 'repository': '', 327 | 'groups': {}, 328 | 'include_source': False 329 | } 330 | 331 | config = yaml.safe_load(open(args.config_path, 'r', encoding='utf-8')) 332 | 333 | config = convert_config(config, args.config_path) 334 | 335 | print(config) 336 | config = {**defaults, **config} 337 | 338 | git_tag = '' 339 | 340 | if args.git: 341 | try: 342 | git_tag = subprocess.check_output( 343 | ['git', 'describe', '--always', '--abbrev=0'], cwd=args.source_path).strip() 344 | git_tag = codecs.decode(git_tag) 345 | print('GIT:') 346 | print(git_tag) 347 | except: 348 | pass 349 | 350 | inputs = config['input'] 351 | 352 | index = [] 353 | for input in inputs: 354 | filenames = [] 355 | for input_mask in input['include']: 356 | filenames += glob.glob(os.path.join(args.source_path, input_mask), recursive=True) 357 | 358 | print('Found', len(filenames), 'files') 359 | 360 | macros = input['hide_tokens'] 361 | macros = {k: '' for k in macros} 362 | compile_options = input['compile_options'] 363 | 364 | cparser.parse(index, args.source_path, filenames, compile_options, macros, config['include_source']) 365 | 366 | index = {'index': index, 'git_tag': git_tag, 'repository': config['repository'].replace( 367 | '{TAG}', git_tag), 'groups': config['groups']} 368 | json.dump(index, open(args.output, 'w', encoding='utf-8'), indent=4) 369 | -------------------------------------------------------------------------------- /generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | from typing import List, Dict 5 | 6 | def padding(str, p=' ', remove_empty_lines=True): 7 | """ 8 | normalize newlines 9 | remove leading and trailing spaces 10 | collapse spaces 11 | collapse newlines 12 | pad lines 13 | """ 14 | str = re.sub(r'[ \t]+\n', '\n', str) 15 | if remove_empty_lines: 16 | str = str.replace('\n\n', '\n') 17 | str = str.replace('\n\n', '\n') 18 | str = p + str.replace('\n', '\n' + p) 19 | return str 20 | 21 | 22 | def filterIndex(index: List[Dict], group: str): 23 | filtered = [] 24 | 25 | for item in index: 26 | if item['group'] == group: 27 | filtered.append(item) 28 | 29 | return filtered 30 | 31 | 32 | def groupList(index: List[Dict]): 33 | groups = [] 34 | 35 | for item in index: 36 | groups.append(item['group']) 37 | 38 | return list(set(groups)) 39 | -------------------------------------------------------------------------------- /generator_markdown.py: -------------------------------------------------------------------------------- 1 | import generator 2 | import argparse 3 | import os 4 | import json 5 | import re 6 | from typing import List, Dict 7 | from common import remove_padding 8 | import collections 9 | 10 | def markdown_safe(s): 11 | return s.replace('>', r'\>') 12 | 13 | labels = { 14 | "exceptions":"Exceptions", 15 | "details":"Details", 16 | "note":"Note", 17 | "threadsafety":"Thread safety", 18 | } 19 | 20 | def generate_descriptions(s): 21 | if isinstance(s, str): 22 | s = [s] 23 | md = '' 24 | for ss in s: 25 | if isinstance(ss, str): 26 | md += markdown_safe(ss) 27 | else: 28 | key = next(iter(ss)) 29 | if key in ['concept', 'class', 'struct', 'function', 'typedef', 'enum']: 30 | pass # Skip 31 | elif key == 'inlinecode': 32 | md += ' `' + ss['inlinecode'] + '` ' 33 | elif key == 'inlinemath': 34 | md += ' $' + ss['inlinemath'] + '$ ' 35 | elif key == 'param': 36 | md += '
**Param** `' + markdown_safe(ss['param']) + '` ' 37 | elif key == 'return': 38 | md += '
**Returns** ' 39 | elif key == 'see': 40 | md += '
**See** ' 41 | elif key == 'tparam': 42 | md += '
**Template param** `' + markdown_safe(ss['tparam']) + '` ' 43 | elif key in ['exceptions', 'details', 'note', 'threadsafety']: 44 | md += '
**' + markdown_safe(labels[key]) + '** ' 45 | elif key == 'blockmath': 46 | md += '\n\n$$\n' + remove_padding(markdown_safe(ss['blockmath'])) + '\n$$\n\n' 47 | elif key == 'blockcode': 48 | md += '\n```c++\n' + \ 49 | remove_padding(markdown_safe(ss['blockcode'])) + '\n```\n' 50 | else: 51 | md += '(UNKNOWN ID: {})'.format(list(ss.items())[0]) 52 | return md 53 | 54 | 55 | def clang_format(code): 56 | import subprocess 57 | return subprocess.check_output(['clang-format', '-style={BasedOnStyle: llvm, ColumnLimit: 60}'], input=code, encoding='utf-8') 58 | 59 | 60 | def generate_item(index, item, indent=0, item_header=[''], subitem=False): 61 | md = '' 62 | p = ' ' * indent 63 | 64 | if not subitem: 65 | md += '---\n' 66 | 67 | name = item['name'] 68 | if re.match(r'\(unnamed.*\)', name): 69 | name = '(unnamed)' 70 | if re.match(r'\(anonymous.*\)', name): 71 | name = '(anonymous)' 72 | 73 | if name != item_header[0]: 74 | hdr = '### `' if subitem else '## `' 75 | md += p + hdr + name + '` ' + item['type'] + '\n\n' 76 | item_header[0] = name 77 | if item['type'] not in ['enumerator']: 78 | md += generator.padding('```c++\n' + clang_format(item['definition']) + 79 | '\n```\n\n', p=p, remove_empty_lines=False) + '\n' 80 | 81 | description = item['description'] 82 | if isinstance(description, collections.abc.Mapping) and 'copy' in description: 83 | for it in index['index']: 84 | if it['qualifiedname'] == description['copy']: 85 | description = it['description'] 86 | break 87 | 88 | description = generate_descriptions(description) 89 | md += generator.padding(description, p=p, remove_empty_lines=False) + '\n\n' 90 | if item['type'] not in ['enumerator'] and item['source'] != "": 91 | md += p+'??? abstract "Source code"\n' 92 | md += generator.padding('```c++\n' + item['source'] + 93 | '\n```\n', p=p+' ', remove_empty_lines=False) + '\n' 94 | url = index['repository'].replace( 95 | "{FILE}", item['file']).replace("{LINE}", str(item['line'])) 96 | md += p+' '+'[' + url + ']('+url+')\n' 97 | md += '\n\n' 98 | if 'content' in item: 99 | for it in item['content']: 100 | md += generate_item(index, it, indent, item_header, True) 101 | md += '\n' 102 | return md 103 | 104 | 105 | def generate_group(index, items: List[Dict], name): 106 | md = '' 107 | md += '# ' + name + '\n' 108 | md += '\n\n' 109 | 110 | for item in groupItems: 111 | md += generate_item(index, item) 112 | 113 | repository = index['repository'].replace( 114 | '{FILE}', '').replace('{LINE}', '') 115 | md += '\n----\n' 116 | md += 'Auto-generated from sources, Revision {0}, [{1}]({1})'.format( 117 | index['git_tag'], re.sub(r'^([^\#]+).*', r'\1', repository)) 118 | return md 119 | 120 | 121 | def generate_index(index, sortedIndex: List[Dict]): 122 | md = '# Index\n\n' 123 | 124 | item_header = '' 125 | 126 | letter = '' 127 | for item in sortedIndex: 128 | if item['name'] != item_header: 129 | item_header = item['name'] 130 | l: str = item['name'][0] 131 | if l != letter: 132 | md += '## ' + l.capitalize() + '\n\n' 133 | letter = l 134 | md += '[{} (C++ {})]({}.md#{})\n\n'.format(item['name'], item['type'], 135 | item['group'] or 'default', item['name'] + '-' + item['type']) 136 | return md 137 | 138 | 139 | if __name__ == "__main__": 140 | 141 | parser = argparse.ArgumentParser( 142 | description='Generate Markdown documentation from C++ index') 143 | parser.add_argument('index_path', help='path to generated index (JSON)') 144 | parser.add_argument('output_path', 145 | help='directory where generated documentation will be written') 146 | parser.add_argument( 147 | '--refindex', help='generate alphabetical index', action='store_true') 148 | 149 | args = parser.parse_args() 150 | 151 | index = json.load(open(args.index_path, "r", encoding="utf-8")) 152 | groups = generator.groupList(index['index']) 153 | 154 | group_names = index['groups'] or dict() 155 | 156 | for g in groups: 157 | print('Generating {}...'.format(g)) 158 | groupItems = generator.filterIndex(index['index'], g) 159 | groupItems.sort(key=lambda x: x['name']) 160 | g = g or 'default' 161 | md = generate_group(index, 162 | groupItems, (group_names[g] if g in group_names else g)) 163 | 164 | if md.startswith('---\n'): 165 | md = md[4:] 166 | 167 | wpath = os.path.join(args.output_path, g + '.md') 168 | print('Writing' + wpath + '...') 169 | open(wpath, 'w', encoding='utf-8').write(md) 170 | 171 | if args.refindex: 172 | print('Generating index...') 173 | sortedIndex = index['index'].copy() 174 | sortedIndex.sort(key=lambda x: x['name']) 175 | 176 | md = generate_index(index, sortedIndex) 177 | 178 | open(os.path.join(args.output_path, 'refindex.md'), 179 | 'w', encoding='utf-8').write(md) 180 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # sources: /src 4 | # docs: /src/$1 5 | # index: /data 6 | # output: /out 7 | # md output: /out/docs 8 | # html output: /out/site 9 | 10 | set -e 11 | 12 | mkdir -p /out/docs/auto 13 | 14 | echo copy /src/${1}/. to /out 15 | cp -a /src/${1}/. /out 16 | 17 | export PATH=/usr/lib/llvm19/bin:$PATH 18 | 19 | # On Alpine 3.20.3, some libclang symlinks are broken, so using the real path here 20 | python3 cparser.py --libclang /usr/lib/llvm19/lib/libclang.so.19.1.4 --output /data/cxxindex.json --git /src/${1}/cxxdox.yml /src 21 | 22 | python3 generator_markdown.py --refindex /data/cxxindex.json /out/docs/auto 23 | 24 | mkdir -p /out/fragments 25 | python3 run_examples.py /out/docs /src/${1}/template /src 26 | 27 | cd /out 28 | 29 | mkdocs build 30 | -------------------------------------------------------------------------------- /run_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import glob 4 | import codecs 5 | import re 6 | import subprocess 7 | import tempfile 8 | import sys 9 | 10 | exec_suffix = '.exe' if sys.platform.startswith('win32') else '' 11 | 12 | 13 | def execute_example(name, code, template_path, output_path, include_dirs): 14 | print(name, '...') 15 | 16 | builddir = tempfile.mkdtemp() 17 | print(builddir) 18 | 19 | cppfile = os.path.join(builddir, 'main.hpp') 20 | 21 | open(cppfile, 'w').write(code) 22 | 23 | if subprocess.call(['cmake', '-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=' + builddir + '', '-DCMAKE_BUILD_TYPE=Release', '-DCMAKE_CXX_FLAGS=-I\"' + include_dirs + '\" -DSNIPPET=\\"' + cppfile + '\\"', template_path], cwd=builddir) == 0: 24 | 25 | if subprocess.call(['cmake', '--build', '.', '--config', 'Release'], cwd=builddir) == 0: 26 | subprocess.call(['ls', '-la'], cwd=builddir) 27 | subprocess.call(['./main'], cwd=builddir) 28 | output = subprocess.check_output([os.path.join(builddir, 'main')+exec_suffix], cwd=builddir) 29 | 30 | print('ok, writing output...') 31 | 32 | open(os.path.join(output_path, name) + '.md', 33 | 'w').write('```\n' + output.decode('utf-8').strip() + '\n```') 34 | 35 | else: 36 | print('Can`t configure example: {}'.format(name)) 37 | 38 | 39 | if __name__ == "__main__": 40 | 41 | parser = argparse.ArgumentParser( 42 | description='Run markdown code examples and save output & images') 43 | parser.add_argument('input_path', help='path to markdown files') 44 | parser.add_argument('template_path', help='path C++ project template') 45 | parser.add_argument('include_dirs', help='include directories') 46 | 47 | args = parser.parse_args() 48 | 49 | print('Running examples in ', args.input_path + os.path.sep + '*.md', '...') 50 | 51 | filenames = glob.glob( 52 | args.input_path + os.path.sep + '*.md', recursive=True) 53 | 54 | for filename in filenames: 55 | print(filename, '...') 56 | md = codecs.open(filename, 'r', encoding='utf-8').read() 57 | for r in re.finditer(r'```c\+\+ tab="([a-zA-Z0-9_-]).cpp"\s+(.*?)\s+```', md, re.DOTALL | re.MULTILINE): 58 | execute_example(r.group(1), r.group(2), args.template_path, os.path.join( 59 | args.input_path, 'fragments'), args.include_dirs) 60 | -------------------------------------------------------------------------------- /test/cxxdox.yml: -------------------------------------------------------------------------------- 1 | title: TEST 2 | 3 | postprocessor: 4 | ignore: 5 | - TEST_EMPTY_DEFINE 6 | 7 | input_directory: . 8 | 9 | masks: ['**/*.hpp'] 10 | 11 | groups: 12 | main: "Main" -------------------------------------------------------------------------------- /test/docs/index.md: -------------------------------------------------------------------------------- 1 | # Test 2 | 3 | ```c++ tab="1.cpp" 4 | test_output(); 5 | // comment 6 | ``` 7 | 8 | Output: 9 | --8<-- "1.md" 10 | -------------------------------------------------------------------------------- /test/docs/js/math.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var katexMath = (function () { 5 | var maths = document.querySelectorAll('.arithmatex'), 6 | tex; 7 | 8 | for (var i = 0; i < maths.length; i++) { 9 | tex = maths[i].textContent || maths[i].innerText; 10 | if (tex.startsWith('\\(') && tex.endsWith('\\)')) { 11 | katex.render(tex.slice(2, -2), maths[i], { 'displayMode': false }); 12 | } else if (tex.startsWith('\\[') && tex.endsWith('\\]')) { 13 | katex.render(tex.slice(2, -2), maths[i], { 'displayMode': true }); 14 | } 15 | } 16 | }); 17 | 18 | (function () { 19 | var onReady = function onReady(fn) { 20 | if (document.addEventListener) { 21 | document.addEventListener("DOMContentLoaded", fn); 22 | } else { 23 | document.attachEvent("onreadystatechange", function () { 24 | if (document.readyState === "interactive") { 25 | fn(); 26 | } 27 | }); 28 | } 29 | }; 30 | 31 | onReady(function () { 32 | if (typeof katex !== "undefined") { 33 | katexMath(); 34 | } 35 | }); 36 | })(); 37 | 38 | /*function load_navpane() { 39 | var width = window.innerWidth; 40 | if (width <= 1200) { 41 | return; 42 | } 43 | 44 | var nav = document.getElementsByClassName("md-nav"); 45 | for(var i = 0; i < nav.length; i++) { 46 | if (typeof nav.item(i).style === "undefined") { 47 | continue; 48 | } 49 | 50 | if (nav.item(i).getAttribute("data-md-level") && nav.item(i).getAttribute("data-md-component")) { 51 | nav.item(i).style.display = 'block'; 52 | nav.item(i).style.overflow = 'visible'; 53 | } 54 | } 55 | 56 | var nav = document.getElementsByClassName("md-nav__toggle"); 57 | for(var i = 0; i < nav.length; i++) { 58 | nav.item(i).checked = true; 59 | } 60 | } 61 | 62 | document.addEventListener("DOMContentLoaded", function() { 63 | load_navpane(); 64 | });*/ 65 | 66 | }()); 67 | -------------------------------------------------------------------------------- /test/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Test 2 | theme: 3 | name: 'material' 4 | feature: 5 | tabs: true 6 | extra: 7 | search: 8 | language: en 9 | 10 | social: 11 | - type: 'github' 12 | link: 'https://github.com/kfrlib' 13 | - type: 'twitter' 14 | link: 'https://twitter.com/kfrlib' 15 | 16 | copyright: 'Copyright © 2016 - 2019 KFR' 17 | 18 | plugins: 19 | - search 20 | 21 | markdown_extensions: 22 | - admonition 23 | - codehilite: 24 | guess_lang: false 25 | - footnotes 26 | - meta 27 | - toc: 28 | permalink: true 29 | - pymdownx.arithmatex: 30 | generic: true 31 | - pymdownx.inlinehilite 32 | - pymdownx.superfences 33 | - pymdownx.details 34 | - pymdownx.snippets: 35 | check_paths: true 36 | base_path: docs/fragments 37 | 38 | extra_javascript: 39 | - js/math.js 40 | - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.js 41 | 42 | extra_css: 43 | - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.css 44 | 45 | repo_url: https://github.com/kfrlib/kfr 46 | repo_name: KFR 47 | 48 | nav: 49 | - index.md 50 | - default.md 51 | - refindex.md 52 | -------------------------------------------------------------------------------- /test/template/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(main CXX) 4 | 5 | add_executable(main main.cpp) 6 | -------------------------------------------------------------------------------- /test/template/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -------------------------------------------------------------------------------- /test/template/main.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | 3 | int main() 4 | { 5 | #include SNIPPET 6 | } 7 | -------------------------------------------------------------------------------- /test/test.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /// regular variable 5 | int regular_variable; 6 | 7 | /// regular constant 8 | const int regular_constant = 1; 9 | 10 | /// template variable 11 | template 12 | T template_variable; 13 | 14 | /// template constant 15 | template 16 | const T template_constant = T(1); 17 | 18 | /// regular typedef 19 | using regular_typedef = int; 20 | 21 | /// template typedef 22 | template 23 | using template_typedef = T; 24 | 25 | /// Struct 26 | struct aaa; 27 | 28 | /// regular function 29 | void regular_function(int, float f); 30 | 31 | /// template function 32 | template 33 | T template_function(int, T); 34 | 35 | /// overloaded function 1 36 | void overloaded_function(int, int, int); 37 | 38 | /// overloaded `function` 2 39 | void overloaded_function(int, int, const char*); 40 | 41 | /// overloaded @c function 3 42 | void overloaded_function(int, regular_typedef); 43 | 44 | /// @copybrief overloaded_function 45 | void overloaded_function(int, aaa*); 46 | 47 | /// regular class 48 | class regular_class 49 | { 50 | public: 51 | /// regular class constructor 52 | regular_class(); 53 | /// regular class destructor 54 | ~regular_class(); 55 | 56 | /// regular class constructor 57 | regular_class(regular_class&&); 58 | 59 | /// regular class constructor 60 | regular_class(const regular_class&); 61 | 62 | /// regular class regular method 63 | int regular_method(); 64 | 65 | /// regular class template method 66 | template 67 | T template_method(); 68 | 69 | /// regular class template struct 70 | template 71 | struct template_nested_struct 72 | { 73 | }; 74 | }; 75 | 76 | /// class enum 77 | enum class class_enum 78 | { 79 | A = 0, ///< value of A 80 | B = 1 ///< value of B 81 | }; 82 | 83 | /// template class 84 | template 85 | class template_class 86 | { 87 | public: 88 | /// template class constructor 89 | template_class(); 90 | /// template class destructor 91 | ~template_class(); 92 | 93 | /// template class constructor 94 | template_class(template_class&&); 95 | 96 | /// template class constructor 97 | template_class(const template_class&); 98 | 99 | /// template class regular method 100 | int regular_method(); 101 | 102 | /// template class template method 103 | template 104 | U template_method(T); 105 | 106 | /// template class template struct 107 | template 108 | struct template_nested_struct 109 | { 110 | }; 111 | }; 112 | 113 | /// regular namespace 114 | namespace regular_namespace 115 | { 116 | /// regular namespace functions 117 | void namespace_function(); 118 | } // namespace regular_namespace 119 | 120 | /// formula: 121 | /// \f[ 122 | /// E=mc^2 123 | /// \f] 124 | void math(); 125 | 126 | 127 | inline void test_output() 128 | { 129 | std::printf("Hello, world!\n"); 130 | } --------------------------------------------------------------------------------