|
| 1 | +# Documentation Guide |
| 2 | + |
| 3 | +This guide explains how to set up automated API reference documentation generation using the `DocumenterReference` extension. This is particularly useful for maintaining comprehensive and up-to-date API documentation as your codebase evolves. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The `DocumenterReference` extension provides the `CTBase.automatic_reference_documentation()` function, which automatically generates API reference pages from your Julia source code. It: |
| 8 | + |
| 9 | +- Extracts docstrings from your modules |
| 10 | +- Separates public and private APIs |
| 11 | +- Generates markdown files suitable for Documenter.jl |
| 12 | +- Handles extensions and optional dependencies gracefully |
| 13 | +- Supports filtering and customization |
| 14 | + |
| 15 | +## Architecture |
| 16 | + |
| 17 | +### Directory Structure |
| 18 | + |
| 19 | +```text |
| 20 | +docs/ |
| 21 | +├── make.jl # Main documentation build script |
| 22 | +├── api_reference.jl # API reference generation logic |
| 23 | +└── src/ |
| 24 | + ├── index.md # Documentation homepage |
| 25 | + ├── developers-guide.md # Testing and coverage guide |
| 26 | + └── documentation-guide.md # This file |
| 27 | +``` |
| 28 | + |
| 29 | +### How It Works |
| 30 | + |
| 31 | +The documentation generation happens in two stages: |
| 32 | + |
| 33 | +1. **`api_reference.jl`**: Defines `generate_api_reference()` which calls `CTBase.automatic_reference_documentation()` for each module |
| 34 | +2. **`make.jl`**: Calls `with_api_reference()` which executes the generation and passes the pages to `Documenter.makedocs()` |
| 35 | + |
| 36 | +## Setting Up API Documentation |
| 37 | + |
| 38 | +### Basic Configuration |
| 39 | + |
| 40 | +The core function is `CTBase.automatic_reference_documentation()`. Here's a minimal example: |
| 41 | + |
| 42 | +```julia |
| 43 | +using CTBase |
| 44 | +using Documenter |
| 45 | + |
| 46 | +CTBase.automatic_reference_documentation(; |
| 47 | + subdirectory=".", |
| 48 | + primary_modules=[MyModule => ["src/MyModule.jl"]], |
| 49 | + title="MyModule API", |
| 50 | + title_in_menu="API", |
| 51 | + filename="api", |
| 52 | +) |
| 53 | +``` |
| 54 | + |
| 55 | +### Key Parameters |
| 56 | + |
| 57 | +- **`subdirectory`**: Where to write generated markdown files (relative to `docs/src`) |
| 58 | +- **`primary_modules`**: Vector of modules to document, optionally with source files |
| 59 | + - Format: `Module` or `Module => ["path/to/file.jl"]` |
| 60 | + - When source files are provided, only symbols from those files are documented |
| 61 | +- **`title`**: Title displayed at the top of the generated page |
| 62 | +- **`title_in_menu`**: Title in the navigation menu (defaults to `title`) |
| 63 | +- **`filename`**: Base filename for the markdown file (without `.md` extension) |
| 64 | +- **`exclude`**: Vector of symbol names to skip from documentation |
| 65 | +- **`public`**: Generate public API page (default: `true`) |
| 66 | +- **`private`**: Generate private API page (default: `true`) |
| 67 | +- **`external_modules_to_document`**: Additional modules to search for docstrings (e.g., `[Base]`) |
| 68 | + |
| 69 | +### Public vs. Private API |
| 70 | + |
| 71 | +The `public` and `private` flags control which symbols are documented: |
| 72 | + |
| 73 | +#### Option 1: Public API Only (`public=true, private=false`) |
| 74 | + |
| 75 | +```julia |
| 76 | +CTBase.automatic_reference_documentation(; |
| 77 | + subdirectory=".", |
| 78 | + primary_modules=[MyModule => src("MyModule.jl")], |
| 79 | + public=true, |
| 80 | + private=false, |
| 81 | + title="MyModule API", |
| 82 | + filename="api", |
| 83 | +) |
| 84 | +``` |
| 85 | + |
| 86 | +**Result**: Only exported symbols are documented. This is ideal for end-user documentation. |
| 87 | + |
| 88 | +#### Option 2: Private API Only (`public=false, private=true`) |
| 89 | + |
| 90 | +```julia |
| 91 | +CTBase.automatic_reference_documentation(; |
| 92 | + subdirectory=".", |
| 93 | + primary_modules=[MyModule => src("MyModule.jl")], |
| 94 | + public=false, |
| 95 | + private=true, |
| 96 | + title="MyModule Internals", |
| 97 | + filename="internals", |
| 98 | +) |
| 99 | +``` |
| 100 | + |
| 101 | +**Result**: Only non-exported (private) symbols are documented. Useful for developer documentation. |
| 102 | + |
| 103 | +#### Option 3: Both Public and Private (`public=true, private=true`) |
| 104 | + |
| 105 | +```julia |
| 106 | +CTBase.automatic_reference_documentation(; |
| 107 | + subdirectory=".", |
| 108 | + primary_modules=[MyModule => src("MyModule.jl")], |
| 109 | + public=true, |
| 110 | + private=true, |
| 111 | + title="MyModule Complete Reference", |
| 112 | + filename="complete_api", |
| 113 | +) |
| 114 | +``` |
| 115 | + |
| 116 | +**Result**: All symbols are documented in a single page. This provides a comprehensive reference. |
| 117 | + |
| 118 | +### Example: CTBase Configuration |
| 119 | + |
| 120 | +Here's how CTBase configures its API documentation in `docs/api_reference.jl`: |
| 121 | + |
| 122 | +```julia |
| 123 | +function generate_api_reference(src_dir::String) |
| 124 | + # Helper functions to build absolute paths |
| 125 | + src(files...) = [abspath(joinpath(src_dir, f)) for f in files] |
| 126 | + ext_dir = abspath(joinpath(src_dir, "..", "ext")) |
| 127 | + ext(files...) = [abspath(joinpath(ext_dir, f)) for f in files] |
| 128 | + |
| 129 | + # Symbols to exclude from all API pages |
| 130 | + EXCLUDE_SYMBOLS = Symbol[:include, :eval] |
| 131 | + |
| 132 | + pages = [ |
| 133 | + # Main CTBase module - private API only |
| 134 | + CTBase.automatic_reference_documentation(; |
| 135 | + subdirectory=".", |
| 136 | + primary_modules=[CTBase => src("CTBase.jl")], |
| 137 | + exclude=EXCLUDE_SYMBOLS, |
| 138 | + public=false, |
| 139 | + private=true, |
| 140 | + title="CTBase", |
| 141 | + title_in_menu="CTBase", |
| 142 | + filename="ctbase", |
| 143 | + ), |
| 144 | + # Other modules... |
| 145 | + ] |
| 146 | + |
| 147 | + # Extensions are checked with Base.get_extension |
| 148 | + DocumenterReference = Base.get_extension(CTBase, :DocumenterReference) |
| 149 | + if !isnothing(DocumenterReference) |
| 150 | + push!( |
| 151 | + pages, |
| 152 | + CTBase.automatic_reference_documentation(; |
| 153 | + subdirectory=".", |
| 154 | + primary_modules=[DocumenterReference => ext("DocumenterReference.jl")], |
| 155 | + external_modules_to_document=[CTBase], |
| 156 | + exclude=EXCLUDE_SYMBOLS, |
| 157 | + public=false, |
| 158 | + private=true, |
| 159 | + title="DocumenterReference", |
| 160 | + title_in_menu="DocumenterReference", |
| 161 | + filename="documenter_reference", |
| 162 | + ), |
| 163 | + ) |
| 164 | + end |
| 165 | + |
| 166 | + return pages |
| 167 | +end |
| 168 | +``` |
| 169 | + |
| 170 | +## Handling Extensions |
| 171 | + |
| 172 | +When your package uses extensions (weak dependencies), you need to check if they're loaded before documenting them: |
| 173 | + |
| 174 | +```julia |
| 175 | +# Check if the extension is loaded |
| 176 | +MyExtension = Base.get_extension(MyPackage, :MyExtension) |
| 177 | +if !isnothing(MyExtension) |
| 178 | + push!( |
| 179 | + pages, |
| 180 | + CTBase.automatic_reference_documentation(; |
| 181 | + subdirectory=".", |
| 182 | + primary_modules=[MyExtension => ext("MyExtension.jl")], |
| 183 | + external_modules_to_document=[MyPackage], |
| 184 | + exclude=EXCLUDE_SYMBOLS, |
| 185 | + public=false, |
| 186 | + private=true, |
| 187 | + title="MyExtension", |
| 188 | + title_in_menu="MyExtension", |
| 189 | + filename="my_extension", |
| 190 | + ), |
| 191 | + ) |
| 192 | +end |
| 193 | +``` |
| 194 | + |
| 195 | +This ensures that: |
| 196 | + |
| 197 | +- Documentation is only generated if the extension is actually loaded |
| 198 | +- The extension can reference types and functions from the main package via `external_modules_to_document` |
| 199 | + |
| 200 | +## Integration with Documenter.jl |
| 201 | + |
| 202 | +In `docs/make.jl`, use `with_api_reference()` to integrate the generated pages: |
| 203 | + |
| 204 | +```julia |
| 205 | +using Documenter |
| 206 | +using CTBase |
| 207 | + |
| 208 | +include("api_reference.jl") |
| 209 | + |
| 210 | +with_api_reference(dirname(@__DIR__)) do api_pages |
| 211 | + makedocs(; |
| 212 | + modules=[CTBase], |
| 213 | + authors="Your Name", |
| 214 | + repo="https://github.com/yourname/yourpackage.jl", |
| 215 | + sitename="YourPackage.jl", |
| 216 | + format=Documenter.HTML(; |
| 217 | + assets=String[], |
| 218 | + ), |
| 219 | + pages=[ |
| 220 | + "Introduction" => "index.md", |
| 221 | + "Developers Guide" => "developers-guide.md", |
| 222 | + "Documentation Guide" => "documentation-guide.md", |
| 223 | + "API Reference" => api_pages, |
| 224 | + ], |
| 225 | + checkdocs=:none, |
| 226 | + ) |
| 227 | +end |
| 228 | +``` |
| 229 | + |
| 230 | +The `with_api_reference()` function: |
| 231 | + |
| 232 | +1. Generates the API reference pages |
| 233 | +2. Passes them to your `makedocs()` call |
| 234 | +3. Cleans up temporary generated files after the build |
| 235 | + |
| 236 | +## DocType System |
| 237 | + |
| 238 | +The `DocumenterReference` extension recognizes several documentation element types: |
| 239 | + |
| 240 | +- **`DOCTYPE_ABSTRACT_TYPE`**: Abstract type declarations |
| 241 | +- **`DOCTYPE_STRUCT`**: Concrete struct types |
| 242 | +- **`DOCTYPE_FUNCTION`**: Functions and callables |
| 243 | +- **`DOCTYPE_MACRO`**: Macros (names starting with `@`) |
| 244 | +- **`DOCTYPE_MODULE`**: Submodules |
| 245 | +- **`DOCTYPE_CONSTANT`**: Constants and non-function values |
| 246 | + |
| 247 | +These types are automatically detected and organized in the generated documentation. |
| 248 | + |
| 249 | +## Best Practices |
| 250 | + |
| 251 | +1. **Exclude internal symbols**: Use the `exclude` parameter to hide implementation details |
| 252 | + |
| 253 | + ```julia |
| 254 | + exclude=Symbol[:_internal_helper, :_private_constant] |
| 255 | + ``` |
| 256 | + |
| 257 | +2. **Separate public and private**: Create separate pages for public and private APIs |
| 258 | + |
| 259 | + ```julia |
| 260 | + # Public API |
| 261 | + CTBase.automatic_reference_documentation(; |
| 262 | + ..., |
| 263 | + public=true, |
| 264 | + private=false, |
| 265 | + filename="api_public", |
| 266 | + ) |
| 267 | + # Private API |
| 268 | + CTBase.automatic_reference_documentation(; |
| 269 | + ..., |
| 270 | + public=false, |
| 271 | + private=true, |
| 272 | + filename="api_private", |
| 273 | + ) |
| 274 | + ``` |
| 275 | + |
| 276 | +3. **Document external modules**: Use `external_modules_to_document` to include methods from other packages |
| 277 | + |
| 278 | + ```julia |
| 279 | + CTBase.automatic_reference_documentation(; |
| 280 | + ..., |
| 281 | + external_modules_to_document=[Base, Documenter], |
| 282 | + ) |
| 283 | + ``` |
| 284 | + |
| 285 | +4. **Check extensions before documenting**: Always use `Base.get_extension()` to safely check for optional dependencies |
| 286 | + |
| 287 | + ```julia |
| 288 | + MyExt = Base.get_extension(MyPackage, :MyExtension) |
| 289 | + if !isnothing(MyExt) |
| 290 | + # Document the extension |
| 291 | + end |
| 292 | + ``` |
| 293 | + |
| 294 | +## Troubleshooting |
| 295 | + |
| 296 | +### Missing Docstrings |
| 297 | + |
| 298 | +If symbols appear without docstrings in the generated documentation, ensure: |
| 299 | + |
| 300 | +- The docstring is defined immediately before the symbol |
| 301 | +- The docstring uses the correct Julia docstring syntax (triple quotes) |
| 302 | +- The symbol is actually exported or included in your module |
| 303 | + |
| 304 | +### Symbols Not Appearing |
| 305 | + |
| 306 | +If expected symbols don't appear in the documentation: |
| 307 | + |
| 308 | +- Check if they're in the `exclude` list |
| 309 | +- Verify the source file path is correct |
| 310 | +- Ensure the symbol is defined in the specified source file (not imported) |
| 311 | + |
| 312 | +### Extension Not Documented |
| 313 | + |
| 314 | +If an extension's documentation isn't generated: |
| 315 | + |
| 316 | +- Verify the extension is loaded with `Base.get_extension()` |
| 317 | +- Check that the extension file path is correct |
| 318 | +- Ensure the extension module is properly defined |
| 319 | + |
| 320 | +## Summary |
| 321 | + |
| 322 | +The `DocumenterReference` extension provides a powerful, flexible system for automatically generating API documentation. By following the patterns shown in this guide, you can maintain comprehensive, up-to-date documentation with minimal manual effort. |
0 commit comments