Skip to content

Commit 246bb08

Browse files
committed
Add documentation and type signatures
1 parent d4db73c commit 246bb08

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

README.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# MustachioRuby
2+
3+
A Ruby port of the powerful [Mustachio](https://github.com/ActiveCampaign/mustachio) templating engine for C# and .NET - a lightweight, fast, and safe templating engine with model inference capabilities.
4+
5+
> **Note**: This is an unofficial Ruby port of the original [Mustachio](https://github.com/ActiveCampaign/mustachio) templating engine created by ActiveCampaign. All credit for the original design, concepts, and innovations goes to the ActiveCampaign team and the Mustachio contributors.
6+
7+
#### What's this for?
8+
9+
_MustachioRuby_ allows you to create simple text-based templates that are fast and safe to render. It's a Ruby implementation that brings the power of the templating engine behind [Postmark Templates](https://postmarkapp.com/blog/special-delivery-postmark-templates) to the Ruby ecosystem.
10+
11+
#### How to use MustachioRuby:
12+
13+
```ruby
14+
# Parse the template:
15+
source_template = "Dear {{name}}, this is definitely a personalized note to you. Very truly yours, {{sender}}"
16+
template = MustachioRuby.parse(source_template)
17+
18+
# Create the values for the template model:
19+
model = {
20+
"name" => "John",
21+
"sender" => "Sally"
22+
}
23+
24+
# Combine the model with the template to get content:
25+
content = template.call(model)
26+
# => "Dear John, this is definitely a personalized note to you. Very truly yours, Sally"
27+
```
28+
29+
#### Extending MustachioRuby with Token Expanders:
30+
31+
```ruby
32+
# You can add support for Partials via Token Expanders.
33+
# Token Expanders can be used to extend MustachioRuby for many other use cases,
34+
# such as: Date/Time formatters, Localization, etc., allowing also custom Token Render functions.
35+
36+
source_template = "Welcome to our website! [[CONTENT]] Yours Truly, John Smith."
37+
string_data = "This is a partial. You can also add variables here {{ testVar }} or use other expanders. Watch out for infinite loops!"
38+
39+
token_expander = MustachioRuby::TokenExpander.new
40+
token_expander.regex = /\[\[CONTENT\]\]/ # Custom syntax to avoid conflicts
41+
token_expander.expand_tokens = lambda do |s, base_options|
42+
# Create new ParsingOptions to avoid infinite loops
43+
new_options = MustachioRuby::ParsingOptions.new
44+
MustachioRuby::Tokenizer.tokenize(string_data, new_options)
45+
end
46+
47+
parsing_options = MustachioRuby::ParsingOptions.new
48+
parsing_options.token_expanders = [token_expander]
49+
template = MustachioRuby.parse(source_template, parsing_options)
50+
51+
# Create the values for the template model:
52+
model = { "testVar" => "Test" }
53+
54+
# Combine the model with the template to get content:
55+
content = template.call(model)
56+
```
57+
58+
#### Installing MustachioRuby:
59+
60+
MustachioRuby can be installed via [RubyGems](https://rubygems.org/):
61+
62+
```bash
63+
gem install mustachio_ruby
64+
```
65+
66+
Or add this line to your application's Gemfile:
67+
68+
```ruby
69+
gem 'mustachio_ruby'
70+
```
71+
72+
And then execute:
73+
74+
$ bundle install
75+
76+
## Usage Examples
77+
78+
### Working with Arrays using `each` blocks:
79+
80+
```ruby
81+
template = "{{#each items}}Item: {{name}} - ${{price}}\n{{/each}}"
82+
renderer = MustachioRuby.parse(template)
83+
84+
model = {
85+
"items" => [
86+
{ "name" => "Apple", "price" => 1.50 },
87+
{ "name" => "Banana", "price" => 0.75 }
88+
]
89+
}
90+
91+
content = renderer.call(model)
92+
# => "Item: Apple - $1.5\nItem: Banana - $0.75\n"
93+
```
94+
95+
### Complex Paths for Nested Objects:
96+
97+
```ruby
98+
template = "User: {{user.profile.name}} ({{user.profile.age}}) from {{user.location.city}}, {{user.location.country}}"
99+
renderer = MustachioRuby.parse(template)
100+
101+
model = {
102+
"user" => {
103+
"profile" => { "name" => "Alice Smith", "age" => 30 },
104+
"location" => { "city" => "New York", "country" => "USA" }
105+
}
106+
}
107+
108+
content = renderer.call(model)
109+
# => "User: Alice Smith (30) from New York, USA"
110+
```
111+
112+
### Conditional Sections:
113+
114+
```ruby
115+
template = "{{#showMessage}}Hello, {{name}}!{{/showMessage}}{{^showMessage}}Goodbye!{{/showMessage}}"
116+
renderer = MustachioRuby.parse(template)
117+
118+
# When showMessage is true
119+
model = { "showMessage" => true, "name" => "World" }
120+
content = renderer.call(model)
121+
# => "Hello, World!"
122+
123+
# When showMessage is false
124+
model = { "showMessage" => false, "name" => "World" }
125+
content = renderer.call(model)
126+
# => "Goodbye!"
127+
```
128+
129+
### HTML Escaping (Safe by Default):
130+
131+
```ruby
132+
# Escaped by default for safety
133+
template = "Content: {{html}}"
134+
renderer = MustachioRuby.parse(template)
135+
model = { "html" => "<script>alert('xss')</script>" }
136+
content = renderer.call(model)
137+
# => "Content: &lt;script&gt;alert('xss')&lt;/script&gt;"
138+
139+
# Unescaped with triple braces
140+
template = "Content: {{{html}}}"
141+
renderer = MustachioRuby.parse(template)
142+
content = renderer.call(model)
143+
# => "Content: <script>alert('xss')</script>"
144+
```
145+
146+
### Model Inference:
147+
148+
```ruby
149+
template = "Hello {{name}}! You have {{#each orders}}{{total}}{{/each}} orders."
150+
result = MustachioRuby.parse_with_model_inference(template)
151+
152+
# result.parsed_template is the compiled template function
153+
# result.inferred_model contains information about expected model structure
154+
model = { "name" => "Alice", "orders" => [{ "total" => 5 }, { "total" => 3 }] }
155+
content = result.parsed_template.call(model)
156+
# => "Hello Alice! You have 53 orders."
157+
```
158+
159+
### Advanced: Parsing Options
160+
161+
```ruby
162+
options = MustachioRuby::ParsingOptions.new
163+
options.disable_content_safety = true # Disable HTML escaping
164+
options.source_name = "my_template" # For error reporting
165+
166+
template = "Unsafe content: {{html}}"
167+
renderer = MustachioRuby.parse(template, options)
168+
model = { "html" => "<b>Bold Text</b>" }
169+
content = renderer.call(model)
170+
# => "Unsafe content: <b>Bold Text</b>"
171+
```
172+
173+
##### Key differences between Mustachio and [Mustache](https://mustache.github.io/)
174+
175+
MustachioRuby contains a few modifications to the core Mustache language that are important:
176+
177+
1. `each` blocks are recommended for handling arrays of values. (We have a good reason!)
178+
2. Complex paths are supported, for example `{{ this.is.a.valid.path }}` and `{{ ../this.goes.up.one.level }}`
179+
3. Template partials are supported via Token Expanders.
180+
181+
###### A little more about the differences:
182+
183+
One awesome feature of Mustachio is that with a minor alteration in the mustache syntax, we can infer what model will be required to completely fill out a template. By using the `each` keyword when iterating over an array, our parser can infer whether an array or object (or scalar) should be expected when the template is used. Normal mustache syntax would prevent us from determining this.
184+
185+
We think the model inference feature is compelling, because it allows for error detection, and faster debugging iterations when developing templates, which justifies this minor change to 'vanilla' mustache syntax.
186+
187+
## Development
188+
189+
After checking out the repo, run:
190+
191+
```bash
192+
bundle install
193+
bundle exec rspec
194+
```
195+
196+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
197+
198+
## Acknowledgments
199+
200+
This Ruby port would not be possible without the incredible work of the original [Mustachio](https://github.com/ActiveCampaign/mustachio) team at ActiveCampaign. Special thanks to:
201+
202+
- **ActiveCampaign Team**: For creating and maintaining the original Mustachio templating engine
203+
- **All Mustachio Contributors**: For their valuable contributions to the original project
204+
- **Postmark Team**: For demonstrating the power of this templating approach in production
205+
206+
MustachioRuby aims to faithfully port the original concepts and functionality while adapting them to Ruby's idioms and conventions.
207+
208+
## License
209+
210+
The gem is available as open source under the [MIT License](LICENSE).

sig/mustachio_ruby.rbs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module MustachioRuby
2+
VERSION: String
3+
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
4+
end

0 commit comments

Comments
 (0)