Skip to content

Commit

Permalink
Merge pull request #31 from diemogebhardt/gleam-string_tree
Browse files Browse the repository at this point in the history
Use string_tree module in favor of deprecated string_builder module of Gleam
  • Loading branch information
michaeljones authored Nov 24, 2024
2 parents 54fe749 + 031cfc1 commit 337653f
Show file tree
Hide file tree
Showing 64 changed files with 556 additions and 520 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ jobs:
uses: actions/checkout@v2

- name: Install OTP and Gleam
uses: erlef/setup-beam@v1.16.0
uses: erlef/setup-beam@v1
with:
otp-version: "25.1"
gleam-version: "0.32.4"
otp-version: "27.1"
gleam-version: "1.6.1"

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
Expand All @@ -107,7 +107,7 @@ jobs:
run: gleam test

- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: matcha-${{ matrix.target }}
path: target/debug/matcha
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/build
/target
/test/templates/*.gleam
46 changes: 22 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ text. Matcha generates well typed code that avoids issues that some more dynamic

That said, if you're planning to generate structured output, it is sensible to use a better suited approach. For data
formats, this should be serialization and deserialization libraries. For formats like HTML, you would likely be better
using a library like [lustre](https://hexdocs.pm/lustre/lustre/element.html#to_string_builder) or
[nakai](https://github.com/nakaixo/nakai).
using a library like [lustre](https://hexdocs.pm/lustre/lustre/element.html#to_string_tree) or
[nakai](https://hexdocs.pm/nakai/nakai.html#to_string_tree).

## Installation

Expand All @@ -45,7 +45,7 @@ files it finds.

Template files should have a `.matcha` extension. Templates are compiled into `.gleam` files that can
be imported like any other regular module. The modules expose a `render` function, that returns a
`String`, and `render_builder` function that returns a `StringBuilder`.
`String`, and `render_tree` function that returns a `StringTree`.

Some errors, mostly syntax, will be picked up by the Rust code but it is possible to generate
invalid modules and so the Gleam compiler will pick up further errors.
Expand Down Expand Up @@ -77,16 +77,16 @@ You can use `{{ name }}` syntax to insert the value of `name` into the rendered
Hello {{ name }}
```

### String Builder Value
### StringTree Value

You can use `{[ name ]}` syntax to insert a string builder value into the rendered template. This
You can use `{[ name ]}` syntax to insert a `StringTree` value into the rendered template. This
has the advantage of using
[string_builder.append_builder](https://hexdocs.pm/gleam_stdlib/gleam/string_builder.html#append_builder)
[string_tree.append_tree](https://hexdocs.pm/gleam_stdlib/gleam/string_tree.html#append_tree)
in the rendered template and so it more efficient for inserting content that is already in a
`StringBuilder`. This can be used to insert content from another template.
`StringTree`. This can be used to insert content from another template.

```jinja
{> with name as StringBuilder
{> with name as StringTree
{[ name ]}
```

Expand Down Expand Up @@ -146,7 +146,7 @@ Lucy {{ second_name }}
{> endfn
```

The function always returns a `StringBuilder` value so you must use `{[ ... ]}` syntax to insert
The function always returns a `StringTree` value so you must use `{[ ... ]}` syntax to insert
them into templates. The function body has its last new line trimmed, so the above function called
as `full_name("Gleam")` would result in `Lucy Gleam` and not `\nLucy Gleam\n` or any other
variation. If you want a trailing new line in the output then add an extra blank line before the `{> endfn`.
Expand Down Expand Up @@ -178,7 +178,7 @@ able to import it from gleam module compiled from the template.
```

If a template only includes function declarations and no meaningful template content then matcha
will not add the `render` and `render_builder`. Instead the module will act as a library of
will not add the `render` and `render_tree`. Instead the module will act as a library of
functions where each function body is a template.

## Output
Expand All @@ -194,37 +194,35 @@ Hello{% if user_obj.is_admin %} Admin{% endif %}
is compiled to a Gleam module:

```gleam
import gleam/string_builder.{StringBuilder}
import gleam/string_tree.{StringTree}
import gleam/list
import my_user.{User}
pub fn render_builder(user_obj user_obj: User) -> StringBuilder {
let builder = string_builder.from_string("")
let builder = string_builder.append(builder, "Hello")
let builder = case user_obj.is_admin {
pub fn render_tree(user_obj user_obj: User) -> StringTree {
let tree = string_tree.from_string("")
let tree = string_tree.append(tree, "Hello")
let tree = case user_obj.is_admin {
True -> {
let builder = string_builder.append(builder, " Admin")
builder
let tree = string_tree.append(tree, " Admin")
tree
}
False -> builder
False -> tree
}
let builder = string_builder.append(builder, "
let tree = string_tree.append(tree, "
")
builder
tree
}
pub fn render(user_obj user_obj: User) -> String {
string_builder.to_string(render_builder(user_obj: user_obj))
string_tree.to_string(render_tree(user_obj: user_obj))
}
```

Which you can import and call `render` or `render_builder` on with the appropriate arguments.
Which you can import and call `render` or `render_tree` on with the appropriate arguments.

## Tests

Rust tests can be run with `cargo test`. They use [insta](http://insta.rs/) for snapshots.

Gleam tests can be run with `cargo run && gleam test`.


4 changes: 2 additions & 2 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
# You typically do not need to edit this file

packages = [
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
{ name = "gleam_stdlib", version = "0.43.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "69EF22E78FDCA9097CBE7DF91C05B2A8B5436826D9F66680D879182C0860A747" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
]

[requirements]
gleam_stdlib = { version = ">= 0.38.0 and < 1.0.0" }
gleeunit = { version = ">= 1.2.0 and < 2.0.0"}
gleeunit = { version = ">= 1.2.0 and < 2.0.0" }
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub fn write<W: termcolor::WriteColor>(writer: &mut W, error: Error) {
},
Error::Parse(error, source) => match error {
ParserError::UnexpectedToken(token, range, expected) => match token {
Token::IdentifierOrGleamToken(name) => explain_with_source(
Token::GleamTokenOrIdentifier(name) => explain_with_source(
writer,
&format!("Unexpected token: {}", name),
source,
Expand Down
17 changes: 10 additions & 7 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ fn parse_for_statement(tokens: &mut TokenIter) -> Result<Node, ParserError> {
fn extract_identifier(tokens: &mut TokenIter) -> Result<(String, Range), ParserError> {
log::trace!("extract_identifier");
match tokens.next() {
Some((Token::IdentifierOrGleamToken(name), range)) => Ok((name.clone(), range.clone())),
Some((Token::GleamTokenOrIdentifier(name), range)) => Ok((name.clone(), range.clone())),
Some((token, range)) => Err(ParserError::UnexpectedToken(
token.clone(),
range.clone(),
vec![Token::IdentifierOrGleamToken("".to_string())],
vec![Token::GleamTokenOrIdentifier("".to_string())],
)),
None => Err(ParserError::UnexpectedEnd),
}
Expand All @@ -247,7 +247,7 @@ fn extract_code(tokens: &mut TokenIter) -> Result<(String, Range), ParserError>

loop {
match tokens.peek() {
Some((Token::IdentifierOrGleamToken(name), token_range)) => {
Some((Token::GleamTokenOrIdentifier(name), token_range)) => {
// Create range and expand it to include all the tokens
// that we're adding to this string
range = range
Expand All @@ -274,7 +274,7 @@ fn extract_code(tokens: &mut TokenIter) -> Result<(String, Range), ParserError>
return Err(ParserError::UnexpectedToken(
token.clone(),
range.clone(),
vec![Token::IdentifierOrGleamToken("".to_string())],
vec![Token::GleamTokenOrIdentifier("".to_string())],
));
} else {
break;
Expand Down Expand Up @@ -366,7 +366,10 @@ mod test {

use super::*;

// Our only use of this is the Debug output but the compiler's dead code analysis ignores
// Debug usage so we have to allow dead_code here
#[derive(Debug)]
#[expect(dead_code)]
pub enum Error {
Scan(ScanError),
Parse(ParserError),
Expand Down Expand Up @@ -481,13 +484,13 @@ mod test {
}

#[test]
fn test_parse_builder_block() {
fn test_parse_tree_block() {
assert_parse!("Hello {[ name ]}, good to meet you");
}

#[test]
fn test_parse_builder_expression() {
assert_parse!("Hello {[ string_builder.from_strings([\"Anna\", \" and \", \"Bob\"]) ]}, good to meet you");
fn test_parse_tree_expression() {
assert_parse!("Hello {[ string_tree.from_strings([\"Anna\", \" and \", \"Bob\"]) ]}, good to meet you");
}

#[test]
Expand Down
Loading

0 comments on commit 337653f

Please sign in to comment.