# Code generation with Mason

> An overview of the open source tool Mason which automates code generation

- Source: https://verygood.ventures/blog/code-generation-with-mason/
- Published: 2021-05-27
- Author: Felix Angelov
- Tags: Flutter, Open Source

---

[**Mason**](https://pub.dev/packages/mason) is an open source tool that generates code from custom templates called bricks. It can be helpful for developers who find themselves writing the same code over and over again, or those looking for a way to customize reusable templates.

**Mason** can increase the efficiency of development teams by enabling greater day-to-day automation. For example, if there is a widget or method that your team will be using throughout an entire project, you can simply write that piece of code once as a brick template. You can then use **mason** at any time to generate that piece of code from the template with dynamic variables. Brick templates can be maintained locally as part of a specific project or remotely via a GitHub repository.

As an individual developer, **mason** can be extremely helpful for creating and generating your own templates to start new projects. In fact, that’s exactly what we did with [**Very Good CLI**](https://github.com/VeryGoodOpenSource/very_good_cli), which uses mason under the hood to generate Very Good Core, our VGV opinionated Flutter starter app template. Another example is [**groovin_cli**](https://github.com/GroovinChip/groovin_cli), an app generator created by [**GroovinChip**](https://twitter.com/GroovinChip) using **mason** and inspired by **Very Good CLI**.

Mason is written in Dart but can be used to generate code in any programming language. It can also generate code from templates of any size — ranging from a few lines to a complete application consisting of hundreds of files and directories.

## How it Works

**Mason** uses the [**mustache templating syntax**](https://mustache.github.io/mustache.5.html) to enable developers to create and maintain complex templates called bricks without having to write any generator code. Internally, **mason** maintains references to all installed brick templates and surfaces them to the developer via the **mason make** command. Brick templates consist of a **brick.yaml** file and a **\_\_brick\_\_** directory which contains the template code.

When you want to generate code from a brick, **mason** gathers the input variables needed for the brick, either via prompts or as command line arguments, and injects them into the brick before writing the newly generated code to disk. Since **mason** is a CLI tool, you can run it straight from your terminal.

## Getting Started

**Mason** can be installed either via [**pub.dev**](https://pub.dev/packages/mason) or [**homebrew**](https://github.com/felangel/homebrew-mason).

```shell
# Install from pub.dev
$ dart pub global activate mason_cli
# Install from homebrew
$ brew tap felangel/mason
$ brew install mason
```

At this point, **mason** should be available as a command. You can verify by running **mason** in your terminal.

```shell
$ mason
⛏️  mason • lay the foundation!
Usage: mason  [arguments]
Global options:
-h, --help       Print this usage information.
    --version    Print the current version.
Available commands:
  bundle   Generates a bundle from a brick template
  cache    Interact with mason cache
  get      Gets all bricks.
  init     Initialize mason in the current directory.
  make     Generate code using an existing brick template.
  new      Creates a new brick template.
Run "mason help " for more information about a command.
```

## Hello World Example

The first example we’ll take a look at is the hello world example which comes out of the box when you initialize **mason**.

### Initializing Mason

Start by running **mason init** in your directory of choice. It should generate a **mason.yaml** along with a **bricks** directory.

```shell
$ mason init
✓ Initialized (0.1ms)
✓ Generated 3 file(s):
  mason.yaml (new)
  bricks/hello/brick.yaml (new)
  bricks/hello/__brick__/HELLO.md (new)
```

The **mason.yaml** is where bricks can be registered similar to the **pubspec.yaml**. Bricks can be referenced either via path or git URL as seen below.

```yaml
# Register bricks which can be consumed via the Mason CLI.
# https://pub.dev/packages/mason
bricks:
  # Sample Brick
  # Run `mason make hello` to try it out.
  hello:
    path: bricks/hello
  # Bricks can also be imported via git url.
  # Uncomment the following lines to import
  # a brick from a remote git url.
  # todos:
  #   git:
  #     url: git@github.com:felangel/mason.git
  #     path: bricks/todos
```

The bricks directory contains the brick templates. In this case, there is only a single brick called, **hello**. The **brick.yaml** contains important metadata about the template such as the name, description, and required variables.

```yaml
name: hello
description: An example hello brick.
vars:
  - name
```

Each brick also has a **\_\_brick\_\_** directory which contains the template itself. Any files or directories within **\_\_brick\_\_** will be part of the generated code when the brick is run.

```shell
├── __brick__
│   └── HELLO.md
└── brick.yaml
```

If we take a quick look at the **HELLO.md** template file, we can see that we're able to access the **name** variable via **{{name}}**.

```md
Hello {{name}}!
```

Now that we've taken a closer look at the parts of a brick template, let's install one.

### Installing Brick Templates

We can install all bricks declared in the **mason.yaml** via the **mason get** command.

```shell
$ mason get
✓ getting bricks (0.0ms)
```

This will generate a **.mason** which contains relevant metadata needed to determine where the templates are installed. We recommend adding the **.mason** directory to your **.gitignore**.

```shell
# .gitignore
.mason/
```

Now we're ready to generate some code!

### Generating Code

Once the bricks have been installed, we can run **mason make hello** to use the **hello** template.

```shell
$ mason make hello --name Felix
✓ Made brick hello (0.0ms)
✓ Generated 1 file(s):
  /HELLO.md (new)
```

**HELLO.md** will be generated in the current directory. The contents of the file should look something like:

```md
Hello Felix!
```

**Mason** will prompt you for any variables that haven't been provided via command line arguments:

```shell
$ mason make hello
name: Dash
✓ Made brick hello (7.7ms)
✓ Generated 1 file(s):
  /HELLO.md (new)
```

**HELLO.md** should now have been overwritten to include the updated name: **Dash**.

```md
Hello Dash!
```

Now let's take a look at how to create our own brick for a custom widget.

## Custom Widget Example

We can use the **mason new** command to create a new brick.

```shell
mason new widget -d "A custom Flutter widget"
✓ Created new brick: widget (0.1ms)
✓ Generated 2 file(s):
  bricks/widget/brick.yaml (new)
  bricks/widget/__brick__/hello.md (new)
```

Note that **mason** automatically declared the new **widget** brick in the **mason.yaml**.

```yaml
bricks:
  hello:
    path: bricks/hello
  widget:
    path: bricks/widget
```

Here is our new widget brick's **brick.yaml** file:

```yaml
name: widget
description: A custom Flutter widget
vars:
  - name
```

Let's add a variable called **routable** which will determine whether or not the new widget should expose a route function.

```yaml
name: widget
description: A custom Flutter widget
vars:
  - name
  - routable
```

Now the **brick.yaml** is complete. Let's start writing the template.

We can delete the default **hello.md** file within the **\_\_brick\_\_** directory and create a new file called:

**{{#snakeCase}}{{name}}{{/snakeCase}}.dart**

The **snakeCase** lambda is provided by **mason**. Currently **mason** provides many lambdas out of the box such as:

-   **camelCase**
-   **constantCase**
-   **dotCase**
-   **headerCase**
-   **lowerCase**
-   **pascalCase**
-   **paramCase**
-   **pathCase**
-   **sentenceCase**
-   **snakeCase**
-   **titleCase**
-   **upperCase**

This will tell **mason** to create a filename, **<name>.dart**, and ensure the filename is snake case. Next, let's modify the content of our widget template file to include the widget code.

```dart
import 'package:flutter/material.dart';

class {{#pascalCase}}{{name}}{{/pascalCase}} extends StatelessWidget {
  const {{#pascalCase}}{{name}}{{/pascalCase}}({Key? key}) : super(key: key);

  {{#routable}}
  static PageRoute route() {
    return MaterialPageRoute(builder: (context) => const {{#pascalCase}}{{name}}{{/pascalCase}}());
  }
  {{/routable}}

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return const SizedBox();
  }
}
```

Here we're defining a **StatelessWidget** called **name** and using the **pascalCase** lambda to ensure it is a valid class name. We're also using mustache sections to define a static route function only if the variable **routable** is **true**.

That's it, now anytime we want to generate a new widget, we can use **mason make widget**.

```shell
$ mason make widget
name: dash container
routable: true
✓ Made brick widget (12.5ms)
✓ Generated 1 file(s):
  dash_container.dart (new)
```

Then we can take a look at the generated code in **dash\_container.dart**:

```dart
import 'package:flutter/material.dart';

class DashContainer extends StatelessWidget {
  const DashContainer({Key? key}) : super(key: key);

  static PageRoute route() {
    return MaterialPageRoute(builder: (context) => const DashContainer());
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return const SizedBox();
  }
}
```

If we want a new widget that is not routable, we can set **routable** to **false**:

```shell
$ mason make widget
name: flutter box
routable: false
✓ Made brick widget (16.8ms)
✓ Generated 1 file(s):
  flutter_box.dart (new)
```

And if we take a look at the newly generated **flutter\_box.dart**, we should see there is no **route** method defined:

```dart
import 'package:flutter/material.dart';

class FlutterBox extends StatelessWidget {
  const FlutterBox({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return const SizedBox();
  }
}
```

## What's Next

We're actively working on several new features including support for global templates and template extensions. You can head over to the [**mason repo**](https://github.com/felangel/mason) to check on the progress, provide feedback, and explore other examples.

Happy templating!
