Tattoo, A simple hyper text programming language and transcompiler to PHP templates 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.
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.
Composer, whoop whoop.
$ composer require mario-deluna/tattoo
$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
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.
Let's just drive directly into the syntax.
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>
Sometimes you want to create a node without any contents.
[img src: 'logo.png']
<img src="logo.png" />
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>
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>
[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>
[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>
HTML:
<form id="login-form" action="/login/">
<span class="info">Please provide you login information.</span>
</form>
[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>
@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>
@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>
// 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 [email protected] {
@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>
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 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.