|
10 | 10 |
|
11 | 11 | SPDX-License-Identifier: Apache-2.0 |
12 | 12 | ----------------------------------------------------------------------------- --> |
| 13 | + |
| 14 | +# PlantUML Linker |
| 15 | + |
| 16 | +The **linker** reads FlatBuffers `.fbs.bin` files produced by the PlantUML parser and generates a `plantuml_links.json` file consumed by the [`clickable_plantuml`](../sphinx/clickable_plantuml/) Sphinx extension to make diagrams interactive. |
| 17 | + |
| 18 | +## How It Works |
| 19 | + |
| 20 | +``` |
| 21 | +.puml files |
| 22 | + │ |
| 23 | + ▼ |
| 24 | +┌──────────┐ ┌────────────┐ ┌──────────────────────┐ ┌──────────────────┐ |
| 25 | +│ Parser │ ──▶ │ .fbs.bin │ ──▶ │ Linker │ ──▶ │ plantuml_links │ |
| 26 | +│ (puml_cli)│ │ (FlatBuf) │ │ (cross-diagram match)│ │ .json │ |
| 27 | +└──────────┘ └────────────┘ └──────────────────────┘ └──────────────────┘ |
| 28 | + │ |
| 29 | + ▼ |
| 30 | + ┌──────────────────┐ |
| 31 | + │ clickable_plantuml│ |
| 32 | + │ (Sphinx extension)│ |
| 33 | + └──────────────────┘ |
| 34 | +``` |
| 35 | + |
| 36 | +### Supported Diagram Types |
| 37 | + |
| 38 | +| Diagram Type | File ID | What Is Extracted | |
| 39 | +|---|---|---| |
| 40 | +| **Component** (`COMP`) | Alias, name, FQN (`id`), parent hierarchy, and relation targets | |
| 41 | +| **Class** (`CLSD`) | Entity name, FQN (`id`), diagram name, and relationship sources/targets | |
| 42 | +| **Sequence** (`SEQD`) | Unique participants (callers and callees from interactions) | |
| 43 | + |
| 44 | +### Link Generation Algorithm |
| 45 | + |
| 46 | +1. **Parse** each `.fbs.bin` file, detecting the diagram type by its 4-byte file identifier at bytes 4–7. |
| 47 | +2. **Build an index** of top-level aliases — components/entities with no parent — mapped to their source `.puml` file. Both the PlantUML alias and the FQN (`id`) are registered as index keys, along with any distinct `name`. |
| 48 | +3. **Register diagram names** (from `@startuml <name>`) as virtual top-level aliases, enabling cross-diagram linking by title. |
| 49 | +4. **Extract relationship targets**: relation targets from component diagrams and relationship sources/targets from class diagrams are added as linkable elements. |
| 50 | +5. **Match**: for each element alias in diagram A, if a top-level entry with the same name exists in diagram B, emit a link `A → B`. |
| 51 | +6. **Deduplicate** and keep one target per alias (PlantUML supports only one `url of` per alias). |
| 52 | + |
| 53 | +### Output Format |
| 54 | + |
| 55 | +```json |
| 56 | +{ |
| 57 | + "links": [ |
| 58 | + { |
| 59 | + "source_file": "overview.puml", |
| 60 | + "source_id": "MyComponent", |
| 61 | + "target_file": "my_component_detail.puml" |
| 62 | + } |
| 63 | + ] |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +## Usage |
| 68 | + |
| 69 | +```bash |
| 70 | +bazel run //plantuml/linker -- \ |
| 71 | + --fbs-files path/to/*.fbs.bin \ |
| 72 | + --output plantuml_links.json \ |
| 73 | + --log-level info |
| 74 | +``` |
| 75 | + |
| 76 | +### Arguments |
| 77 | + |
| 78 | +| Argument | Default | Description | |
| 79 | +|---|---|---| |
| 80 | +| `--fbs-files` | *(required)* | One or more `.fbs.bin` FlatBuffer files to process | |
| 81 | +| `--output` | `plantuml_links.json` | Output JSON file path | |
| 82 | +| `--log-level` | `warn` | Log verbosity: `error`, `warn`, `info`, `debug`, `trace` | |
| 83 | + |
| 84 | +## Build & Test |
| 85 | + |
| 86 | +```bash |
| 87 | +# Build |
| 88 | +bazel build //plantuml/linker |
| 89 | + |
| 90 | +# Build with Clippy lint |
| 91 | +bazel build //plantuml/linker --config=clippy |
| 92 | + |
| 93 | +# Run tests |
| 94 | +bazel test //plantuml/linker:linker_test |
| 95 | +``` |
| 96 | + |
| 97 | +## Linking Examples |
| 98 | + |
| 99 | +### Component ↔ Component (alias match) |
| 100 | + |
| 101 | +A top-level component in one diagram links to its detailed view in another: |
| 102 | + |
| 103 | +```plantuml |
| 104 | +' overview.puml |
| 105 | +@startuml |
| 106 | +[AuthService] --> [UserDB] |
| 107 | +@enduml |
| 108 | +``` |
| 109 | + |
| 110 | +```plantuml |
| 111 | +' auth_detail.puml |
| 112 | +@startuml |
| 113 | +package AuthService { |
| 114 | + [TokenManager] |
| 115 | + [SessionStore] |
| 116 | +} |
| 117 | +@enduml |
| 118 | +``` |
| 119 | + |
| 120 | +**Result:** `AuthService` in `overview.puml` becomes clickable → navigates to `auth_detail.puml`. |
| 121 | + |
| 122 | +### Component ↔ Sequence (participant match) |
| 123 | + |
| 124 | +Sequence diagram participants link back to component diagrams that define them: |
| 125 | + |
| 126 | +```plantuml |
| 127 | +' login_flow.puml |
| 128 | +@startuml |
| 129 | +AuthService -> TokenManager : validate() |
| 130 | +TokenManager -> SessionStore : getSession() |
| 131 | +@enduml |
| 132 | +``` |
| 133 | + |
| 134 | +**Result:** `AuthService`, `TokenManager`, and `SessionStore` in `login_flow.puml` become clickable → navigate to `auth_detail.puml` (where they are top-level components). |
| 135 | + |
| 136 | +### Class ↔ Class (relationship target) |
| 137 | + |
| 138 | +Relationship targets in class diagrams link to the diagram where the target class is defined: |
| 139 | + |
| 140 | +```plantuml |
| 141 | +' models.puml |
| 142 | +@startuml |
| 143 | +class User { |
| 144 | + +name: String |
| 145 | +} |
| 146 | +class Order { |
| 147 | + +items: List |
| 148 | +} |
| 149 | +User --> Order : places |
| 150 | +@enduml |
| 151 | +``` |
| 152 | + |
| 153 | +```plantuml |
| 154 | +' order_detail.puml |
| 155 | +@startuml |
| 156 | +class Order { |
| 157 | + +items: List |
| 158 | + +total: float |
| 159 | +} |
| 160 | +class Payment { |
| 161 | + +amount: float |
| 162 | +} |
| 163 | +Order --> Payment |
| 164 | +@enduml |
| 165 | +``` |
| 166 | + |
| 167 | +**Result:** `Order` in `models.puml` becomes clickable → navigates to `order_detail.puml`. `Payment` extracted from the relationship also becomes linkable. |
| 168 | + |
| 169 | +### FQN / ID match |
| 170 | + |
| 171 | +When a component's fully qualified `id` (e.g. `auth.TokenManager`) is indexed, diagrams referencing that FQN can match even when the PlantUML alias differs: |
| 172 | + |
| 173 | +```plantuml |
| 174 | +' system.puml — component with id "auth.TokenManager", alias "TokenManager" |
| 175 | +``` |
| 176 | + |
| 177 | +Other diagrams with a top-level element named `auth.TokenManager` will link to `system.puml`. |
| 178 | + |
| 179 | +### Diagram name match |
| 180 | + |
| 181 | +A diagram's `@startuml <name>` title acts as a virtual alias: |
| 182 | + |
| 183 | +```plantuml |
| 184 | +' auth_detail.puml |
| 185 | +@startuml auth_detail |
| 186 | +package AuthService { ... } |
| 187 | +@enduml |
| 188 | +``` |
| 189 | + |
| 190 | +Any component with alias `auth_detail` in another diagram links to `auth_detail.puml`. |
| 191 | + |
| 192 | +### Component name + alias (both registered) |
| 193 | + |
| 194 | +When a component has both an `alias` and a distinct `name`, both are registered as linkable elements: |
| 195 | + |
| 196 | +```plantuml |
| 197 | +' services.puml |
| 198 | +@startuml |
| 199 | +[Authentication Service] as AuthSvc |
| 200 | +@enduml |
| 201 | +``` |
| 202 | + |
| 203 | +**Result:** Both `AuthSvc` (alias) and `Authentication Service` (name) are indexed. A component named `Authentication Service` in another diagram will link here. |
| 204 | + |
| 205 | +### Component relation targets |
| 206 | + |
| 207 | +Dependency arrows extract their targets as linkable elements, even when the target isn't explicitly declared as a separate component: |
| 208 | + |
| 209 | +```plantuml |
| 210 | +' gateway.puml |
| 211 | +@startuml |
| 212 | +[APIGateway] --> [AuthService] : authenticates |
| 213 | +[APIGateway] --> [RateLimiter] : throttles |
| 214 | +@enduml |
| 215 | +``` |
| 216 | + |
| 217 | +**Result:** `AuthService` and `RateLimiter` are extracted from relation targets. If either is a top-level component in another diagram, the arrow target becomes clickable. |
| 218 | + |
| 219 | +### Class ↔ Component (cross-type linking) |
| 220 | + |
| 221 | +Class entity names are matched against component aliases across diagram types: |
| 222 | + |
| 223 | +```plantuml |
| 224 | +' class_model.puml |
| 225 | +@startuml |
| 226 | +class AuthService { |
| 227 | + +login(user: String): Token |
| 228 | +} |
| 229 | +@enduml |
| 230 | +``` |
| 231 | + |
| 232 | +```plantuml |
| 233 | +' system_overview.puml (component diagram) |
| 234 | +@startuml |
| 235 | +[AuthService] --> [UserDB] |
| 236 | +@enduml |
| 237 | +``` |
| 238 | + |
| 239 | +**Result:** `AuthService` in `system_overview.puml` becomes clickable → navigates to `class_model.puml` (where it is defined as a class entity). |
| 240 | + |
| 241 | +### Sequence ↔ Class (cross-type linking) |
| 242 | + |
| 243 | +Sequence diagram participants match class entity names from class diagrams: |
| 244 | + |
| 245 | +```plantuml |
| 246 | +' login_flow.puml |
| 247 | +@startuml |
| 248 | +AuthService -> TokenManager : validate() |
| 249 | +@enduml |
| 250 | +``` |
| 251 | + |
| 252 | +```plantuml |
| 253 | +' token_classes.puml |
| 254 | +@startuml |
| 255 | +class TokenManager { |
| 256 | + +validate(): bool |
| 257 | +} |
| 258 | +@enduml |
| 259 | +``` |
| 260 | + |
| 261 | +**Result:** `TokenManager` in `login_flow.puml` becomes clickable → navigates to `token_classes.puml`. |
| 262 | + |
| 263 | +## Related Changes |
| 264 | + |
| 265 | +The linker works in concert with two other modified components: |
| 266 | + |
| 267 | +- **`puml_serializer` ([class_serializer.rs](../parser/puml_serializer/src/serialize/class_serializer.rs))**: Prepends the actual source filename to the serialized `source_files` vector so the linker can correlate class diagrams with their `.puml` file. |
| 268 | + |
| 269 | +- **`clickable_plantuml` ([clickable_plantuml.py](../sphinx/clickable_plantuml/clickable_plantuml.py))**: Extended alias formatting to accept hyphens and dots in element identifiers (e.g., `my-component`, `pkg.Class`), enabling `url of` directives for FQN-style names. |
0 commit comments