Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ jobs:
uses: actions/setup-go@v6
with:
go-version-file: go.mod
-
name: Extract release notes from CHANGELOG.md
run: scripts/changelog.sh extract "${{ github.ref_name }}" > /tmp/release-notes.md
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
version: latest
args: release --clean
args: release --clean --release-notes=/tmp/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ checksum:
name_template: 'checksums.txt'
snapshot:
version_template: "{{ .Tag }}-next"
# CHANGELOG.md is the source of truth for release notes: publish.yml and
# `make release` pass --release-notes with the relevant CHANGELOG.md section,
# which skips this generated changelog. It's kept as a fallback for ad-hoc
# `goreleaser release` runs made without going through the Makefile/scripts.
changelog:
sort: asc
filters:
Expand Down
92 changes: 73 additions & 19 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
# Unreleased
# Changelog

* Fix `.mot`/`.mopt` formatting collapsing the space in a single-element array
literal whose element is a Jinja expression (e.g. `{ {{ data[...] }} }`),
which produced an ambiguous `{{{ ... }}}` triple-brace sequence. A single
disambiguating space is now preserved whenever a restored Jinja expression's
own `{{`/`}}` delimiters would otherwise merge with an adjacent Modelica
array brace.
All notable changes to this project are documented in this file. The format
is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and each
released version corresponds to a tagged
[GitHub release](https://github.com/urbanopt/modelica-fmt/releases).

## [Unreleased]

## [v0.3.1] - 2026-07-14

* Add `-format-html`: pretty-print HTML content embedded in Modelica annotation
strings, e.g. `Documentation(info="<html>...</html>")` and `revisions="..."`. Opt-in and
conservative: only whitespace/indentation changes (tags, attributes, entities and text are
preserved exactly), block-level elements are indented to their nesting depth while
paragraphs with inline-only content, plain `<h4>` headings, and inline/phrasing elements
(e.g., `<b>`, `<a>`, `<code>`, `<br/>`) stay condensed on one line, whitespace-sensitive
elements (`<pre>`, `<textarea>`, `<script>`, `<style>`) are left verbatim, and
malformed/unbalanced HTML is reported as a clear error (leaving the file unchanged and
exiting non-zero) rather than being silently emitted unformatted (issue #40).
* Tweak default formatting for readability (issue #26):
* Keep a function call with a single argument on one line instead of breaking
the argument onto its own line, e.g. `pre(x)`, `der(y)`, `sin(z)`,
Expand All @@ -17,6 +29,20 @@
* Collapse the stray empty line often left between the final `</ul>` and
`</html>` of a `revisions` docstring (`</ul>\n\n</html>` becomes
`</ul>\n</html>`).
* Fix `.mot`/`.mopt` formatting collapsing the space in a single-element array
literal whose element is a Jinja expression (e.g. `{ {{ data[...] }} }`),
which produced an ambiguous `{{{ ... }}}` triple-brace sequence. A single
disambiguating space is now preserved whenever a restored Jinja expression's
own `{{`/`}}` delimiters would otherwise merge with an adjacent Modelica
array brace.

## [v0.3.1-pr1] - 2026-07-13

* Broaden `.mot` template support to handle more difficult real-world files
(additional Jinja constructs and edge cases).

## [v0.3.0] - 2026-07-13

* Add native support for formatting templated Modelica (`.mot`/`.mopt`) files that embed Jinja
constructs (`{{ ... }}`, `{% ... %}`, `{% raw %}`), ported from geojson-modelica-translator's
`format_modelica_files.py`. `.mot` and `.mopt` files are detected automatically (including in directory
Expand All @@ -26,17 +52,45 @@
non-whitespace output (e.g., a non-Modelica file matched as an empty definition), the
formatter now returns an error and leaves the file unchanged instead of emptying it.
* Add `-wrap-arrays` option to format multidimensional arrays (`{...}`) across
multiple lines outside of annotations (issue #29)
* Add `-format-html`: pretty-print HTML content embedded in Modelica annotation
strings, e.g. `Documentation(info="<html>...</html>")` and `revisions="..."`. Opt-in and
conservative: only whitespace/indentation changes (tags, attributes, entities and text are
preserved exactly), block-level elements are indented to their nesting depth while
paragraphs with inline-only content, plain `<h4>` headings, and inline/phrasing elements
(e.g., `<b>`, `<a>`, `<code>`, `<br/>`) stay condensed on one line, whitespace-sensitive
elements (`<pre>`, `<textarea>`, `<script>`, `<style>`) are left verbatim, and
malformed/unbalanced HTML is reported as a clear error (leaving the file unchanged and
exiting non-zero) rather than being silently emitted unformatted (issue #40).
multiple lines outside of annotations (issue #29).
* Add `-extra-padding` (BETA): inserts a blank line after (almost) every
semicolon for legibility, skipping lines with a `within` clause or inside a
matrix (`[...]`) literal, and avoiding duplicate blank lines/trailing
newlines at the end of a file.

## [v0.2-pr.2] - 2021-02-19

* Add a `-line-length` CLI flag and break lines longer than 80 characters.
* Don't allow a line break immediately at a curly brace.

## [v0.2-pr.1] - 2021-01-28

* Add a `-v` flag to display version info.
* Indent arguments of external function calls.
* Show CLI usage when no arguments are provided.
* Move CI from Travis to GitHub Actions.

## [v0.1] - 2020-09-30

* Support formatting of Modelica files.

## [v0.1-pr.3] - 2020-08-17

* Fix handling of comments trailing at the end of a file.

## [v0.1-pr.2] - 2020-07-31

* Always indent modifications and arguments.
* Allow indentation in model annotation vectors.
* Remove extraneous spacing: no space after commas, around array constructors,
or around the `+` operator.
* Add basic tests for example files.

# Version 0.1
## [v0.1-pr.1] - 2020-06-22

* Support formatting of Modelica files
* Initial formatter implementation for Modelica (`.mo`) files: annotation and
if-expression formatting/indentation rules.
* Add an option to write the formatted result back to the source file.
* Add rudimentary comment handling.
* Rename the project/binary to `modelica-fmt`/`modelicafmt`.
* Set up Travis CI and GoReleaser.
36 changes: 31 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
.PHONY: help all build test clean generate-parser update-testdata release-check release
.PHONY: help all build test clean generate-parser update-testdata changelog-check release-check release

.DEFAULT_GOAL := help

GO ?= go
BINARY ?= modelica-fmt
REMOTE ?= origin
CHANGELOG ?= CHANGELOG.md
VERSION ?=
TAG ?= $(if $(VERSION),$(if $(filter v%,$(VERSION)),$(VERSION),v$(VERSION)))
MESSAGE ?= Release $(TAG)
SHA ?= HEAD
VERSION_ID := $(patsubst v%,%,$(TAG))
COMMIT := $(shell git rev-parse --short=12 $(SHA) 2>/dev/null)
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
RELEASE_DATE := $(shell date -u +%Y-%m-%d)
RELEASE_LDFLAGS := -X main.version=$(VERSION_ID) -X main.commit=$(COMMIT) -X main.date=$(DATE) -X main.builtBy=make

help:
Expand All @@ -22,8 +24,13 @@ help:
@echo " clean remove build artifacts"
@echo " generate-parser regenerate parser from grammar"
@echo " update-testdata regenerate formatter test data"
@echo " changelog-check verify $(CHANGELOG) has unreleased notes"
@echo " release-check verify release build metadata (VERSION=x.y.z)"
@echo " release check, tag, and push a release (VERSION=x.y.z)"
@echo ""
@echo "$(CHANGELOG) is the source of truth for release notes: entries added"
@echo "under '## [Unreleased]' are moved into a dated '## [vX.Y.Z]' section by"
@echo "'make release' and published verbatim as the GitHub release body."

all: build test

Expand All @@ -42,7 +49,14 @@ generate-parser:
update-testdata:
./update_testdata.sh

release-check: test
changelog-check:
@notes="$$(scripts/changelog.sh extract Unreleased $(CHANGELOG))"; \
if [ -z "$$(printf '%s' "$$notes" | tr -d '[:space:]')" ]; then \
echo "error: $(CHANGELOG) has no entries under '## [Unreleased]'; add release notes before releasing"; \
exit 2; \
fi

release-check: test changelog-check
@if [ -z "$(TAG)" ]; then \
echo "error: set VERSION=x.y.z or TAG=vx.y.z"; \
exit 2; \
Expand Down Expand Up @@ -70,9 +84,21 @@ release-check: test
echo "error: expected $(BINARY) -v to report version $(VERSION_ID)"; \
exit 2; \
}
goreleaser check
goreleaser release --snapshot --clean
@notes_file="$$(mktemp)"; \
trap 'rm -f "$$notes_file"' EXIT; \
scripts/changelog.sh extract Unreleased $(CHANGELOG) > "$$notes_file"; \
goreleaser check; \
goreleaser release --snapshot --clean --release-notes "$$notes_file"

release: release-check
git tag -a "$(TAG)" -m "$(MESSAGE)" "$(SHA)"
@if [ "$$(git rev-parse $(SHA))" != "$$(git rev-parse HEAD)" ]; then \
echo "error: automatic $(CHANGELOG) finalization requires SHA=HEAD (got $(SHA));"; \
echo " finalize $(CHANGELOG) for $(TAG) manually and retry, or release from HEAD"; \
exit 2; \
fi
scripts/changelog.sh finalize "$(TAG)" "$(RELEASE_DATE)" $(CHANGELOG)
git add $(CHANGELOG)
git commit -m "docs: update $(CHANGELOG) for $(TAG)"
git tag -a "$(TAG)" -m "$(MESSAGE)" HEAD
git push "$(REMOTE)" HEAD
git push "$(REMOTE)" "$(TAG)"
42 changes: 30 additions & 12 deletions docs/release.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
# Releases

Modelica formatter uses [GoReleaser](https://goreleaser.com/) for building assets and documenting tags for release. This is run automatically by GitHub Actions whenever a new tag is pushed to remote. See [.goreleaser.yml](/.goreleaser.yml) and [publish.yml](/.github/workflows/publish.yml) for the configuration.

## CHANGELOG.md is the source of truth

[CHANGELOG.md](/CHANGELOG.md) follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
notable changes are added as bullets under `## [Unreleased]` as they land
(e.g. in the same PR that makes the change). When a release is cut,
`make release` moves that section into a dated `## [vX.Y.Z] - YYYY-MM-DD`
heading, and the GitHub Actions release workflow extracts that exact section
and publishes it verbatim as the GitHub release body (via
`scripts/changelog.sh` and goreleaser's `--release-notes` flag). This keeps
CHANGELOG.md and the GitHub releases page in sync automatically — there is no
separate release notes to write.

## How to make a release
Pick the release version and update `CHANGELOG.md` so `# Unreleased` is moved to a versioned heading for the release.

Make sure `CHANGELOG.md` has one or more entries under `## [Unreleased]`
describing the release; `make release-check` fails otherwise.

Run the release preflight:

```bash
make release-check VERSION=<version>
```

Where `<version>` is a semantic version without the leading `v`, for example `1.2.3` or `1.2.3-rc.1`. This runs tests, builds the binary with the requested release metadata, verifies that `modelica-fmt -v` reports the requested version id, checks the GoReleaser configuration, and runs a GoReleaser snapshot build.
Where `<version>` is a semantic version without the leading `v`, for example `1.2.3` or `1.2.3-rc.1`. This runs tests, verifies `CHANGELOG.md` has unreleased notes, builds the binary with the requested release metadata, verifies that `modelica-fmt -v` reports the requested version id, checks the GoReleaser configuration, and runs a GoReleaser snapshot build using the unreleased notes.

Then create and push the annotated tag:

```bash
make release VERSION=<version> MESSAGE="<message>"
```

This creates and pushes tag `v<version>` to `origin`, triggering the GitHub Actions GoReleaser workflow.

To tag a specific commit instead of `HEAD`, pass `SHA=<commit>`:

```bash
make release VERSION=<version> MESSAGE="<message>" SHA=<commit>
```
This moves the `## [Unreleased]` section of `CHANGELOG.md` into a new
`## [v<version>] - <date>` heading, commits that change, creates and pushes
tag `v<version>` (and the commit) to `origin`, triggering the GitHub Actions
GoReleaser workflow. Automatic CHANGELOG finalization requires releasing from
`HEAD` (the default).

If you need to tag manually, run the same preflight first and then create and push the tag:
To tag a specific commit instead of `HEAD`, pass `SHA=<commit>`. In that case
you must finalize `CHANGELOG.md` for the release yourself first (rename
`## [Unreleased]` to `## [v<version>] - <date>` and leave a fresh, empty
`## [Unreleased]` above it), since it isn't safe to automatically rewrite
history at an arbitrary commit:

```bash
make release-check VERSION=<version>
git tag -a v<version> -m "<message>" [SHA]
git tag -a v<version> -m "<message>" <SHA>
git push origin v<version>
```

After the build successfully finishes, go to the releases page on Github, check that it looks good, and publish it.
After the build successfully finishes, go to the releases page on GitHub,
check that it looks good, and publish it (releases are created as drafts).
66 changes: 66 additions & 0 deletions scripts/changelog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Keep CHANGELOG.md and GitHub releases in sync.
#
# CHANGELOG.md is the single source of truth for release notes: `make
# release` finalizes the "[Unreleased]" section into a dated version
# section, and the same section is extracted and handed to goreleaser as the
# GitHub release body (see publish.yml and the Makefile).
#
# Usage:
# changelog.sh extract <heading> Print the notes under "## [<heading>]"
# changelog.sh finalize <tag> <date> [file] Turn "## [Unreleased]" into
# "## [Unreleased]" + "## [<tag>] - <date>"
set -euo pipefail

usage() {
echo "usage: $0 extract <heading> [file]" >&2
echo " $0 finalize <tag> <date> [file]" >&2
exit 2
}

cmd="${1:-}"
[ -n "$cmd" ] || usage
shift

case "$cmd" in
extract)
heading="${1:-}"
file="${2:-CHANGELOG.md}"
[ -n "$heading" ] || usage
awk -v tag="$heading" '
BEGIN { pat = "^## \\[" tag "\\]"; found = 0; n = 0 }
$0 ~ pat { found = 1; next }
found && /^## \[/ { exit }
found { n++; buf[n] = $0 }
END {
start = 1; end = n
while (start <= end && buf[start] ~ /^[[:space:]]*$/) start++
while (end >= start && buf[end] ~ /^[[:space:]]*$/) end--
for (i = start; i <= end; i++) print buf[i]
}
' "$file"
;;
finalize)
tag="${1:-}"
date="${2:-}"
file="${3:-CHANGELOG.md}"
[ -n "$tag" ] && [ -n "$date" ] || usage
if ! grep -q '^## \[Unreleased\]' "$file"; then
echo "error: $file has no '## [Unreleased]' heading" >&2
exit 2
fi
tmp="$(mktemp)"
awk -v tag="$tag" -v date="$date" '
{ print }
!inserted && $0 ~ /^## \[Unreleased\]/ {
print ""
print "## [" tag "] - " date
inserted = 1
}
' "$file" > "$tmp"
mv "$tmp" "$file"
;;
*)
usage
;;
esac