asciidoctor-ldl 0.1.0
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 +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE +21 -0
- data/README.adoc +365 -0
- data/lib/asciidoctor/ldl/extension.rb +156 -0
- data/lib/asciidoctor/ldl/js/ldl_render.mjs +218 -0
- data/lib/asciidoctor/ldl/renderer.rb +0 -0
- data/lib/asciidoctor/ldl/version.rb +7 -0
- data/lib/asciidoctor/ldl.rb +21 -0
- data/lib/asciidoctor-ldl.rb +6 -0
- metadata +111 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d37e7971f951167055c3491ecfeb5490b04b57871428bedb4cdd9e1184ddda18
|
|
4
|
+
data.tar.gz: 66950750f09e748e71f498678f0d305beb92ce31b04e8aa5cb2a47bc0f344069
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5bb1d501146cece951dbc5268ec36d73b058af6aceaaf3124bef4c870594fbc7cac86041f5b6fa342b18b3ba4037e3cc07ccb21f3d7ec52f9c23bc34eb623dd3
|
|
7
|
+
data.tar.gz: a07923b23d094eabfff664acf3d95b4451802e5c3263616eba076cbd11b86b71d32baedbec57fdf5f552f7a897f677431431e7145eefe0e515953fd555cbd674
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-07-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Initial release.
|
|
14
|
+
- `[ldl]` delimited block and `ldl::file.ldl[]` block macro that render
|
|
15
|
+
[Logic Diagram Language](https://github.com/OpenPowerShift/logic-diagram-language)
|
|
16
|
+
source to an image at conversion time.
|
|
17
|
+
- SVG output (vector, isomorphic, no extra dependency) and PNG output (via
|
|
18
|
+
`@resvg/resvg-js`).
|
|
19
|
+
- `format`, `scale`, `theme`, `show-ids` and `show-labels` attributes, settable
|
|
20
|
+
per block or document-wide with the `ldl-` prefix.
|
|
21
|
+
- Every generated image carries `ldl`, `ldl-<format>` and `ldl-<theme>` roles for
|
|
22
|
+
CSS / asciidoctor-pdf theme targeting.
|
|
23
|
+
- Content-addressed caching with a digest sidecar so unchanged diagrams are not
|
|
24
|
+
re-rendered.
|
|
25
|
+
|
|
26
|
+
[Unreleased]: https://github.com/OpenPowerShift/asciidoctor-ldl/compare/v0.1.0...HEAD
|
|
27
|
+
[0.1.0]: https://github.com/OpenPowerShift/asciidoctor-ldl/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Mulholland
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.adoc
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
= asciidoctor-ldl
|
|
2
|
+
:idprefix:
|
|
3
|
+
:idseparator: -
|
|
4
|
+
:source-language: asciidoc
|
|
5
|
+
:ldl-url: https://github.com/OpenPowerShift/logic-diagram-language
|
|
6
|
+
:npm-url: https://www.npmjs.com/package/@openpowershift/logic-diagram-language
|
|
7
|
+
:toc: macro
|
|
8
|
+
:toclevels: 3
|
|
9
|
+
|
|
10
|
+
An https://asciidoctor.org[Asciidoctor] extension that renders *logic and
|
|
11
|
+
protection-relay diagrams* written in the {ldl-url}[Logic Diagram Language (LDL)]
|
|
12
|
+
into *SVG* or *PNG* at conversion time, driven by AsciiDoc attributes.
|
|
13
|
+
|
|
14
|
+
You write boolean equations in an AsciiDoc block; the extension renders a clean,
|
|
15
|
+
laid-out diagram and embeds it as an image — no manual placement, no separate
|
|
16
|
+
build step.
|
|
17
|
+
|
|
18
|
+
[source]
|
|
19
|
+
----
|
|
20
|
+
[ldl]
|
|
21
|
+
....
|
|
22
|
+
TRIP.Name = "Trip"
|
|
23
|
+
TRIP = OVERCURRENT AND NOT BLOCK
|
|
24
|
+
OR (EARTH AND MANUAL)
|
|
25
|
+
....
|
|
26
|
+
----
|
|
27
|
+
|
|
28
|
+
toc::[]
|
|
29
|
+
|
|
30
|
+
== How it works
|
|
31
|
+
|
|
32
|
+
Rendering is delegated to the official {npm-url}[`@openpowershift/logic-diagram-language`]
|
|
33
|
+
npm package (the same renderer used by the
|
|
34
|
+
{ldl-url}[LDL playground]). The gem ships a tiny Node helper that imports the
|
|
35
|
+
package and returns the rendered artifact; the Ruby side handles attributes,
|
|
36
|
+
output paths and caching.
|
|
37
|
+
|
|
38
|
+
* *SVG* output is produced by the LDL renderer directly (isomorphic — pure
|
|
39
|
+
Node). It needs only Node and the npm package. Being vector, it embeds
|
|
40
|
+
cleanly in `asciidoctor-pdf`, scales without loss, and stays small.
|
|
41
|
+
* *PNG* output additionally rasterises the SVG with
|
|
42
|
+
https://github.com/thx/resvg-js[`@resvg/resvg-js`] — a single prebuilt,
|
|
43
|
+
dependency-free native module (no system libraries such as ImageMagick,
|
|
44
|
+
Cairo or a headless browser required).
|
|
45
|
+
|
|
46
|
+
The dependency footprint is deliberately small: *Ruby*, *Node*, and the *LDL npm
|
|
47
|
+
package* (plus `@resvg/resvg-js` only if you want PNG).
|
|
48
|
+
|
|
49
|
+
== Prerequisites
|
|
50
|
+
|
|
51
|
+
* Ruby (works on older versions — 2.5+) with Asciidoctor 2.0+.
|
|
52
|
+
* https://nodejs.org[Node.js] 20 or newer, on `PATH`.
|
|
53
|
+
* The LDL npm package, installed where you build your documents:
|
|
54
|
+
+
|
|
55
|
+
[source,console]
|
|
56
|
+
----
|
|
57
|
+
$ npm install @openpowershift/logic-diagram-language
|
|
58
|
+
$ npm install @resvg/resvg-js # only needed for PNG output
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
The extension resolves the npm package from your project's `node_modules`
|
|
62
|
+
(your build directory), or from a location you point it at with the
|
|
63
|
+
`ldl-package-dir` attribute or the `LDL_PACKAGE_DIR` environment variable.
|
|
64
|
+
|
|
65
|
+
== Installation
|
|
66
|
+
|
|
67
|
+
Add the gem to your `Gemfile`:
|
|
68
|
+
|
|
69
|
+
[source,ruby]
|
|
70
|
+
----
|
|
71
|
+
gem 'asciidoctor-ldl'
|
|
72
|
+
----
|
|
73
|
+
|
|
74
|
+
or install it directly:
|
|
75
|
+
|
|
76
|
+
[source,console]
|
|
77
|
+
----
|
|
78
|
+
$ gem install asciidoctor-ldl
|
|
79
|
+
----
|
|
80
|
+
|
|
81
|
+
== Registering the extension
|
|
82
|
+
|
|
83
|
+
On the command line, require it when converting:
|
|
84
|
+
|
|
85
|
+
[source,console]
|
|
86
|
+
----
|
|
87
|
+
$ asciidoctor -r asciidoctor-ldl document.adoc
|
|
88
|
+
$ asciidoctor-pdf -r asciidoctor-ldl document.adoc
|
|
89
|
+
----
|
|
90
|
+
|
|
91
|
+
From Ruby, requiring the gem auto-registers it with the global registry:
|
|
92
|
+
|
|
93
|
+
[source,ruby]
|
|
94
|
+
----
|
|
95
|
+
require 'asciidoctor'
|
|
96
|
+
require 'asciidoctor-ldl'
|
|
97
|
+
|
|
98
|
+
Asciidoctor.convert_file 'document.adoc', safe: :safe
|
|
99
|
+
----
|
|
100
|
+
|
|
101
|
+
To register against a specific registry instead of globally:
|
|
102
|
+
|
|
103
|
+
[source,ruby]
|
|
104
|
+
----
|
|
105
|
+
registry = Asciidoctor::Extensions.create
|
|
106
|
+
Asciidoctor::Ldl.register(registry)
|
|
107
|
+
----
|
|
108
|
+
|
|
109
|
+
== Usage
|
|
110
|
+
|
|
111
|
+
=== Inline block
|
|
112
|
+
|
|
113
|
+
Put LDL source in a block named `ldl`. Use a literal (`....`) delimiter so LDL's
|
|
114
|
+
own punctuation is never interpreted as AsciiDoc:
|
|
115
|
+
|
|
116
|
+
[source]
|
|
117
|
+
----
|
|
118
|
+
[ldl]
|
|
119
|
+
....
|
|
120
|
+
O1.Name = "Trip"
|
|
121
|
+
I1.Name = "Overcurrent"
|
|
122
|
+
O1 = I1 AND NOT I2
|
|
123
|
+
....
|
|
124
|
+
----
|
|
125
|
+
|
|
126
|
+
Optional positional attributes are `target` (a stable output file name) and
|
|
127
|
+
`format`:
|
|
128
|
+
|
|
129
|
+
[source]
|
|
130
|
+
----
|
|
131
|
+
[ldl,trip-logic,svg]
|
|
132
|
+
....
|
|
133
|
+
O1 = I1 AND NOT I2
|
|
134
|
+
....
|
|
135
|
+
----
|
|
136
|
+
|
|
137
|
+
=== From a file
|
|
138
|
+
|
|
139
|
+
Use the `ldl::` block macro to render an external `.ldl` file (path relative to
|
|
140
|
+
the document):
|
|
141
|
+
|
|
142
|
+
[source]
|
|
143
|
+
----
|
|
144
|
+
ldl::diagrams/trip-matrix.ldl[format=png,scale=2]
|
|
145
|
+
----
|
|
146
|
+
|
|
147
|
+
== Attributes
|
|
148
|
+
|
|
149
|
+
Every option can be set on the individual block/macro, or document-wide using
|
|
150
|
+
the `ldl-` prefixed form (block/macro wins). Set document attributes in the
|
|
151
|
+
header or on the command line (`-a ldl-format=png`).
|
|
152
|
+
|
|
153
|
+
[cols="1m,1m,1,3",options="header"]
|
|
154
|
+
|===
|
|
155
|
+
| Block attribute | Document attribute | Default | Description
|
|
156
|
+
|
|
157
|
+
| format
|
|
158
|
+
| ldl-format
|
|
159
|
+
| `svg`
|
|
160
|
+
| Output format: `svg` or `png`.
|
|
161
|
+
|
|
162
|
+
| scale
|
|
163
|
+
| ldl-scale
|
|
164
|
+
| `1`
|
|
165
|
+
| Scale multiplier. For `svg`, sets explicit `width`/`height` from the diagram's
|
|
166
|
+
intrinsic size (the `viewBox` is preserved, so it still scales crisply). For
|
|
167
|
+
`png`, multiplies the rasterised device pixels.
|
|
168
|
+
|
|
169
|
+
| theme
|
|
170
|
+
| ldl-theme
|
|
171
|
+
| `light`
|
|
172
|
+
| Colour theme: `light` or `dark`.
|
|
173
|
+
|
|
174
|
+
| show-ids
|
|
175
|
+
| ldl-show-ids
|
|
176
|
+
| `false`
|
|
177
|
+
| Draw bare identifiers (e.g. `I1`, `AND#G1`) instead of / in addition to labels.
|
|
178
|
+
|
|
179
|
+
| show-labels
|
|
180
|
+
| ldl-show-labels
|
|
181
|
+
| `true`
|
|
182
|
+
| Draw `.Name`/`.Description` labels.
|
|
183
|
+
|
|
184
|
+
| target
|
|
185
|
+
| —
|
|
186
|
+
| _(hash)_
|
|
187
|
+
| Output file basename (without extension). When omitted, a content-addressed
|
|
188
|
+
name (`ldl-<hash>.<ext>`) is used so identical diagrams share a file.
|
|
189
|
+
|
|
190
|
+
| _(n/a)_
|
|
191
|
+
| ldl-node
|
|
192
|
+
| `node`
|
|
193
|
+
| Path to the Node executable.
|
|
194
|
+
|
|
195
|
+
| _(n/a)_
|
|
196
|
+
| ldl-package-dir
|
|
197
|
+
| _(auto)_
|
|
198
|
+
| Directory from which to resolve the LDL npm package (and `@resvg/resvg-js`).
|
|
199
|
+
Overrides auto-detection; the `LDL_PACKAGE_DIR` env var does the same.
|
|
200
|
+
|
|
201
|
+
| _(n/a)_
|
|
202
|
+
| ldl-cache
|
|
203
|
+
| `true`
|
|
204
|
+
| Set to `false` to always re-render, ignoring the cache.
|
|
205
|
+
|===
|
|
206
|
+
|
|
207
|
+
Standard image attributes — `alt`, `title`, `width`, `height`, `align`,
|
|
208
|
+
`float`, `id`, `link`, `role` — pass straight through to the generated image.
|
|
209
|
+
|
|
210
|
+
TIP: Most *diagram* styling (inversion bubbles vs NOT gates, input bars,
|
|
211
|
+
compactness, stroke width, margins, …) is authored in the LDL source itself with
|
|
212
|
+
`OPTION` lines — see the {ldl-url}[LDL documentation]. This extension only
|
|
213
|
+
surfaces the render-time view options above.
|
|
214
|
+
|
|
215
|
+
=== Language `OPTION` lines vs. attributes
|
|
216
|
+
|
|
217
|
+
The LDL source can carry its own `OPTION` lines; the extension honours them and
|
|
218
|
+
then applies the attribute-driven overrides (`theme`, `show-ids`,
|
|
219
|
+
`show-labels`) on top. For example:
|
|
220
|
+
|
|
221
|
+
[source]
|
|
222
|
+
----
|
|
223
|
+
[ldl,theme=dark]
|
|
224
|
+
....
|
|
225
|
+
OPTION INVERSION = BUBBLES
|
|
226
|
+
OPTION MARGIN = 16
|
|
227
|
+
O1 = I1 AND NOT I2
|
|
228
|
+
....
|
|
229
|
+
----
|
|
230
|
+
|
|
231
|
+
== Output formats
|
|
232
|
+
|
|
233
|
+
Choosing a format:
|
|
234
|
+
|
|
235
|
+
[cols="1m,4",options="header"]
|
|
236
|
+
|===
|
|
237
|
+
| Format | When to use
|
|
238
|
+
|
|
239
|
+
| svg
|
|
240
|
+
| *Default and recommended for `asciidoctor-pdf`.* Vector, so it is sharp at any
|
|
241
|
+
zoom or print resolution, embeds via `prawn-svg` with no rasterisation, and
|
|
242
|
+
compresses to a fraction of an equivalent PNG. Also ideal for HTML.
|
|
243
|
+
|
|
244
|
+
| png
|
|
245
|
+
| When a raster image is required (e.g. a target that cannot embed SVG). Use
|
|
246
|
+
`scale` to control resolution — `scale=2` renders at 2× device pixels.
|
|
247
|
+
Requires `@resvg/resvg-js`.
|
|
248
|
+
|===
|
|
249
|
+
|
|
250
|
+
For `asciidoctor-pdf`, prefer `svg`: it keeps PDFs small and crisp. If you must
|
|
251
|
+
raster for PDF, `png` with `scale=2` or higher gives good print quality at the
|
|
252
|
+
cost of size.
|
|
253
|
+
|
|
254
|
+
== Styling with roles
|
|
255
|
+
|
|
256
|
+
Every generated image is tagged with roles so it can be targeted from CSS or an
|
|
257
|
+
`asciidoctor-pdf` theme without touching each block:
|
|
258
|
+
|
|
259
|
+
* `ldl` — on every LDL diagram.
|
|
260
|
+
* `ldl-svg` / `ldl-png` — the output format.
|
|
261
|
+
* `ldl-light` / `ldl-dark` — the theme.
|
|
262
|
+
|
|
263
|
+
Any `role` you set on the block is preserved and appended.
|
|
264
|
+
|
|
265
|
+
In HTML the roles appear as classes on the image block, so you can style them
|
|
266
|
+
with CSS:
|
|
267
|
+
|
|
268
|
+
[source,css]
|
|
269
|
+
----
|
|
270
|
+
.imageblock.ldl { margin-block: 1.5rem; }
|
|
271
|
+
.imageblock.ldl img { max-width: 100%; }
|
|
272
|
+
.imageblock.ldl-dark { background: #1b1b1f; padding: 0.5rem; border-radius: 6px; }
|
|
273
|
+
----
|
|
274
|
+
|
|
275
|
+
In `asciidoctor-pdf`, target the same roles from your theme (roles map to
|
|
276
|
+
`role_<name>` keys). For example, to centre and pad LDL diagrams:
|
|
277
|
+
|
|
278
|
+
[source,yaml]
|
|
279
|
+
----
|
|
280
|
+
role:
|
|
281
|
+
ldl:
|
|
282
|
+
align: center
|
|
283
|
+
ldl-dark:
|
|
284
|
+
background-color: '1B1B1F'
|
|
285
|
+
----
|
|
286
|
+
|
|
287
|
+
Because the roles are stable, a document-wide look needs no per-block markup.
|
|
288
|
+
|
|
289
|
+
== Caching
|
|
290
|
+
|
|
291
|
+
Rendered files are content-addressed and cached: the source plus the resolved
|
|
292
|
+
options (format, scale, theme, label toggles, and the gem version) are hashed
|
|
293
|
+
into a small sidecar (`<image>.ldlcache`). On the next build, an unchanged
|
|
294
|
+
diagram is reused and Node is not invoked. Editing the diagram or changing an
|
|
295
|
+
option transparently regenerates it. Disable with `-a ldl-cache=false`.
|
|
296
|
+
|
|
297
|
+
Generated images are written to the usual Asciidoctor image output location —
|
|
298
|
+
`imagesoutdir` if set, otherwise `imagesdir` under the output directory — so
|
|
299
|
+
they interoperate with the rest of your image pipeline.
|
|
300
|
+
|
|
301
|
+
== Error handling
|
|
302
|
+
|
|
303
|
+
If a diagram fails to parse or render, the extension emits a visible error block
|
|
304
|
+
(role `ldl-error`) with the message and logs a warning, rather than aborting the
|
|
305
|
+
whole conversion. This keeps a single broken diagram from failing a large book.
|
|
306
|
+
|
|
307
|
+
== Example document
|
|
308
|
+
|
|
309
|
+
[source]
|
|
310
|
+
----
|
|
311
|
+
= Protection Scheme
|
|
312
|
+
:ldl-format: svg
|
|
313
|
+
:ldl-theme: light
|
|
314
|
+
:imagesdir: images
|
|
315
|
+
|
|
316
|
+
== Transformer trip
|
|
317
|
+
|
|
318
|
+
[ldl,xfmr-trip]
|
|
319
|
+
....
|
|
320
|
+
TRIP.Name = "Transformer trip"
|
|
321
|
+
DIFF.Name = "Differential (87T)"
|
|
322
|
+
OC.Name = "Overcurrent (50/51)"
|
|
323
|
+
BUCH.Name = "Buchholz (63)"
|
|
324
|
+
TRIP = DIFF OR OC OR BUCH
|
|
325
|
+
....
|
|
326
|
+
|
|
327
|
+
== SEL-style seal-in (from a file)
|
|
328
|
+
|
|
329
|
+
ldl::diagrams/seal-in.ldl[]
|
|
330
|
+
----
|
|
331
|
+
|
|
332
|
+
Convert to HTML and PDF:
|
|
333
|
+
|
|
334
|
+
[source,console]
|
|
335
|
+
----
|
|
336
|
+
$ asciidoctor -r asciidoctor-ldl protection-scheme.adoc
|
|
337
|
+
$ asciidoctor-pdf -r asciidoctor-ldl protection-scheme.adoc
|
|
338
|
+
----
|
|
339
|
+
|
|
340
|
+
== Development
|
|
341
|
+
|
|
342
|
+
[source,console]
|
|
343
|
+
----
|
|
344
|
+
$ bundle install
|
|
345
|
+
$ (cd test/js && npm install) # LDL package + resvg for the test suite
|
|
346
|
+
$ bundle exec rake test # Ruby unit + end-to-end tests
|
|
347
|
+
$ bundle exec rake jstest # Node helper tests
|
|
348
|
+
----
|
|
349
|
+
|
|
350
|
+
The Ruby end-to-end tests convert real AsciiDoc through Asciidoctor and assert on
|
|
351
|
+
the generated files, roles and markup. They resolve the npm package from
|
|
352
|
+
`test/js` (or `LDL_PACKAGE_DIR`) and skip cleanly if Node or the package is
|
|
353
|
+
unavailable.
|
|
354
|
+
|
|
355
|
+
== Releasing
|
|
356
|
+
|
|
357
|
+
Releases publish to RubyGems via https://guides.rubygems.org/trusted-publishing/[Trusted
|
|
358
|
+
Publishing] (OIDC) — no API key is stored in the repository. Push a version tag
|
|
359
|
+
(`git tag v0.1.0 && git push origin v0.1.0`) and the `Release` workflow builds,
|
|
360
|
+
tests and publishes the gem. Configure the trusted publisher once on RubyGems to
|
|
361
|
+
match `.github/workflows/release.yml`.
|
|
362
|
+
|
|
363
|
+
== License
|
|
364
|
+
|
|
365
|
+
MIT © Daniel Mulholland. See link:LICENSE[].
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'asciidoctor'
|
|
4
|
+
require 'asciidoctor/extensions'
|
|
5
|
+
require_relative 'version'
|
|
6
|
+
require_relative 'renderer'
|
|
7
|
+
|
|
8
|
+
module Asciidoctor
|
|
9
|
+
module Ldl
|
|
10
|
+
# Shared behaviour for the block and block-macro processors: read options
|
|
11
|
+
# from attributes, drive the Renderer, and emit an image (or, on failure,
|
|
12
|
+
# a visible error block).
|
|
13
|
+
module Processor
|
|
14
|
+
# Turn LDL +source+ into an image block placed in +parent+.
|
|
15
|
+
def render_ldl(parent, source, attrs, target_hint)
|
|
16
|
+
doc = parent.document
|
|
17
|
+
renderer = build_renderer(doc, attrs)
|
|
18
|
+
out_dir = renderer.out_dir
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
filename = renderer.render(source, resolve_basename(attrs, target_hint))
|
|
22
|
+
rescue Renderer::Error => e
|
|
23
|
+
return error_block(parent, attrs, e.message)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
create_ldl_image(parent, attrs, filename, renderer, doc)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_renderer(doc, attrs)
|
|
32
|
+
Renderer.new(
|
|
33
|
+
format: attr(attrs, doc, 'format', 'ldl-format', 'svg'),
|
|
34
|
+
scale: attr(attrs, doc, 'scale', 'ldl-scale', nil),
|
|
35
|
+
theme: attr(attrs, doc, 'theme', 'ldl-theme', 'light'),
|
|
36
|
+
show_ids: attr(attrs, doc, 'show-ids', 'ldl-show-ids', nil),
|
|
37
|
+
show_labels: attr(attrs, doc, 'show-labels', 'ldl-show-labels', nil),
|
|
38
|
+
node: doc.attr('ldl-node', 'node'),
|
|
39
|
+
package_dir: doc.attr('ldl-package-dir') || doc.attr('ldl-node-modules'),
|
|
40
|
+
out_dir: image_output_dir(doc),
|
|
41
|
+
cache: cache_enabled?(doc)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Block/macro attribute wins; otherwise the document attribute; otherwise
|
|
46
|
+
# the default. Empty strings are treated as "unset".
|
|
47
|
+
def attr(attrs, doc, block_key, doc_key, default)
|
|
48
|
+
value = attrs[block_key]
|
|
49
|
+
value = doc.attr(doc_key) if value.nil? || value.to_s.empty?
|
|
50
|
+
value.nil? || value.to_s.empty? ? default : value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def cache_enabled?(doc)
|
|
54
|
+
raw = doc.attr('ldl-cache')
|
|
55
|
+
return true if raw.nil?
|
|
56
|
+
|
|
57
|
+
!%w[false 0 no off].include?(raw.to_s.strip.downcase)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resolve_basename(attrs, target_hint)
|
|
61
|
+
candidate = attrs['target'] || target_hint
|
|
62
|
+
return nil if candidate.nil? || candidate.to_s.strip.empty?
|
|
63
|
+
|
|
64
|
+
candidate
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Physical directory the image is written to, following the same
|
|
68
|
+
# conventions as asciidoctor-diagram (imagesoutdir overrides; otherwise
|
|
69
|
+
# imagesdir under the output/base directory).
|
|
70
|
+
def image_output_dir(doc)
|
|
71
|
+
images_outdir = doc.attr('imagesoutdir')
|
|
72
|
+
return images_outdir if images_outdir && !images_outdir.to_s.empty?
|
|
73
|
+
|
|
74
|
+
base = doc.attr('outdir')
|
|
75
|
+
base = doc.options[:to_dir] if (base.nil? || base.empty?) && doc.options[:to_dir]
|
|
76
|
+
base ||= doc.base_dir
|
|
77
|
+
images_dir = doc.attr('imagesdir')
|
|
78
|
+
(images_dir && !images_dir.to_s.empty?) ? File.join(base, images_dir) : base
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_ldl_image(parent, attrs, filename, renderer, _doc)
|
|
82
|
+
image_attrs = { 'target' => filename }
|
|
83
|
+
%w[alt title width height align float id link].each do |key|
|
|
84
|
+
image_attrs[key] = attrs[key] if attrs.key?(key)
|
|
85
|
+
end
|
|
86
|
+
image_attrs['alt'] ||= (attrs['target'] || 'LDL logic diagram')
|
|
87
|
+
image_attrs['role'] = image_roles(attrs, renderer)
|
|
88
|
+
create_image_block(parent, image_attrs)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Always tag the image with the +ldl+ role plus format/theme modifier
|
|
92
|
+
# roles, so it can be targeted from CSS or an asciidoctor-pdf theme.
|
|
93
|
+
# Any author-supplied role is preserved (and wins for ordering).
|
|
94
|
+
def image_roles(attrs, renderer)
|
|
95
|
+
roles = %w[ldl]
|
|
96
|
+
roles << "ldl-#{renderer.format}"
|
|
97
|
+
roles << "ldl-#{renderer.theme}"
|
|
98
|
+
author = attrs['role']
|
|
99
|
+
roles.concat(author.to_s.split) if author && !author.to_s.empty?
|
|
100
|
+
roles.uniq.join(' ')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def error_block(parent, attrs, message)
|
|
104
|
+
warn "asciidoctor-ldl: #{message}"
|
|
105
|
+
text = "LDL diagram error:\n#{message}"
|
|
106
|
+
Asciidoctor::Block.new(parent, :listing, source: text,
|
|
107
|
+
attributes: { 'role' => 'ldl-error' })
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Delimited block:
|
|
112
|
+
#
|
|
113
|
+
# [ldl]
|
|
114
|
+
# ----
|
|
115
|
+
# O1 = I1 AND NOT I2
|
|
116
|
+
# ----
|
|
117
|
+
#
|
|
118
|
+
# Optional positional attributes: [ldl, target-basename, format].
|
|
119
|
+
class BlockProcessor < Asciidoctor::Extensions::BlockProcessor
|
|
120
|
+
include Processor
|
|
121
|
+
|
|
122
|
+
use_dsl
|
|
123
|
+
named :ldl
|
|
124
|
+
on_contexts :listing, :literal, :paragraph, :open
|
|
125
|
+
name_positional_attributes 'target', 'format'
|
|
126
|
+
parse_content_as :raw
|
|
127
|
+
|
|
128
|
+
def process(parent, reader, attrs)
|
|
129
|
+
render_ldl(parent, reader.source, attrs, attrs['target'])
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Block macro reading from a file:
|
|
134
|
+
#
|
|
135
|
+
# ldl::path/to/diagram.ldl[format=png, scale=2]
|
|
136
|
+
class BlockMacroProcessor < Asciidoctor::Extensions::BlockMacroProcessor
|
|
137
|
+
include Processor
|
|
138
|
+
|
|
139
|
+
use_dsl
|
|
140
|
+
named :ldl
|
|
141
|
+
name_positional_attributes 'format'
|
|
142
|
+
|
|
143
|
+
def process(parent, target, attrs)
|
|
144
|
+
doc = parent.document
|
|
145
|
+
path = doc.normalize_system_path(target, doc.attr('docdir'))
|
|
146
|
+
unless File.readable?(path)
|
|
147
|
+
return error_block(parent, attrs, "cannot read LDL file: #{target}")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
source = File.read(path)
|
|
151
|
+
hint = attrs['target'] || File.basename(target, File.extname(target))
|
|
152
|
+
render_ldl(parent, source, attrs, hint)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ldl_render.mjs — render LDL source to SVG or PNG for the asciidoctor-ldl gem.
|
|
3
|
+
//
|
|
4
|
+
// Contract (kept deliberately small so the Ruby side is easy to drive):
|
|
5
|
+
// * LDL source is read from stdin.
|
|
6
|
+
// * Options come from CLI flags (see parseArgs below).
|
|
7
|
+
// * The rendered artifact is written to the path given by --out.
|
|
8
|
+
// * On success nothing is written to stdout; a one-line JSON summary
|
|
9
|
+
// ({"format","width","height","out"}) is written to stderr for logging.
|
|
10
|
+
// * On failure a human-readable message is written to stderr and the
|
|
11
|
+
// process exits non-zero.
|
|
12
|
+
//
|
|
13
|
+
// SVG rendering is isomorphic (only needs @openpowershift/logic-diagram-language).
|
|
14
|
+
// PNG rendering additionally needs @resvg/resvg-js, resolved lazily so an
|
|
15
|
+
// SVG-only install never has to have it present.
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
|
+
import { dirname, join, resolve, isAbsolute } from 'node:path';
|
|
21
|
+
|
|
22
|
+
const PACKAGE = '@openpowershift/logic-diagram-language';
|
|
23
|
+
|
|
24
|
+
// Resolve an installed package's ESM entry point from a base directory,
|
|
25
|
+
// without relying on a `require` export condition (the LDL package is
|
|
26
|
+
// ESM-only, so `require.resolve(pkg)` throws ERR_PACKAGE_PATH_NOT_EXPORTED).
|
|
27
|
+
// The `./package.json` subpath *is* exported, so resolve that and read the
|
|
28
|
+
// declared entry ourselves. Returns a file:// URL string, or null.
|
|
29
|
+
function resolveEntryFrom(base, pkg) {
|
|
30
|
+
let pkgJsonPath;
|
|
31
|
+
try {
|
|
32
|
+
const req = createRequire(pathToFileURL(join(base, 'package.json')).href);
|
|
33
|
+
pkgJsonPath = req.resolve(`${pkg}/package.json`);
|
|
34
|
+
} catch (_) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const meta = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
38
|
+
const dot = meta.exports && meta.exports['.'];
|
|
39
|
+
const entry =
|
|
40
|
+
(dot && typeof dot === 'object' && (dot.import || dot.default)) ||
|
|
41
|
+
(typeof dot === 'string' ? dot : null) ||
|
|
42
|
+
meta.module ||
|
|
43
|
+
meta.main ||
|
|
44
|
+
'index.js';
|
|
45
|
+
return pathToFileURL(resolve(dirname(pkgJsonPath), entry)).href;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Candidate base directories to search, in priority order.
|
|
49
|
+
function candidateBases(packageDir) {
|
|
50
|
+
const bases = [];
|
|
51
|
+
if (packageDir) bases.push(packageDir);
|
|
52
|
+
if (process.env.LDL_PACKAGE_DIR) bases.push(process.env.LDL_PACKAGE_DIR);
|
|
53
|
+
bases.push(process.cwd());
|
|
54
|
+
bases.push(dirname(fileURLToPath(import.meta.url)));
|
|
55
|
+
return bases;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseArgs(argv) {
|
|
59
|
+
const opts = {
|
|
60
|
+
format: 'svg',
|
|
61
|
+
scale: 1,
|
|
62
|
+
theme: 'light',
|
|
63
|
+
showIds: false,
|
|
64
|
+
showLabels: true,
|
|
65
|
+
out: null,
|
|
66
|
+
packageDir: null,
|
|
67
|
+
};
|
|
68
|
+
for (let i = 0; i < argv.length; i++) {
|
|
69
|
+
const arg = argv[i];
|
|
70
|
+
const next = () => argv[++i];
|
|
71
|
+
switch (arg) {
|
|
72
|
+
case '--format': opts.format = String(next()).toLowerCase(); break;
|
|
73
|
+
case '--scale': opts.scale = Number(next()); break;
|
|
74
|
+
case '--theme': opts.theme = String(next()).toLowerCase(); break;
|
|
75
|
+
case '--show-ids': opts.showIds = true; break;
|
|
76
|
+
case '--no-show-ids': opts.showIds = false; break;
|
|
77
|
+
case '--show-labels': opts.showLabels = true; break;
|
|
78
|
+
case '--no-show-labels': opts.showLabels = false; break;
|
|
79
|
+
case '--out': opts.out = next(); break;
|
|
80
|
+
case '--package-dir': opts.packageDir = next(); break;
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!opts.out) throw new Error('missing required --out <path>');
|
|
86
|
+
if (!Number.isFinite(opts.scale) || opts.scale <= 0) {
|
|
87
|
+
throw new Error(`invalid --scale: ${opts.scale}`);
|
|
88
|
+
}
|
|
89
|
+
if (opts.format !== 'svg' && opts.format !== 'png') {
|
|
90
|
+
throw new Error(`unsupported --format: ${opts.format} (expected svg or png)`);
|
|
91
|
+
}
|
|
92
|
+
return opts;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Resolve the LDL package from a set of candidate base directories, so it can
|
|
96
|
+
// live in the user's project, a globally installed location, or a checkout
|
|
97
|
+
// pointed at by --package-dir / LDL_PACKAGE_DIR.
|
|
98
|
+
async function loadLibrary(packageDir) {
|
|
99
|
+
const tried = [];
|
|
100
|
+
for (const base of candidateBases(packageDir)) {
|
|
101
|
+
// Allow a base that points straight at a package checkout (…/lib/index.js).
|
|
102
|
+
for (const direct of [join(base, 'lib', 'index.js'), join(base, 'index.js')]) {
|
|
103
|
+
try {
|
|
104
|
+
return await import(pathToFileURL(direct).href);
|
|
105
|
+
} catch (e) { tried.push(`${direct} (${e.code || 'error'})`); }
|
|
106
|
+
}
|
|
107
|
+
// Otherwise resolve it as an installed dependency from that base.
|
|
108
|
+
const entry = resolveEntryFrom(base, PACKAGE);
|
|
109
|
+
if (entry) {
|
|
110
|
+
try {
|
|
111
|
+
return await import(entry);
|
|
112
|
+
} catch (e) { tried.push(`${entry} (${e.code || 'error'})`); }
|
|
113
|
+
} else {
|
|
114
|
+
tried.push(`${base}:${PACKAGE} (not found)`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw new Error(
|
|
118
|
+
`could not resolve ${PACKAGE}.\n` +
|
|
119
|
+
`Install it where your document is built, e.g.\n` +
|
|
120
|
+
` npm install ${PACKAGE}\n` +
|
|
121
|
+
`or point the ldl-package-dir attribute / LDL_PACKAGE_DIR env var at it.\n` +
|
|
122
|
+
`Searched:\n ${tried.join('\n ')}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function loadResvg(packageDir) {
|
|
126
|
+
for (const base of candidateBases(packageDir)) {
|
|
127
|
+
try {
|
|
128
|
+
const req = createRequire(pathToFileURL(join(base, 'package.json')).href);
|
|
129
|
+
return await import(pathToFileURL(req.resolve('@resvg/resvg-js')).href);
|
|
130
|
+
} catch (_) { /* try next */ }
|
|
131
|
+
const entry = resolveEntryFrom(base, '@resvg/resvg-js');
|
|
132
|
+
if (entry) {
|
|
133
|
+
try { return await import(entry); } catch (_) { /* try next */ }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw new Error(
|
|
137
|
+
'PNG output needs @resvg/resvg-js, which was not found.\n' +
|
|
138
|
+
'Install it alongside the LDL package:\n' +
|
|
139
|
+
' npm install @resvg/resvg-js\n' +
|
|
140
|
+
'or use format=svg (the default), which needs no extra dependency and is\n' +
|
|
141
|
+
'ideal for asciidoctor-pdf (vector, scalable, compact).');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The renderer emits <svg viewBox="0 0 W H" …> with no intrinsic width/height.
|
|
145
|
+
// Add them (scaled) so downstream consumers — prawn-svg in asciidoctor-pdf,
|
|
146
|
+
// browsers, resvg — get a concrete size and the scale attribute is honoured.
|
|
147
|
+
function sizeFromViewBox(svg) {
|
|
148
|
+
const m = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/);
|
|
149
|
+
if (!m) return null;
|
|
150
|
+
return { width: parseFloat(m[1]), height: parseFloat(m[2]) };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function applySvgScale(svg, scale) {
|
|
154
|
+
const size = sizeFromViewBox(svg);
|
|
155
|
+
if (!size) return { svg, size };
|
|
156
|
+
const w = size.width * scale;
|
|
157
|
+
const h = size.height * scale;
|
|
158
|
+
// Insert width/height right after "<svg"; drop the max-width/height clamp so
|
|
159
|
+
// an explicit size actually takes effect when embedded as an image.
|
|
160
|
+
const scaled = svg
|
|
161
|
+
.replace(/<svg/, `<svg width="${w}" height="${h}"`)
|
|
162
|
+
.replace(/\s*max-width:100%;max-height:100%;/, '');
|
|
163
|
+
return { svg: scaled, size: { width: w, height: h } };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function main() {
|
|
167
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
168
|
+
const source = readFileSync(0, 'utf8'); // fd 0 = stdin
|
|
169
|
+
|
|
170
|
+
const lib = await loadLibrary(opts.packageDir);
|
|
171
|
+
const { parse, renderDiagram, resolveOptions, LIGHT_DIAGRAM, DARK_DIAGRAM } = lib;
|
|
172
|
+
|
|
173
|
+
const { diagram, errors } = parse(source);
|
|
174
|
+
if (errors && errors.length) {
|
|
175
|
+
const lines = errors.map((e) => {
|
|
176
|
+
const pos = e.position ? ` (line ${e.position.line}, col ${e.position.column})` : '';
|
|
177
|
+
return ` ${e.message}${pos}`;
|
|
178
|
+
});
|
|
179
|
+
throw new Error(`LDL parse error:\n${lines.join('\n')}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const renderOptions = resolveOptions(diagram.options);
|
|
183
|
+
renderOptions.showIds = opts.showIds;
|
|
184
|
+
renderOptions.showLabels = opts.showLabels;
|
|
185
|
+
const theme = opts.theme === 'dark' ? DARK_DIAGRAM : LIGHT_DIAGRAM;
|
|
186
|
+
|
|
187
|
+
let svg = renderDiagram(diagram, renderOptions, theme);
|
|
188
|
+
const scaled = applySvgScale(svg, opts.scale);
|
|
189
|
+
svg = scaled.svg;
|
|
190
|
+
const size = scaled.size || { width: 0, height: 0 };
|
|
191
|
+
|
|
192
|
+
const outPath = isAbsolute(opts.out) ? opts.out : join(process.cwd(), opts.out);
|
|
193
|
+
|
|
194
|
+
if (opts.format === 'svg') {
|
|
195
|
+
writeFileSync(outPath, svg, 'utf8');
|
|
196
|
+
process.stderr.write(JSON.stringify({ format: 'svg', width: size.width, height: size.height, out: outPath }) + '\n');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// PNG
|
|
201
|
+
const { Resvg } = await loadResvg(opts.packageDir);
|
|
202
|
+
// Rasterise from the *unscaled* viewBox using resvg's own zoom, so the scale
|
|
203
|
+
// multiplies device pixels crisply rather than baking a size into the SVG.
|
|
204
|
+
const base = renderDiagram(diagram, renderOptions, theme);
|
|
205
|
+
const resvg = new Resvg(base, {
|
|
206
|
+
fitTo: { mode: 'zoom', value: opts.scale },
|
|
207
|
+
background: 'white',
|
|
208
|
+
});
|
|
209
|
+
const rendered = resvg.render();
|
|
210
|
+
const png = rendered.asPng();
|
|
211
|
+
writeFileSync(outPath, png);
|
|
212
|
+
process.stderr.write(JSON.stringify({ format: 'png', width: rendered.width, height: rendered.height, out: outPath }) + '\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().catch((err) => {
|
|
216
|
+
process.stderr.write(`[asciidoctor-ldl] ${err && err.message ? err.message : err}\n`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ldl/version'
|
|
4
|
+
require_relative 'ldl/renderer'
|
|
5
|
+
require_relative 'ldl/extension'
|
|
6
|
+
|
|
7
|
+
module Asciidoctor
|
|
8
|
+
module Ldl
|
|
9
|
+
# Register the block and block-macro processors with an Asciidoctor
|
|
10
|
+
# extension registry (defaults to the global one).
|
|
11
|
+
def self.register(registry = Asciidoctor::Extensions)
|
|
12
|
+
registry.block Asciidoctor::Ldl::BlockProcessor, :ldl
|
|
13
|
+
registry.block_macro Asciidoctor::Ldl::BlockMacroProcessor, :ldl
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Asciidoctor::Extensions.register do
|
|
19
|
+
block Asciidoctor::Ldl::BlockProcessor, :ldl
|
|
20
|
+
block_macro Asciidoctor::Ldl::BlockMacroProcessor, :ldl
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: asciidoctor-ldl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Daniel Mulholland
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: asciidoctor
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '2.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: minitest
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.0'
|
|
40
|
+
type: :development
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: rake
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
description: |
|
|
62
|
+
An Asciidoctor extension that renders protection-relay / logic diagrams
|
|
63
|
+
written in the Logic Diagram Language (LDL) — the
|
|
64
|
+
@openpowershift/logic-diagram-language npm package — into SVG or PNG at
|
|
65
|
+
conversion time. SVG output is vector and embeds cleanly in asciidoctor-pdf;
|
|
66
|
+
PNG is available via @resvg/resvg-js. Format, scale, theme and label
|
|
67
|
+
visibility are controlled with AsciiDoc attributes.
|
|
68
|
+
email:
|
|
69
|
+
- dan.mulholland@gmail.com
|
|
70
|
+
executables: []
|
|
71
|
+
extensions: []
|
|
72
|
+
extra_rdoc_files: []
|
|
73
|
+
files:
|
|
74
|
+
- CHANGELOG.md
|
|
75
|
+
- LICENSE
|
|
76
|
+
- README.adoc
|
|
77
|
+
- lib/asciidoctor-ldl.rb
|
|
78
|
+
- lib/asciidoctor/ldl.rb
|
|
79
|
+
- lib/asciidoctor/ldl/extension.rb
|
|
80
|
+
- lib/asciidoctor/ldl/js/ldl_render.mjs
|
|
81
|
+
- lib/asciidoctor/ldl/renderer.rb
|
|
82
|
+
- lib/asciidoctor/ldl/version.rb
|
|
83
|
+
homepage: https://github.com/OpenPowerShift/asciidoctor-ldl
|
|
84
|
+
licenses:
|
|
85
|
+
- MIT
|
|
86
|
+
metadata:
|
|
87
|
+
homepage_uri: https://github.com/OpenPowerShift/asciidoctor-ldl
|
|
88
|
+
source_code_uri: https://github.com/OpenPowerShift/asciidoctor-ldl
|
|
89
|
+
bug_tracker_uri: https://github.com/OpenPowerShift/asciidoctor-ldl/issues
|
|
90
|
+
changelog_uri: https://github.com/OpenPowerShift/asciidoctor-ldl/blob/main/CHANGELOG.md
|
|
91
|
+
rubygems_mfa_required: 'true'
|
|
92
|
+
post_install_message:
|
|
93
|
+
rdoc_options: []
|
|
94
|
+
require_paths:
|
|
95
|
+
- lib
|
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: 2.5.0
|
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
requirements: []
|
|
107
|
+
rubygems_version: 3.5.22
|
|
108
|
+
signing_key:
|
|
109
|
+
specification_version: 4
|
|
110
|
+
summary: Asciidoctor extension for OpenPowerShift LDL logic diagrams
|
|
111
|
+
test_files: []
|