-
Notifications
You must be signed in to change notification settings - Fork 3
/
Kroki.jl
218 lines (192 loc) · 6.51 KB
/
Kroki.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
"""
The main `Module` containing the necessary types of functions for integration
with a Kroki service.
Defines `Base.show` and corresponding `Base.showable` methods for different
output formats and [`Diagram`](@ref Kroki.Diagram) types, so they render in
their most optimal form in different environments (e.g. the documentation
system, Documenter output, Pluto, Jupyter, etc.).
"""
module Kroki
using Base64: base64encode
using CodecZlib: ZlibCompressor, transcode
using HTTP: request
using Reexport: @reexport
include("./kroki/documentation.jl")
using .Documentation
@setupDocstringMarkup()
include("./kroki/service.jl")
using .Service: ENDPOINT
export Diagram, render
# Convenience short-hand to make further type definitions more straightforward
# to write
const Maybe{T} = Union{Nothing, T} where {T}
"""
A representation of a diagram that can be rendered by a Kroki service.
# Examples
```
julia> Kroki.Diagram(:PlantUML, "Kroki -> Julia: Hello Julia!")
┌─────┐ ┌─────┐
│Kroki│ │Julia│
└──┬──┘ └──┬──┘
│ Hello Julia! │
│───────────────>│
┌──┴──┐ ┌──┴──┐
│Kroki│ │Julia│
└─────┘ └─────┘
```
"""
struct Diagram
"The textual specification of the diagram."
specification::AbstractString
"""
The type of diagram specification (e.g. ditaa, Mermaid, PlantUML, etc.). This
value is case-insensitive.
"""
type::Symbol
end
"""
Constructs a [`Diagram`](@ref) from the `specification` for a specific `type`
of diagram.
"""
Diagram(type::Symbol, specification::AbstractString) = Diagram(specification, type)
include("./kroki/exceptions.jl")
using .Exceptions: DiagramPathOrSpecificationError, RenderError
"""
Constructs a [`Diagram`](@ref) from the `specification` for a specific `type`
of diagram, or loads the `specification` from the provided `path`.
Specifying both keyword arguments, or neither, is invalid.
"""
function Diagram(
type::Symbol;
path::Maybe{AbstractString} = nothing,
specification::Maybe{AbstractString} = nothing,
)
path_provided = !isnothing(path)
specification_provided = !isnothing(specification)
if path_provided && specification_provided
throw(DiagramPathOrSpecificationError(path, specification))
elseif !path_provided && !specification_provided
throw(DiagramPathOrSpecificationError(path, specification))
elseif path_provided
Diagram(type, read(path, String))
else
Diagram(type, specification)
end
end
"""
Compresses a [`Diagram`](@ref)'s `specification` using
[zlib](https://zlib.net), turning the resulting bytes into a URL-safe Base64
encoded payload (i.e. replacing `+` by `-` and `/` by `_`) to be used in
communication with a Kroki service.
See the [Kroki documentation](https://docs.kroki.io/kroki/setup/encode-diagram)
for more information.
"""
UriSafeBase64Payload(diagram::Diagram) = foldl(
replace,
['+' => '-', '/' => '_'];
init = base64encode(transcode(ZlibCompressor, diagram.specification)),
)
"""
Renders a [`Diagram`](@ref) through a Kroki service to the specified output
format.
If the Kroki service responds with an error, throws an
[`InvalidDiagramSpecificationError`](@ref
Kroki.Exceptions.InvalidDiagramSpecificationError) or
[`InvalidOutputFormatError`](@ref Kroki.Exceptions.InvalidOutputFormatError) if
a know type of error occurs. Other errors (e.g.
`HTTP.ExceptionRequest.StatusError` for connection errors) are propagated if
they occur.
_SVG output is supported for all [`Diagram`](@ref) types_. See [Kroki's
website](https://kroki.io/#support) for an overview of other supported output
formats per diagram type. Note that this list may not be entirely up-to-date.
"""
render(diagram::Diagram, output_format::AbstractString) =
try
getfield(
request(
"GET",
join(
[
ENDPOINT[],
lowercase("$(diagram.type)"),
output_format,
UriSafeBase64Payload(diagram),
],
'/',
),
),
:body,
)
catch exception
throw(RenderError(diagram, exception))
end
"""
Some MIME types are not supported by all diagram types, this constant contains
all these limitations. The union of all values corresponds to all supported
[`Diagram`](@ref) `type`s.
"""
const LIMITED_DIAGRAM_SUPPORT = Dict{AbstractString, Tuple{Symbol, Vararg{Symbol}}}(
"application/pdf" => (
:blockdiag,
:seqdiag,
:actdiag,
:nwdiag,
:packetdiag,
:rackdiag,
:erd,
:graphviz,
:vega,
:vegalite,
),
"image/jpeg" => (:c4plantuml, :erd, :graphviz, :plantuml, :umlet),
"image/png" => (
:blockdiag,
:seqdiag,
:actdiag,
:nwdiag,
:packetdiag,
:rackdiag,
:c4plantuml,
:ditaa,
:erd,
:graphviz,
:mermaid,
:plantuml,
:structurizr,
:umlet,
:vega,
:vegalite,
),
# Although all diagram types support SVG, these _only_ support SVG so are
# included separately
"image/svg+xml" =>
(:bpmn, :bytefield, :excalidraw, :nomnoml, :pikchr, :svgbob, :wavedrom),
"text/plain" => (:c4plantuml, :plantuml),
)
# `Base.show` methods should only be defined for diagram types that actually
# support the desired output format. This would make sure incompatible formats
# are not accidentally rendered on compatible `AbstractDisplay`s causing
# [`InvalidOutputFormatError`](@ref)s. As the diagram type information is only
# available within [`Diagram`](@ref) instances, the `show` method is defined
# generically, but then restricted using `Base.showable` to only those types
# that actually support the format
Base.show(io::IO, ::MIME{Symbol("image/png")}, diagram::Diagram) =
write(io, render(diagram, "png"))
Base.showable(::MIME{Symbol("image/png")}, diagram::Diagram) =
diagram.type ∈ LIMITED_DIAGRAM_SUPPORT["image/png"]
# SVG output is supported by _all_ diagram types, so there's no additional
# checking for support. This makes sure SVG output also works for new diagram
# types if they get added to Kroki, but not yet to this package
Base.show(io::IO, ::MIME"image/svg+xml", diagram::Diagram) =
write(io, render(diagram, "svg"))
# PlantUML is capable of rendering textual representations, all other diagram
# types are not
Base.show(io::IO, diagram::Diagram) =
if endswith(lowercase("$(diagram.type)"), "plantuml")
write(io, render(diagram, "utxt"))
else
write(io, diagram.specification)
end
include("./string_literals.jl")
@reexport using .StringLiterals
end