Skip to content
OpenC3Public

About

Cross-Language ERB Templating supporting Ruby, Python, and Rust

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

ERBLite

A sandboxed template engine that uses Ruby ERB syntax, with native implementations in Ruby, Python and Rust. Each implementation produces identical output, and none of them needs Ruby installed.

<%- cfg = JSON.parse(File.read("targets.json")) -%>
<%- cfg["targets"].each_with_index do |name, i| -%>
TARGET <%= name.upcase %> <%= format("%04X", 0x100 + i) %>
<%- end -%>
Total: <%= cfg["targets"].size %>

Why

ERB is a convenient way to write config files and code generators, but evaluating an ERB template means running arbitrary Ruby. ERBLite keeps the syntax but runs a small, interpreted subset of Ruby:

  • No code execution escapes. Templates are lexed, parsed and run by a tree-walking interpreter. No implementation uses eval. Method calls go through fixed allowlists, and templates cannot require, send, reach ENV, run system commands or touch globals.
  • The same language everywhere. Python and Rust tools can render the same templates as Ruby tools, with the same output.
  • Ruby-compatible. A template that renders without error in ERBLite gives the same output in real Ruby ERB (trim_mode: "-"), apart from a few documented [ERBLite] differences such as 64-bit integers.

Language overview

  • Tags: <% %>, <%= %>, <%# %>, <%%, plus -%> and <%- trimming.
  • Literals: integers, floats, strings with #{} interpolation, symbols, arrays, hashes, ranges, %w[].
  • Variables, += ||= and similar, multiple assignment, all the usual operators, &..
  • Control flow: if/elsif/else/unless, modifiers, ternary, case/when, while, until, for, loop, break, next.
  • Blocks: each, map, select, each_with_index, inject, group_by, … and enumerator chains (map.with_index).
  • Several hundred built-in String/Integer/Float/Array/Hash/Range methods, plus format.
  • Data: File.read, Dir.glob and render partials, sandboxed to a base directory, and JSON.parse / JSON.generate / to_json.
  • Not included: def, classes, lambdas, regex, begin/rescue, require, globals.

See SPEC.md for the full definition.

See examples/plugin.txt.erb for a larger example that uses JSON, partials, format and nested loops. It renders identically in all three implementations and in real Ruby ERB.

Implementations

Language Directory Usage
Python python/ erblite.render(src, {"x": 1}, base_dir="cfg")
Ruby ruby/ ERBLite.render(src, {x: 1}, base_dir: "cfg")
Rust rust/ erblite::render(src, &vars) / Template::compile(..)?.render(..)

Testing

  • conformance/: the shared case suite every implementation must pass. Expected output is generated by real Ruby ERB (ruby conformance/generate.rb).
  • tools/difftest.rb: a differential fuzzer. It generates random expressions, uses Ruby as the oracle, and compares all three CLIs: ruby tools/difftest.rb --count 2000.
BUNDLE_GEMFILE=conformance/Gemfile bundle exec ruby conformance/generate.rb --check
(cd python && .venv/bin/pytest)
(cd ruby && bundle exec rake spec)
(cd rust && cargo test)

Releasing

All three packages share one version number. tools/version.rb keeps every version location in sync.

ruby tools/version.rb 0.2.0          # bump python, ruby and rust together
git commit -am "Release 0.2.0"
git tag v0.2.0 && git push origin main v0.2.0

Pushing a vX.Y.Z tag starts .github/workflows/release.yml. It checks that the tag matches the package versions, runs the full CI suite, and builds the packages. It then publishes to PyPI, RubyGems and crates.io using trusted publishing (the one-time registry setup is described at the top of the workflow) and creates a GitHub release with the built packages attached.

About

Cross-Language ERB Templating supporting Ruby, Python, and Rust

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages