https://github.com/rattleycooper/pagecraft
DSL for generating HTML with Nim.
https://github.com/rattleycooper/pagecraft
domain-specific-language dsl html macros metaprogramming nim nim-lang template-engine
Last synced: about 1 month ago
JSON representation
DSL for generating HTML with Nim.
- Host: GitHub
- URL: https://github.com/rattleycooper/pagecraft
- Owner: RattleyCooper
- License: mit
- Created: 2024-03-03T19:01:00.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-20T01:58:02.000Z (about 2 months ago)
- Last Synced: 2025-03-20T02:42:00.257Z (about 2 months ago)
- Topics: domain-specific-language, dsl, html, macros, metaprogramming, nim, nim-lang, template-engine
- Language: Nim
- Homepage:
- Size: 49.8 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ✧ PageCraft ✧
A powerful Template Engine/DSL for generating dynamic HTML.
Expands on the concepts introduced at:https://hookrace.net/blog/introduction-to-metaprogramming-in-nim/
to create a more feature rich DSL for generating HTML in nim. It features full data interpolation, logic/control flow, and exception handling.
Pagecraft focuses on ease of development. There are very few things you really need to know to write pagecraft templates. If you know html, you can create webpages very quickly with pagecraft.
## Install
Run `nimble install` inside the directory you extract this repository into or run:
`nimble install https://github.com/RattleyCooper/PageCraft`
## Usage
Define a `proc`, tag it with `{.htmlTemplate.}`, then you can generate HTML using `pagecraft` syntax within that procedure and it will return a string containing the HTML. If you need/want to use nim in your template code you can mix in most nim control flow constructs seamlessly, but if you want to run nim code without the macro messing with anything you can put the code into a `nim` or `nimcode` block. This will bypass pagecraft's evaluation of the code entirely.
You may run into tags or keywords that do not work, as they are reserved by Nim. One of these is `div`(for division). You can write `` `div` `` (enclosed with backticks) instead of `div` to create a `` tag. If you run into other tags or keywords that don't work because they're used by nim, enclose the tag with backticks. The same goes for keywords in HTML tags.PageCraft is tag-agnostic and will not create closing tags if there is no content. For example, `script src="/webui.js"` will NOT create a closing `` tag because there is no content defined. You can add an empty string as content(`script src="/webui.js: ""`), or add `/script` on the next line to force the creation of a closing tag for you. This is shown in the example below.
## Examples
```nim
import pagecraft
import strutils# Alternatively pass in records from a debby query,
# or params you can use for a db query.
proc myTemplate(title: string, content: string, contentURI: string, css: string) {.htmlTemplate.} =
# Run some nim code and set a new variable.
nimcode:
echo "This is regular nim code"
var newVar = "This is a newly created variable"# Add raw strings
""# Access the currently generated HTML through
# the `result` variable.
nimcode:
echo "this is the current HTML:\n" & result# Define HTML using whitespace instead of <>
html lang="en":
head:
meta charset="UTF-8"
meta name="viewport", content="width=device-width, initial-scale=1.0"
# Mix pagecraft with nim
if title == "Hello":
title:
# Evaluate strings using `{}`
{title.toUpper()}
elif title == "World":
title: {title.toLower()}
else:
title: "UNEXPECTED TITLE"
# Use `{}` when inserting into tag keywords
link rel="stylesheet", href={css} # adds "" automatically
body:
header:
h1:
a href="/", class="homepage-link":
{title.toUpper()}# Let's use the new variable we created.
p: {newVar}
`div` class="myContentImage":
# If you don't use {} in tag kwargs then
# nim will treat your kwarg value as a
# string literal.
case title:
of "Hello", "World":
pagecraft:
a href="/":
img src={contentURI}
else:
""
# Running more pagecraft with nim.
`div` class="moreContent":
for x in 0 .. 10:
p: {"Remix pagecraft code into nim " & $x}`div` class="myContent":
h2: "Here is my content!"
p: {content}
# Add strings to the inner html of a tag
`div` class="contentWrapup":
"""
I hope you enjoyed my content!
"""footer:
p: "© 2024. All rights reserved. ʕ⊙ᴥ⊙ʔ"
# Use a script and add a closing script tag
script src="/scripts/prism.js"
/scriptecho myTemplate("This is my webpage", "Oh wow, this content!", "/assets/contentImg.png", "/scripts/prism.css")
```## PageCraft, Debby and Mummy
Pagecraft works very well with the `Mummy` HTML/Web socket server and the `Debby` ORM. Pull in data with `Debby`, generate HTML with `PageCraft` and serve it with `Mummy`.
```nim
import mummy, mummy/routers
import debby/[pools, sqlite]
import pagecraft
import strutils# Use debby pools with mummy to be safe
let pool = newPool()
for i in 0 ..< 10:
pool.add openDatabase("site.db")# DB Model
type Auto = ref object
id: int
make: string
model: string
year: int# Migrate
pool.dropTableIfExists(Auto)
pool.createTable(Auto)
var theAuto1 = Auto(
make: "Chevrolet",
model: "Camaro Z28",
year: 1970
)
var theAuto2 = Auto(
make: "Dodge",
model: "Challenger",
year: 1970
)
pool.insert(theAuto1)
pool.insert(theAuto2)# Tiny stylesheet for sanity
const style = """body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; }
section { padding: 20px; }
div { margin-bottom: 10px; padding: 10px; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 4px; }"""# Align the template stubs using `alignTemplate`
proc carCard(car: Auto) {.alignTemplate: 8.} =
`div` class="auto-div", id={$car.id}:
p: {"Make: " & car.make}
p: {"Model: " & car.model}
p: {"Year: " & $car.year}proc carsSection(cars: seq[Auto]) {.alignTemplate: 2.} =
# Main Autos section
section:
h2:
"Check out my cars!"
`div`:
# ~~~ Insert a new car card for each new car
section class="auto-section":
for car in cars:
{carCard(car)}# Generate our dynamic HTML
proc indexTemplate(cars: seq[Auto]) {.htmlTemplate.} =
# Define our site.
""
html lang="en":
head:
title: "Some Site"
style: {style} # Style for sanity
body:
h1: "Welcome to my car website!"
# Only display cars section if we have
# cars to show.
if cars.len > 0:
{carsSection(cars)}
else:
p: "No cars :("proc indexHandler(request: Request) =
var headers: HttpHeaders
headers["Content-Type"] = "text/html"var cars = pool.filter(Auto, it.year == 1970)
request.respond(200, headers, indexTemplate(cars))var router: Router
router.get("/", indexHandler)let server = newServer(router)
echo "Serving on http://localhost:8080"
server.serve(Port(8080))
```