SkillAgentSearch skills...

Scribble

Scribble is a customer facing template language similar to Liquid build in Ruby

Install / Use

/learn @stefankroes/Scribble
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Scribble

Scribble is a customer facing template language similar to Liquid. Scribble was written in Ruby and can be used in any Ruby or Ruby on Rails project. It takes a template file, consisting of text and Scribble tags and transforms it into text. Scribble can be used to transform any plain text format like HTML, Markdown, JSON, XML or plain text. Customer facing means that it is safe to use Scribble to run/evaluate user provided templates.

Scribble is only compatible with Ruby 2.0 and Ruby 2.1 because it uses keyword arguments.

Scribble was developed by Stefan Kroes at Lab 01 (Dutch website, available for hire ;-) as a part of Sitebox.io (Service that lets you create/edit a website using files in your Dropbox).

How do I use it?

The following example shows how to load a Scribble template and evaluate it.

Scribble::Template.new("I'm a template! {{1 + 1}}").render

The template constructor and and render method both take options. The following example shows the same call but with all options. We will describe the specifics of formats, loaders, converters and registers later.

template_options = {
  format: :markdown,             # The template itself is in Markdown format
  loader: my_partial_loader,     # Object handling the loading of partials and layouts
  converters: [html2md, md2html] # Two converters to convert between HTML and Markdown
}

render_options = {
  variables: {a: 1, b: 'Foo'},   # Expose some data to the template
  registers: {c: 2, d: 'Bar'},   # For internal use by Scribble method implementations
  format: :html                  # Request output conversion to HTML format
}

Scribble::Template.new(source, template_options).render render_options

What does it look like?

The following example is a Scribble template repeating some text, folowed by an if statement.

{{ 3.times }}Hello World!{{ end }}

{{ if foo = 3 | bar = 'baz' }}
  Either foo equals the number 3 or bar equals the string 'baz'
{{ elsif 3 * 3 != foo & bar }}
  Foo does not equal 9 and bar is either true, a non-zero number or a non-empty string
{{ else }}
  None of the above
{{ end }}

In the example above:

  • Everything between {{ and }} are Scribble tags, the rest is text.
  • times and if are block methods that can be used to manipulate the associated block (up until the next end).
  • foo and bar are variables that can be inserted by passing them into the render method from Ruby.
  • =, |, &, *, != are operators. Operators are invoked on their left hand side with their right hand side as an argument.
  • elsif and else are methods that are defined only in the context of an if block to split up the block.

Why Scribble instead of Liquid?

While Liquid is fine for many use-cases, we decided to replace it on the Sitebox.io project for the following reasons:

  • Liquid uses regular expressions for parsing and tends to ignore most runtime errors, we want to be a little more strict and present the user with helpful error messages that include line and column numbers
  • We needed a template language that's able to convert between different formats when using partials (in particular between Markdown and HTML)
  • While the Liquid syntax can be friendly to non-programmers, an object oriented expression based language is more powerful and expressive
  • Liquid uses different syntax for blocks and inlining, we wanted a single syntax for both

Installation

Add this line to your application's Gemfile:

gem 'scribble'

And then execute:

$ bundle

Or install it yourself as:

$ gem install scribble

Project status and features

Scribble currently has solid architecture and features and it is already used in production in Sitebox.io. The actual scripting API is still somewhat sparse regarding supported types and methods but will be extended in future minor releases and can also easily be extended on a per application basis. Pull requests enriching the API are very welcome. Scribble currently supports:

  • A proper grammar based parser, generating user-friendly syntax errors that unclude line and column numbers
  • Excellent runtime error reporting (Wrong number of arguments (0 for 1-2) for 'partial' at line 1 column 4)
  • Simple and consistent tag syntax using {{ ... }}
  • Unary and binary operators with proper precedence rules and parentheses
  • Method invocation, method chaining, block methods and command style method invocation (without parentheses)
  • Nested execution scopes (blocks) and rescursive resolving of methods and variables
  • Boolean, integer, string and nil scripting types (Arrays or hashes are not currently supported)
  • Rich object oriented API for scripting with these types
  • Pluggable architecture for additional (application specific) types
  • If/elsif/else, layout and partial methods
  • Pluggable loader architecture for working with partials and layouts
  • Ability to convert between formats when inlining partials and layouts with mixed formats (for example: Markdown and HTML)

Template and rendering options

Scribble supports a few options when initializing and rendering templates. This section describes those options.

Supplying a partial loader

When you supply Scribble with a partial loader you can use the partial and layout methods in order to inline other Scribble templates. A loader is an objects that implements a single instance method named load which takes the partial name as a single string argument. The method should return a Scribble::Partial or nil if the partial cannot be found. Initializing the partial with a format is optional.

class MyLoader
  def load name

    # ...

    unless partial_source.nil?
      Scribble::Partial.new partial_source, format: partial_format
    end
  end
end

Scribble::Template.new(template_source, loader: MyLoader.new).render

Formats and converters

Scribble is able to convert between different formats when inlining partials and rendering templates. This functionality is completely optional, just ignore all format and converter options to disable it. If you want to use this functionality you should tell Scribble the format of your templates and partials when initializing them using symbols. Additionally you should tell Scribble in what format to render and supply the format convertors that will be needed to do so.

md2html = Scribble.converter :markdown => :html do |markdown|
  Kramdown::Document.new(markdown).to_html
end

template = Scribble::Template.new template_source, format: :markdown, converters: [md2html]

template.render :html

Variables and registers

Variables are the way to pass data from your template into your Scribble template. In addition to variables, you can pas registers. Registers will not be available as Scribble variables but they will be available to the Ruby implementations of your methods.

template = Scribble::Template.new template_source

template.render variables: {a: 1}, registers: {some_private_resource: 2}

Template language

The Scribble template language takes a lot of cues from Ruby:

  • Everything is an object
  • Objects only support methods, no attributes
  • Methods can take a block of code, delimited by the keyword end
  • Operators are just method calls, invoked on their left hand side
  • Parentheses are optional when passing arguments into a method
  • Multiple methods can be chained using dot . notation

Some key differences are:

  • Scribble only implements a very small subset of the Ruby syntax.
  • Blocks of code don't need to be opened. The interpreter knows which methods take a block.
  • Constructs like an if statement are also implemented as block methods, using so called split methods for constructs like elsif and else.
  • Meanings of operators have been simplified because Scribble only needs to support certain operations.

Syntax

The Scribble grammar is defined using Parslet in parser.rb.

A Scribble template consists of text and tags. Tags are everything between {{ and }}. Tags containing only the keyword end are special tags that close a block.

3 times 6 is: {{ 3 * 6 }}

{{ 3.times }}
Hello World!
{{ end }}

Scribble supports integers, strings, booleans and nil. Nil doesn't have a literal, it can only be returned. Other objects do have literals.

{{ 'This is string' }}

This is a number: {{ 3 }}

{{ true }} and {{ false }} are booleans

Methods are called globally or on objects with the dot . notation. Parentheses are optional when invoking a method and are not allowed when calling a variable. Methods can be chained and used as arguments to other methods.

{{ some_method }}
{{ some_method() }}
{{ some_method(1, true, 'test') }}
{{ some_method 1, true, 'test' }}
{{ some_variable }}
{{ -3.abs }}
{{ 'Scribble'.replace('Scr', 'Dr').upcase }}
{{ true.or 1 }}
{{ 3.multiply(5.subtract(2)) }}
{{ some_method another_method }}
This will cause an error: {{ some_variable() }}

Unary and binary operators are supported. As are parentheses.

{{ -2 * (3 + 5) }}
{{ 'Scribble' - 'Scr' }}
{{ 3 * 'Repeat me! ' }}

Operators and precedence

The table below shows all supported operators in order of precedence. Operators are invoked as methods on their left hand side (or on their only operand in case of a unary operator). The table also shows the method name that is associated with each operator for this purpose. This essentially enables operator overloading since you just define the associated method.

Operator | Precedence | Description | Associated method ------------------- | ---------- | ---------------- | ----------------- ! | 1

View on GitHub
GitHub Stars120
CategoryDevelopment
Updated1y ago
Forks7

Languages

Ruby

Security Score

80/100

Audited on Jan 23, 2025

No findings