4 releases

0.1.3 Aug 22, 2022
0.1.2 Aug 21, 2022
0.1.1 Aug 6, 2022
0.1.0 Aug 6, 2022

#1926 in Web programming

MIT license

125KB
3K SLoC

mini-builder-rs

A static website generator.

Programming languages use functions to avoid rewriting similar logic. HTML doesn't have a similar features. This crate provides a simple language that can be inserted into HTML (or really any) files in order to programmatically generate new files.

For example, consider the following HTML:

<section>
    <a href="page 1" class="selected">Page1</a>  
    <a href="page 2" class="">Page2</a>  
    <a href="page 3" class="">Page3</a>  
</section>

Without a static website generator, this html snippet would have to be copied and pasted into each of the pages with a minor change of which a tag will have the selected class. However with a static website generator, this snippet could be stored in a file for example navigation.html and then reused in every page. For example:

navigation.html

<section>
    <a href="page 1" class="{{ if page == 'page 1' ? 'selected' : ''}}">Page1</a>  
    <a href="page 2" class="{{ if page == 'page 2' ? 'selected' : ''}}">Page2</a>  
    <a href="page 3" class="{{ if page == 'page 3' ? 'selected' : ''}}">Page3</a>  
</section>

page1.html

...
{{@ navigation(page = 'page 1')}}
...

Examples

For some code examples check out the examples directory.

Builder

The Builder handles

mini_builder_rs::builder::Builder::new(
	// sources - the source for the pages that will be generated
	Some("./examples/example_site/sources".into()),
	// templates - the partial files that can be referenced
	//             from sources or other templates
	Some("./examples/example_site/templates".into()),
	// generated - where the transformed sources will be placed
	Some("./examples/example_site/generated".into()),
	// variables - a file containing global variables and their values
	Some("./examples/example_site/variables".into()),
	// see mini_builder_rs::builder::BuilderOptions
	Default::default(),
)
.unwrap()
// variable that will be available for every source and template
// (unless shadowed)
.add_variable("title", Value::text("Website Title"))
.add_variable(
	"files",
	Value::List(DATA.iter().map(|x| Value::text(x.0)).collect::<Vec<_>>()),
)
// functions that can be called from every source and template
.add_function("get_file_size", Box::new(get_file_size) as _)
// watch for changes in the sources and templates directories,
// updating the generated directory live
.watch()
.unwrap();

...

fn get_file_size(values: &[Value]) -> Value {
    if let Some(Value::Text(name)) = values.get(0) {
        for (file_name, file_size) in DATA {
            if file_name == name {
                return Value::Number(*file_size);
            }
        }
    }
    Value::None
}

Sources and Templates

Sources the files that correspond to the output files. Templates are files that are used in order by sources or other templates to generate text. For example if the sources are page_1.html, page_2.html and the templates are navigation.html, and footer.html, then after the site has finished, the output will be the expanded versions of only page_1.html and page_2.html.

Templates are called with the syntax @ template(key1 = value1, ...).

The name of the template is either given explicitly when passed as a string, or is the path of the file relative to the templates directory when it loaded from a file ignoring extensions (for example the template template/sub_dir1/temp1.html name would be sub_dir1/tmp1).

Variables and Functions

Values are defined with the following enum:

pub enum Value {
    Number(f32),
    Bool(bool),
    Text(String),
    List(Vec<Value>),
    None,
}

Functions are defined by the type: Box<dyn Fn(&[Value]) -> Value + 'static>.

Directives

A directive is a piece of code with the pattern {{...}} that adds logic to plain files. Control flow directives such as if statements use a slightly different pattern: {{# ...}}. The language used inside the directives can be summarized in a few examples:

  • Expressions:
<!-- if the variables `a` and `b` are defined, they will be added and returned
-->
<p> a + b = {{ a + b }} </p>
  • If statements:
{{# if page == 'page 1' }}
    <!-- if the variable `page` exists and equals to 'page 1' this section will
	be evaluated  -->
    ...
{{# elif page == 'page 2'}}
    <!-- elif is short for else if -->
    ...
{{# else }}
    ....
{{#}}
  • For loops:
<!-- the for each loop will be evaluated if the variable `files` exists and is
a list -->
{{# for file in files}}
    <h2>{{ file }}</h2>
{{#}}
  • Ternary:
{# if a != None && b != None}}
    <p>a is{{ a > b ? 'greater than : a < b ? 'smaller than' : 'equals to'}} b</p>
{{# else}}
    <p>a or b are none</p>
{{#}}
  • Templates:
<!-- Templates are not control flow, they are expressions and therefore can be
with other expressions. if no variables are passed to a template then both the
syntaxes `@ template` and `@ template()` are valid. -->
{{ use_pretty_header ? @ pretty_header : @ normal_header}}

<!-- call the `footer` template with a single variable called
`use_pretty_footer` whose value equals to the variable `use_pretty_header`
value's -->
{{@ footer(use_pretty_footer = use_pretty_header)}}

Dependencies

~3–12MB
~115K SLoC