• Stars
    star
    104
  • Rank 330,604 (Top 7 %)
  • Language
    Ruby
  • License
    Other
  • Created over 15 years ago
  • Updated over 10 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

ParseTree meets Markaby
  
  ~ ~ NOTE~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

  I'm just pushing the latest changes out before I'll go on
  holiday, so at the moment it's not working 100% correctly.
  
  If you still want to play around you could:
    1) git checkout 0797b2a253e40103c8132374996c1dc9ed5aa4aa (old version)
    2) clone SexpTemplate and SexpBuilder (see github.com/judofyr) and
       put them in the load path at the top of lib/parkaby.rb
       
  I'll release it as a gem when I'll get back home.
  
  //Magnus Holm
  
  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

= Parkaby, ParseTree meets Markaby

    In the beginning God created ERB. And ERB was waste and void;
    and darkness was upon the face of the deep: and the Spirit of
    God moved upon the face of the templates. And God said, Let
    there be Ruby: and there was Markaby.
    
                                ~~ Genesis 1:1-3, The Template Engine Bible

Ruby is nice. I think that's something we all can agree on. When you suddenly
need to output some HTML in the middle of your app, it's very convenient to
continue writing Ruby, instead of switching to String and interpolation.
That's where Markaby comes in:

  mab {
    html {
      head {
        title "happy title"
      }
      body {
        h1 "happy heading"
        a "a link", "href" => "url"
      }
    }
  }
  
Of course, we all know that Ruby is slow. Needless to say, that makes Markaby
slow too. Not even Tagz, Ara T Howard's fast Markaby-clone, can't stand a chance
against Erubis and Haml. It's time we fight back. Let's show them that pure,
readable Ruby can get on par with percent signs and forced indentations:

  Parkaby {
    html {
      head {
        title "happy title"
      }
      body {
        h1 "happy heading"
        a "a link", "href" => "url"
      }
    }
  }
  
See, no changes at all, but take a look at the benchmark:

  ~> ruby bench/run.rb simple 10000
  
                             user     system      total        real
  Erubis                 0.030000   0.000000   0.030000 (  0.022264)
  Haml                   0.110000   0.000000   0.110000 (  0.117887)
  Parkaby (def_method)   0.130000   0.000000   0.130000 (  0.135996)
  Parkaby (render)       0.150000   0.010000   0.160000 (  0.150680)
  Parkaby (inline)       0.970000   0.000000   0.970000 (  0.988010)
  Tagz                   3.250000   0.040000   3.290000 (  3.400699)
  Markaby               12.610000   0.140000  12.750000 ( 13.067794)
      
Okay, with all respect: this is a really crappy benchmark. Luckily, the Haml
guys have written a very nasty template:

  ~> ruby bench/run.rb nasty 500
  
                             user     system      total        real
  Erubis                 0.190000   0.010000   0.200000 (  0.198487)
  Parkaby (def_method)   0.350000   0.000000   0.350000 (  0.363106)
  Parkaby (render)       0.360000   0.010000   0.370000 (  0.365007)
  Parkaby (inline)       0.570000   0.000000   0.570000 (  0.614286)
  Haml                   2.490000   0.030000   2.520000 (  2.620025)
  Tagz                   5.100000   0.060000   5.160000 (  5.394778)
  Markaby                7.220000   0.090000   7.310000 (  7.630588)
     
That's more like it!

It's still not a truly fair comparision though. Both Parkaby, Tagz and Markaby
escapes all input, while in Erubis and Haml you'll need to explicitly mark it
where needed. In the nasty template, nearly nothing needs to be escaped, so
Parkaby, Tagz and Markaby are doing a lot of escaping for nothing!

  ~ ~ UPDATE  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  
  Nathan Weizenbaum reports in: "You say in the Parkaby docs that in Haml you
  need to manually mark escaping, but that's not true. Haml supports an
  option (:escape_html) that makes it default to escaping all input."
  
  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

In a real-life scenario you want to escape nearly everything which comes from
the user, so I'm still looking for a better template to run benchmarks on.

= Synopsis

  require 'lib/parkaby'
  
  before = self
  
  ## inline
  Parkaby {
  
    self == before # => true
    
    self << "<!DOCTYPE html>"
    # or: text "<!DOCTYPE html>"
    
    html {
    
      head { something }        # tag unless self.respond_to?(:head)
      tag.head { something }    # force a tag
      self.head { something }   # force a method call
      
      span "<em>Escape!</em>" # => "<span>&lt;em&gt;Escape!&lt;/em&gt;</span>"
      span { "<em>No escape!</em>" } # => "<span><em>No escape!</em></span>"
      
      div {
        strong "Ruby!"  # You can't mix tags
        "Silent."       # and return values in blocks
      } # => "<div><strong>Ruby!</strong></div>"
      
      a "something", :href => 'http://google.com'
      
      a(:href => 'http://google.com') { "something else" } 
    }
    
  } # => lots of html
  
  ## Using Parkaby::Template
  
  temp = Parkaby::Template.string('strong self')
  #    = Parkaby::Template.block { strong self }
  
  ctemp = temp.compile(Helpers)
  ctemp.render("Ruby!") # => "<strong>Ruby!</strong>"
  
  temp.def_method(String, :strong)                  
  "Ruby!".strong # => "<strong>Ruby!</strong>" 
  
= More

The easiest way to use Parkaby is simply to pass in a block:

  Parkaby { html { ... } } # => "<html>...</html>"

Let's however take a look at how to use Parkaby::Template:

  temp = Parkaby::Template.string('html { ... }')
  temp = Parkaby::Template.block { html { ... } }
  
After you got a Parkaby::Template, you'll have to compile it to a
Parkaby::CompiledTemplate. There are two types of a compiled template: One
that stores the template as a string, and one that stores it as a proc. The
latter is quite a lot faster the former, but if you're going to evaluate the
template under a binding, you'll have to use a string.

You also have to give it a helper object when you compile it. This makes sure
it won't turn methods into HTML-blocks if they exist on the helper object.
(You can however always prepend the method with "self." to force a method
call.)

 ctemp = temp.compile_as_string(helper || binding)
 ctemp = temp.compile_as_proc(helper || binding)

 temp.compile(binding) == temp.compile_as_string(binding)
 temp.compile(helper)  == temp.compile_as_proc(helper)

After you've compiled it, you just call #render with the object that you want
to be `self`, or the binding it should be called with. Notice that both
compile_as_proc and compile_as_string supports both bindings and regular
objects as arguments (same goes for their #render). They're smart enough to
convert it the way they want it.

You can also use Template#def_method to define it as a method on an object:

  temp.def_method(String, :cool)
  "awesome".cool
  
  obj = Object.new
  temp.def_method(obj, :cool)
  obj.cool
  
  # Use :instance_eval to make it a class method on a class
  temp.def_method(String, :cool, :instance_eval)
  String.cool

= How

It's quite obvious that Parkaby is fast. How? The secret ingredient is:
ParseTree! ParseTree turns this:

  html {
    head {
      title "happy title"
    }
    body {
      h1 "happy heading"
      a "a link", "href" => "url"
    }
  }

into this:

  s(:iter,
   s(:call, nil, :html, s(:arglist)),
   nil,
   s(:block,
    s(:iter,
     s(:call, nil, :head, s(:arglist)),
     nil,
     s(:call, nil, :title, s(:arglist, s(:str, "happy title")))),
    s(:iter,
     s(:call, nil, :body, s(:arglist)),
     nil,
     s(:block,
      s(:call, nil, :h1, s(:arglist, s(:str, "happy heading"))),
      s(:call,
       nil,
       :a,
       s(:arglist,
        s(:str, "a link"),
        s(:hash, s(:str, "href"), s(:str, "url"))))))))

then Parkaby::Processor turns it into this:

  s(:parkaby,
   :begin,
   s(:parkaby,
    :blocktag, 
    :html,  
    s(:block,         
     s(:parkaby,
      :blocktag,
      :head,
      s(:parkaby, :tag, :title, s(:str, "happy title"), nil),
      nil),
     s(:parkaby,
      :blocktag,
      :body,
      s(:block,
       s(:parkaby, :tag, :h1, s(:str, "happy heading"), nil),
       s(:parkaby,
        :tag,
        :a,
        s(:str, "a link"),
        s(:hash, s(:str, "href"), s(:str, "url")))),
      nil)),
    nil))
    
and Parkaby::Generator turns that into this:

  _parkaby_buffer = [_parkaby_current = []]
  _parkaby_current << "<html>"
  _parkaby_buffer << (_parkaby_current = [])
  _parkaby_value = begin
  (_parkaby_current << "<head>"
  _parkaby_buffer << (_parkaby_current = [])
  _parkaby_value = begin
  _parkaby_current << "<title>happy title</title>"
  end
  _parkaby_current << _parkaby_value if _parkaby_current.empty?
  _parkaby_current << '</head>'
  _parkaby_current << "<body>"
  _parkaby_buffer << (_parkaby_current = [])
  _parkaby_value = begin
  (_parkaby_current << "<h1>happy heading</h1>"
  _parkaby_current << "<a href=\"url\">a link</a>"
  )
  end
  _parkaby_current << _parkaby_value if _parkaby_current.empty?
  _parkaby_current << '</body>'
  )
  end
  _parkaby_current << _parkaby_value if _parkaby_current.empty?
  _parkaby_current << '</html>'
  _parkaby_buffer.join 
  
In this specific example you can clearly see that we still have quite a few
optimizations left until it's perfect, but it's still 23 times faster than
Markaby's:

  mab {
    html {
      head {
        title "happy title"
      }
      body {
        h1 "happy heading"
        a "a link", "href" => "url"
      }
    }
  }

= Notes

* Parkaby doesn't change `self` at all.
* It's smart (maybe too smart):
  * Parkaby { li "I", "got", "three arguments" }     # => NoMethodError
  * Parkaby { li({:hash => :fist}, "content after")} # => NoMethodError
  * Parkaby { self.li }                              # => NoMethodError
  * Parkaby { li } # => "<li/>"
  * def li end; Parkaby { li } # => ""
* Currently, you must give it a Hash-literal for the attributes. [BUG]
* CssProxy and capture isn't implemented yet.
* I'm thinking of adding a a follow-feature: when it sees `follow.some_method`
  it fetches the method definition and inlines it in the main template.
* Yes, this is very experimental and I don't think anyone is ever going to use 
  it.
  

More Repositories

1

temple

Template compilation framework in Ruby
Ruby
482
star
2

perloku

Perl on Heroku
Shell
154
star
3

timeless

A mixture of a blog, wiki and CMS, inspired by Christoffer Sawicki's Termos and James Adam's Vanilla.rb
Ruby
116
star
4

glush

A parser toolkit
Ruby
95
star
5

duktape.rb

Ruby bindings to the Duktape JavaScript interpreter
C
76
star
6

gash

Git + Hash
Ruby
58
star
7

github-js

GitHub.js
JavaScript
52
star
8

tubby

HTML templates as Ruby
Ruby
46
star
9

minz

Minimal string compression
Zig
37
star
10

minitest-line

Ruby
35
star
11

rubyscript

A Ruby VM implemented in JavaScript
Ruby
33
star
12

bankid-api

API documentation for the Norwegian BankID protocol
32
star
13

zini

Minimal perfect hash function for Zig
Zig
28
star
14

camping

the 4k pocket full-of-gags web microframework
Ruby
27
star
15

parg

Lightweight argument parser for Zig
Zig
25
star
16

cab

Lightweight code reloader for Ruby/Rack
Ruby
24
star
17

rcpu

DCPU assembler and emulator in Ruby
Ruby
22
star
18

ish

Sketches for Zig
Zig
20
star
19

gemify

The lightweight gemspec editor.
Ruby
20
star
20

recursive

The Recursive Benchmark
JavaScript
20
star
21

nokogirl

Nokogirl โ€” because developers can't spell
Ruby
16
star
22

kramer

Generalized parser combinators in Ruby
Ruby
15
star
23

rgb-tree

RGB trees, a generalization of red-black trees. Implemented in Zig.
Zig
15
star
24

grancher

Easily copy folders and files to other Git branches
Ruby
15
star
25

quickgem

Speed up load time
Ruby
13
star
26

quickedit

Quickedit is a micro-CMS which you can easily embed in any Rails, Sinatra or Rack apps
13
star
27

devlicious

Chrome DevTools for Mojolicious
Perl
13
star
28

handlebars-jit

A JIT for Mustache
Ruby
13
star
29

imba-rails

Ruby
12
star
30

ippon

The gentle way
Ruby
11
star
31

try-camping

RSoC: Interactive tutorials for learning web development in Ruby
10
star
32

woop-2018

a programming language and environment that is almost entirely written in itself
10
star
33

ImbaKit

C
8
star
34

mockie

The beginning of a proper mockup-driven template language
JavaScript
8
star
35

hox

Web toolkit in Nim
Nim
7
star
36

filtering_camping

Adding before/after-filters to Camping
Ruby
7
star
37

quickload.js

JavaScript
7
star
38

rumble

Ruby
6
star
39

toml.nim

TOML parser for Nim
Nim
6
star
40

imba-loader

JavaScript
6
star
41

imba-styles

JavaScript
6
star
42

dep.js

a tiny dependency manager
JavaScript
5
star
43

sodium.nim

Nim
5
star
44

solarbeam

Super effective Solr client in Perl that uses Mojolicious' event loop
Perl
5
star
45

aoc2018

Brainfuck
5
star
46

tvo-lang

Ruby
4
star
47

arch-musl

PKGBUILDs for various statically built libraries using musl
Shell
4
star
48

shasm.js

JavaScript
4
star
49

camping-test

Lightweight testing framework for Camping
Ruby
4
star
50

uno-lang

A programming language
Ruby
4
star
51

sexp_template

A template engine which allows you to express S-expressions in pure Ruby
Ruby
3
star
52

imba-experiments

JavaScript
3
star
53

sexp_builder

Easily process and rewrite S-expressions using SexpPath
Ruby
3
star
54

imba-template

Ruby
3
star
55

zig-dot-builder

Easily create .dot files from Zig
Zig
3
star
56

rpython-example

Just me playing with RPython
Python
3
star
57

ethercode

Ruby
3
star
58

forter

Forte implementation in Ruby
Ruby
3
star
59

random

Ruby
3
star
60

flexmex

C
2
star
61

doo

Development tool for starting and running services
Go
2
star
62

jason.lua

Lua
2
star
63

imba-source

Ruby
2
star
64

bob

Bob the Builder
Nim
2
star
65

hix

HTTP server for Nim
Nim
2
star
66

glick

Implementation of Glicko-2 in Ruby
Ruby
2
star
67

dotfiles

Vim Script
2
star
68

tapper

Write simple TAP tests in Ruby
Ruby
2
star
69

mockle

TODO: Implement this
JavaScript
2
star
70

wordle-analyze

Ruby
2
star
71

mockle.rb

Just me playing with another template engine. Move along.
Ruby
2
star
72

nir

C++
2
star
73

heffigy

Fast DOM manipulation template framework
Ruby
2
star
74

sofaskull

Skull+
Ruby
2
star
75

Paws.rb

Paws implementation in Ruby
2
star
76

tipi

Ruby
1
star
77

ufwx

Just 4-letter random project name that Mike Perham won't take. Still in need of some code thoughโ€ฆ
1
star
78

latcher

A more precise Matcher (for CtrlP)
Lua
1
star
79

zrb

Ruby
1
star
80

json-builder

Perl
1
star
81

minad

Shell
1
star
82

willdo

Task manager in Vim for programmers
Vim Script
1
star
83

minitest-chain

Ruby
1
star
84

tada

Ruby
1
star
85

smockle

1
star
86

dominic

Ruby
1
star
87

appy

Ruby
1
star
88

mockle.spec

Specification for the Mockle template language
Ruby
1
star
89

coordinated-omission-example

Go
1
star
90

perloku-debug

Shell
1
star
91

phat

No idea what this project is about. I just wanted the name so people in the future can call me "the fat guy" the same way the call Marc "the thin guy"
1
star