6 releases

0.4.2 Jan 25, 2024
0.4.1 Dec 29, 2023
0.3.4 Aug 22, 2023
0.3.3 Jul 29, 2022
0.3.2 Feb 18, 2022

#253 in Command line utilities

MIT license

115KB
2K SLoC

Brix

Brix is a CLI tool written in Rust for scaffolding and code generation.

Attributions

Special thanks to Caleb Cushing for the original Java version, early interface design and internal architecture.

Installation

Brix is available on crates.io and the AUR for Arch Linux.

Install with cargo:

cargo install brix

Arch Linux (use an AUR helper like yay or trizen)

yay -S brix-git

Running

Usage:

brix [LANGUAGE] [CONFIG NAME] [PROJECT] [MODULE]
brix [OPTIONS] --config-dir | -d [CONFIG DIRECTORY]
brix [OPTIONS] --workdir | -w [WORKING DIRECTORY]

Building locally

Requirements
  • Cargo and a minimum Rust version of 1.43.1
Running
  • Run cargo build
  • Run cargo run
Testing

Run cargo test --all to test the entire workspace.

Generating Docs

Run cargo doc --no-deps --workspace --document-private-items --open

Using Brix

Language directory

To start using Brix in your project, create a .config/brix directory in your project. This directory will contain your configuration files. Specifying the first argument after running brix will tell it which subdirectory to use. For example, starting Brix with brix java will search the .config/brix/java directory for configuration files in your project. This parameter is known as the language. If a config file isn't found, Brix will move up to the parent directory all the way up to HOME, until it finds a directory that contains a .config/brix/java. This parameter doesn't necessarily have to be resitrcted to a programming language, it's there to group configuration files used in a similar way, perhaps for bootstraping a specific project.

Config name

Of course, running brix [language] without pointing Brix to a specific config file is a bit useless. The second argument specifies the file name to use. Running brix java tutorial will search for a file in .config/brix/java named tutorial.brix.yml or tutorial.brix.yaml. As of right now the files must be written in YAML, but more options will be supported in the future, like JSON and TOML.

Project and module

These two arguments are specific to your config file, you can use them however you want, so let's take a look at how the config file is structured first.

Config file

Brix offers various commands for you to use to scaffold and generate your project. At the top-most level, the config file is just a list of commands, therefore we can start by just declaring the commands property:

# example.brix.yml
commands:
  - search_replace:
    # ...
  - exec:
    # ...

The commands you list will be executed from top to bottom, one after the other. The following commands are supported:

  • copy

    Copies a file or directory to a new location.

  • search_replace

    Searches for a strin or regular expression in a file and replaces it with another string.

  • exec

    Executes a list of commands.

  • mkdir

    Creates a directory.

  • template

    Templates a file to a new location.

Let's start with the most basic copy command, and use Brix to simply copy a .gitignore file. Our config file would look something like this:

# .config/brix/js/gitignore.brix.yml
commands:
  - copy:
      source: .gitignore
      destination: app/.gitignore

The source directory is always relative to where the config file is located. In this case it would be .config/brix/js/.gitignore. The destination directory is relative to where you run brix from, this is also known as the working directory, and can be overriden with the --workdir or -w flag. Now, if we were to run brix js gitignore in our project directory, we would actually get an error. That's because Brix requires the project and module arguments to be specified. In our case, we're not using them yet so we can specify anything. Now, running brix js gitignore project module will run the command. If you didn't already have an app directory, Brix will create one for you and copy the .gitignore file to it.

You might notice though that this isn't that convenient for our project if we wan't to put the file somewhere other than app. Let's say now we have a backend folder in addition to app. To copy the .gitignore file to backend, we would have to change the destination in the config file each time. Instead, this is where the project and module arguments come in handy. Let's change the config file to:

# .config/brix/js/gitignore.brix.yml
commands:
  - copy:
      source: .gitignore
      destination: {{project}}/.gitignore

Here, we are using the project argument in the destination. Running brix js gitignore backend module will now copy the file to backend/.gitignore. If we wanted to copy it to somewhere else, all we would have to do is run brix js gitignore somewhere_else module.

We could also use the module parameter to, for example, sample a different gitignore file.

# .config/brix/js/gitignore.brix.yml
commands:
  - copy:
      source: {{module}}/.gitignore
      destination: {{project}}/.gitignore

Running brix js gitignore dashboard default will use .config/brix/js/default/.gitignore and copy it to dashboard/.gitignore.

Now, let's take a full look at all of the commands.

Copy

commands:
  - copy:
      source: file.txt
      destination: output/file.txt
      overwrite: true # Optional, will ask by default to overwrite if the file already exists

Search replace

Search replace uses fancy regex for regular expressions in the search field and supports backreferences. The syntax is best explained here.

commands:
  - search_replace:
      destination: file-to-search-replace.txt
      search: search string # fancy-regex supported
      replace: replace string

Exec

Executes commands in order.

commands:
  - exec:
      commands:
        - 'echo "Hello World!"'
        - "prettier --write ."
        - "cargo --version"
      stdout: true # Optional

Mkdir

Creates a directory.

commands:
  - mkdir:
      destination: output/directory

Template

Templates a file.

commands:
  - template:
      source: file.ex.hbs
      destination: output/file.ex
      overwrite: true # Optional
      context: # Optional
        first: {{project}}Service
        second: {{module}}

file.ex.hbs could look something like:

defmodule App.{{first}.{{second}} do
  # ...
end

And when templated, output/file.ex:

defmodule App.UsersService.Store do
  # ...
end

Context and Templating

Brix uses Handlebars, specifically the Rust version with both the template command and config files in general. The context parameter in the command isn't required, since {{project}} and {{module}} are automatically handled if specified in the template file.

There's also a way to specify global context in the config file:

context:
  edition: 2022
  name: {{project}}
commands:
  - template:
      source: file.txt.hbs
      destination: output/file-{{edition}}.txt
  - copy:
      source: {{edition}}/notes.txt
      destination: output/notes.txt

In this case, parameters edition, name, project, and module will all be available for use within the config file itself and all template files referenced in template commands. Global context is also useful for declaring constants that are shared between multiple commands.

Templating helpers

Brix also provides useful helpers for manipulating these variables, specifically for altering capitalization and case. The following helpers are provided:

  • to-upper
  • to-lower
  • to-title
  • to-case
  • to-flat
  • to-java-package
  • to-java-package-path

All of these can be used to replace, for example, usage of {{project}}:

context:
  project: 'foo BAR 40'
{{to-upper              project}} # FOO BAR 40
{{to-lower              project}} # foo bar 40
{{to-title              project}} # Foo Bar 40
{{to-flat               project}} # foobar40
{{to-java-package       project}} # foo.bar40
{{to-java-package-path  project}} # foo/bar40

Reusing templates

Brix has the added benefit of not requiring a specific folder structure to be used inside the language directories. Templates and other files are completely independent from config files. This means that reusing templates is a lot easier, as everything is referenced with just a path. If you need to use the same template within two different config files, simply reference the path to the file in both and use a different context.

Full Example

Finally, let's take a look at a full example using Brix to bootstrap a Java project. The .config/brix directory is conveniently located in HOME in order to be able to run brix from anywhere and create a project like this.

commands:
  - copy: # Copy all of the shared files to the project directory
      source: project/shared
      destination: .
      overwrite: true
  - template: # Template build.gradle.kts
      source: module/build.gradle.kts.hbs
      destination: "module/{{module}}/build.gradle.kts"
      overwrite: false
  - template: # Template module-info.java
      source: module/src/main/java/module-info.java.hbs
      destination: "module/{{module}}/src/main/java/module-info.java"
      overwrite: false
  - template: # Teplate package.json
      source: project/templates/shared/package.json.hbs
      destination: "package.json"
      overwrite: false
  - template: # Template settings.gradle.kts.hbs
      source: project/templates/shared/settings.gradle.kts.hbs
      destination: "settings.gradle.kts"
      overwrite: false
  - mkdir: # Create the main directory for the module
      destination: "module/{{module}}/src/main/java/com/example/{{module}}"
  - mkdir: # Create the test directory for the module
      destination: "module/{{module}}/src/test/java/com/example/{{module}}"
  - exec: # Run the following commands with no stdout
      commands:
        - git init
        - git add .
        - git commit -m initial
        - yarn up
      stdout: false

More Examples

There are a few extra examples located in ./config/brix/rust.

  • copy cargo run -- rust copy brix foo
  • exec cargo run -- rust exec foo foo
  • mkdir cargo run -- rust mkdir brix foo
  • search_replace cargo run -- rust search_replace brix foo
  • template cargo run -- rust template brix foo

Dependencies

~12–22MB
~333K SLoC