Pietro Peterlongo, Milan, Italy 🇮🇹
Python Data Scientist working on a Supply Chain Planning Software
talked previously about nimib at NimConf2021
helped organize Nim Devroom at FOSDEM 2022
let's organize again the Nim Devroom and meet at FOSDEM 2023!
Engineering Physics student
Nimib maintainer - since July 2022
nimiSlides
SciNim member - since the start 2020
NumericalNim
Scinim/getting-started (uses nimiBook)
NbBlock
👷nbJsFromCode
, nbKaraxCode
NbBlock
👷nbJsFromCode
, nbKaraxCode
CodeAsInSource
NbBlock
👷
nbPython
nbJsFromCode
, nbKaraxCode
CodeAsInSource
NbBlock
👷
nbPython
nbJsFromCode
, nbKaraxCode
CodeAsInSource
CodeFromAst
)Code in source file:
import math, strformat
let
n = 1
echo n + 1 # 2
## documentation comment
struct(http):
s: _ = "HTTP/"
*header: {headers}
Code as shown in html:
import
math, strformat
let n = 1
echo n + 1
## documentation comment
struct(http):
s:
_ = "HTTP/"
*header
CodeFromAst
)Code in source file:
import math, strformat
let
n = 1
echo n + 1 # 2
## documentation comment
struct(http):
s: _ = "HTTP/"
*header: {headers}
Code as shown in html:
import
math, strformat
let n = 1
echo n + 1
## documentation comment
struct(http):
s:
_ = "HTTP/"
*header
Uses macro toStr(body) = body.toStrLit
Still available with -d:nimibCodeFromAst
CodeAsInSource
)Code in source file:
import math, strformat
let
n = 1
echo n + 1 # 2
## documentation comment
struct(http):
s: _ = "HTTP/"
*header: {headers}
Same as shown in html:
import math, strformat
let
n = 1
echo n + 1 # 2
## documentation comment
struct(http):
s: _ = "HTTP/"
*header: {headers}
This is what you expect!✨
Does not compose well (134), there might still be 🐞s
NbBlock
👷import nimib
nbInit # creates a nb: NbDoc object
# it has attribute nb.blocks: seq[NbBlock] = []
nbText: "hi" # add new NbBlock to nb.blocks = [🟩]
nbCode: echo "hi" # add new NbBlock to nb.blocks = [⬛, 🟩]
nbImage("hi.png") # add new NbBlock to nb.blocks = [⬛, ⬛, 🟩]
nbSave # process and render each block (+theme stuff +save file)
collected when block is created
optional: process data
(e.g. convert markdown to html)
apply templates to data
there are multiple render backends (e.g markdown)
we will focus on HTML backend
NbBlock
?type
NbBlock = ref object
## DATA 👇
code*: string ## nbCode source
output*: string ## nbCode output / nbText text
context*: Context ## think of this as a JsonNode
command*: string ## (NbCode, NbText, ...) used for render
NbDoc* = object
blocks*: seq[NbBlock]
blk*: NbBlock ## current block being processed
## PROCESS 👇
renderPlans*: Table[string, seq[string]]
## key is command, value is a seq of proc names
renderProcs*: Table[string, NbRenderProc]
## key is proc name, value is implementation
## RENDER 👇
partials*: Table[string, string]
## key is command, value is a mustache template
NbRenderProc* = proc (doc: var NbDoc, blk: var NbBlock) {.nimcall.}
var partials: Table[string,string]
partials["doc"] = """
<head>
{{> head }}
</head>
<body>
{{> main }}
</body>"""
partials["head"] = """
{{#t}}<title>{{t}}</title>{{/t}}
"""
partials["main"] = """
{{&html}}
{{code}}"""
var context = newContext()
context["t"] = "hi"
context["html"] = "<p>hi</p>"
context["code"] = "1 < 2"
context.searchTable(partials)
echo "{{>doc}}".render(context)
<head> <title>hi</title> </head> <body> <p>hi</p> 1 < 2</body>
nbText
template nbText*(text: string) =
newNbSlimBlock("nbText"):
nb.blk.output = text
nb.renderPlans["nbText"] = @["mdOutputToHtml"]
nb.renderProcs["mdOutputToHtml"] = mdOutputToHtml
# mdOutputToHtml adds `outputToHtml` to data
nb.partials["nbText"] = "{{&outputToHtml}}"
nbCode
template nbCode*(body: untyped) =
newNbCodeBlock("nbCode", body): # create block and save source
captureStdout(nb.blk.output): # run and capture output
body
nb.renderPlans["nbCode"] = @["highlightCode"]
nb.renderProcs["highlightCode"] = highlightCode
# highlightCode adds `codeHighlighted` to data
nb.partials["nbCode"] = """
{{>nbCodeSource}}
{{>nbCodeOutput}}"""
nb.partials["nbCodeSource"] =
"<pre><code class=\"nim hljs\">{{&codeHighlighted}}</code></pre>"
nb.partials["nbCodeOutput"] = """{{#output}}
<pre class="nb-output"><samp>{{output}}</samp></pre>
{{/output}}"""
nbImage
template nbImage*(url: string, caption = "") =
newNbSlimBlock("nbImage"):
nb.blk.context["url"] = url # *special handling of relative paths
nb.blk.context["caption"] = caption
nb.partials["nbImage"] = """<figure>
<img src="{{url}}" alt="{{caption}}">
<figcaption>{{caption}}</figcaption>
</figure>"""
nbCodeInBlock
: a nbCode
in a block:
nbTextWithCode
: a nbText
that saves code source
nbFile
: writes a file with content (there is an untyped version for nim code)
nbRawHtml
: used to output raw html
template newNbCodeBlock*(cmd: string, body, blockImpl: untyped) =
discard
template newNbSlimBlock*(cmd: string, blockImpl: untyped) =
discard
(a slim block is a block without body)
Copying and customizing blocks
template nbCodeHtmlOutput(body: untyped) =
nbCode:
body
nb.blk.command = "nbCodeHtmlOutput"
nb.partials["nbCodeHtmlOutput"] =
nb.partials["nbCode"].replace("{{>nbCodeOutput}}", "{{&output}}")
nbCodeHtmlOutput:
for color in ["blue", "green", "yellow"]:
echo "<span style=\"color:" & color & "\">" & color & "</span>"
blue
green
yellow
Composing other blocks
template nbTextRepeat(text: string, repeat: int) =
for i in 1 .. repeat:
nbText: text
nbTextRepeat("All work and no play makes Jack a dull boy", 3)
All work and no play makes Jack a dull boy
All work and no play makes Jack a dull boy
All work and no play makes Jack a dull boy
nbRawHtml
is particularly powerful
template nbDetails(summary: string, body: untyped) =
nbRawHtml: "<small><details><summary>" & summary & "</summary>"
body
nbRawHtml: "</details></small>"
nbDetails("click to reveal details"):
nbTextSmall: "one block"
nbCode: discard "another block"
one block
discard "another block"
nbRawHtml
is particularly powerful
(nimislides uses it for slide
template)
limited to html backend
cannot be customized
a better solution will come with the container block!
blocks which use external javascript functionalities
all examples taken from nblog
<head>
sectionnb.partials["head"] &= "<style>..."
<body>
sectionnb.partials["main"] &= "<script>..."
nbPython: hlPy"""
print("Hello World")
"""
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-5, 5)
y = np.sin(x)
plt.plot(x, y)
plt.title("nbPython Plot")
plt.savefig("matplotlib_example.png", dpi=60)
Create interactive elements in Nimib using Nim!
Engaging content
Comfortable - Nim all the way
Runs locally
Fun!
Nim → JS
Capture variables
nbJsFromCode
- compiles code to JSnbKaraxCode
- sugar for KaraxnbJsFromCode
nbRawHtml: """<p id="text-id">You have clicked 0 times!</p>
<button id="btn-id">Click me!</button>"""
nbJsFromCode:
import std / dom
let btn = getElementById("btn-id")
let paragraph = getElementById("text-id")
var counter: int
btn.addEventListener("click", proc (event: Event) =
counter += 1
paragraph.innerHtml = "You have clicked " & $counter & " times!"
)
You have clicked 0 times!
Capture variables
let name = "Hugo"
let food = "hot dogs"
nbRawHtml: """
<p id="text2-id">...</p>
<button id="btn2-id">Click me!</button>
"""
nbJsFromCode(name, food):
import std / dom
let btn = getElementById("btn2-id")
let paragraph = getElementById("text2-id")
btn.addEventListener("click", proc (event: Event) =
paragraph.innerHtml = name & "'s favourite food is " & food
)
...
nbJsFromCode + Karax
let rootId = "karax-" & $nb.newId()
nbRawHtml: "<div id=\"" & rootId & "\"></div>"
nbJsFromCode:
include karax / prelude
var counter: int
proc createDom(): VNode =
result = buildHtml(tdiv):
p:
text "You have clicked " & $counter & " times!"
button:
text "Click me!"
proc onClick() =
counter += 1
setRenderer(createDom, root = rootId.cstring)
nbKaraxCode
nbKaraxCode:
var counter: int
karaxHtml:
p:
text "You have clicked " & $counter & " times!"
button:
text "Click me!"
proc onClick() =
counter += 1
postRender
nbKaraxCode:
import jscanvas, dom, colors, math, random
postRender:
var c = getElementById("canvas-id").CanvasElement
# canvas will be nil if it hasn't
# been created by Karax yet
c.width = 500
c.height = 100
var ctx = c.getContext2d()
# Fill background
ctx.fillStyle = $colBlack
ctx.fillRect(0,0, c.width, c.height)
# Draw ball
var x = rand(0..c.width)
var y = rand(0..c.height)
var ballRadius = 10
ctx.beginPath()
ctx.arc(x, y, ballRadius, 0, Pi*2)
ctx.fillStyle = $colBlue
ctx.fill()
ctx.closePath()
karaxHtml:
canvas(id="canvas-id")
example of a more complex nbKarax app
based on a plot function built with js canvas
preliminary api for karax widgets
VS Codium/VS Code extension
Syntax highlighting
Preview
Let's head over to VSCodium!
first goal: produce stuff with nimib 0.3.x!
more scinim/getting-started tutorials! 👩🔬
Advent of code! 🎄
next 0.4 target: backend maker
side projects:
(Pietro) a blog theme / jekyll clone
(after 0.4) new backends (e.g latex, twitter?, ...)
build a library directly from documentation (like in nbdev)?
nimib executable for scaffolding
possibility of editing document in the browser
docs are now built in CI
deploy preview!
we added tests (and removed ptest)
updated docs and added a separate changelog
a new CONTRIBUTE.md!
me and Hugo have been meeting regularly
thinking of keeping this up (once a month)
open to anyone using nimib or contributing
will announce somewhere (nimib discussions?)
(inspired by Simon Willson)