Skip to content

Commit

Permalink
Support with_terminal and update Pluto to 0.18.4 (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
rikhuijzer authored Mar 19, 2022
1 parent 578e179 commit 8fd732f
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 8 deletions.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "PlutoStaticHTML"
uuid = "359b1769-a58e-495b-9770-312e911026ad"
authors = ["Rik Huijzer <t.h.huijzer@rug.nl>"]
version = "4.0.2"
version = "4.0.3"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Expand All @@ -11,5 +11,5 @@ SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[compat]
Pluto = "=0.18.2"
Pluto = "=0.18.4"
julia = "1.6"
5 changes: 3 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ function build()
return nothing
end

if !("DISABLE_NOTEBOOK_BUILD" in keys(ENV))
if !("DISABLE_NOTEBOOKS_BUILD" in keys(ENV))
build()
end

sitename = "PlutoStaticHTML.jl"
pages = [
"PlutoStaticHTML" => "index.md",
"Example notebook" => "notebooks/example.md"
"Example notebook" => "notebooks/example.md",
"`with_terminal`" => "with_terminal.md"
]

# Using MathJax3 since Pluto uses that engine too.
Expand Down
87 changes: 87 additions & 0 deletions docs/src/with_terminal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# `with_terminal`

`PlutoUI` has a well-known function `with_terminal` to show terminal output with a black background and colored text.
For example, when having loaded `PlutoUI` via `using PlutoUI`, the following code will show the text "Some terminal output" in a mini terminal window inside `Pluto`:

```julia
with_terminal() do
println("Some terminal output")
end
```

This functionality is supported by `PlutoStaticHTML` too.
To make it work, `PlutoStaticHTML` takes the output from `Pluto`, which looks roughly as follows:

```html
<div style="display: inline; white-space: normal;">
<script type="text/javascript" id="plutouiterminal">
let txt = "Some terminal output"
...
</script>
</div>
```

and changes it to:

```html
<pre id="plutouiterminal">
Some terminal output
</pre>
```

This output is now much simpler to style to your liking.
Below, there is an example style that you can apply which will style the terminal output just like it would in `Pluto`.

In terminals, the colors are enabled via so called ANSI escape codes.
These ANSI colors can be shown correctly by adding the following Javascript to the footer of your website.
This code will loop through all the HTML elements with `id="plutouiterminal"` and apply the `ansi_to_html` function to the body of those elements:

```html
<script type="text/javascript">
async function color_ansi() {
const terminalOutputs = document.querySelectorAll("[id=plutouiterminal]");
// Avoid loading AnsiUp if there is no terminal output on the page.
if (terminalOutputs.length == 0) {
return
};
try {
const { default: AnsiUp } = await import("https://cdn.jsdelivr.net/gh/JuliaPluto/ansi_up@v5.1.0-es6/ansi_up.js");
const ansiUp = new AnsiUp();
// Indexed loop is needed here, the array iterator doesn't work for some reason.
for (let i = 0; i < terminalOutputs.length; ++i) {
const terminalOutput = terminalOutputs[i];
const txt = terminalOutput.innerHTML;
terminalOutput.innerHTML = ansiUp.ansi_to_html(txt);
};
} catch(e) {
console.error("Failed to import/call ansiup!", e);
};
};
color_ansi();
</script>
```

Next, the output can be made more to look like an embedded terminal by adding the following to your CSS:

```css
#plutouiterminal {
max-height: 300px;
overflow: auto;
white-space: pre;
color: white;
background-color: black;
border-radius: 3px;
margin-top: 8px;
margin-bottom: 8px;
padding: 15px;
display: block;
font-size: 14px;
}
```

!!! note
Note that the Javascript code above downloads the `ansi_up.js` file from a content delivery network (CDN).
This is not advised because CDNs are bad for privacy, may go offline and are bad for performance.
They are bad for performance because browsers do not cache CDN downloaded content between different domains for security reasons.
Therefore, CDN content will cause at least an extra DNS lookup in most cases.
1 change: 1 addition & 0 deletions src/PlutoStaticHTML.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ include("module_doc.jl")
include("context.jl")
include("cache.jl")
include("mimeoverride.jl")
include("with_terminal.jl")
include("html.jl")
include("build.jl")

Expand Down
9 changes: 7 additions & 2 deletions src/html.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,17 @@ end

function _output2html(cell::Cell, ::MIME"text/html", hopts)
body = cell.output.body

if contains(body, """<script type="text/javascript" id="plutouiterminal">""")
return _patch_with_terminal(string(body))
end

# The docstring is already visible in Markdown and shouldn't be shown below the code.
if startswith(body, """<div class="pluto-docs-binding">""")
return ""
else
return body
end

return body
end

function _output2html(cell::Cell, ::MIME"application/vnd.pluto.stacktrace+object", hopts)
Expand Down
21 changes: 21 additions & 0 deletions src/with_terminal.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function _extract_txt(body)
sep = '\n'
lines = split(body, sep)
index = findfirst(contains("let txt = "), lines)
txt_line = strip(lines[index])
txt = txt_line[12:end-1]
with_newlines = replace(txt, "\\n" => '\n')
without_extraneous_newlines = strip(with_newlines, '\n')
return without_extraneous_newlines
end

function _patch_with_terminal(body::String)
txt = _extract_txt(body)
@show txt
return """
<pre id="plutouiterminal">
$txt
</pre>
"""
end
precompile(_patch_with_terminal, (String,))
19 changes: 17 additions & 2 deletions test/html.jl
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,28 @@ end
end

@testset "show_output_above_code" begin
notebook = Notebook([
nb = Notebook([
Cell("x = 1 + 1020"),
])
hopts = HTMLOptions(; show_output_above_code=true)
html, nb = notebook2html_helper(notebook, hopts; use_distributed=false)
html, _ = notebook2html_helper(nb, hopts; use_distributed=false)
lines = split(html, '\n')

@test contains(lines[1], "1021")
@test contains(lines[2], "1 + 1020")
end

@testset "with_terminal" begin
nb = Notebook([
Cell("using PlutoUI"),
Cell("f(x) = Base.inferencebarrier(x);"),
Cell("""
with_terminal() do
@code_warntype f(1)
end
""")
])
html, _ = notebook2html_helper(nb)
# Basically, this only tests whether `_patch_with_terminal` is applied.
@test contains(html, """<pre id="plutouiterminal">""")
end
4 changes: 4 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ end
include("mimeoverride.jl")
end

@timed_testset "with_terminal" begin
include("with_terminal.jl")
end

@timed_testset "html" begin
include("html.jl")
end
Expand Down
48 changes: 48 additions & 0 deletions test/with_terminal.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Body for `cell.output.body` of
# using PlutoUI
# f(x) = Base.inferencebarrier(x);
# with_terminal() do
# @code_warntype f(1)
# end
body = raw"""
<div style="display: inline; white-space: normal;">
<script type="text/javascript" id="plutouiterminal">
let txt = "MethodInstance for Main.workspace#4.f(::Int64)\n from f(x) in Main.workspace#4 at /home/rik/git/blog/posts/notebooks/inference.jl#==#9dbfb7d5-7035-4ea2-a6c0-efa00e39e90f:1\nArguments\n #self#[36m::Core.Const(Main.workspace#4.f)[39m\n x[36m::Int64[39m\nBody[91m[1m::Any[22m[39m\n[90m1 ─[39m %1 = Base.getproperty(Main.workspace#4.Base, :inferencebarrier)[36m::Core.Const(Base.inferencebarrier)[39m\n[90m│ [39m %2 = (%1)(x)[91m[1m::Any[22m[39m\n[90m└──[39m return %2\n\n"
var container = html`
<pre
class="PlutoUI_terminal"
style="
max-height: 300px;
overflow: auto;
white-space: pre;
color: white;
background-color: black;
border-radius: 3px;
margin-top: 8px;
margin-bottom: 8px;
padding: 15px;
display: block;
"
></pre>
`
try {
const { default: AnsiUp } = await import("https://cdn.jsdelivr.net/gh/JuliaPluto/ansi_up@v5.1.0-es6/ansi_up.js");
container.innerHTML = new AnsiUp().ansi_to_html(txt);
} catch(e) {
console.error("Failed to import/call ansiup!", e)
container.innerText = txt
}
return container
</script>
</div>
"""

txt = strip(PlutoStaticHTML._extract_txt(body))
@test startswith(txt, "MethodInstance")
@test endswith(txt, "return %2")
@test contains(txt, '\n')

patched = PlutoStaticHTML._patch_with_terminal(body)
@test contains(patched, """<pre id="plutouiterminal">""")

2 comments on commit 8fd732f

@rikhuijzer
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/56897

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v4.0.3 -m "<description of version>" 8fd732fec5d12f16780d402f44a87774f3839289
git push origin v4.0.3

Please sign in to comment.