Project

Markaby

0.08
No release in over 3 years
Tim Fletcher and _why's ruby driven HTML templating system
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

>= 2.0.0
 Project Readme

Markaby (Markup as Ruby)¶ ↑

Markaby is a very short bit of code for writing HTML pages in pure Ruby. It is an alternative to ERb which weaves the two languages together. Also a replacement for templating languages which use primitive languages that blend with HTML.

Install it¶ ↑

Just do what everyone else does:

# in Gemfile:
gem 'markaby'

then bundle install:

bundle install

Using Markaby with Rails 4/5+:¶ ↑

Install the gem (using bundler), then:

# in config/initializers/markaby.rb:

require 'markaby/rails'

Markaby::Rails::TemplateHandler.register!({
  tagset: Markaby::HTML5,
  indent: 2,
})

Name your templates with an html.mab extension. You’ll also want to configure your text editor to see .mab as ruby.

Here’s how you’d do that for Atom:

  1. Install the file-types module:

    apm install file-types
    
  2. in your config: Atom -> Config:

    "*":
      "file-types":
        "\\.mab$": "source.ruby"

Now that’s some chunky bacon!

Using Markaby in helpers:¶ ↑

Just call Markaby::Builder with a block as below.

You can also require ‘markaby/kernel_method’ to make it even easier:

# my_helper.rb:
require 'markaby/kernel_method' # or put this in an initializer

module MyHelper
  # note - you can also use Markaby::Builder.new { }.to_s
  def chunky_bacon
    mab do
      p "Chunky Bacon!"
    end
  end
end

Using Markaby with Sinatra (1.0+)¶ ↑

get '/foo' do
  mab :my_template # my_template.mab in the sinatra view path
end

If you are looking for sinatra support pre 0.7, see github.com/sbfaulkner/sinatra-markaby

Using Markaby with other frameworks¶ ↑

Tilt has a Markaby module, so in principle, any web framework that supports Tilt will also support Markaby. See the appropriate tilt documentation:

github.com/rtomayko/tilt

Using Markaby as a Ruby class¶ ↑

Markaby is flaming easy to call from your Ruby classes.

require 'markaby'

mab = Markaby::Builder.new
mab.html do
  head { title "Boats.com" }
  body do
    h1 "Boats.com has great deals"
    ul do
      li "$49 for a canoe"
      li "$39 for a raft"
      li "$29 for a huge boot that floats and can fit 5 people"
    end
  end
end
puts mab.to_s

Markaby::Builder.new does take two arguments for passing in variables and a helper object. You can also affix the block right on to the class.

See Markaby::Builder for all of that.

A Note About instance_eval¶ ↑

The Markaby::Builder class is different from the normal Builder class, since it uses instance_eval when running blocks. This cleans up the appearance of the Markaby code you write. If instance_eval was not used, the code would look like this:

mab = Markaby::Builder.new
mab.html do
  mab.head { mab.title "Boats.com" }
  mab.body do
    mab.h1 "Boats.com has great deals"
  end
end
puts mab.to_s

So, the advantage is the cleanliness of your code. The disadvantage is that the block will run inside the Markaby::Builder object’s scope. This means that inside these blocks, self will be your Markaby::Builder object. When you use instance variables in these blocks, they will be instance variables of the Markaby::Builder object.

This doesn’t affect Rails users, but when used in regular Ruby code, it can be a bit disorienting. You are recommended to put your Markaby code in a module where it won’t mix with anything.

The Six Steps of Markaby¶ ↑

If you dive right into Markaby, it’ll probably make good sense, but you’re likely to run into a few kinks. Why not review these six steps and commit them memory so you can really know what you’re doing?

1. Element Classes¶ ↑

Element classes may be added by hooking methods onto container elements:

div.entry do
  h2.entryTitle 'Son of WebPage'
  div.entrySection %{by Anthony}
  div.entryContent 'Okay, once again, the idea here is ...'
end

Which results in:

<div class="entry">
  <h2 class="entryTitle">Son of WebPage</h2>
  <div class="entrySection">by Anthony</div>
  <div class="entryContent">Okay, once again, the idea here is ...</div>
</div>

Alternatively you can define the class as an attribute on your element - see below.

2. Element IDs¶ ↑

IDs may be added by the use of bang methods:

div.page! do
  div.content! do
    h1 "A Short Short Saintly Dog"
  end
end

Which results in:

<div id="page">
  <div id="content">
    <h1>A Short Short Saintly Dog</h1>
  </div>
</div>

Alternatively you can define the ID as an attribute on your element - see below.

3. Validate Your XHTML 1.0 Output¶ ↑

If you’d like Markaby to help you assemble valid XHTML documents, you can use the html5, xhtml_transitional or xhtml_strict methods in place of the normal html tag.

html5 do
  head { ... }
  body { ... }
end

This will add the XML instruction and the doctype tag to your document (for xhtml_strict and xhtml_transitional). Also, a character set meta tag will be placed inside your head tag.

Now, since Markaby knows which doctype you’re using, it checks a big list of valid tags and attributes before printing anything.

>> div styl: "padding: 10px" do
>>   img src: "samorost.jpg"
>> end
InvalidHtmlError: no such attribute `styl'

Markaby will also make sure you don’t use the same element ID twice!

4. Escape or No Escape?¶ ↑

Markaby uses a simple convention for escaping stuff: if a string is an argument, it gets escaped. If the string is in a block, it doesn’t.

This is handy if you’re using something like RedCloth or RDoc inside an element. Pass the string back through the block and it’ll skip out of escaping.

div.comment { RedCloth.new(str).to_html }

But, if we have some raw text that needs escaping, pass it in as an argument:

div.comment raw_str

One caveat: if you have other tags inside a block, the string passed back will be ignored.

div.comment do
  div.author "_why"
  div.says "Torpedoooooes!"
  "<div>Silence.</div>"
end

The final div above won’t appear in the output. You can’t mix tag modes like that, friend.

5. Auto-stringification¶ ↑

If you end up using any of your Markaby “tags” as a string, the tag won’t be output. It’ll be up to you to add the new string back into the HTML output.

This means if you call to_s, you’ll get a string back.

div.title { "Rock Bottom" + span(" by Robert Wyatt").to_s }

But, when you’re adding strings in Ruby, to_s happens automatically.

div.title { "Rock Bottom" + span(" by Robert Wyatt") }

Interpolation works fine.

div.title { "Rock Bottom #{span(" by Robert Wyatt")}" }

And any other operation you might perform on a string.

div.menu! \
  ['5.gets', 'bits', 'cult', 'inspect', '-h'].map do |category|
    link_to category
  end.
  join( " | " )

6. The tag! Method¶ ↑

If you need to force a tag at any time, call tag! with the tag name followed by the possible arguments and block. The CssProxy won’t work with this technique.

tag! :select, :id => "country_list" do
  countries.each do |country|
    tag! :option, country
  end
end

If you wish to register your own, specialist, tags, you can install a TagHandler

Some other notes, so you aren’t confused:¶ ↑

On using different tagsets:¶ ↑

Because of the ways various frameworks sub-render templates, to use a different tagset in a rendered sub template, you may need to set it at the top of your sub-template:

self.tagset = Markaby::HTML5
# or Markaby::Transitional, Markaby::XHTMLStrict, Markaby::XHTMLFrameset

Note, this is only necessary if you were rendering, say, a one off page as html transitional but had the default engine as html5.

Defining attributes on elements¶ ↑

If you do not use the CssProxy (div.entry to define the class, div.page! to define the ID), then you can pass a hash to your element to define any arbitrary attributes.

div id: "page-123", class: "entry" do
  div style: "display: inline-block;" do
    p "Have you noticed this book is basically written by a lunatic?"
  end
end

Will result in

<div id="page-123" class="entry">
  <div style="display: inline-block;">
    <p>Have you noticed this book is basically written by a lunatic?</p>
  </div>
</div>

If you pass a hash to your attribute definition, Markaby will expand the hash entries. This is useful for data attributes (for example, if you are using a Stimulus controller), or aria attributes. Any attributes will have underscores replaced with dashes when the hash is expanded.

div data: { controller: "something" } do
  div do
    h1(data: { something_target: "title" }) { "There goes my pickup" }
  end
end

Will result in

<div data-controller="something">
  <div>
    <h1 data-something-target="title">There goes my pickup</h1>
  </div>
</div>

Custom elements and web components¶ ↑

If you are using the HTML5 tagset (which is the default), and your document has custom-elements defined, you can create those in the same way as standard elements.

Unlike standard elements, there is no validity checking for your attributes.

article do
  my_custom_panel variant: "primary" do
    p "Our careers are so over"
  end
end

Results in

<article>
  <my-custom-panel variant="primary">
    <p>Our careers are so over</p>
  </my-custom-panel>
</article>

Credits¶ ↑

Markaby is a work of immense hope by Tim Fletcher and why the lucky stiff. It is maintained by joho, spox, and smtlaissezfaire. Thank you for giving it a whirl.

Markaby is inspired by the HTML library within cgi.rb. Hopefully it will turn around and take some cues.

Patches from contributors:¶ ↑

aredridel (Aria Stewart - aredridel@nbtsc.org)

- Make exceptions inherit from StandardError (f259c0)