Skip to content

Instantly share code, notes, and snippets.

@unscriptable
Last active February 12, 2024 00:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save unscriptable/f50c7738b9985300a0320d31c203d039 to your computer and use it in GitHub Desktop.
Save unscriptable/f50c7738b9985300a0320d31c203d039 to your computer and use it in GitHub Desktop.
Mostly for fun, I created a simple template function using functional JavaScript patterns and ES6 syntax. Kinda like mustache, but much simpler.
// Take a text string containing tokens (of type `${name}`) and return
// a function that will replace the tokens with the properties of a given
// object. If we need to get much more sophisticated, we should
// probably use mustache or similar.
// TODO: allow dev to specify a format for each token?
export default
template => createRenderAll(partition(String(template)))
// ------------------------------------------------------------
// Given a set of compiled template render functions, return a function that
// runs the functions on the input data and merges the resulting text.
// Note: this is the function that is the most important to performance, but
// I'm leaving it in this ultra-simple form unless there's a requirement
// for more speed.
const createRenderAll
= parts => props => parts.map(createRenderEach(props)).join('')
// Given an object of properties, return a function to convert a template part
// (text run or token) to text.
const createRenderEach
= props => render => render(props)
// Find template parts (text runs and tokens) in a template.
const partition
= template => nextToken(findToken(findTokensRx()), template)
// Recursively find the next token in a text string.
const nextToken
= (findToken, text, start=0, parts=[]) => {
const [ name, pos, next ] = findToken(text)
return name
? nextToken(
findToken, text, next,
parts.concat([textRender(text, start, pos), tokenRender(name)])
)
: parts.concat(textRender(text, start))
}
// Given a regular expression, return a function that accepts a text string
// and returns a triple of token information:
// [name-of-token, position-of-token, position-to-start-next-search].
// If no token is found, an "empty" triple is returned.
const findToken
= rx => text => {
const r = rx.exec(text)
return r ? [ r[1], r.index, rx.lastIndex ] : []
}
// RegExp to find simple `${name}` tokens.
// We create a new RegExp each time as a "good habit". If this code were
// async and we reused the same RegExp object for multiple templates,
// we'd suffer from hard-to-find *mutable state* bugs!
const findTokensRx
= () => /\$\{([^}]*)\}/g
// Create a text render function from a slice of a template.
const textRender
= (text, start, end) => {
const snip = text.slice(start, end)
return () => snip
}
// Create a token render function from a token in a template.
const tokenRender
= token => {
const prop = token.trim()
return props => props && props[prop]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment