⚡️ A high-performance template engine & markup language
Fast • Compiled • Written in Nim 👑
nimble install tim
/ npm install @openpeeps/tim
or more like a todo list
json
, js
, yaml
, css
Tim as a Package: For developers looking to incorporate Tim's power into their projects, Tim Engine is
also available for Nim development as a Nimble package and for JavaScript developers as a native Node.js & Bun .addon
.
This allows you to seamlessly integrate Tim compilation within your existing workflow, empowering you to leverage
Tim's capabilities directly within your codebase.
nimble install tim
npm install @openpeeps/tim
Standalone CLI App
This user-friendly command-line interface allows you to easily compile Tim code
directly to your desired target source code. Simply provide your Tim code as input, and the CLI will
output the equivalent code in Nim
, JavaScript
, Ruby
, or Python
.
SSR
via ZeroMQ
div.container > div.row > div.col-lg-7.mx-auto
h1.display-3.fw-bold: "Tim is Awesome"
a href="https://github.com/openpeeps/tim" title="This is hot!": "Check Tim on GitHub"
👉 Tim Syntax Highlighting plugins
VSCode Extension | Sublime Text 4
Check /example folder to better understand Tim's structure. Also check the generated HTML file
Tim has its own little filesystem that continuously monitors .timl
for changes/creation or deletion.
Here is a basic filesystem structure:
Tim Engine seamlessly shifts rendering to the client side for dynamic interactions, using the intuitive @client
block statement.
body
section#contact > div.container
div.row > div.col-12 > h3.fw-bold: "Leave a message"
div#commentForm
@client target="#commentForm"
form method="POST" action="/submitComment"
div.form-floating
input.form-control type="text" name="username"
placeholder="Your name" autocomplete="off" required=""
label: "Your name"
div.form-floating.my-3
textarea.form-control name="message" style="height: 140px" required="": "Your message"
label: "Your message"
div.text-center > button.btn.btn-dark.px-4.rounded-pill type="submit": "Submit your message"
@end
Tim provides 3 types of data storages. Global and Local as JsonNode objects for handling immutable data from the app to your timl
templates,
and Template based data at template level using Tim's built-in AST-based interpreter.
$app
in a template will mark it as JIT.
timl.precompile(
global = %*{
"year": parseInt(now().format("yyyy"))
}
)
Accessing global data can be done using the $app
constant:
footer > div.container > div.row > div.col-12
small: "© " & $app.year & " — Made by Humans from OpenPeeps"
$this
can be used to access data from the local storage.$this
in a template will mark it as JIT.timl.render("index", local = %*{
loggedin: true,
username: "Johnny Boy"
})
if $this.loggedin:
h1.fw-bold: "Hello, " & $this.username
a href="https://github.com/openpeeps/tim/blob/main/logout": "Log out"
else:
h1: "Hello!"
a href="https://github.com/openpeeps/tim/blob/main/login": "Please login to view this page"
var
or const
. The only difference
between these two is that constants are immutable and requires initialization.The scope of a declared variable is limited to the branch in which it was declared.
var a = 1 // a global scoped variable
if $a == 1:
var b = 2 // a block-scoped variable
echo $a + b // prints 3
echo $b // error, undeclared variable
Template variables are known at compile time. So the final output is generated as .html
.
If the assigned value comes from local or global storage, then it will automatically trigger the JIT flag
and the final result will be saved as .ast
Supported datatypes: string
, int
, float
, bool
, array
, object
var a = "Hello"
var b = 10
var c = 10.5
var d = true
var e = [] // init an empty array
var f = {} // init an empty object
Math is cool.
var x = 2 * 2 - 1.5
echo $x // 2.5
Sometimes you want to know what the heck is going on! For debug reasons you can use echo
to print data.
echo "Hello, World!"
echo $this.weirdThing
Also, Tim provides an assert
command so you can unit test your code.
var x = "Tim is awesome, right?"
assert $x.type == string
assert $x == "Tim is awesome, right?"
Note, assert
commands are cleared when in release
mode
fn say(x: string): string // forward declaration
fn say(x: string): string =
return "Hello, " & $x
echo say("Pantzini!")
echo say "Pantzini" // this works too
// function overloading works too
fn say(x: int): int =
return $x * 1
echo say(1)
h1 > span: say(2)
var x = 1
if $x == 1 and $x > 0:
span: "one"
elif $x == 0 or $x < 1:
span: "zero"
else:
span: "nope"
var boxes = [
{
title: "Chimney Sweep"
description: "Once feared for the soot they carried,
these skilled climbers cleaned fireplaces to prevent
fires and improve indoor air quality"
}
{
title: "Town Crier"
description: "With booming voices and ringing bells,
they delivered news and announcements in the days
before mass media"
}
{
title: "Ratcatcher"
description: "These pest controllers faced smelly
challenges, but their work helped prevent the
spread of diseases like the plague"
}
]
div.container > div.row.mb-3
div.col-12 > h3.fw-bold: "Forgotten Professions"
for $box in $boxes:
div.col-lg-4 > div.card > div.card-body
div.card-title.fw-bold.h4: $box.title
p.card-text: $box.description
var
i = 0
x = ["fork", "work", "push"]
while $i < $x.high:
span: $x[$i]
inc $i
break
command can be used in for
and while
loops to immediately leave the loop body
for $c in "hello":
echo $x
break
todo
Tim integrates a variety of embeddable code formats, including: JavaScript, YAML/JSON and CSS
@js
document.addEventListener('DOMContentLoaded', function() {
console.log("Hello, hello, hello!")
});
@end
Note that JSON and YAML blocks requires identification, a #someIdent
is required after @json
or @yaml
@json#sayHelloJson
{"hello": "hello"}
@end
Tim can parse and validate YAML contents.
@yaml#sayHelloYaml
hello: "hello"
@end
todo
todo
Tim provides a built-in standard library of functions and small utilities:
std/system
(loaded by default), std/[os, strings, arrays, objects, math]
// std/system
fn random*(max: int): int
fn len*(x: string): int
fn encode*(x: string): string
fn decode*(x: string): string
fn toString*(x: int): string
fn timl*(code: string): string
// std/math
fn ceil*(x: float): float
fn floor*(x: float): float
fn max*(x: int, y: int): int
fn min*(x: int, y: int): int
fn round*(x: float): float
fn hypot*(x: float, y: float): float
fn log*(x: float, base: float): float
fn pow*(x: float, y: float): float
fn sqrt*(x: float): float
fn cos*(x: float): float
fn sin*(x: float): float
fn tan*(x: float): float
fn acos*(x: float): float
fn asin*(x: float): float
fn rad2deg*(d: float): float
fn deg2rad*(d: float): float
fn atan*(x: float): float
fn atan2*(x: float, y: float): float
fn trunc*(x: float): float
// std/strings
fn endsWith*(s: string, suffix: string): bool
fn startsWith*(s: string, prefix: string): bool
fn capitalize*(s: string): string
fn replace*(s: string, sub: string, by: string): string
fn toLower*(s: string): string
fn contains*(s: string, sub: string): bool
fn parseBool*(s: string): bool
fn parseInt*(s: string): int
fn parseFloat*(s: string): float
fn format*(s: string, a: array): string
// std/arrays
fn contains*(x: array, item: string): bool
fn add*(x: array, item: string): void
fn shift*(x: array): void
fn pop*(x: array): void
fn shuffle*(x: array): void
fn join*(x: array, sep: string): string
fn delete*(x: array, pos: int): void
fn find*(x: array, item: string): int
// std/os
fn absolutePath*(path: string): string
fn dirExists*(path: string): bool
fn fileExists*(path: string): bool
fn normalize*(path: string): string
fn getFilename*(path: string): string
fn isAbsolute*(path: string): bool
fn readFile*(path: string): string
fn isRelative*(path: string, base: string): bool
fn getCurrentDir*(): string
fn join*(head: string, tail: string): string
fn parentDir*(path: string): string
fn walkFiles*(path: string): array
Compile your project with -d:timHotCode
flag, then connect to Tim's WebSocket server to auto reload the page when there are changes on disk.
Note that this feature is not available when compiling with -d:release
.
The internal websocket returns "1"
when detecting changes, otherwise "0"
{
function connectWatchoutServer() {
const watchout = new WebSocket('ws://127.0.0.1:6502/ws');
watchout.addEventListener('message', (e) => {
if(e.data == '1') location.reload()
});
watchout.addEventListener('close', () => {
setTimeout(() => {
console.log('Watchout WebSocket is closed. Try again...')
connectWatchoutServer()
}, 300)
})
}
connectWatchoutServer()
}
Tim Engine | LGPLv3
license. Made by Humans from OpenPeeps.
Copyright © 2024 OpenPeeps & Contributors — All rights reserved.