Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/9bf102b56063fd9628fb to your computer and use it in GitHub Desktop.
Save dfkaye/9bf102b56063fd9628fb to your computer and use it in GitHub Desktop.
notes on https://github.com/dfkaye/stringtemplate token set & syntax (usage)

stringtemplate

stringtemplate is a strict separation or "logic-less" template module (still under development) - essentially, the merge() operation between a document string with "holes" and data for filling this out.

stringtemplate takes an "eval-less" approach (no Function() constructors or eval() statements), due to the arrival of Content Security Policy, a very serious restriction coming to our "modern" browsers that financial, medical, retailing and other security-sensitive entities will embrace increasingly over time.

stringtemplate supports no formatting or escaping characters, case logic, quiet references, fallbacks for unexpected values, helpers, includes or inheritance. stringtemplate is not concerned with producing HTML or producing a DOM - its concern is simply producing a new string from a string and some data. The focus is on "simple" transformations.

when is "logic" necessary?

when the structure of the data path can change unpredictably, as when generating a sitemap.

See http://jinja.pocoo.org/docs/dev/templates/#loop-filtering on the recursive keyword in foreach loops.

TODO ~ figure out a way to specific this in in the token set.

logic-less templates for strict mv separation

Terence Parr, The ANTLR Guy argues that templates are documents with "holes" and should contain no business logic. Instead, it is up to us who use template engines to provide data that is already modeled properly.

A template should merely represent a view of a data set and be totally divorced from the underlying data computations whose results it will display.

Read Parr's full argument in his paper on [Enforcing Strict Model-View Separation in Template Engines] (http://www.cs.usfca.edu/~parrt/papers/mvc.templates.pdf)

Parr has implemented this strict separation in his own StringTemplate project for java (with ports for C#, Python).

I like that well enough to re-use the stringtemplate name for this project.

Tell, Don't Ask

This example is taken verbatim from Ben Orenstein's Tell, Don't Ask;

Not so good:

  <% if current_user.admin? %>
    <%= current_user.admin_welcome_message %>
  <% else %>
    <%= current_user.user_welcome_message %>
  <% end %>

Better:

  <%= current_user.welcome_message %>

Good OOP is about telling objects what you want done, not querying an object and acting on its behalf. Data and operations that depend on that data belong in the same object.

[ TODO - FLESH OUT THE DATA/METHOD PART ]

string templating cons

String templating is a batch string-replace operation. Strict separation means doing that with no "logic" support whatsoever. Some argue that that is really bad because it requires “data massaging” beforehand, claiming this a tedious step - see, for example, [The Case Against Logic-less Templates] (http://www.ebaytechblog.com/2012/10/01/the-case-against-logic-less-templates/) and [The Cult of Logic-less templates] (http://www.boronine.com/2012/09/07/Cult-Of-Logic-less-Templates/).

@Rich_Harris, author of [Ractive.js] (https://github.com/RactiveJS/Ractive), on the other hand, warns more generally in String Templating Considered Harmful that relying on "string" templates for re-rendering in the DOM is terribly inefficient.

Both objections are true. Strict separation means more pre-conditioning of data before feeding it to a template. Using a template to re-render the same HTML is twice-over inefficient if you use it to redraw the same elements repeatedly.

stringtemplate does not address the data massaging problem nor does it write to a DOM. Those are very different problems, both to each other, and to the batch-replace template operation. stringtemplate is concerned with only one step in a longer process, a transformation pipeline, so to speak. The other problems belong to something else to manage, such as a JSON transform method which would use parse() and stringify() internally, or a DocumentFragment handler that accepts the output of stringtemplate that produces a fragment then clones that and inserts it into the DOM. Of course, things could get complicated, but those 2 ideas can be handled separately, and likely require far less than 100+kB of JavaScript in the browser (which is what big bang solutions tend to become).

token set

Still thinking it through, but here's the basic token set as of 14 OCT 2014. Assuming an object we'll call data,

  • $name$, $path.name$
    ~ inline value token at data.name, data.path.name

  • $#name$
    ~ collection start token (object or array)

  • $/name$ ~ collection end token (object or array)

  • $.$ ~ member token (value for each array index or object property)

  • $.name$ ~ member property token (value of 'name' property in each object member of an object or array)

  • $#.$ ~ member collection start token (collection at each array index or object property)

  • $/.$ ~ member collection end token (collection at each array index or object property)

  • $#.name$ ~ member property collection start token (collection at 'name' property of each object member of an object or array)

  • $/.name$ ~ member property collection end token (collection at 'name' property of each object member of an object or array)

REVISIONS & NEW VISIONS 4/5 NOV 2014

Having spotted jsonpath out there I'm rethinking everything. Here goes:

lessons

  • do not support nested blocks ~ this has been the root of all headache
  • support quiet references and if/else blocks with trinary operator syntax
  • identify a path for further processing with a suffix, not a prefix ~ $path.to.value:$, $path.to.collection[]:$
  • support conditional predicates in the collection reference, not the internal value reference

basic reference

  • $path.to.item$ ~ value & object/collection paths refer to data nodes from the root, { path: { to: { item: ':)' } } }

objects/collections:

  • identify the start of an iteration block by a [] suffix ~ $path.to.collection[]$

  • access key/index and value of each item in a collection iteration block:

    • not sure which I prefer
    • $[key]$, $[index]$, $[value]$
    • $.key$, $.index$, $.value$
  • identify the end of an iteration block by a simple tag ~ $/$

  • thus: not sure which I prefer

      $path.to.collection[]$
        $[key]$ ~ show the object key of the item
        $[index]$ ~ show the array index of the item
        $[value]$ ~ show the value of the item
      $/$
    
  • or:

      $path.to.collection[]$
        $.key$ ~ show the object key of the item
        $.index$ ~ show the array index of the item
        $.value$ ~ show the value of the item
      $/$
    

conditional processing:

  • $path.to.value?$ ~ quiet reference for value ~ replace template if value found; else hide template

  • if block

      $path.to.item?$
        show truthy case
      $/$
    
  • if-else fallback block

      $path.to.item?$
        show truthy case
      $:$
        else show fallback case
      $/$
    

collection predicates

  • $path.to.collection[?]$ ~ quiet reference for object/collection ~ apply template to items if collection exists ~ else hide template
  • $path.to.collection[?(@.length - 1)]$ ~ apply template if item is last in array
  • $path.to.collection[?(@.isbn)]$ ~ apply template if item has isbn property

template directives

  • $path.to.collection[]:(..)$ ~ visit each item recursively if its value is an object/collection, applying the current template
  • $path.to.collection[]:ref-name()$ ~ call a directive on the object/collection
  • $path.to.collection[]:ref-name(..)$ ~ visit each value recursively if it is an object/collection, applying the ref-name() directive

define directives

  • $@ref-name$ template-body $/@$

syntax (usage)

given this data structure:

var data = { 
  name: 'david',
  fullname: {
    first: 'david',
    last: 'eyak'
  }
  address: {
    street: '1234 fifth st.',
    city: 'centerburg',
    state: 'ST'
  }
  friends: [
    { name: 'suzy' },
    { name: 'larry' },
    { name: 'jerry jeff' }
  ]
};

Values

print value

$name$ 

 'david'

print value at path

$address.street$ 

 '1234 fifth st.'

Collections

print values of object properties by name/keyword

$#address$
 + $.street$
 + $.city$, $.state$
$/address$

 + '1234 fifth st.'
 + 'centerburg', 'ST'

print value of object property by name/keyword in an array of objects

// for each friend in friends, print friend.name

$#friends$
 + $.name$
$/friends$

 + 'suzy'
 + 'larry'
 + 'jerry jeff'

Context data as a Collection

print value of each context-array index

// for each index in data, print value

var data = [ 'a', 'c', 'e', 'z' ];

$#.$
 + $.$
$/.$

 + 'a'
 + 'c'
 + 'e'
 + 'z'

print value of each context-object property

// for each property in data, print property value

var data = { 
  first: 'first part', 
  last: 'last part' 
};

$#.$
 + $.$
$/.$

 + 'first part'
 + 'last part'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment