metanorma-plugin-plantuml 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07e9ee8fdf5e99900198be1e8ea51dd948957c62475694b0b40660fd25bff4d2
4
- data.tar.gz: c6b49a7ec9b20d96e45c6600f2a368c3b5df542e6ee3bcc3f75ed17dd7d929bc
3
+ metadata.gz: c7c85595e30e3155c473b4f44936209c3f95c8ab5cf29d55a2368bad6584c136
4
+ data.tar.gz: d7e58a9934d9995c9b36c3b86dc543a7c633d0c31fab5dd543707b7f347207c8
5
5
  SHA512:
6
- metadata.gz: f9903747e63488c1af15f954441acbdaaa17c715935b1d0d390d515173f5e052df6defc0828de335e6491e019c69b9fa915365850a91a91155e5d68e3ee60376
7
- data.tar.gz: 73e6bac1cfc02b057d52eb39913bfb833f0b70237fb8d00323424ef17a6b5ea302baf57faae486dda1779f0254035a4b449e0abb9648c21ceb18e30a39234778
6
+ metadata.gz: '05039945787b1a1d65f8a37193db39346f2d42ac820dff54d70724fbb09bdbe2808a9ca20477a78e78b1357f65de337c289a075feabf27c9e3ae92264e671865'
7
+ data.tar.gz: 1cff1673fbed5b3e00a6e05d7642cccce29e6d5f5d2cd62500860fddf089a8fc7046a2c49df41b422804b03eb762bd0bb6ac21d67e8763e6d38583f29df4e0ed
@@ -10,6 +10,6 @@ on:
10
10
 
11
11
  jobs:
12
12
  rake:
13
- uses: metanorma/ci/.github/workflows/plantuml-rake.yml@main
13
+ uses: metanorma/ci/.github/workflows/generic-rake.yml@main
14
14
  secrets:
15
15
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
data/Gemfile CHANGED
@@ -17,7 +17,7 @@ gem "canon"
17
17
  gem "debug"
18
18
  gem "metanorma"
19
19
  gem "metanorma-standoc", github: "metanorma/metanorma-standoc",
20
- branch: "feature/extract-plantuml"
20
+ branch: "main"
21
21
  gem "rake"
22
22
  gem "rspec"
23
23
  gem "rspec-html-matchers"
data/README.adoc CHANGED
@@ -4,7 +4,7 @@ image:https://github.com/metanorma/metanorma-plugin-plantuml/workflows/rake/badg
4
4
 
5
5
  == Purpose
6
6
 
7
- This is a Metanorma plugin to enable usage of PlantUML diagrams in Metanorma
7
+ This Metanorma plugin enables the usage of PlantUML diagrams in Metanorma
8
8
  documents.
9
9
 
10
10
  It provides a Ruby API that wraps a bundled PlantUML JAR file.
@@ -59,12 +59,134 @@ $ gem install metanorma-plugin-plantuml
59
59
 
60
60
  == Usage
61
61
 
62
+ === General
63
+
62
64
  This plugin enables PlantUML diagram generation in Metanorma documents through
63
- the `plantuml` block directive.
65
+ multiple methods, including:
66
+
67
+ * Block syntax using `[plantuml]` blocks
68
+ * Command syntax using `plantuml_image::{path}[{options}]`
69
+
70
+ Both syntaxes support various diagram types, output formats, and image
71
+ attributes, as well as PlantUML's `include!` and `includesub!` directives for
72
+ modular diagrams.
73
+
74
+ The effect of both syntaxes is the same: they generate and embed PlantUML diagrams
75
+ as images in the output document.
76
+
77
+ However, there are important differences in using them with external files, as
78
+ it is described in the various options.
79
+
80
+ The difference lies in how the PlantUML source is provided:
81
+
82
+ * In the block syntax, the PlantUML source is defined directly within the
83
+ document, and hence generated by PlantUML without a specific file context.
84
+
85
+ * In the command syntax, the PlantUML source is read from an external file,
86
+ which provides a file context for the PlantUML source.
87
+
88
+ Generally, the command syntax is more suitable for referencing external PlantUML
89
+ files.
90
+
91
+
92
+ === Command syntax for external diagrams
93
+
94
+ PlantUML diagrams can be directly referenced from external files using the
95
+ `plantuml_image::{path}[{options}]` command.
96
+
97
+ Syntax:
98
+
99
+ [source,asciidoc]
100
+ ----
101
+ plantuml_image::{plantuml-source.puml}[{options}]
102
+ ----
103
+
104
+ Where,
105
+
106
+ `{plantuml-source.puml}`:: Path to the PlantUML source file, relative to the
107
+ path of the current file.
108
+
109
+ `{options}`:: (Optional) Comma-separated list of options to customize diagram
110
+ generation.
111
+
112
+ .Specifying an external PlantUML diagram using the `plantuml_image` command without options
113
+ [example]
114
+ ====
115
+ [source,asciidoc]
116
+ ----
117
+ plantuml_image::foo/bar.puml[]
118
+ ----
119
+
120
+ The file `foo/bar.puml` looks like:
121
+
122
+ [source,plantuml]
123
+ ----
124
+ @startuml
125
+ Alice -> Bob: Hello
126
+ Bob -> Alice: Hi there
127
+ @enduml
128
+ ----
129
+ ====
130
+
131
+ .Specifying an external PlantUML diagram using the `plantuml_image` command with options
132
+ [example]
133
+ ====
134
+ [source,asciidoc]
135
+ ----
136
+ plantuml_image::foo/bar.puml[format=svg]
137
+ ----
138
+
139
+ The file `foo/bar.puml` is the same as above.
140
+ The `format=svg` option specifies the output format as SVG.
141
+ ====
142
+
64
143
 
65
144
 
66
- === Basic syntax
145
+ === Block syntax for inline diagrams
146
+
147
+ PlantUML diagrams can be defined directly within your Metanorma document using
148
+ `[plantuml]` blocks.
149
+
150
+ Syntax:
67
151
 
152
+ [source,asciidoc]
153
+ ----
154
+ [plantuml]
155
+ ....
156
+ {PlantUML diagram source here}
157
+ ....
158
+ ----
159
+
160
+ Where,
161
+
162
+ `[plantuml]`:: Specifies that the block contains PlantUML source code.
163
+
164
+ `....`:: Delimiters that indicate the start and end of the PlantUML source code.
165
+
166
+ `{PlantUML diagram source here}`:: The actual PlantUML diagram definition using
167
+ PlantUML syntax.
168
+
169
+
170
+ The block can be used with or without options.
171
+
172
+ Syntax with options:
173
+
174
+ [source,asciidoc]
175
+ ----
176
+ [plantuml,{options}]
177
+ ....
178
+ {PlantUML diagram source here}
179
+ ....
180
+ ----
181
+
182
+ Where,
183
+
184
+ `{options}`:: Comma-separated list of options to customize diagram generation.
185
+
186
+
187
+ .Specifying a simple PlantUML diagram in the `[plantuml]` block
188
+ [example]
189
+ ====
68
190
  [source,asciidoc]
69
191
  ----
70
192
  [plantuml]
@@ -75,10 +197,14 @@ Bob -> Alice: Hi there
75
197
  @enduml
76
198
  ....
77
199
  ----
200
+ ====
201
+
202
+
78
203
 
79
204
  === Supported diagram types
80
205
 
81
- PlantUML supports various diagram types, each with its own `@start` and `@end` directives:
206
+ PlantUML, and therefore this plugin, supports various diagram types, each with
207
+ its own `@start` and `@end` directives:
82
208
 
83
209
  `@startuml` / `@enduml`:: UML diagrams (sequence, class, activity, etc.)
84
210
  `@startmindmap` / `@endmindmap`:: Mind map diagrams
@@ -129,12 +255,40 @@ Bob --> Alice: Authentication Response
129
255
  ----
130
256
  ====
131
257
 
258
+
132
259
  === Format options
133
260
 
134
261
  ==== Single format specification
135
262
 
136
- Specify the output format using the `format` attribute:
263
+ The output format is specified using the `format` option, which can be applied
264
+ in both block and command syntaxes.
265
+
266
+ Block syntax:
267
+
268
+ [source,adoc]
269
+ ----
270
+ [plantuml,format={format}]
271
+ ----
272
+
273
+ Command syntax:
274
+
275
+ [source,adoc]
276
+ ----
277
+ plantuml_image::{path}[format={format}]
278
+ ----
279
+
280
+ Where `{format}` can be one of the following:
281
+
282
+ `png`:: (default) Portable Network Graphics
283
+ `svg`:: Scalable Vector Graphics
284
+ `pdf`:: Portable Document Format
285
+ `txt`:: ASCII art text output
286
+ `eps`:: Encapsulated PostScript
287
+
137
288
 
289
+ .Block syntax with format option
290
+ [example]
291
+ ====
138
292
  [source,asciidoc]
139
293
  ----
140
294
  [plantuml,format=svg]
@@ -144,19 +298,47 @@ Alice -> Bob: Hello
144
298
  @enduml
145
299
  ....
146
300
  ----
301
+ ====
147
302
 
148
- Supported formats:
303
+ .Command syntax with format option
304
+ [example]
305
+ ====
306
+ [source,asciidoc]
307
+ ----
308
+ plantuml_image::path/to/my-plantuml.puml[format=svg]
309
+ ----
310
+ ====
149
311
 
150
- `png`:: (default) Portable Network Graphics
151
- `svg`:: Scalable Vector Graphics
152
- `pdf`:: Portable Document Format
153
- `txt`:: ASCII art text output
154
- `eps`:: Encapsulated PostScript
155
312
 
156
313
  ==== Multiple format generation
157
314
 
158
- Generate multiple formats simultaneously using the `formats` attribute:
315
+ It is possible to generate multiple output formats simultaneously by specifying
316
+ the `formats` option, which accepts a comma-separated list of formats.
317
+
318
+ While Metanorma does not currently support embedding multiple images for a single
319
+ diagram, generating multiple formats can be useful for documentation or other
320
+ purposes.
159
321
 
322
+ Block syntax:
323
+
324
+ [source,adoc]
325
+ ----
326
+ [plantuml,formats="{format1},{format2},..."]
327
+ ----
328
+
329
+ Command syntax:
330
+
331
+ [source,adoc]
332
+ ----
333
+ plantuml_image::{path}[formats="{format1},{format2},..."]
334
+ ----
335
+
336
+ Where `{format1}`, `{format2}`, etc. can be any of the supported formats listed
337
+ above.
338
+
339
+ .Block syntax with multiple formats
340
+ [example]
341
+ ====
160
342
  [source,asciidoc]
161
343
  ----
162
344
  [plantuml,formats="png,svg,pdf"]
@@ -166,11 +348,26 @@ Alice -> Bob: Hello
166
348
  @enduml
167
349
  ....
168
350
  ----
351
+ ====
352
+
169
353
 
170
354
  ==== Document-level format configuration
171
355
 
172
- Set the default format for all PlantUML diagrams in your document:
356
+ The default format for all PlantUML diagrams in a document can be set using the
357
+ `plantuml-image-format` document attribute.
358
+
359
+ Syntax:
360
+
361
+ [source,asciidoc]
362
+ ----
363
+ :plantuml-image-format: {format}
364
+ ----
365
+
366
+ Where `{format}` can be any of the supported formats listed above.
173
367
 
368
+ .Document with document-level format configuration
369
+ [example]
370
+ ====
174
371
  [source,asciidoc]
175
372
  ----
176
373
  :plantuml-image-format: svg
@@ -182,22 +379,246 @@ Alice -> Bob: Hello
182
379
  @enduml
183
380
  ....
184
381
  ----
382
+ ====
185
383
 
186
- === Image attributes
384
+ === Using `!include` and `!includesub`
385
+
386
+ ==== General
387
+
388
+ PlantUML supports modular diagram definitions using the `!include` and
389
+ `!includesub` directives to include external PlantUML files or specific parts of
390
+ them.
391
+
392
+ PlantUML is able to resolve relative paths in these directives based on the context
393
+ of the PlantUML source.
394
+
395
+ This is where the behavior differs between the block syntax and the command
396
+ syntax:
397
+
398
+ * In the block syntax, as the PlantUML source is defined inline within the
399
+ document, there is no associated file path for the PlantUML source. The
400
+ consequence is that PlantUML cannot resolve relative paths in `!include` or
401
+ `!includesub` directives, as there is no associated file path. In order to
402
+ resolve includes, you must specify the `includedirs` option to provide
403
+ directories to search for included files.
404
+
405
+ * In the command syntax, the PlantUML source is read from an external file. This
406
+ means that relative paths in `!include` or `!includesub` directives are resolved
407
+ relative to the location of the PlantUML source file. The `includedirs` option
408
+ is not needed for resolving includes, but can still be used to add additional
409
+ directories to search.
410
+
411
+
412
+ ==== Setting `includedirs`
187
413
 
188
- Standard AsciiDoc image attributes are supported:
414
+ PlantUML allows you to specify include directories using the `includedirs`
415
+ option. This option can be set at both the document level and the block level,
416
+ through the option of the same name.
417
+
418
+ There are two ways to set `includedirs`:
419
+
420
+ * Document-level configuration using the `plantuml-includedirs` document attribute
421
+
422
+ * The `includedirs` option in the `[plantuml]` block or `plantuml_image` command
423
+
424
+ Note that when using the `plantuml_image` command, the directory that contains
425
+ the specified PlantUML file will be automatically added as one of the
426
+ `includedirs` directories. This means that any relative includes in the PlantUML
427
+ file will be resolved relative to the file's location, even if `includedirs` is
428
+ not explicitly set.
429
+
430
+
431
+ ==== Document-level `plantuml-includedirs` attribute
432
+
433
+ It is possible to set default include directories (separated by semicolons) for
434
+ all PlantUML diagrams in a document using the `plantuml-includedirs` document
435
+ attribute.
436
+
437
+ This is useful when a document contains multiple PlantUML diagrams that share
438
+ common include files stored in specific directories, e.g. style definitions.
439
+
440
+ The directories specified in this attribute will be used as the default include
441
+ paths for all PlantUML diagrams in the document, including for both `[plantuml]`
442
+ blocks and `plantuml_image` commands.
443
+
444
+ NOTE: When using the `plantuml_image` command, the directory that contains the
445
+ specified PlantUML file will always be automatically added as one of the
446
+ `includedirs` directories.
447
+
448
+ Syntax:
189
449
 
190
450
  [source,asciidoc]
191
451
  ----
192
- [plantuml,id=my-diagram,title="My Sequence Diagram",width=600,height=400]
452
+ :plantuml-includedirs: {path1};{path2};...
453
+ ----
454
+
455
+ .Resolving PlantUML includes with document-level `includedirs` attribute
456
+ [example]
457
+ ====
458
+ [source,asciidoc]
459
+ ----
460
+ :plantuml-includedirs: path/to/plantuml/include-1;path/to/plantuml/include-2
461
+
462
+ [plantuml]
193
463
  ....
194
464
  @startuml
195
- Alice -> Bob: Hello
465
+ !include sequences.puml!1
466
+ @enduml
467
+ ....
468
+
469
+ [plantuml]
470
+ ....
471
+ @startuml
472
+ !include components.puml!FRONTEND
473
+ !include components.puml!BACKEND
474
+
475
+ WebApp --> APIGateway
476
+ MobileApp --> APIGateway
477
+ APIGateway --> DB
478
+ @enduml
479
+ ....
480
+
481
+ [plantuml]
482
+ ....
483
+ @startuml
484
+ title this contains only B and D
485
+ !includesub subpart.puml!BASIC
486
+ @enduml
487
+ ....
488
+ ----
489
+
490
+ These `[plantuml]` blocks use `!include` and `!includesub` directives to include
491
+ external PlantUML files. PlantUML will search the include directories specified
492
+ by `includedirs` options to find `sequences.puml`, `components.puml` and
493
+ `subpart.puml`, at:
494
+
495
+ * `path/to/plantuml/include-1`
496
+ * `path/to/plantuml/include-2`
497
+ ====
498
+
499
+
500
+ .Resolving PlantUML includes in `plantuml_image` command with document-level `includedirs` attribute
501
+ [example]
502
+ ====
503
+ [source,asciidoc]
504
+ ----
505
+ :plantuml-includedirs: path/to/plantuml/include-1;path/to/plantuml/include-2
506
+
507
+ plantuml_image::path/to/my-plantuml-1.puml[]
508
+
509
+ plantuml_image::path/to/my-plantuml-2.puml[]
510
+ ----
511
+
512
+ With `path/to/my-plantuml-1.puml` as:
513
+
514
+ [source,plantuml]
515
+ ----
516
+ @startuml
517
+ !include sequences.puml!1
518
+ @enduml
519
+ ----
520
+
521
+ With `path/to/my-plantuml-2.puml` as:
522
+
523
+ [source,plantuml]
524
+ ----
525
+ @startuml
526
+ !include components.puml!FRONTEND
527
+ !include components.puml!BACKEND
528
+
529
+ WebApp --> APIGateway
530
+ MobileApp --> APIGateway
531
+ APIGateway --> DB
196
532
  @enduml
533
+ ----
534
+
535
+ In using the `plantuml_image` command, the directory containing each PlantUML file
536
+ (`path/to` in this case) is automatically added to the `includedirs`.
537
+
538
+ Thus in rendering `path/to/my-plantuml-1.puml`, PlantUML will search for
539
+ `sequences.puml` in both `path/to` and the directories specified by the
540
+ `plantuml-includedirs` attribute.
541
+
542
+ Similarly, in rendering `path/to/my-plantuml-2.puml`, PlantUML will search for
543
+ `components.puml` in both `path/to` and the directories specified by the
544
+ `plantuml-includedirs` attribute.
545
+ ====
546
+
547
+
548
+ ==== Diagram-level `includedirs` option
549
+
550
+ The `includedirs` option can be used to specify include directories (separated
551
+ by semicolons) for both `[plantuml]` blocks and the `plantuml_image` command.
552
+
553
+ This option applies only to a single diagram in the document without affecting
554
+ others.
555
+
556
+ The diagram-level `includedirs` configuration can be used together with the
557
+ document-level configuration to provide more granular control over include
558
+ paths, where it is considered to have higher precedence than the document-level
559
+ configuration.
560
+
561
+ Syntax:
562
+
563
+ [source,asciidoc]
564
+ ----
565
+ [plantuml,includedirs="{path1};{path2};..."]
566
+ ----
567
+
568
+ Where,
569
+
570
+ `{path1}`, `{path2}`, etc.:: paths to directories containing PlantUML files to
571
+ be included, delimited by semicolons.
572
+
573
+
574
+ .Resolving PlantUML includes using diagram-level `includedirs` in `[plantuml]` blocks
575
+ [example]
576
+ ====
577
+ [source,asciidoc]
578
+ ----
579
+ [plantuml,includedirs="path/to/plantuml/include-1"]
580
+ ....
581
+ @startuml
582
+ !include sequences.puml!1
583
+ @enduml
584
+ ....
585
+
586
+ [plantuml,includedirs="path/to/plantuml/include-2"]
197
587
  ....
588
+ @startuml
589
+ !include components.puml!FRONTEND
590
+ !include components.puml!BACKEND
591
+
592
+ WebApp --> APIGateway
593
+ MobileApp --> APIGateway
594
+ APIGateway --> DB
595
+ @enduml
596
+ ....
597
+ ----
598
+
599
+ This plugin will search `sequences.puml` in `path/to/plantuml/include-1` and
600
+ `components.puml` in `path/to/plantuml/include-2`.
601
+ ====
602
+
603
+
604
+ .Resolving PlantUML includes using diagram-level `includedirs` with `plantuml_image` command
605
+ [example]
606
+ ====
607
+ [source,asciidoc]
608
+ ----
609
+ plantuml_image::path/to/my-plantuml-1.puml[includedirs=path/to/plantuml/include-1]
610
+
611
+ plantuml_image::path/to/my-plantuml-2.puml[includedirs=path/to/plantuml/include-2]
198
612
  ----
613
+ ====
614
+
615
+
616
+ === Image attributes
617
+
618
+ The block and command syntaxes both support standard AsciiDoc image attributes
619
+ to customize the appearance and behavior of the generated PlantUML diagrams.
199
620
 
200
- Supported attributes:
621
+ Supported attributes are as follows:
201
622
 
202
623
  `id`:: Element identifier
203
624
  `title`:: Image title/caption
@@ -208,34 +629,124 @@ Supported attributes:
208
629
  `float`:: Float positioning
209
630
  `role`:: CSS class/role
210
631
 
632
+
633
+ Block syntax:
634
+
635
+ [source,asciidoc]
636
+ ----
637
+ [plantuml,{image-attributes}]
638
+ ....
639
+ {PlantUML diagram source here}
640
+ ....
641
+ ----
642
+
643
+ Command syntax:
644
+
645
+ [source,asciidoc]
646
+ ----
647
+ plantuml_image::{path}[{image-attributes}]
648
+ ----
649
+
650
+ Where,
651
+
652
+ `{image-attributes}`:: Comma-separated list of AsciiDoc image attributes in
653
+ `key=value` format.
654
+
655
+
656
+ .Specifying image attributes in `[plantuml]` block
657
+ [example]
658
+ ====
659
+ [source,asciidoc]
660
+ ----
661
+ [plantuml,id=my-diagram,title="My Sequence Diagram",width=600,height=400]
662
+ ....
663
+ @startuml
664
+ Alice -> Bob: Hello
665
+ @enduml
666
+ ....
667
+ ----
668
+ ====
669
+
670
+
211
671
  === Filename specification
212
672
 
213
- Specify custom filenames within the PlantUML source:
673
+ PlantUML supports specifying custom filenames for generated diagrams using the
674
+ `@start{type} [filename]` directive, where `{type}` is the diagram type (e.g.,
675
+ `uml`, `mindmap`, etc.) and `filename` is the desired name for the output
676
+ file.
677
+
678
+ This feature is not well documented in official PlantUML documentation, but is
679
+ described at:
680
+
681
+ * PlantUML Language Reference Guide, 4.7, where `@startuml PERT` is used
682
+ * https://forum.plantuml.net/19896/name-conventions-for-%40startuml-filename[PlantUML Forum: Name conventions for @startuml filename]
683
+ * https://forum.plantuml.net/5483/please-specify-filename-%40startuml-extension-automatically[PlantUML Forum: Please specify filename @startuml extension automatically]
684
+
685
+ When a custom filename is specified, PlantUML generates the output file using
686
+ the specified filename and the appropriate file extension based on the diagram
687
+ type.
688
+
689
+ This custom filename feature is supported in both block and command syntaxes.
690
+
691
+ Syntax:
692
+
693
+ [source,asciidoc]
694
+ ----
695
+ [plantuml]
696
+ ....
697
+ @startuml {custom-filename}
698
+ {PlantUML diagram source here}
699
+ @enduml
700
+ ....
701
+ ----
702
+
703
+ Where,
214
704
 
705
+ `{custom-filename}`:: Desired name for the generated diagram file, without
706
+ file extension.
707
+
708
+ .Specifying a custom filename in `[plantuml]` block
709
+ [example]
710
+ ====
215
711
  [source,asciidoc]
216
712
  ----
217
713
  [plantuml]
218
714
  ....
219
- @startuml my-custom-name
715
+ @startuml AliceToBob
220
716
  Alice -> Bob: Hello
221
717
  @enduml
222
718
  ....
223
719
  ----
224
720
 
225
- This generates `my-custom-name.png` (or specified format) instead of an
226
- auto-generated filename.
721
+ This generates `AliceToBob.png` (which is the default format since none was
722
+ specified) instead of an auto-generated filename. This file is then embedded in
723
+ the output document using the specified filename.
724
+ ====
725
+
227
726
 
228
727
 
229
- === Configuration options
728
+ === Disable PlantUML processing
230
729
 
231
- ==== Disable PlantUML processing
730
+ It is possible to disable PlantUML processing either document-wide or via an
731
+ environment variable.
232
732
 
233
- Disable PlantUML processing document-wide:
733
+ When disabled, PlantUML blocks are rendered as code listings instead of
734
+ diagrams.
735
+
736
+ The `:plantuml-disabled:` document attribute can be used to disable PlantUML
737
+ processing for a specific document.
738
+
739
+ Syntax:
234
740
 
235
741
  [source,asciidoc]
236
742
  ----
237
743
  :plantuml-disabled:
744
+ ----
238
745
 
746
+ [example]
747
+ ====
748
+ [source,asciidoc]
749
+ ----
239
750
  [plantuml]
240
751
  ....
241
752
  @startuml
@@ -244,22 +755,36 @@ Alice -> Bob: Hello
244
755
  ....
245
756
  ----
246
757
 
247
- When disabled, PlantUML blocks are rendered as code listings instead of diagrams.
758
+ This renders the PlantUML block as a code listing instead of a diagram.
759
+ ====
248
760
 
249
- ==== Environment variable
761
+ The same effect can be achieved using by setting the `PLANTUML_DISABLED`
762
+ environment variable to `true`.
250
763
 
251
- Disable PlantUML processing via environment variable:
764
+ Syntax:
252
765
 
766
+ [source,console]
767
+ ----
768
+ $ PLANTUML_DISABLED=true metanorma ...
769
+ ----
770
+
771
+ [example]
772
+ ====
253
773
  [source,console]
254
774
  ----
255
775
  $ PLANTUML_DISABLED=true metanorma document.adoc
256
776
  ----
777
+ ====
778
+
257
779
 
258
780
  === File organization
259
781
 
260
- Generated PlantUML images are stored in the `_plantuml_images/` directory
261
- relative to your document location. This directory is automatically created if
262
- it doesn't exist.
782
+ Generated PlantUML images are stored in a `_plantuml_images/` directory
783
+ relative to the document location (the document root, if it is made of multiple
784
+ files).
785
+
786
+ This directory is automatically created if it doesn't exist.
787
+
263
788
 
264
789
  == Development
265
790
 
@@ -270,20 +795,24 @@ Metanorma integration and PlantUML execution:
270
795
 
271
796
  [source]
272
797
  ----
273
- Metanorma Document
274
-
275
- BlockProcessor ← (processes [plantuml] blocks)
276
-
277
- Backend ← (Metanorma integration, paths, validation)
278
-
279
- Wrapper ← (Java/JAR execution, file I/O)
280
-
281
- PlantUML JAR ← (diagram generation)
798
+ Metanorma Document
799
+
800
+ BlockProcessor ← (processes `[plantuml]` blocks)
801
+ and ImageBlockMacroProcessor ← (processes `plantuml_image::` commands)
802
+
803
+ Backend ← (Metanorma integration, paths, validation)
804
+
805
+ Wrapper ← (Java/JAR execution, file I/O)
806
+
807
+ PlantUML JAR ← (diagram generation)
282
808
  ----
283
809
 
284
810
  `BlockProcessor`:: Processes `[plantuml]` blocks in Metanorma documents and
285
811
  integrates with the Metanorma rendering pipeline.
286
812
 
813
+ `ImageBlockMacroProcessor`:: Processes `plantuml_image::{path}[{options}]` commands
814
+ in Metanorma documents and integrates with the Metanorma rendering pipeline.
815
+
287
816
  `Backend`:: Handles Metanorma-specific logic including document paths, PlantUML
288
817
  source validation, filename extraction, and attribute mapping.
289
818
 
@@ -14,19 +14,21 @@ module Metanorma
14
14
  class Backend
15
15
  class << self
16
16
  def plantuml_installed?
17
- unless Wrapper.available?
18
- raise "PlantUML not installed"
19
- end
17
+ return true if plantuml_available?
18
+
19
+ raise "PlantUML not installed"
20
20
  end
21
21
 
22
22
  def plantuml_available?
23
23
  Wrapper.available?
24
24
  end
25
25
 
26
- def generate_file(parent, reader, format_override = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
26
+ def generate_file( # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
27
+ parent, reader, format_override: nil, options: {}
28
+ )
27
29
  ldir, imagesdir, fmt = generate_file_prep(parent)
28
30
  fmt = format_override if format_override
29
- plantuml_content = prep_source(reader)
31
+ plantuml_content = prep_source(parent, reader)
30
32
 
31
33
  # Extract filename from PlantUML source if specified
32
34
  filename = generate_unique_filename(fmt)
@@ -43,6 +45,7 @@ module Metanorma
43
45
  plantuml_content,
44
46
  format: fmt,
45
47
  output_file: output_file,
48
+ includedirs: options[:includedirs],
46
49
  )
47
50
 
48
51
  unless result[:success]
@@ -52,10 +55,12 @@ module Metanorma
52
55
  File.join(relative_path, filename)
53
56
  end
54
57
 
55
- def generate_multiple_files(parent, reader, formats, attrs)
58
+ def generate_multiple_files(
59
+ parent, reader, formats, attrs, options: {}
60
+ )
56
61
  # Generate files for each format
57
62
  filenames = formats.map do |format|
58
- generate_file(parent, reader, format)
63
+ generate_file(parent, reader, format, options: options)
59
64
  end
60
65
 
61
66
  # Return data for BlockProcessor to create image block
@@ -72,8 +77,9 @@ module Metanorma
72
77
  def generate_file_prep(parent)
73
78
  ldir = localdir(parent)
74
79
  imagesdir = parent.document.attr("imagesdir")
75
- fmt = parent
76
- .document.attr("plantuml-image-format")&.strip&.downcase || "png"
80
+ fmt = parent.document
81
+ .attr("plantuml-image-format")&.strip&.downcase ||
82
+ Wrapper::DEFAULT_FORMAT
77
83
  [ldir, imagesdir, fmt]
78
84
  end
79
85
 
@@ -100,8 +106,15 @@ module Metanorma
100
106
  ]
101
107
  end
102
108
 
103
- def prep_source(reader)
104
- src = reader.source
109
+ def prep_source(parent, reader) # rubocop:disable Metrics/MethodLength
110
+ src = if reader.respond_to?(:source)
111
+ # get content from BlockProcessor
112
+ reader.source
113
+ else
114
+ # get content from ImageBlockMacroProcessor
115
+ docdir = parent.document.attributes["docdir"]
116
+ File.read(File.join(docdir, reader))
117
+ end
105
118
 
106
119
  # Validate that we have matching start/end pairs
107
120
  validate_plantuml_delimiters(src)
@@ -1,29 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "asciidoctor"
4
- require "asciidoctor/extensions"
5
- require_relative "backend"
6
-
7
3
  module Metanorma
8
4
  module Plugin
9
5
  module Plantuml
10
- # PlantUML block processor for Asciidoctor
6
+ # PlantUML block processor
11
7
  class BlockProcessor < ::Asciidoctor::Extensions::BlockProcessor
8
+ include ::Metanorma::Plugin::Plantuml::BlockProcessorBase
12
9
  use_dsl
13
10
  named :plantuml
14
11
  on_context :literal
15
12
  parse_content_as :raw
16
13
 
17
- def abort(parent, reader, attrs, msg)
18
- warn msg
19
- attrs["language"] = "plantuml"
20
- create_listing_block(
21
- parent,
22
- reader.source,
23
- attrs.reject { |k, _v| k.to_s.match?(/^\d+$/) },
24
- )
25
- end
26
-
27
14
  def process(parent, reader, attrs) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
28
15
  # Check for document-level disable flag
29
16
  if parent.document.attr("plantuml-disabled")
@@ -37,45 +24,24 @@ module Metanorma
37
24
 
38
25
  # Parse format specifications
39
26
  formats = parse_formats(attrs, parent.document)
27
+ options = parse_options(parent, reader, attrs)
40
28
 
41
- if formats.length == 1
42
- # Single format - original behavior
43
- filename = Backend.generate_file(parent, reader, formats.first)
44
- through_attrs = Backend.generate_attrs(attrs)
45
- through_attrs["target"] = filename
46
- else
47
- # Multiple formats - generate multiple files
48
- through_attrs = Backend
49
- .generate_multiple_files(parent, reader, formats, attrs)
50
- end
51
- create_image_block parent, through_attrs
29
+ process_image_block(parent, reader, attrs, formats, options)
52
30
  rescue StandardError => e
53
31
  abort(parent, reader, attrs, e.message)
54
32
  end
55
33
 
56
34
  private
57
35
 
58
- def parse_formats(attrs, document) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
59
- # Check for formats attribute (multiple formats)
60
- if attrs["formats"]
61
- formats = attrs["formats"].split(",").map(&:strip).map(&:downcase)
62
- return formats.select { |f| valid_format?(f) }
63
- end
64
-
65
- # Check for format attribute (single format override)
66
- if attrs["format"]
67
- format = attrs["format"].strip.downcase
68
- return [format] if valid_format?(format)
69
- end
36
+ def parse_options(parent, _reader, attrs)
37
+ options = {}
70
38
 
71
- # Fall back to document attribute or default
72
- default_format = document
73
- .attr("plantuml-image-format")&.strip&.downcase || "png"
74
- [default_format]
75
- end
39
+ # Parse include directory
40
+ options[:includedirs] = add_attrs_to_includedirs(
41
+ parent.document, attrs, parse_doc_includedirs(parent.document)
42
+ )
76
43
 
77
- def valid_format?(format)
78
- %w[png svg pdf txt eps].include?(format)
44
+ options
79
45
  end
80
46
  end
81
47
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "asciidoctor"
4
+ require "asciidoctor/extensions"
5
+ require_relative "backend"
6
+
7
+ module Metanorma
8
+ module Plugin
9
+ module Plantuml
10
+ module BlockProcessorBase
11
+ def abort(parent, reader, attrs, msg)
12
+ warn msg
13
+ attrs["language"] = "plantuml"
14
+ create_listing_block(
15
+ parent,
16
+ reader.respond_to?(:source) ? reader.source : reader,
17
+ attrs.reject { |k, _v| k.to_s.match?(/^\d+$/) },
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def parse_doc_includedirs(document)
24
+ docdir = document.attributes["docdir"]
25
+ includedirs = document.attr("plantuml-includedirs")&.split(";") || []
26
+
27
+ includedirs.map! do |includedir|
28
+ if Pathname.new(includedir).relative?
29
+ Pathname.new(docdir).join(includedir).to_s
30
+ else
31
+ includedir
32
+ end
33
+ end
34
+
35
+ includedirs.compact.uniq
36
+ end
37
+
38
+ def parse_formats(attrs, document) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
39
+ # Check for formats attribute (multiple formats)
40
+ if attrs["formats"]
41
+ formats = attrs["formats"].split(",").map(&:strip).map(&:downcase)
42
+ return formats.select { |f| valid_format?(f) }
43
+ end
44
+
45
+ # Check for format attribute (single format override)
46
+ if attrs["format"]
47
+ format = attrs["format"].strip.downcase
48
+ return [format] if valid_format?(format)
49
+ end
50
+
51
+ # Fall back to document attribute or default
52
+ default_format = document
53
+ .attr("plantuml-image-format")&.strip&.downcase ||
54
+ Wrapper::DEFAULT_FORMAT
55
+
56
+ [default_format]
57
+ end
58
+
59
+ def valid_format?(format)
60
+ Wrapper::SUPPORTED_FORMATS.include?(format)
61
+ end
62
+
63
+ def add_attrs_to_includedirs(document, attrs, includedirs)
64
+ docdir = document.attributes["docdir"]
65
+ attrs_includedirs = attrs["includedirs"]&.split(";") || []
66
+
67
+ attrs_includedirs.each do |attrs_includedir|
68
+ includedirs << if Pathname.new(attrs_includedir).relative?
69
+ Pathname.new(docdir).join(attrs_includedir).to_s
70
+ else
71
+ attrs_includedir
72
+ end
73
+ end
74
+
75
+ includedirs.compact.uniq
76
+ end
77
+
78
+ def process_image_block(parent, reader, attrs, formats, options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
79
+ if formats.length == 1
80
+ # Single format - original behavior
81
+ filename = Backend.generate_file(
82
+ parent, reader, format_override: formats.first, options: options
83
+ )
84
+ through_attrs = Backend.generate_attrs(attrs)
85
+ through_attrs["target"] = filename
86
+ else
87
+ # Multiple formats - generate multiple files
88
+ through_attrs = Backend
89
+ .generate_multiple_files(
90
+ parent, reader, formats, attrs, options: options
91
+ )
92
+ end
93
+
94
+ create_image_block parent, through_attrs
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Plugin
5
+ module Plantuml
6
+ # PlantUML block processor
7
+ class ImageBlockMacroProcessor < ::Asciidoctor::Extensions::BlockMacroProcessor
8
+ include ::Metanorma::Plugin::Plantuml::BlockProcessorBase
9
+ use_dsl
10
+ named :plantuml_image
11
+
12
+ def process(parent, reader, attrs) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
13
+ # Check for document-level disable flag
14
+ if parent.document.attr("plantuml-disabled")
15
+ return abort(parent, reader, attrs, "PlantUML processing disabled")
16
+ end
17
+
18
+ # Check PlantUML availability explicitly
19
+ unless Backend.plantuml_available?
20
+ return abort(parent, reader, attrs, "PlantUML not installed")
21
+ end
22
+
23
+ # Parse format specifications
24
+ formats = parse_formats(attrs, parent.document)
25
+ options = parse_options(parent, reader, attrs)
26
+
27
+ process_image_block(parent, reader, attrs, formats, options)
28
+ rescue StandardError => e
29
+ abort(parent, reader, attrs, e.message)
30
+ end
31
+
32
+ private
33
+
34
+ def add_image_path_to_includedirs(document, image_path, includedirs)
35
+ docdir = document.attributes["docdir"]
36
+ includedirs << File.dirname(File.join(docdir, image_path))
37
+ includedirs.compact.uniq
38
+ end
39
+
40
+ def parse_options(parent, reader, attrs)
41
+ options = {}
42
+
43
+ # Parse include directory
44
+ options[:includedirs] = parse_doc_includedirs(parent.document)
45
+ options[:includedirs] = add_attrs_to_includedirs(
46
+ parent.document, attrs, options[:includedirs]
47
+ )
48
+ options[:includedirs] = add_image_path_to_includedirs(
49
+ parent.document, reader, options[:includedirs]
50
+ )
51
+
52
+ options
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,7 +1,7 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Plantuml
4
- VERSION = "1.0.1".freeze
4
+ VERSION = "1.0.3".freeze
5
5
  PLANTUML_JAR_VERSION = "1.2025.4".freeze
6
6
  end
7
7
  end
@@ -32,7 +32,7 @@ module Metanorma
32
32
  options
33
33
  end
34
34
 
35
- def generate( # rubocop:disable Metrics/MethodLength
35
+ def generate( # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
36
36
  content,
37
37
  format: DEFAULT_FORMAT,
38
38
  output_file: nil,
@@ -42,6 +42,9 @@ module Metanorma
42
42
  ensure_jar_available!
43
43
  ensure_java_available!
44
44
 
45
+ include_files = get_include_files(content, options)
46
+ options[:include_files] = include_files unless include_files.empty?
47
+
45
48
  result = if output_file
46
49
  generate_to_file(content, format, output_file, options)
47
50
  elsif base64
@@ -55,6 +58,24 @@ module Metanorma
55
58
  { success: false, error: e }
56
59
  end
57
60
 
61
+ def get_include_files(content, _options) # rubocop:disable Metrics/MethodLength
62
+ include_files = []
63
+ content.each_line do |line|
64
+ case line
65
+ when /(!include|!includesub)\s(.+){1}/
66
+ found_file = $2.split("!").first
67
+
68
+ # skip web links and standard libraries
69
+ if found_file.start_with?("<", "http")
70
+ found_file = nil
71
+ end
72
+
73
+ include_files << found_file
74
+ end
75
+ end
76
+ include_files.compact.uniq
77
+ end
78
+
58
79
  def generate_from_file(
59
80
  input_file, format: DEFAULT_FORMAT,
60
81
  output_file: nil, base64: false, **options
@@ -169,47 +190,85 @@ module Metanorma
169
190
  end
170
191
 
171
192
  def execute_plantuml(content, format, output_file, options) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
172
- Tempfile.create(["plantuml_input", ".puml"]) do |input_file| # rubocop:disable Metrics/BlockLength
173
- input_file.write(content)
174
- input_file.close
175
-
176
- # PlantUML generates output files based on filename specified in
177
- # @start... line
178
- # We need to use a temp directory and then move the file
179
- Dir.mktmpdir do |temp_dir| # rubocop:disable Metrics/BlockLength
180
- cmd = build_command(input_file.path, format, temp_dir, options)
181
-
182
- output, error, status = Open3.capture3(*cmd)
183
-
184
- unless status.success?
185
- error_message = if error.empty?
186
- "Unknown PlantUML error"
187
- else
188
- error.strip
189
- end
190
- raise GenerationError.new(error_message, error)
193
+ # PlantUML generates output files based on filename specified in
194
+ # @start... line
195
+ # We need to use a temp directory and then move the file
196
+ Dir.mktmpdir do |temp_dir| # rubocop:disable Metrics/BlockLength
197
+ # create input file
198
+ File.open("#{temp_dir}/plantuml_input.puml", "w") do |f|
199
+ f.write(content)
200
+ end
201
+
202
+ # Handle include files
203
+ if options[:include_files] && !options[:include_files].empty?
204
+ if options[:includedirs].empty?
205
+ # raise error when include files are found but includedirs
206
+ # is nil
207
+ raise PlantumlError.new(
208
+ "includedirs is required when include files are specified",
209
+ )
191
210
  end
192
211
 
193
- # Find the generated file and move it to the desired location
194
- if output_file
195
- generated_file = find_generated_file(temp_dir, content,
196
- format)
197
- if generated_file && File.exist?(generated_file)
198
- FileUtils.mv(generated_file, output_file)
199
- else
200
- # Debug: List what files were actually generated
201
- generated_files = Dir.glob(File.join(temp_dir, "*"))
202
- error_msg = "Generated file not found in temp directory. "
203
- error_msg += "Expected: #{generated_file}. "
204
- error_msg += "Found files: #{generated_files.map do |f|
205
- File.basename(f)
206
- end.join(', ')}"
207
- raise GenerationError.new(error_msg)
212
+ options[:include_files].each do |include_file|
213
+ # find local include file in includedirs
214
+ found_include_file = nil
215
+ options[:includedirs].each do |includedir|
216
+ include_file_path = File.join(includedir, include_file)
217
+ if File.exist?(include_file_path)
218
+ found_include_file = include_file_path
219
+ break
220
+ end
221
+ end
222
+
223
+ if found_include_file
224
+ # create include file in temp directory
225
+ temp_include_file = File.join(temp_dir, include_file)
226
+ FileUtils.mkdir_p(File.dirname(temp_include_file))
227
+
228
+ File.open(temp_include_file, "w") do |f|
229
+ f.write(File.read(found_include_file))
230
+ end
208
231
  end
209
232
  end
233
+ end
234
+
235
+ cmd = build_command(
236
+ "#{temp_dir}/plantuml_input.puml",
237
+ format,
238
+ temp_dir,
239
+ options,
240
+ )
210
241
 
211
- output
242
+ output, error, status = Open3.capture3(*cmd)
243
+
244
+ unless status.success?
245
+ error_message = if error.empty?
246
+ "Unknown PlantUML error"
247
+ else
248
+ error.strip
249
+ end
250
+ raise GenerationError.new(error_message, error)
212
251
  end
252
+
253
+ # Find the generated file and move it to the desired location
254
+ if output_file
255
+ generated_file = find_generated_file(temp_dir, content,
256
+ format)
257
+ if generated_file && File.exist?(generated_file)
258
+ FileUtils.mv(generated_file, output_file)
259
+ else
260
+ # Debug: List what files were actually generated
261
+ generated_files = Dir.glob(File.join(temp_dir, "*"))
262
+ error_msg = "Generated file not found in temp directory. "
263
+ error_msg += "Expected: #{generated_file}. "
264
+ error_msg += "Found files: #{generated_files.map do |f|
265
+ File.basename(f)
266
+ end.join(', ')}"
267
+ raise GenerationError.new(error_msg)
268
+ end
269
+ end
270
+
271
+ output
213
272
  end
214
273
  end
215
274
 
@@ -11,4 +11,6 @@ require "metanorma/plugin/plantuml/config"
11
11
  require "metanorma/plugin/plantuml/wrapper"
12
12
  require "metanorma/plugin/plantuml/utils"
13
13
  require "metanorma/plugin/plantuml/backend"
14
+ require "metanorma/plugin/plantuml/block_processor_base"
14
15
  require "metanorma/plugin/plantuml/block_processor"
16
+ require "metanorma/plugin/plantuml/image_block_macro_processor"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-plugin-plantuml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-28 00:00:00.000000000 Z
11
+ date: 2025-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -58,8 +58,10 @@ files:
58
58
  - lib/metanorma-plugin-plantuml.rb
59
59
  - lib/metanorma/plugin/plantuml/backend.rb
60
60
  - lib/metanorma/plugin/plantuml/block_processor.rb
61
+ - lib/metanorma/plugin/plantuml/block_processor_base.rb
61
62
  - lib/metanorma/plugin/plantuml/config.rb
62
63
  - lib/metanorma/plugin/plantuml/errors.rb
64
+ - lib/metanorma/plugin/plantuml/image_block_macro_processor.rb
63
65
  - lib/metanorma/plugin/plantuml/utils.rb
64
66
  - lib/metanorma/plugin/plantuml/version.rb
65
67
  - lib/metanorma/plugin/plantuml/wrapper.rb