mario-deluna / tattoo
A simple hyper text programming language
This package is auto-updated.
Last update: 2024-10-28 05:44:48 UTC
README
Tattoo is a simple hyper text programming / template language that renders into HTML.
h1 => 'Welcome to Tattoo :)'
There are still PHP5.3 users and I don't want to discriminate these outlaws. Means Tattoo works from 5.3 to 7.
Tattoo is not finished but in heavy development.
Tell me more!
A programming language written in PHP that compiles into PHP? When all you have is a hammer everything starts to look like a nail hu?
There is no real use case where you would build a stand alone Tattoo application. So making Tattoo work as a library seemed to be best way to go. Why PHP? Huge community, pretty fast and Im used to the syntax.
Installation
Composer, whoop whoop.
$ composer require mario-deluna/tattoo
Usage
$tattoo = new Tattoo\Tattoo(array('cache' => 'path/to/my/cache/dir/')); echo $tattoo->render('/path/to/a/tattoofile.tto', array( 'name' => $_GET['name'], ));
h1.page-title => 'Hello ' % @name
The great goal
After years of developing web applications I've got kind of sick that the only thing that always stayed a mess was the HTML markup. So there was this idea stuck in my head of a templating engine that actually was a programming language.
Let me show you an example:
<ul id="main-navigation" class="nav"> <li <?php if ($currentUrl === '/') : ?>class="active"<?php endif; ?>> <a class="navigation-item navigation-item-home <?php if ($currentUrl === '/') : ?> navigation-item-active <?php endif; ?>" href="/" > Home </a> </li> </ul>
This something i've seen a lot. We have got two inline statements here and honestly I dont know how to format that piece of code decently. Well this is the same piece of code in tattoo:
[ul #main-navigation .nav]
{
[li][a .navigation-item ~ home] => 'Home'
{
if @currentUrl == '/' {
@this.class.add('navigation-item-active')
@this.parent.class.add('active')
}
}
}
Tattoo considers html tags as scoped objects this allows you to write your markup in a real new dynamic way.
Syntax
Let's just drive directly into the syntax.
Nodes
Tattoo nodes are basically HTML tags. So let's write the alltime classic hello world:
h1 => "Hello World"
<h1>Hello World</h1>
You can use the first argument to add classes and set the nodes id.
h1 #page-title .underlined => "Hello World"
<h1 id="page-title" class="underlined">Hello World</h1>
All other arguments will be used as node attributes.
a.btn.btn-sm, href: '/login' => 'Sign in'
<a class="btn btn-sm" href="/login">Sign In</a>
Value less nodes
Sometimes you want to create a node without any contents.
[img src: 'logo.png']
<img src="logo.png" />
Scoped nodes
Obviously you are going to build a tree structure with tattoo. Node definitions inside []
allow a scope.
[div.image-container]
{
[img src: 'wallpaper.jpg']
}
<div class="image-container"> <img src="wallpaper.jpg"> </div>
You are still able to directly assign a text value.
[p] => 'Hello '
{
span => 'World'
}
<p> Hello <span>World</span> </p>
Node tree
Often you have a tree with many levels that just contain one child. Instead of creating a scope for every level you can just forward them.
[header][nav.navbar][ul][li.active][a href: '/'] => 'Home'
{
i.glyphicon ~ home => ''
}
<header> <nav class="navbar"> <ul> <li class="active"> <a href="/">Home <i class="glyphicon glyphicon-home"></i></a> </li> </ul> </nav> </header>
#### Appending classes
[div.row][div .col ~ md-4 ~ sm-6 ~ xs-12]
{
a .btn ~ lg ~ primary ~ block => 'Click Me'
}
<div class="row"> <div class="col col-md-4 col-sm-6 col-xs-12"> <a class="btn btn-lg btn-primary btn-block">Click Me</a> </div> </div>
## OLD STUFF KEEP UNTIL I REWROTE EVERYTHING
[form #login-form, action: '/login', method: 'post']
{
p .info => "Please provide your login information."
[input ]
}
button.btn title: 'Amazing Tooltip', data: {toggle: 'tooltip', placement: 'left'} => 'Hello!'
<button class="btn" title="Amazing Tooltip" data-toggle="tooltip" data-placement="left" >Hello!</button>
tag with scope
HTML:
<form id="login-form" action="/login/"> <span class="info">Please provide you login information.</span> </form>
modifying scope data
[a .ajax-trigger, href: "/notes/save/"]
{
@this.data: {
noteId: 123,
userId: 42,
revision: 1
};
@this.text = "Save your Note"
}
HTML:
<a class="ajax-trigger" href="/notes/save/" data-noteId="123" data-userId="42" data-revision="1">Save your Note</a>
The are vars
@applicationName = "Tattoo Application"
[head] {
title => 'Welcome | ' + @applicationName
}
[body][footer] {
span .small => 'Powerd by ' + @applicationName
}
HTML:
<head> <title>Welcome | Tattoo Application</title> </head> <body> <footer> <span class="small">Powerd by Tattoo Application</span> </footer> </body>
Loops and tree modifications
@pages = {
{ title: 'Home', link: '/home/', isActive: false },
{ title: 'About', link: '/about/', isActive: true },
{ title: 'Terms', link: '/terms/', isActive: false }
}
[ul .navigation]
{
each @page in @pages
{
[li][a .navigation-item, href: @page.link]
{
span.navigation-item-title => @page.title
if @page.isActive
{
@this.parent.class.add('active')
}
}
}
}
HTML:
<ul class="navigation"> <li> <a class="navigation-item" href="/home/"> <span class="navigation-item-title">Home</span> </a> </li> <li class="active"> <a class="navigation-item" href="/about/"> <span class="navigation-item-title">About</span> </a> </li> <li> <a class="navigation-item" href="/terms/"> <span class="navigation-item-title">Terms</span> </a> </li> </ul>
Extending tags
// the '*' tells tattoo to not automatically print the tag
extend input*: @input
{
[div .form-group]
{
@input.id = 'input-' + @input.name
label .form-label, for: @input.id => @input.placeholder
@input.class.add( 'form-control' )
if !@this.type {
@this.type = 'text'
}
render @input
}
}
[form action: '/login', method: 'post']
{
[input name: 'username', placeholder: 'your username']
[input name: 'password', placeholder: 'your password', type: 'password']
}
HTML:
<form action="/login" method="post"> <div class="form-group"> <label for="input-username">your username</label> <input id="input-username" class="form-control" type="text" name="username" placeholder="your username" /> </div> <div class="form-group"> <label for="input-password">your password</label> <input id="input-password"class="form-control" type="password" name="password" placeholder="your password" /> </div> </form>
Views
view page-header
{
default @title = 'Unknown'
default @subtitle = ''
default @underline = true
[div .page-title]
{
[h1]
{
print @title + ' '
if @subtitle
{
small .subtitle => @subtitle
}
if @underline
{
[hr .page-title-underline]{}
}
}
}
}
[page-header title: 'Welcome', subtitle: 'to tattoo']
HTML:
<div class="page-title"> <h1>Welcome <small>to tattoo</small></h1> <hr class="page-title-underline" /> </div>
Notes
Notes to myself and everyone who's bored.
A node ( [div]
, span => 'foo'
) always directly represents an element. All given data of such a node object is interpreted as element attribute.
Extending (extend
) a node allows to a callback like thingy after a the given node has been created also from the context of the node.
Preparing (prepare
) does the same thing as extend but the callback acts before any custom data is passed.
A view does not stand in any context of a node so all given data / arguments have to be handeled by the view itself.
You should be able to store nodes in variables and modify them before printing. ( see concept/objectvars.tto
, concept/bootstrapmodal.tto
)
Only nodes can be printed. When you print a string or a number to parser basically creates a text node.
A view can be loaded using the dobule point :
initiator.