Skip to content

Commit

Permalink
Merge b0e5975 into 9e1d040
Browse files Browse the repository at this point in the history
  • Loading branch information
bauglir committed Jul 16, 2022
2 parents 9e1d040 + b0e5975 commit 7e87e50
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 48 deletions.
3 changes: 3 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Kroki
Diagram
Diagram(::Symbol, ::AbstractString)
Diagram(::Symbol; ::Union{Nothing,AbstractString}, ::Union{Nothing,AbstractString})
SUPPORTED_TEXT_PLAIN_SHOW_MIME_TYPES
TEXT_PLAIN_SHOW_MIME_TYPE
render
```

Expand All @@ -35,6 +37,7 @@ Modules = [ Kroki.StringLiterals ]

```@docs
LIMITED_DIAGRAM_SUPPORT
MIME_TO_RENDER_ARGUMENT_MAP
UriSafeBase64Payload
```

Expand Down
62 changes: 55 additions & 7 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ $alice -> $bob: Rendering diagrams!
## [The `Diagram` type](@id examples-diagram-type)

String literals are effectively short-hands for instantiating a
[`Diagram`](@ref Kroki.Diagram) for a specific type of diagram. In certain
cases, it may be more straightforward, or even necessary, to directly
instantiate a [`Diagram`](@ref Kroki.Diagram). For instance, when a type of
diagram is supported by the Kroki service but support for it has not been added
to this package. In those cases, basic functionality like rendering to an SVG
should typically still work in line with the following examples.
[`Diagram`](@ref) for a specific type of diagram. In certain cases, it may be
more straightforward, or even necessary, to directly instantiate a
[`Diagram`](@ref). For instance, when a type of diagram is supported by the
Kroki service but support for it has not been added to this package. In those
cases, basic functionality like rendering to an SVG should typically still work
in line with the following examples.

```@example diagrams
Diagram(:mermaid, """
Expand All @@ -114,7 +114,7 @@ graph TD

When the diagram description contains special characters, e.g. `\`s, keep
in mind that these need to be escaped for proper handling when
instantiating a [`Diagram`](@ref Kroki.Diagram).
instantiating a [`Diagram`](@ref).

Escaping is not typically necessary when using [string literals](@ref
api-string-literals).
Expand Down Expand Up @@ -278,3 +278,51 @@ write("mermaid_diagram.svg", render(mermaid_diagram, "svg"))
```

![Mermaid diagram as SVG example](mermaid_diagram.svg)

## Controlling text rendering

Some diagrams support rendering to text, e.g. PlantUML and Structurizr. This
can be based on ASCII or Unicode character sets. Which character set is used,
is controlled using the [`Kroki.TEXT_PLAIN_SHOW_MIME_TYPE`](@ref) variable.

Setting a `text/plain` MIME type results in the use of the limited ASCII
character set.

```@setup diagrams
text_plain_show_mime_type_backup = Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[]
```

```@example diagrams
plantuml_diagram = plantuml"""
Kroki -> Documenter: I can render this as text in two ways!
Kroki <- Documenter: Nice!
"""
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = MIME"text/plain"()
println(sprint(show, MIME"text/plain"(), plantuml_diagram))
```

Setting a `text/plain; charset=utf-8` MIME type, which is the default, results
in nicer looking diagrams due to the use of Unicode characters.

```@example diagrams
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = MIME"text/plain; charset=utf-8"()
println(sprint(show, MIME"text/plain"(), plantuml_diagram))
```

Configuring an invalid MIME type results in an error upon rendering to a
`text/plain` target.

```@example diagrams
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = MIME"not-a-known/mime-type"()
try
sprint(show, MIME"text/plain"(), plantuml_diagram)
catch exception
println(sprint(showerror, exception))
end
```

```@setup diagrams
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = text_plain_show_mime_type_backup
```
112 changes: 89 additions & 23 deletions src/Kroki.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function Diagram(type::Symbol, specification::AbstractString; kwargs...)
end

include("./kroki/exceptions.jl")
using .Exceptions: DiagramPathOrSpecificationError, RenderError
using .Exceptions: DiagramPathOrSpecificationError, RenderError, UnsupportedMIMETypeError

"""
Constructs a [`Diagram`](@ref) from the `specification` for a specific `type`
Expand Down Expand Up @@ -180,8 +180,8 @@ 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" => (
const LIMITED_DIAGRAM_SUPPORT = Dict{MIME, Tuple{Symbol, Vararg{Symbol}}}(
MIME"application/pdf"() => (
:blockdiag,
:seqdiag,
:actdiag,
Expand All @@ -193,8 +193,8 @@ const LIMITED_DIAGRAM_SUPPORT = Dict{AbstractString, Tuple{Symbol, Vararg{Symbol
:vega,
:vegalite,
),
"image/jpeg" => (:c4plantuml, :erd, :graphviz, :plantuml, :umlet),
"image/png" => (
MIME"image/jpeg"() => (:c4plantuml, :erd, :graphviz, :plantuml, :structurizr, :umlet),
MIME"image/png"() => (
:blockdiag,
:seqdiag,
:actdiag,
Expand All @@ -215,9 +215,25 @@ const LIMITED_DIAGRAM_SUPPORT = Dict{AbstractString, Tuple{Symbol, Vararg{Symbol
),
# Although all diagram types support SVG, these _only_ support SVG so are
# included separately
"image/svg+xml" =>
MIME"image/svg+xml"() =>
(:bpmn, :bytefield, :excalidraw, :nomnoml, :pikchr, :svgbob, :wavedrom),
"text/plain" => (:c4plantuml, :plantuml),
# Diagrams that can be rendered to plain text support both ASCII and Unicode
# rendering
MIME"text/plain"() => (:c4plantuml, :plantuml, :structurizr),
MIME"text/plain; charset=utf-8"() => (:c4plantuml, :plantuml, :structurizr),
)

"""
Maps MIME types to the arguments that have to be passed to the [`render`](@ref)
function, which are in turned passed to the Kroki service.
"""
const MIME_TO_RENDER_ARGUMENT_MAP = Dict{MIME, String}(
MIME"application/pdf"() => "pdf",
MIME"image/jpeg"() => "jpeg",
MIME"image/png"() => "png",
MIME"image/svg+xml"() => "svg",
MIME"text/plain"() => "txt",
MIME"text/plain; charset=utf-8"() => "utxt",
)

# `Base.show` methods should only be defined for diagram types that actually
Expand All @@ -227,25 +243,75 @@ const LIMITED_DIAGRAM_SUPPORT = Dict{AbstractString, Tuple{Symbol, Vararg{Symbol
# 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"))
Base.show(io::IO, ::T, diagram::Diagram) where {T <: MIME} =
write(io, render(diagram, MIME_TO_RENDER_ARGUMENT_MAP[T()]))

# The `text/plain` MIME type needs to be explicitly defined to remove method
# ambiguities. As the two argument `Base.show` method is the one that is meant
# to render this MIME type, it is simply forwarded to that method
Base.show(io::IO, ::MIME"text/plain", diagram::Diagram) = show(io, diagram)

# SVG output is supported by _all_ diagram types. An additional `showable`
# method is necessary as `LIMITED_DIAGRAM_SUPPORT` documents only those diagram
# types that _only_ support SVG. This makes sure SVG output also works for new
# diagram types if they get added to the Kroki service, but not yet to this
# package
Base.showable(::MIME"image/svg+xml", ::Diagram) = true
Base.showable(::T, diagram::Diagram) where {T <: MIME} =
Symbol(lowercase(String(diagram.type))) get(LIMITED_DIAGRAM_SUPPORT, T(), Tuple([]))

# Calling `Base.show` for JPEGs is explicitly disabled, for the time being.
# JPEG rendering is broken for all, supposedly supported, diagram types in the
# Kroki service. Should the support be fixed in the service, this method can be
# easily redefined by consuming software to support JPEG in case Kroki.jl has
# not been updated and released.
#
# Note that this only affects automatic rendering of `Diagram`s to JPEGs in
# supported environments. It is still possible to use `render` to render JPEGs
Base.showable(::MIME"image/jpeg", ::Diagram) = false

"""
Defines the MIME type to be used when `show` gets called on a [`Diagram`](@ref)
for the `text/plain` MIME type.
Should be set to a variation of the `text/plain` MIME type. For instance,
`text/plain; charset=utf-8` to enable Unicode rendering for certain diagrams,
e.g. PlantUML and Structurizr. Only a select number of variations are
supported, see [`LIMITED_DIAGRAM_SUPPORT`](@ref) for details.
Defaults to `text/plain; charset=utf-8`.
"""
const TEXT_PLAIN_SHOW_MIME_TYPE = Ref{MIME}(MIME"text/plain; charset=utf-8"())

"All values that can be used to configure [`TEXT_PLAIN_SHOW_MIME_TYPE`](@ref)."
const SUPPORTED_TEXT_PLAIN_SHOW_MIME_TYPES =
filter(mime -> startswith(string(mime), "text/plain"), keys(LIMITED_DIAGRAM_SUPPORT))

# The two-argument `Base.show` version is used to render the "text/plain" MIME
# type. Those `Diagram` types that support text-based rendering, e.g. PlantUML,
# Structurizr, should render those. All others should render their
# `specification`.
#
# Whether `text/plain` rendering uses ASCII or Unicode characters is controlled
# using the `Kroki.TEXT_PLAIN_SHOW_MIME_TYPE` variable
function Base.show(io::IO, diagram::Diagram)
text_plain_show_mimetype = TEXT_PLAIN_SHOW_MIME_TYPE[]

if text_plain_show_mimetype SUPPORTED_TEXT_PLAIN_SHOW_MIME_TYPES
throw(
UnsupportedMIMETypeError(
text_plain_show_mimetype,
SUPPORTED_TEXT_PLAIN_SHOW_MIME_TYPES,
),
)
end

if showable(text_plain_show_mimetype, diagram)
write(io, render(diagram, MIME_TO_RENDER_ARGUMENT_MAP[text_plain_show_mimetype]))
else
write(io, diagram.specification)
end
end

include("./kroki/string_literals.jl")
@reexport using .StringLiterals
Expand Down
23 changes: 23 additions & 0 deletions src/kroki/exceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,27 @@ function RenderError(diagram::Diagram, exception::StatusError)
end
RenderError(::Diagram, exception::Exception) = exception

"""
An `Exception` to be thrown when the `selected_mime_type` is not an element of
the set of `supported_mime_types`.
"""
struct UnsupportedMIMETypeError <: Exception
"The selected `MIME` type that is not supported."
selected_mime_type::MIME

"The set of supported `MIME` types."
supported_mime_types::Set{MIME}
end

Base.showerror(io::IO, error::UnsupportedMIMETypeError) = join(
io,
[
"The selected `MIME` type must be one of `",
join(error.supported_mime_types, "`, `", "` or `"),
"`. Got `",
error.selected_mime_type,
"`.",
],
)

end
19 changes: 18 additions & 1 deletion test/kroki/exceptions_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ using Kroki.Exceptions:
DiagramPathOrSpecificationError,
InvalidDiagramSpecificationError,
InvalidOutputFormatError,
StatusError # Imported from HTTP through Kroki
StatusError, # Imported from HTTP through Kroki
UnsupportedMIMETypeError
using Kroki.Service: setEndpoint!

testRenderError(
Expand Down Expand Up @@ -115,6 +116,22 @@ end
setEndpoint!()
end
end

@testset "`UnsupportedMIMETypeError`" begin
@testset "rendering" begin
selected_mime_type = MIME"text/plain"()
supported_mime_types = Set([MIME"application/pdf"(), MIME"image/png"()])

rendered_error = sprint(
showerror,
UnsupportedMIMETypeError(selected_mime_type, supported_mime_types),
)

@test startswith(rendered_error, "The selected `MIME` type")
@test occursin(join(supported_mime_types, "`, `", "` or `"), rendered_error)
@test occursin("Got `$(selected_mime_type)`", rendered_error)
end
end
end

end

0 comments on commit 7e87e50

Please sign in to comment.