Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Dockerfile
**/node_modules/**
docker-compose.yml
docker-compose.dev.yml
docker/.buildx-cache
doc/**
.dockerignore
**/build
Expand Down
8 changes: 1 addition & 7 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
name: Docker Release

# This workflow was largely generated from the following prompt:
# "only run this on merge to main; clone the repo; set the VERSION environment
# variable from packages/runtime/package.json; use `docker manifest inspect`
# to verify the release doesn't already exist; create a multi-platform builder;
# build and push the image"

on:
push:
branches: [main]
Expand All @@ -20,7 +14,7 @@ jobs:
- name: Get version
id: version
run: |
echo "VERSION=$(cat packages/runtime/package.json | jq -r '.version')" >> "$GITHUB_OUTPUT"
echo "VERSION=$(cat packages/compiler/package.json | jq -r '.version')" >> "$GITHUB_OUTPUT"

- name: Check if image already exists
id: check
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Go Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.work

- name: Compile grammar to wasm
run: ./golang/runtime/generate.sh

- name: Run Go tests
working-directory: golang/runtime
run: go test ./...
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ yarn-error.log

# api-extractor temp files
temp/

# Docker buildx local layer cache
docker/.buildx-cache
23 changes: 16 additions & 7 deletions doc/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,16 @@ The `-v $(pwd):/local` mount makes your current directory available at `/local`

The `ohm-dev:latest` images is 1.62 GB and is 97% efficient with only 64 MB potentially wasted space.

### Publishing to Docker Hub
### Publishing to Docker Hub (from development machine)

**Note: it is perferable to have the gha docker-release.yml do this**
But there can be a chicken and egg situation where it is easier to do this from a development machine.

Build and push a versioned image to Docker Hub using the git tag as the version:

```sh
# if not set default to ohmjs (ie docker hub using the ohmjs org)
export DOCKER_REPO=<custom docker repo>
# if not set defaults to 'development'
export VERSION=$(cat packages/runtime/package.json | jq -r '.version')
# or export VERSION=$(git describe --tag --dirty)

# # it might be necessary (particularly on osx) to create a new builder
# # the default builder might not support multi-platform builds
Expand All @@ -129,14 +129,23 @@ export VERSION=$(cat packages/runtime/package.json | jq -r '.version')
# # or if already created
# docker buildx use ohmjs-builder

# generate a person access token at https://app.docker.com/accounts/millergarym/settings/personal-access-tokens
# generate a person access token at https://app.docker.com/accounts/<github username>/settings/personal-access-tokens
# assuming DHPAT contains your PAT
echo $DHPAT | docker login -u <personal username> --password-stdin
cd docker
docker buildx bake --allow=fs.read=.. --push
# if not set defaults to 'development'
export VERSION=$(cat ../packages/compiler/package.json | jq -r '.version')
docker buildx use ohmjs-builder
docker buildx bake \
--set="*.cache-from=type=local,src=.buildx-cache" \
--set="*.cache-to=type=local,dest=.buildx-cache,mode=max" \
--allow=fs.read=.. \
--push
```

`git describe --tag --dirty` produces a version string based on the nearest git tag, appending commit info and a `-dirty` suffix if there are uncommitted changes.
Builds use a local layer cache stored in `docker/.buildx-cache`.
On subsequent runs this avoids re-downloading base layers and reinstalling dependencies.
Note the `docker/.buildx-cache` directory is over 1GB, and needs to be cleaned up manually.

The defaults in `docker-compose.yml` are `DOCKER_REPO=ohmjs` and `VERSION=development`. See [docker-compose.yml](../docker/docker-compose.yml) for details.

Expand Down
6 changes: 6 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ services:
platforms:
- linux/amd64
- linux/arm64/v8
# # Not specifying cache here as it would just slow down gha builds.
# # When doing local development, see docker.md for instructions on how to use it local cache.
# cache-from:
# - type=local,src=.buildx-cache
# cache-to:
# - type=local,dest=.buildx-cache,mode=max
target: ${TARGET:-dist}
volumes:
- .:/local
Expand Down
7 changes: 7 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
go 1.24.2

use (
./golang/runtime
./golang/examples
./golang/cli
)
8 changes: 8 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/jpillora/opts v1.2.3 h1:Q0YuOM7y0BlunHJ7laR1TUxkUA7xW8A2rciuZ70xs8g=
github.com/jpillora/opts v1.2.3/go.mod h1:7p7X/vlpKZmtaDFYKs956EujFqA6aCrOkcCaS6UBcR4=
github.com/posener/complete v1.2.2-0.20190308074557-af07aa5181b3 h1:GqpA1/5oN1NgsxoSA4RH0YWTaqvUlQNeOpHXD/JRbOQ=
github.com/posener/complete v1.2.2-0.20190308074557-af07aa5181b3/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E=
1 change: 1 addition & 0 deletions golang/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ohmgo
156 changes: 156 additions & 0 deletions golang/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# OhmGo — CLI for Ohm, for Go

`ohmgo` is a command-line tool that helps you compile `.ohm` grammar files into WebAssembly (`.wasm`) for use with the [`goohm`](https://github.com/ohmjs/goohm) Go runtime.

It does not compile grammars directly. Instead, it generates the Docker command needed to compile them via the official `ohmjs/ohm` image — letting you review, adapt, or automate the compilation step however you like.

## Overview

The typical workflow for using Ohm in a Go project looks like this:

```
my-grammar.ohm → (docker compile) → my-grammar.wasm → embedded in Go binary
```

`ohmgo` handles the middle step by generating the correct `docker run` invocation, pinned to the version of the `goohm` runtime you're using.

## Installation

```bash
go install github.com/ohmjs/ohmgo@latest
```

Or build from source:

```bash
git clone https://github.com/ohmjs/ohmgo
cd ohmgo
go build -o ohmgo .
```

## Commands

### `generate command`

Generates a Docker command to compile a `.ohm` grammar file into a `.wasm` file compatible with the current `goohm` runtime version.

<!--tmpl,code=bash:go run main.go generate command -h -->
``` bash

Usage: ohmgo generate command [options] <source-file>

Generate a command, in the specified format (default is 'command') to compile a .ohm grammar file
using the ohmjs/ohm docker image. Does not actually compile the file.

Path to .ohm grammar file to compile.

Options:
--debug, -d
--docker-tag, -t The version tag of the ohmjs/ohm docker image to use in the generated command.
Defaults to the version of the goohm runtime included in this cli. (default
18.0.0-beta.14)
--grammar-name, -g
--output, -o
--format, -f Output format. One of: command, go_generate, script. (default command)
--help, -h display help

```
<!--/tmpl-->

The `--docker-tag` default is derived at runtime from the `goohm` package, so it always matches the runtime version bundled with this CLI — preventing version mismatches between the compiler and the runtime.

## Output Formats

The `--format` flag controls how the generated command is wrapped.

### `command` (default)

A ready-to-run shell snippet with a comment:

<!--tmpl,code=bash:go run main.go generate command my-grammar.ohm -->
``` bash

# To generate a .wasm file for use with this version of the runtime, run:
docker run --rm -v "$PWD":/local ohmjs/ohm:18.0.0-beta.14 compile my-grammar.ohm
```
<!--/tmpl-->

### `go_generate`

A `//go:generate` directive for embedding in a Go source file:

<!--tmpl,code=bash:go run main.go generate command --format=go_generate my-grammar.ohm -->
``` bash

//go:generate docker run --rm -v $PWD:/local ohmjs/ohm:18.0.0-beta.14 compile my-grammar.ohm
```
<!--/tmpl-->

Paste this into any `.go` file in your package. Running `go generate ./...` will compile the grammar and produce `my-grammar.wasm` in the same directory.

### `script`

A standalone shell script with a shebang:

<!--tmpl,code=bash:go run main.go generate command --format=script my-grammar.ohm -->
``` bash
#!/bin/sh

docker run --rm -v "$PWD":/local ohmjs/ohm:18.0.0-beta.14 compile my-grammar.ohm
```
<!--/tmpl-->

Useful when you want a reusable script checked into your repo:

```bash
ohmgo generate command --format=script my-grammar.ohm > compile.sh
chmod +x compile.sh
./compile.sh
```

## Using the Compiled Grammar in Go

Once you have a `.wasm` file, load it with `goohm`:

```go
package main

import (
"context"
_ "embed"
"log"

goohm "github.com/ohmjs/goohm"
)

//go:embed my-grammar.wasm
var wasmBytes []byte

func main() {
ctx := context.Background()
grmr, err := goohm.NewGrammar(ctx, wasmBytes)
if err != nil {
log.Fatalf("creating grammar: %v", err)
}
defer grmr.Close()

result, err := grmr.Match("Hello, world!")
if err != nil {
log.Fatalf("matching: %v", err)
}
defer result.Close()

if result.Succeeded() {
log.Println("match succeeded")
}
}
```

## Shell Completion

Install or remove zsh, bash or fish completion:

```bash
ohmgo --install # install zsh, bash or fish completions
ohmgo --uninstall # remove zsh, bash or fish completion
```
79 changes: 79 additions & 0 deletions golang/cli/gencmd/gencmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package gencmd

import (
"fmt"
"strings"

"github.com/ohmjs/goohm"
)

type genCmdCmd struct {
Debug bool
DockerTag string `opts:"short=t" help:"The version tag of the ohmjs/ohm docker image to use in the generated command. Defaults to the version of the goohm runtime included in this cli."`
GrammarName string
Output string
Format format `help:"Output format. One of: command, go_generate, script."`
SourceFile string `opts:"mode=arg" help:"Path to .ohm grammar file to compile."`
}

type format string

var validFormats = []string{"command", "go_generate", "script"}

func (format) Complete(s string) []string {
return validFormats
}

func (e *format) Set(s string) error {
for _, v := range validFormats {
if s == v {
*e = format(s)
return nil
}
}
return fmt.Errorf("must be one of: %v", strings.Join(validFormats, ", "))
}

func NewGenCmdCmd() *genCmdCmd {
return &genCmdCmd{
Format: format("command"),
DockerTag: ((*goohm.Grammar)(nil)).MatchingDockerImageTags()[0],
}
}

func (c *genCmdCmd) Run() error {
var (
debug = ""
grammar = ""
output = ""
)
if c.Debug {
debug = "--debug "
}
if c.GrammarName != "" {
grammar = fmt.Sprintf("--grammarName %s ", c.GrammarName)
}
if c.Output != "" {
output = fmt.Sprintf("--output %s ", c.Output)
}

switch c.Format {
case "command":
fmt.Printf(`
# To generate a .wasm file for use with this version of the runtime, run:
docker run --rm -v "$PWD":/local ohmjs/ohm:%s compile %s%s%s%s
`, c.DockerTag, debug, grammar, output, c.SourceFile)
case "go_generate":
fmt.Printf(`
//go:generate docker run --rm -v $PWD:/local ohmjs/ohm:%s compile %s%s%s%s
`, c.DockerTag, debug, grammar, output, c.SourceFile)
case "script":
fmt.Printf(`#!/bin/sh

docker run --rm -v "$PWD":/local ohmjs/ohm:%s compile %s%s%s%s
`, c.DockerTag, debug, grammar, output, c.SourceFile)
default:
return fmt.Errorf("invalid format: %s", c.Format)
}
return nil
}
12 changes: 12 additions & 0 deletions golang/cli/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/ohmjs/ohmgo

require github.com/jpillora/opts v1.2.3

require (
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/jpillora/md-tmpl v1.3.0 // indirect
github.com/posener/complete v1.2.2-0.20190308074557-af07aa5181b3 // indirect
)

go 1.24.2
Loading
Loading