diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 83cf42d57..c62c88718 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -2,9 +2,7 @@ name: daily on: workflow_dispatch: schedule: - # daily at 00:42 to avoid hourly loads in GitHub actions - # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule - - cron: '42 0 * * *' + - cron: '42 0 * * *' # daily at 00:42 concurrency: group: ${{ github.workflow }} cancel-in-progress: true diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml new file mode 100644 index 000000000..3adc6d9a4 --- /dev/null +++ b/.github/workflows/project.yml @@ -0,0 +1,15 @@ +name: d2-project + +on: + issues: + types: + - opened + +jobs: + d2-project: + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.4.0 + with: + project-url: https://github.com/orgs/terrastruct/projects/34 + github-token: ${{ secrets._GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 67d08864e..7af7b3572 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.got.json *.got.svg e2e_report.html +bin diff --git a/Makefile b/Makefile index d4241d86f..fdc039a33 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ all: fmt gen lint build test .PHONY: fmt fmt: - prefix "$@" ./ci/sub/fmt/make.sh + prefix "$@" ./ci/fmt.sh .PHONY: gen gen: prefix "$@" ./ci/gen.sh diff --git a/README.md b/README.md index db72cafa3..36e20ec24 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A modern diagram scripting language that turns text to diagrams. -[Language docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) +[Language docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) [![ci](https://github.com/terrastruct/d2/actions/workflows/ci.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/ci.yml) [![release](https://img.shields.io/github/v/release/terrastruct/d2)](https://github.com/terrastruct/d2/releases) @@ -19,28 +19,26 @@ # Table of Contents +- What does D2 look like? +- Quickstart +- Install +- D2 as a library +- Themes +- Fonts +- Export file types +- Language tooling +- Plugins +- Comparison +- Contributing +- License +- Related + - VSCode extension + - Vim extension + - Language docs + - Misc +- FAQ -- [What does D2 look like?](#what-does-d2-look-like) -- [Quickstart](#quickstart) -- [Install](#install) -- [D2 as a library](#d2-as-a-library) -- [Themes](#themes) -- [Fonts](#fonts) -- [Export file types](#export-file-types) -- [Language tooling](#language-tooling) -- [Plugins](#plugins) -- [Comparison](#comparison) -- [Contributing](#contributing) -- [License](#license) -- [Related](#related) - * [VSCode extension](#vscode-extension) - * [Vim extension](#vim-extension) - * [Misc](#misc) -- [FAQ](#faq) - - - -# What does D2 look like? +## What does D2 look like? ```d2 # Actors @@ -97,70 +95,34 @@ The easiest way to install is with our install script: curl -fsSL https://d2lang.com/install.sh | sh -s -- ``` -To uninstall: +You can run the install script with `--dry-run` to see the commands that will be used +to install without executing them. + +Or if you have Go installed you can install from source though you won't get the manpage: + +```sh +go install oss.terrastruct.com/d2@latest +``` + +To uninstall with the install script: ```sh curl -fsSL https://d2lang.com/install.sh | sh -s -- --uninstall ``` -For detailed installation docs, with alternative methods and examples for each OS, see -[./docs/INSTALL.md](./docs/INSTALL.md). +For detailed installation docs, see [./docs/INSTALL.md](./docs/INSTALL.md). +We demonstrate alternative methods and examples for each OS. + +As well, the functioning of the install script is described in detail to alleviate any +concern of its use. We recommend using your OS's package manager directly instead for +improved security but the install script is by no means insecure. ## D2 as a library In addition to being a runnable CLI tool, D2 can also be used to produce diagrams from Go programs. -```go -import ( - "context" - "io/ioutil" - "path/filepath" - "strings" - - "oss.terrastruct.com/d2/d2compiler" - "oss.terrastruct.com/d2/d2exporter" - "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" - "oss.terrastruct.com/d2/d2renderers/d2svg" - "oss.terrastruct.com/d2/d2renderers/textmeasure" - "oss.terrastruct.com/d2/d2themes/d2themescatalog" -) - -func main() { - graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), &d2compiler.CompileOptions{UTF16: true}) - ruler, _ := textmeasure.NewRuler() - graph.SetDimensions(nil, ruler) - d2dagrelayout.Layout(context.Background(), graph) - diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID) - out, _ := d2svg.Render(diagram) - ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) -} -``` - -D2 is built to be hackable -- the language has an API built on top of it to make edits -programmatically. Modifying the above diagram: - -```go -import ( - "oss.terrastruct.com/d2/d2renderers/textmeasure" - "oss.terrastruct.com/d2/d2themes/d2themescatalog" -) - -// Create a shape with the ID, "meow" -graph, _, _ = d2oracle.Create(graph, "meow") -// Style the shape green -color := "green" -graph, _ = d2oracle.Set(graph, "meow.style.fill", nil, &color) -// Create a shape with the ID, "cat" -graph, _, _ = d2oracle.Create(graph, "cat") -// Move the shape "meow" inside the container "cat" -graph, _ = d2oracle.Move(graph, "meow", "cat.meow") -// Prints formatted D2 script -println(d2format.Format(graph.AST)) -``` - -This makes it easy to build functionality on top of D2. Terrastruct uses the above API to -implement editing of D2 from mouse actions in a visual interface. +For examples, see [./docs/examples/lib](./docs/examples/lib). ## Themes diff --git a/ci/dev.sh b/ci/dev.sh new file mode 100755 index 000000000..cbc71bf79 --- /dev/null +++ b/ci/dev.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/.." +. ./ci/sub/lib.sh + +sh_c go build --tags=dev -o=bin/d2 . +sh_c ./bin/d2 "$@" diff --git a/ci/fmt.sh b/ci/fmt.sh new file mode 100755 index 000000000..4fb39da34 --- /dev/null +++ b/ci/fmt.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu +. "$(dirname "$0")/sub/lib.sh" +cd -- "$(dirname "$0")/.." + +if is_changed README.md; then + sh_c tocsubst --skip 1 README.md +fi +if is_changed docs/INSTALL.md; then + sh_c tocsubst --skip 1 docs/INSTALL.md +fi +./ci/sub/fmt/make.sh diff --git a/ci/release/_build.sh b/ci/release/_build.sh index c5170219a..4abfd6dec 100755 --- a/ci/release/_build.sh +++ b/ci/release/_build.sh @@ -14,7 +14,7 @@ export GOOS=$(goos "$OS") export GOARCH="$ARCH" sh_c mkdir -p "$HW_BUILD_DIR/bin" sh_c go build -ldflags "'-X oss.terrastruct.com/d2/lib/version.Version=$VERSION'" \ - -o "$HW_BUILD_DIR/bin/d2" ./cmd/d2 + -o "$HW_BUILD_DIR/bin/d2" . ARCHIVE=$PWD/$ARCHIVE cd "$(dirname "$HW_BUILD_DIR")" diff --git a/ci/release/_install.sh b/ci/release/_install.sh index a66f2d730..510a9c52d 100755 --- a/ci/release/_install.sh +++ b/ci/release/_install.sh @@ -91,6 +91,9 @@ note: Deleting the unarchived releases will cause --uninstall to stop working. You can rerun install.sh to update your version of D2. install.sh will avoid reinstalling if the installed version is the latest unless --force is passed. + +See https://github.com/terrastruct/d2/blob/master/docs/INSTALL.md#security for +documentation on its security. EOF } @@ -450,13 +453,10 @@ uninstall_tala_brew() { } is_prefix_writable() { - sh_c "mkdir -p '$INSTALL_DIR' 2>/dev/null" || true # The reason for checking whether $INSTALL_DIR is writable is that on macOS you have # /usr/local owned by root but you don't need root to write to its subdirectories which # is all we want to do. - if [ ! -w "$INSTALL_DIR" ]; then - return 1 - fi + is_writable_dir "$INSTALL_DIR" } cache_dir() { @@ -509,4 +509,7 @@ brew() { HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_AUTO_UPDATE=1 command brew "$@" } +# The main function does more than provide organization. It provides robustness in that if +# the install script was to only partial download into sh, sh will not execute it because +# main is not invoked until the very last byte. main "$@" diff --git a/ci/release/build.sh b/ci/release/build.sh index ce2e4eaf4..e42133c24 100755 --- a/ci/release/build.sh +++ b/ci/release/build.sh @@ -6,6 +6,7 @@ cd -- "$(dirname "$0")/../.." help() { cat </d2---.tar.gz @@ -38,6 +39,12 @@ Flags: --lockfile-force Forcefully take ownership of remote builder lockfiles. + +--install + Ensure a release using --host-only and install it. + +--uninstall + Ensure a release using --host-only and uninstall it. EOF } @@ -77,6 +84,18 @@ main() { flag_noarg && shift "$FLAGSHIFT" LOCKFILE_FORCE=1 ;; + install) + flag_noarg && shift "$FLAGSHIFT" + INSTALL=1 + HOST_ONLY=1 + LOCAL=1 + ;; + uninstall) + flag_noarg && shift "$FLAGSHIFT" + UNINSTALL=1 + HOST_ONLY=1 + LOCAL=1 + ;; *) flag_errusage "unrecognized flag $FLAGRAW" ;; @@ -90,8 +109,12 @@ main() { VERSION=${VERSION:-$(git_describe_ref)} BUILD_DIR=ci/release/build/$VERSION if [ -n "${HOST_ONLY-}" ]; then - runjob $(os)-$(arch) "OS=$(os) ARCH=$(arch) build" & - waitjobs + runjob $(os)-$(arch) "OS=$(os) ARCH=$(arch) build" + if [ -n "${INSTALL-}" ]; then + ( sh_c make -sC "ci/release/build/$VERSION/$(os)-$(arch)/d2-$VERSION" install) + elif [ -n "${UNINSTALL-}" ]; then + ( sh_c make -sC "ci/release/build/$VERSION/$(os)-$(arch)/d2-$VERSION" uninstall) + fi return 0 fi diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 9705e0993..6446165c6 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,7 @@ -#### Features ๐Ÿš€ +#### Features ๐Ÿ’ธ +- Formatting of d2 scripts is supported on the CLI with the `fmt` subcommand. + [#292](https://github.com/terrastruct/d2/pull/292) - Latex is now supported. See [docs](https://d2lang.com/tour/text) for more. [#229](https://github.com/terrastruct/d2/pull/229) - `direction` keyword is now supported to specify `up`, `down`, `right`, `left` layouts. See @@ -12,8 +14,10 @@ - Querying shapes and connections by ID is now supported in renders. [#218](https://github.com/terrastruct/d2/pull/218) - [install.sh](./install.sh) now accepts `-d` as an alias for `--dry-run`. [#266](https://github.com/terrastruct/d2/pull/266) +- `-b/--bundle` flag to `d2` now works and bundles all image assets directly as base64 + data urls. [#278](https://github.com/terrastruct/d2/pull/278) -#### Improvements ๐Ÿ”ง +#### Improvements ๐Ÿงน - Local images can now be included, e.g. `icon: ./my_img.png`. [#146](https://github.com/terrastruct/d2/issues/146) @@ -21,8 +25,14 @@ [#251](https://github.com/terrastruct/d2/pull/251) - [install.sh](./install.sh) prints the dry run message more visibly. [#266](https://github.com/terrastruct/d2/pull/266) +- `d2` now lives in the root folder of the repository instead of as a subcommand. + So you can run `go install oss.terrastruct.com/d2@latest` to install from source + now. + [#290](https://github.com/terrastruct/d2/pull/290) +- `BROWSER=0` now works to disable opening a browser on `--watch`. + [#311](https://github.com/terrastruct/d2/pull/311) -#### Bugfixes ๐Ÿ”ด +#### Bugfixes โ›‘๏ธ - 3D style was missing border and other styles for its top and right faces. [#187](https://github.com/terrastruct/d2/pull/187) @@ -30,5 +40,10 @@ [#159](https://github.com/terrastruct/d2/issues/159) - Fixes markdown newlines created with a trailing double space or backslash. [#214](https://github.com/terrastruct/d2/pull/214) -- Fixes images not loading in PNG exports +- Fixes images not loading in PNG exports. [#224](https://github.com/terrastruct/d2/pull/224) +- Avoid logging benign file watching errors. + [#293](https://github.com/terrastruct/d2/pull/293) +- `$BROWSER` now works to open a custom browser correctly. + For example, to open Firefox on macOS: `BROWSER='open -aFirefox'` + [#311](https://github.com/terrastruct/d2/pull/311) diff --git a/ci/release/changelogs/template.md b/ci/release/changelogs/template.md index dd3f64b05..131061b70 100644 --- a/ci/release/changelogs/template.md +++ b/ci/release/changelogs/template.md @@ -1,5 +1,5 @@ -#### Features ๐Ÿš€ +#### Features ๐Ÿ’ธ -#### Improvements ๐Ÿ”ง +#### Improvements ๐Ÿงน -#### Bugfixes ๐Ÿ”ด +#### Bugfixes โ›‘๏ธ diff --git a/ci/release/gen_template_lib.sh b/ci/release/gen_template_lib.sh index 1f523767a..27e63ac9e 100755 --- a/ci/release/gen_template_lib.sh +++ b/ci/release/gen_template_lib.sh @@ -14,6 +14,7 @@ sh_c cat >./ci/release/template/scripts/lib.sh <\>./ci/release/template/scripts/lib.sh sh_c chmod -w ./ci/release/template/scripts/lib.sh diff --git a/ci/release/template/Makefile b/ci/release/template/Makefile index 5b9ba1353..44f22ddb0 100644 --- a/ci/release/template/Makefile +++ b/ci/release/template/Makefile @@ -1,6 +1,11 @@ .POSIX: .SILENT: +.PHONY: all +all: + (. ./scripts/lib.sh && echoerr "You must provide a target of install or uninstall for this Makefile") + exit 1 + PREFIX = $(DESTDIR)/usr/local .PHONY: install diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index 8cf1ce5e2..275eca9da 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -9,16 +9,11 @@ .Op Fl -watch Ar false .Op Fl -theme Em 0 .Ar file.d2 -.Op Ar file.svg -| -.Op Ar file.png -.Nm d2 -.Op Fl -watch Ar false -.Op Fl -theme Em 0 -.Ar file.d2 -.Op Ar ... +.Op Ar file.svg | file.png .Nm d2 .Ar layout Op Ar name +.Nm d2 +.Ar fmt Ar file.d2 .Sh DESCRIPTION .Nm compiles and renders @@ -29,10 +24,21 @@ to .Ar file.png .Ns . .Pp +It defaults to +.Ar file.svg +if no output path is passed. +.Pp Pass - to have .Nm read from stdin or write to stdout. .Pp +Never use the presence of the output file to check for success. +Always use the exit status of +.Nm d2 +.Ns . +This is because sometimes when errors occur while rendering, d2 still write out a partial +render anyway to enable iteration on a broken diagram. +.Pp See more docs, the source code and license at .Lk https://oss.terrastruct.com/d2 .Sh OPTIONS @@ -71,6 +77,10 @@ Print version information and exit. Lists available layout engine options with short help. .It Ar layout Op Ar name Display long help for a particular layout engine. +.It Ar fmt Ar file.d2 +Format +.Ar file.d2 +.Ns . .El .Sh SEE ALSO .Xr d2plugin-tala 1 diff --git a/ci/release/template/scripts/install.sh b/ci/release/template/scripts/install.sh index 043e5d098..4127f25a8 100755 --- a/ci/release/template/scripts/install.sh +++ b/ci/release/template/scripts/install.sh @@ -9,10 +9,15 @@ main() { return 1 fi - sh_c mkdir -p "$PREFIX/bin" - sh_c install ./bin/d2 "$PREFIX/bin/d2" - sh_c mkdir -p "$PREFIX/share/man/man1" - sh_c install ./man/d2.1 "$PREFIX/share/man/man1" + sh_c="sh_c" + if ! is_writable_dir "$PREFIX/bin"; then + sh_c="sudo_sh_c" + fi + + "$sh_c" mkdir -p "$PREFIX/bin" + "$sh_c" install ./bin/d2 "$PREFIX/bin/d2" + "$sh_c" mkdir -p "$PREFIX/share/man/man1" + "$sh_c" install ./man/d2.1 "$PREFIX/share/man/man1" } main "$@" diff --git a/ci/release/template/scripts/lib.sh b/ci/release/template/scripts/lib.sh index 3dc704bb0..cf17657e7 100644 --- a/ci/release/template/scripts/lib.sh +++ b/ci/release/template/scripts/lib.sh @@ -7,6 +7,7 @@ # # - ./ci/sub/lib/rand.sh # - ./ci/sub/lib/log.sh +# - ./ci/sub/lib/release.sh # # Generated by ./ci/release/gen_template_lib.sh. # ************* @@ -55,22 +56,26 @@ tput() { should_color() { if [ -n "${COLOR-}" ]; then - if [ "$COLOR" = 0 -o "$COLOR" = false ]; then - _COLOR= - return 1 - elif [ "$COLOR" = 1 -o "$COLOR" = true ]; then + if [ "$COLOR" = 1 -o "$COLOR" = true ]; then _COLOR=1 + __COLOR=1 return 0 + elif [ "$COLOR" = 0 -o "$COLOR" = false ]; then + _COLOR= + __COLOR=0 + return 1 else printf '$COLOR must be 0, 1, false or true but got %s\n' "$COLOR" >&2 fi fi - if [ -t 1 ]; then + if [ -t 1 -a "${TERM-}" != dumb ]; then _COLOR=1 + __COLOR=1 return 0 else _COLOR= + __COLOR=0 return 1 fi } @@ -89,7 +94,8 @@ _echo() { get_rand_color() { # 1-6 are regular and 9-14 are bright. # 1,2 and 9,10 are red and green but we use those for success and failure. - pick "$*" 3 4 5 6 11 12 13 14 + pick "$*" 1 2 3 4 5 6 \ + 9 10 11 12 13 14 } echop() { @@ -113,9 +119,9 @@ printfp() {( fi should_color || true if [ $# -eq 0 ]; then - printf '%s' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")" + printf '%s' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" else - printf '%s: %s\n' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")" "$(printf "$@")" + printf '%s: %s\n' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" "$(printf "$@")" fi )} @@ -124,7 +130,7 @@ catp() { shift should_color || true - sed "s/^/$(COLOR=${_COLOR-} printfp "$prefix" '')/" + sed "s/^/$(COLOR=$__COLOR printfp "$prefix" '')/" } repeat() { @@ -151,17 +157,17 @@ printferr() { logp() { should_color >&2 || true - COLOR=${_COLOR-} echop "$@" | humanpath >&2 + COLOR=$__COLOR echop "$@" | humanpath >&2 } logfp() { should_color >&2 || true - COLOR=${_COLOR-} printfp "$@" | humanpath >&2 + COLOR=$__COLOR printfp "$@" | humanpath >&2 } logpcat() { should_color >&2 || true - COLOR=${_COLOR-} catp "$@" | humanpath >&2 + COLOR=$__COLOR catp "$@" | humanpath >&2 } log() { @@ -286,3 +292,63 @@ runtty() { return 1 esac } + +capcode() { + set +e + "$@" + code=$? + set -e +} +#!/bin/sh +if [ "${LIB_RELEASE-}" ]; then + return 0 +fi +LIB_RELEASE=1 + +goos() { + case $1 in + macos) echo darwin ;; + *) echo $1 ;; + esac +} + +os() { + uname=$(uname) + case $uname in + Linux) echo linux ;; + Darwin) echo macos ;; + FreeBSD) echo freebsd ;; + *) echo "$uname" ;; + esac +} + +arch() { + uname_m=$(uname -m) + case $uname_m in + aarch64) echo arm64 ;; + x86_64) echo amd64 ;; + *) echo "$uname_m" ;; + esac +} + +gh_repo() { + gh repo view --json nameWithOwner --template '{{ .nameWithOwner }}' +} + +manpath() { + if command -v manpath >/dev/null; then + command manpath + elif man -w 2>/dev/null; then + man -w + else + echo "${MANPATH-}" + fi +} + +is_writable_dir() { + # The path has to exist for -w to succeed. + sh_c "mkdir -p '$1' 2>/dev/null" || true + if [ ! -w "$1" ]; then + return 1 + fi +} diff --git a/ci/release/template/scripts/uninstall.sh b/ci/release/template/scripts/uninstall.sh index e21877ca9..56336e8d1 100755 --- a/ci/release/template/scripts/uninstall.sh +++ b/ci/release/template/scripts/uninstall.sh @@ -9,8 +9,13 @@ main() { return 1 fi - sh_c rm -f "$PREFIX/bin/d2" - sh_c rm -f "$PREFIX/share/man/man1/d2.1" + sh_c="sh_c" + if ! is_writable_dir "$PREFIX/bin"; then + sh_c="sudo_sh_c" + fi + + "$sh_c" rm -f "$PREFIX/bin/d2" + "$sh_c" rm -f "$PREFIX/share/man/man1/d2.1" } main "$@" diff --git a/ci/sub b/ci/sub index 28fb67e3b..70a9ad95e 160000 --- a/ci/sub +++ b/ci/sub @@ -1 +1 @@ -Subproject commit 28fb67e3bf11d7df2be9ad57b67b78a1733a7f2d +Subproject commit 70a9ad95ea0ae1de83fa3b7f7d4a160db4853c20 diff --git a/cmd/d2plugin-dagre/main.go b/cmd/d2plugin-dagre/main.go index 20cb9af5a..a2350dc4e 100644 --- a/cmd/d2plugin-dagre/main.go +++ b/cmd/d2plugin-dagre/main.go @@ -3,8 +3,9 @@ package main import ( + "oss.terrastruct.com/util-go/xmain" + "oss.terrastruct.com/d2/d2plugin" - "oss.terrastruct.com/d2/lib/xmain" ) func main() { diff --git a/d2ast/d2ast.go b/d2ast/d2ast.go index fa2cefffa..2ffb448f1 100644 --- a/d2ast/d2ast.go +++ b/d2ast/d2ast.go @@ -32,7 +32,7 @@ import ( "unicode/utf16" "unicode/utf8" - "oss.terrastruct.com/xdefer" + "oss.terrastruct.com/util-go/xdefer" ) // Node is the base interface implemented by all d2 AST nodes. diff --git a/d2ast/d2ast_test.go b/d2ast/d2ast_test.go index 883a68018..b477d7239 100644 --- a/d2ast/d2ast_test.go +++ b/d2ast/d2ast_test.go @@ -9,14 +9,16 @@ import ( "strings" "testing" - "oss.terrastruct.com/xrand" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/xrand" - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/diff" + + "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2parser" - "oss.terrastruct.com/d2/lib/go2" ) func TestRange(t *testing.T) { @@ -193,18 +195,18 @@ func TestRange(t *testing.T) { var p d2ast.Position p = p.Advance('a', false) - diff.AssertJSONEq(t, `"0:1:1"`, p) + assert.StringJSON(t, `"0:1:1"`, p) p = p.Advance('\n', false) - diff.AssertJSONEq(t, `"1:0:2"`, p) + assert.StringJSON(t, `"1:0:2"`, p) p = p.Advance('รจ', false) - diff.AssertJSONEq(t, `"1:2:4"`, p) + assert.StringJSON(t, `"1:2:4"`, p) p = p.Advance('๐€€', false) - diff.AssertJSONEq(t, `"1:6:8"`, p) + assert.StringJSON(t, `"1:6:8"`, p) p = p.Subtract('๐€€', false) - diff.AssertJSONEq(t, `"1:2:4"`, p) + assert.StringJSON(t, `"1:2:4"`, p) p = p.Subtract('รจ', false) - diff.AssertJSONEq(t, `"1:0:2"`, p) + assert.StringJSON(t, `"1:0:2"`, p) }) t.Run("UTF-16", func(t *testing.T) { @@ -212,18 +214,18 @@ func TestRange(t *testing.T) { var p d2ast.Position p = p.Advance('a', true) - diff.AssertJSONEq(t, `"0:1:1"`, p) + assert.StringJSON(t, `"0:1:1"`, p) p = p.Advance('\n', true) - diff.AssertJSONEq(t, `"1:0:2"`, p) + assert.StringJSON(t, `"1:0:2"`, p) p = p.Advance('รจ', true) - diff.AssertJSONEq(t, `"1:1:3"`, p) + assert.StringJSON(t, `"1:1:3"`, p) p = p.Advance('๐€€', true) - diff.AssertJSONEq(t, `"1:3:5"`, p) + assert.StringJSON(t, `"1:3:5"`, p) p = p.Subtract('๐€€', true) - diff.AssertJSONEq(t, `"1:1:3"`, p) + assert.StringJSON(t, `"1:1:3"`, p) p = p.Subtract('รจ', true) - diff.AssertJSONEq(t, `"1:0:2"`, p) + assert.StringJSON(t, `"1:0:2"`, p) }) }) } @@ -411,7 +413,7 @@ name to "America". }, } - diff.AssertJSONEq(t, `{ + assert.StringJSON(t, `{ "range": "json_test.d2,0:0:0-5:1:50", "nodes": [ { @@ -807,7 +809,7 @@ _park `, t.Parallel() ast := d2ast.RawString(tc.str, tc.inKey) - diff.AssertStringEq(t, tc.exp, d2format.Format(ast)) + assert.String(t, tc.exp, d2format.Format(ast)) }) } } diff --git a/d2chaos/d2chaos.go b/d2chaos/d2chaos.go index 56b8c40b4..8b6c74f5e 100644 --- a/d2chaos/d2chaos.go +++ b/d2chaos/d2chaos.go @@ -6,12 +6,13 @@ import ( "strings" "time" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2oracle" "oss.terrastruct.com/d2/d2target" - "oss.terrastruct.com/d2/lib/go2" ) func GenDSL(maxi int) (_ string, err error) { diff --git a/d2chaos/d2chaos_test.go b/d2chaos/d2chaos_test.go index a73af4d8d..ddbfa9dc2 100644 --- a/d2chaos/d2chaos_test.go +++ b/d2chaos/d2chaos_test.go @@ -17,8 +17,8 @@ import ( "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" - "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/lib/log" + "oss.terrastruct.com/d2/lib/textmeasure" ) // usage: D2_CHAOS_MAXI=100 D2_CHAOS_N=100 ./ci/test.sh ./d2chaos diff --git a/d2compiler/compile.go b/d2compiler/compile.go index b5de7915b..775ced5ec 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -8,12 +8,13 @@ import ( "strconv" "strings" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2target" - "oss.terrastruct.com/d2/lib/go2" ) // TODO: should Parse even be exported? guess not. IR should contain list of files and diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index ea9311d2a..b6c5eba12 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -6,9 +6,8 @@ import ( "strings" "testing" - "oss.terrastruct.com/diff" - - "github.com/stretchr/testify/assert" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2format" @@ -775,8 +774,8 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) - assert.Empty(t, g.Edges[0].Attributes.Shape.Value) + assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) + assert.String(t, "", g.Edges[0].Attributes.Shape.Value) // Make sure the DSL didn't change. this is a regression test where it did exp := `x -> y: { source-arrowhead: { @@ -814,13 +813,13 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) - diff.AssertStringEq(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value) - diff.AssertStringEq(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value) - diff.AssertStringEq(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value) - assert.Empty(t, g.Edges[0].Attributes.Shape.Value) - assert.Empty(t, g.Edges[0].Attributes.Label.Value) - assert.Nil(t, g.Edges[0].Attributes.Style.Filled) + assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) + assert.String(t, "Reisner's Rule of Conceptual Inertia", g.Edges[0].SrcArrowhead.Label.Value) + assert.String(t, "QOTD", g.Edges[0].DstArrowhead.Label.Value) + assert.String(t, "true", g.Edges[0].DstArrowhead.Style.Filled.Value) + assert.String(t, "", g.Edges[0].Attributes.Shape.Value) + assert.String(t, "", g.Edges[0].Attributes.Label.Value) + assert.JSON(t, nil, g.Edges[0].Attributes.Style.Filled) }, }, { @@ -836,8 +835,8 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) - assert.Empty(t, g.Edges[0].Attributes.Shape.Value) + assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) + assert.String(t, "", g.Edges[0].Attributes.Shape.Value) }, }, { @@ -853,8 +852,8 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value) - assert.Empty(t, g.Edges[0].Attributes.Shape.Value) + assert.String(t, "triangle", g.Edges[0].SrcArrowhead.Shape.Value) + assert.String(t, "", g.Edges[0].Attributes.Shape.Value) }, }, { @@ -880,8 +879,8 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "yo", g.Edges[0].SrcArrowhead.Label.Value) - assert.Empty(t, g.Edges[0].Attributes.Label.Value) + assert.String(t, "yo", g.Edges[0].SrcArrowhead.Label.Value) + assert.String(t, "", g.Edges[0].Attributes.Label.Value) }, }, { @@ -899,8 +898,8 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) - assert.Empty(t, g.Edges[0].Attributes.Shape.Value) + assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) + assert.String(t, "", g.Edges[0].Attributes.Shape.Value) }, }, { @@ -920,9 +919,9 @@ x -> y: { if len(g.Objects) != 2 { t.Fatalf("expected 2 objects: %#v", g.Objects) } - diff.AssertStringEq(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) - diff.AssertStringEq(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value) - assert.Empty(t, g.Edges[0].Attributes.Shape.Value) + assert.String(t, "diamond", g.Edges[0].SrcArrowhead.Shape.Value) + assert.String(t, "diamond", g.Edges[0].DstArrowhead.Shape.Value) + assert.String(t, "", g.Edges[0].Attributes.Shape.Value) }, }, { @@ -1333,7 +1332,7 @@ y -> x.style if len(g.Objects) != 1 { t.Fatal(g.Objects) } - diff.AssertStringEq(t, `b + assert.String(t, `b b`, g.Objects[0].Attributes.Label.Value) }, }, @@ -1420,9 +1419,9 @@ b`, g.Objects[0].Attributes.Label.Value) if len(g.Objects) != 1 { t.Fatal(g.Objects) } - diff.AssertStringEq(t, `field here`, g.Objects[0].Class.Fields[0].Name) - diff.AssertStringEq(t, `GetType()`, g.Objects[0].Class.Methods[0].Name) - diff.AssertStringEq(t, `Is()`, g.Objects[0].Class.Methods[1].Name) + assert.String(t, `field here`, g.Objects[0].Class.Fields[0].Name) + assert.String(t, `GetType()`, g.Objects[0].Class.Methods[0].Name) + assert.String(t, `Is()`, g.Objects[0].Class.Methods[1].Name) }, }, { @@ -1438,8 +1437,8 @@ b`, g.Objects[0].Attributes.Label.Value) if len(g.Objects) != 1 { t.Fatal(g.Objects) } - diff.AssertStringEq(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name) - diff.AssertStringEq(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name) + assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name) + assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name) }, }, { @@ -1463,8 +1462,8 @@ b`, g.Objects[0].Attributes.Label.Value) if len(g.Objects[0].ChildrenArray) != 1 { t.Fatal(g.Objects) } - diff.AssertStringEq(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name) - diff.AssertStringEq(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name) + assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name) + assert.String(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name) }, }, { @@ -1509,7 +1508,7 @@ dst.id <-> src.dst_id } `, assertions: func(t *testing.T, g *d2graph.Graph) { - diff.AssertStringEq(t, "sequence_diagram", g.Objects[0].Attributes.Shape.Value) + assert.String(t, "sequence_diagram", g.Objects[0].Attributes.Shape.Value) }, }, { @@ -1518,7 +1517,7 @@ dst.id <-> src.dst_id text: `shape: sequence_diagram `, assertions: func(t *testing.T, g *d2graph.Graph) { - diff.AssertStringEq(t, "sequence_diagram", g.Root.Attributes.Shape.Value) + assert.String(t, "sequence_diagram", g.Root.Attributes.Shape.Value) }, }, { @@ -1526,7 +1525,7 @@ dst.id <-> src.dst_id text: `direction: right`, assertions: func(t *testing.T, g *d2graph.Graph) { - diff.AssertStringEq(t, "right", g.Root.Attributes.Direction.Value) + assert.String(t, "right", g.Root.Attributes.Direction.Value) }, }, { @@ -1534,7 +1533,7 @@ dst.id <-> src.dst_id text: `x`, assertions: func(t *testing.T, g *d2graph.Graph) { - diff.AssertStringEq(t, "", g.Objects[0].Attributes.Direction.Value) + assert.String(t, "", g.Objects[0].Attributes.Direction.Value) }, }, { @@ -1544,7 +1543,7 @@ dst.id <-> src.dst_id direction: left }`, assertions: func(t *testing.T, g *d2graph.Graph) { - diff.AssertStringEq(t, "left", g.Objects[0].Attributes.Direction.Value) + assert.String(t, "left", g.Objects[0].Attributes.Direction.Value) }, }, { @@ -1594,10 +1593,8 @@ dst.id <-> src.dst_id Err: err, } - err = diff.Testdata(filepath.Join("..", "testdata", "d2compiler", t.Name()), got) - if err != nil { - t.Fatal(err) - } + err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2compiler", t.Name()), got) + assert.Success(t, err) }) } } diff --git a/d2exporter/export_test.go b/d2exporter/export_test.go index 1d63d0604..1c08fbe1d 100644 --- a/d2exporter/export_test.go +++ b/d2exporter/export_test.go @@ -8,18 +8,17 @@ import ( "cdr.dev/slog" - "oss.terrastruct.com/diff" - - "github.com/stretchr/testify/assert" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" - "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2themes/d2themescatalog" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/log" + "oss.terrastruct.com/d2/lib/textmeasure" ) type testCase struct { @@ -215,10 +214,10 @@ func run(t *testing.T, tc testCase) { } ruler, err := textmeasure.NewRuler() - assert.Nil(t, err) + assert.JSON(t, nil, err) err = g.SetDimensions(nil, ruler) - assert.Nil(t, err) + assert.JSON(t, nil, err) err = d2dagrelayout.Layout(ctx, g) if err != nil { @@ -252,8 +251,6 @@ func run(t *testing.T, tc testCase) { got.Connections[i].LabelPosition = "" } - err = diff.Testdata(filepath.Join("..", "testdata", "d2exporter", t.Name()), got) - if err != nil { - t.Fatal(err) - } + err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got) + assert.Success(t, err) } diff --git a/d2format/escape_test.go b/d2format/escape_test.go index 2d50d6e4a..c0090c03f 100644 --- a/d2format/escape_test.go +++ b/d2format/escape_test.go @@ -3,7 +3,7 @@ package d2format_test import ( "testing" - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2format" @@ -42,7 +42,7 @@ func TestEscapeSingleQuoted(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - diff.AssertStringEq(t, tc.exp, d2format.Format(&d2ast.SingleQuotedString{ + assert.String(t, tc.exp, d2format.Format(&d2ast.SingleQuotedString{ Value: tc.str, })) }) @@ -104,7 +104,7 @@ func TestEscapeDoubleQuoted(t *testing.T) { } else { n = d2ast.FlatDoubleQuotedString(tc.str) } - diff.AssertStringEq(t, tc.exp, d2format.Format(n)) + assert.String(t, tc.exp, d2format.Format(n)) }) } } @@ -203,7 +203,7 @@ func TestEscapeUnquoted(t *testing.T) { n = d2ast.FlatUnquotedString(tc.str) } - diff.AssertStringEq(t, tc.exp, d2format.Format(n)) + assert.String(t, tc.exp, d2format.Format(n)) }) } } @@ -286,7 +286,7 @@ func TestEscapeBlockString(t *testing.T) { Value: tc.value, } - diff.AssertStringEq(t, tc.exp, d2format.Format(n)) + assert.String(t, tc.exp, d2format.Format(n)) }) } } diff --git a/d2format/format_test.go b/d2format/format_test.go index 224154ca3..4bfea1519 100644 --- a/d2format/format_test.go +++ b/d2format/format_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2parser" @@ -605,7 +605,7 @@ hi # Fraud is the homage that force pays to reason. if err != nil { t.Fatal(err) } - diff.AssertStringEq(t, tc.exp, d2format.Format(ast)) + assert.String(t, tc.exp, d2format.Format(ast)) }) } } @@ -621,6 +621,6 @@ func TestEdge(t *testing.T) { t.Fatalf("expected one edge: %#v", mk.Edges) } - diff.AssertStringEq(t, `x -> y`, d2format.Format(mk.Edges[0])) - diff.AssertStringEq(t, `[0]`, d2format.Format(mk.EdgeIndex)) + assert.String(t, `x -> y`, d2format.Format(mk.Edges[0])) + assert.String(t, `[0]`, d2format.Format(mk.EdgeIndex)) } diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 5e3f5e566..fa5dc3bc6 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -7,16 +7,17 @@ import ( "strconv" "strings" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2renderers/d2latex" - "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/lib/geo" - "oss.terrastruct.com/d2/lib/go2" + "oss.terrastruct.com/d2/lib/textmeasure" ) // TODO: Refactor with a light abstract layer on top of AST implementing scenarios, diff --git a/d2graph/d2graph_test.go b/d2graph/d2graph_test.go index d207ee89c..b705cb7b4 100644 --- a/d2graph/d2graph_test.go +++ b/d2graph/d2graph_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2parser" @@ -44,7 +44,7 @@ func TestKey(t *testing.T) { if err != nil { t.Fatal(err) } - diff.AssertStringEq(t, tc.exp, strings.Join(d2graph.Key(k), ".")) + assert.String(t, tc.exp, strings.Join(d2graph.Key(k), ".")) }) } } diff --git a/d2graph/serde.go b/d2graph/serde.go index bfaac7c80..e5a8b1fbc 100644 --- a/d2graph/serde.go +++ b/d2graph/serde.go @@ -3,7 +3,7 @@ package d2graph import ( "encoding/json" - "oss.terrastruct.com/d2/lib/go2" + "oss.terrastruct.com/util-go/go2" ) type SerializedGraph struct { diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 672871226..0162820cb 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -11,12 +11,13 @@ import ( "cdr.dev/slog" v8 "rogchap.com/v8go" - "oss.terrastruct.com/xdefer" + "oss.terrastruct.com/util-go/xdefer" + + "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" - "oss.terrastruct.com/d2/lib/go2" "oss.terrastruct.com/d2/lib/label" "oss.terrastruct.com/d2/lib/log" "oss.terrastruct.com/d2/lib/shape" diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 2df4c65be..e88f01d74 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -13,12 +13,13 @@ import ( "rogchap.com/v8go" - "oss.terrastruct.com/xdefer" + "oss.terrastruct.com/util-go/xdefer" + + "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" - "oss.terrastruct.com/d2/lib/go2" "oss.terrastruct.com/d2/lib/label" ) diff --git a/c.go b/d2lib/c.go similarity index 90% rename from c.go rename to d2lib/c.go index 1fa3fc225..2774d943e 100644 --- a/c.go +++ b/d2lib/c.go @@ -1,6 +1,6 @@ //go:build cgo -package d2 +package d2lib import "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" diff --git a/d2.go b/d2lib/d2.go similarity index 86% rename from d2.go rename to d2lib/d2.go index afa8085fa..54883b47d 100644 --- a/d2.go +++ b/d2lib/d2.go @@ -1,4 +1,4 @@ -package d2 +package d2lib import ( "context" @@ -9,9 +9,8 @@ import ( "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2graph" - "oss.terrastruct.com/d2/d2layouts/d2sequence" - "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/textmeasure" ) type CompileOptions struct { @@ -23,7 +22,7 @@ type CompileOptions struct { ThemeID int64 } -func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, error) { +func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) { if opts == nil { opts = &CompileOptions{} } @@ -32,12 +31,12 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target UTF16: opts.UTF16, }) if err != nil { - return nil, err + return nil, nil, err } err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler) if err != nil { - return nil, err + return nil, nil, err } if layout, err := getLayout(opts); err != nil { @@ -47,7 +46,7 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target } diagram, err := d2exporter.Export(ctx, g, opts.ThemeID) - return diagram, err + return diagram, g, err } func getLayout(opts *CompileOptions) (func(context.Context, *d2graph.Graph) error, error) { diff --git a/d2oracle/edit.go b/d2oracle/edit.go index 5e943056e..b379df0d5 100644 --- a/d2oracle/edit.go +++ b/d2oracle/edit.go @@ -7,9 +7,11 @@ import ( "strings" "unicode" - "oss.terrastruct.com/xdefer" + "oss.terrastruct.com/util-go/xdefer" - "oss.terrastruct.com/xrand" + "oss.terrastruct.com/util-go/xrand" + + "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2compiler" @@ -17,7 +19,6 @@ import ( "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2target" - "oss.terrastruct.com/d2/lib/go2" ) func Create(g *d2graph.Graph, key string) (_ *d2graph.Graph, newKey string, err error) { diff --git a/d2oracle/edit_test.go b/d2oracle/edit_test.go index 84090a4e0..7feba4572 100644 --- a/d2oracle/edit_test.go +++ b/d2oracle/edit_test.go @@ -7,18 +7,16 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - - "oss.terrastruct.com/xjson" - - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/util-go/xjson" "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2oracle" "oss.terrastruct.com/d2/d2target" - "oss.terrastruct.com/d2/lib/go2" ) // TODO: make assertions less specific @@ -966,10 +964,10 @@ z: { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, 3, len(g.Objects)) - assert.Equal(t, 1, len(g.Edges)) - assert.Equal(t, "q", g.Edges[0].Src.ID) - assert.Equal(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value) + assert.JSON(t, 3, len(g.Objects)) + assert.JSON(t, 1, len(g.Edges)) + assert.JSON(t, "q", g.Edges[0].Src.ID) + assert.JSON(t, "0.4", g.Edges[0].Attributes.Style.Opacity.Value) }, }, { @@ -1617,8 +1615,8 @@ func TestMove(t *testing.T) { exp: `b `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 1) - assert.Equal(t, g.Objects[0].ID, "b") + assert.JSON(t, len(g.Objects), 1) + assert.JSON(t, g.Objects[0].ID, "b") }, }, { @@ -1636,8 +1634,8 @@ func TestMove(t *testing.T) { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 2) - assert.Equal(t, g.Objects[1].ID, "c") + assert.JSON(t, len(g.Objects), 2) + assert.JSON(t, g.Objects[1].ID, "c") }, }, { @@ -1692,9 +1690,9 @@ c } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 3) - assert.Equal(t, "a", g.Objects[0].ID) - assert.Equal(t, 2, len(g.Objects[0].Children)) + assert.JSON(t, len(g.Objects), 3) + assert.JSON(t, "a", g.Objects[0].ID) + assert.JSON(t, 2, len(g.Objects[0].Children)) }, }, { @@ -1733,9 +1731,9 @@ c } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 2) - assert.Equal(t, "a", g.Objects[0].ID) - assert.Equal(t, 1, len(g.Objects[0].Children)) + assert.JSON(t, len(g.Objects), 2) + assert.JSON(t, "a", g.Objects[0].ID) + assert.JSON(t, 1, len(g.Objects[0].Children)) }, }, { @@ -1752,9 +1750,9 @@ c b `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 2) - assert.Equal(t, "a", g.Objects[0].ID) - assert.Equal(t, 0, len(g.Objects[0].Children)) + assert.JSON(t, len(g.Objects), 2) + assert.JSON(t, "a", g.Objects[0].ID) + assert.JSON(t, 0, len(g.Objects[0].Children)) }, }, { @@ -1863,11 +1861,11 @@ c: { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 3) - assert.Equal(t, "a", g.Objects[0].ID) - assert.Equal(t, 0, len(g.Objects[0].Children)) - assert.Equal(t, "c", g.Objects[1].ID) - assert.Equal(t, 1, len(g.Objects[1].Children)) + assert.JSON(t, len(g.Objects), 3) + assert.JSON(t, "a", g.Objects[0].ID) + assert.JSON(t, 0, len(g.Objects[0].Children)) + assert.JSON(t, "c", g.Objects[1].ID) + assert.JSON(t, 1, len(g.Objects[1].Children)) }, }, { @@ -1929,7 +1927,7 @@ a: { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 3) + assert.JSON(t, len(g.Objects), 3) }, }, { @@ -1986,7 +1984,7 @@ c: { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 3) + assert.JSON(t, len(g.Objects), 3) }, }, { @@ -2004,7 +2002,7 @@ d: { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 4) + assert.JSON(t, len(g.Objects), 4) }, }, { @@ -2023,7 +2021,7 @@ c: { } `, assertions: func(t *testing.T, g *d2graph.Graph) { - assert.Equal(t, len(g.Objects), 4) + assert.JSON(t, len(g.Objects), 4) }, }, { @@ -4422,10 +4420,8 @@ func (tc editTest) run(t *testing.T) { Err: fmt.Sprintf("%#v", err), } - err = diff.Testdata(filepath.Join("..", "testdata", "d2oracle", t.Name()), got) - if err != nil { - t.Fatal(err) - } + err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2oracle", t.Name()), got) + assert.Success(t, err) } func TestMoveIDDeltas(t *testing.T) { @@ -4635,7 +4631,7 @@ x.a -> x.b t.Fatal(err) } - ds, err := diff.Strings(tc.exp, xjson.MarshalIndent(deltas)) + ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas))) if err != nil { t.Fatal(err) } @@ -4825,7 +4821,7 @@ x.y.z.w.e.p.l -> x.y.z.1.2.3.4 t.Fatal(err) } - ds, err := diff.Strings(tc.exp, xjson.MarshalIndent(deltas)) + ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas))) if err != nil { t.Fatal(err) } @@ -4977,7 +4973,7 @@ x.y.z.w.e.p.l -> x.y.z.1.2.3.4 t.Fatal(err) } - ds, err := diff.Strings(tc.exp, xjson.MarshalIndent(deltas)) + ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas))) if err != nil { t.Fatal(err) } diff --git a/d2parser/parse.go b/d2parser/parse.go index 3c99dc1eb..d8351dcbc 100644 --- a/d2parser/parse.go +++ b/d2parser/parse.go @@ -9,8 +9,9 @@ import ( "unicode" "unicode/utf8" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2ast" - "oss.terrastruct.com/d2/lib/go2" ) type ParseOptions struct { diff --git a/d2parser/parse_test.go b/d2parser/parse_test.go index 9c820e83e..61a02336d 100644 --- a/d2parser/parse_test.go +++ b/d2parser/parse_test.go @@ -6,7 +6,8 @@ import ( "strings" "testing" - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2parser" @@ -382,10 +383,8 @@ q.(x -> y).z: (rawr) Err: err, } - err = diff.Testdata(filepath.Join("..", "testdata", "d2parser", t.Name()), got) - if err != nil { - t.Fatal(err) - } + err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2parser", t.Name()), got) + assert.Success(t, err) }) } } diff --git a/d2plugin/exec.go b/d2plugin/exec.go index 467fcdc40..b4929dcc2 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -9,7 +9,7 @@ import ( "os/exec" "time" - "oss.terrastruct.com/xdefer" + "oss.terrastruct.com/util-go/xdefer" "oss.terrastruct.com/d2/d2graph" ) diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 0354b8355..56798c88f 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -9,8 +9,9 @@ import ( "context" "os/exec" + "oss.terrastruct.com/util-go/xexec" + "oss.terrastruct.com/d2/d2graph" - "oss.terrastruct.com/d2/lib/xexec" ) // plugins contains the bundled d2 plugins. diff --git a/d2plugin/serve.go b/d2plugin/serve.go index 2db42d215..e7ca429aa 100644 --- a/d2plugin/serve.go +++ b/d2plugin/serve.go @@ -7,8 +7,9 @@ import ( "fmt" "io" + "oss.terrastruct.com/util-go/xmain" + "oss.terrastruct.com/d2/d2graph" - "oss.terrastruct.com/d2/lib/xmain" ) // Serve returns a xmain.RunFunc that will invoke the plugin p as necessary to service the diff --git a/d2renderers/d2latex/latex.go b/d2renderers/d2latex/latex.go index 79330ad3a..43a3f9d71 100644 --- a/d2renderers/d2latex/latex.go +++ b/d2renderers/d2latex/latex.go @@ -9,7 +9,7 @@ import ( "regexp" "strconv" - "oss.terrastruct.com/xdefer" + "oss.terrastruct.com/util-go/xdefer" v8 "rogchap.com/v8go" ) diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index d2f41b9d7..692db2cca 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -20,15 +20,16 @@ import ( "github.com/alecthomas/chroma/lexers" "github.com/alecthomas/chroma/styles" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2renderers/d2latex" - "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/color" "oss.terrastruct.com/d2/lib/geo" - "oss.terrastruct.com/d2/lib/go2" "oss.terrastruct.com/d2/lib/label" "oss.terrastruct.com/d2/lib/shape" + "oss.terrastruct.com/d2/lib/textmeasure" ) const ( diff --git a/d2target/d2target.go b/d2target/d2target.go index 3f749fa87..9c2dd2c9b 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -5,9 +5,10 @@ import ( "net/url" "strings" + "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/lib/geo" - "oss.terrastruct.com/d2/lib/go2" "oss.terrastruct.com/d2/lib/label" "oss.terrastruct.com/d2/lib/shape" ) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 71d3fcd0a..97ec69d1b 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,15 +1,16 @@ # install -You may install D2 through any of the following methods. +You may install `d2` through any of the following methods. - -- [install.sh](#installsh) -- [macOS (Homebrew)](#macos-homebrew) -- [Standalone](#standalone) -- [From source](#from-source) - - +- install.sh + - Security +- macOS (Homebrew) +- Standalone + - Manual + - PREFIX +- From source +- Coming soon ## install.sh @@ -31,6 +32,36 @@ methods: curl -fsSL https://d2lang.com/install.sh | sh -s -- --help ``` +### Security + +The install script is not the most secure way to install d2. We recommend that if +possible, you use your OS's package manager directly or install from source with `go` as +described below. + +But this does not mean the install script is insecure. There is no major flaw that +the install script is more vulnerable to than any other method of manual installation. +The most secure installation method involves a second independent entity, i.e your OS +package repos or Go's proxy server. + +We're careful shell programmers and are aware of the many footguns of the Unix shell. Our +script was written carefully and with detail. For example, it is not vulnerable to partial +execution and the entire script runs with `set -eu` and very meticulous quoting. + +It follows the XDG standards, installs `d2` properly into a Unix hierarchy path (defaulting +to /usr/local though you can use ~/.local to avoid sudo if you'd like) and allows for easy +uninstall. + +Some other niceties are that it'll tell you if you need to adjust `$PATH` or `$MANPATH` to +access `d2` and its manpages. It can also install +[TALA](https://github.com/terrastruct/tala) for you with `--tala`. You can also use it to +install a specific version of `d2` with `--version`. Run it with `--help` for more more +detailed docs on its various options and features. + +If you're still concerned, remember you can run with `--dry-run` to avoid writing anything. + +The install script does not yet verify any signature on the downloaded release +but that is coming soon. [#315](https://github.com/terrastruct/d2/issues/315) + ## macOS (Homebrew) If you're on macOS, you can install with `brew`. @@ -46,8 +77,28 @@ brew install d2 ## Standalone We publish standalone release archives for every release on Github. -Download the `.tar.gz` release for your OS/ARCH combination and then run the following -inside the extracted directory to install: + +Here's a minimal example script that downloads a standalone release, extracts it into the +current directory and then installs it. +Adjust VERSION, OS, and ARCH as needed. + +```sh +VERSION=v0.0.13 OS=macos ARCH=amd64 curl -fsSLO \ + "https://github.com/terrastruct/d2/releases/download/$VERSION/d2-$VERSION-$OS-$ARCH.tar.gz" \ + && tar -xzf "d2-$VERSION-$OS-$ARCH.tar.gz" \ + && make -sC "d2-$VERSION" install +``` + +To uninstall: + +```sh +VERSION=v0.0.13 make -sC "d2-$VERSION" uninstall +``` + +### Manual + +You can also manually download the `.tar.gz` release for your OS/ARCH combination and then +run the following inside the extracted directory to install: ```sh make install @@ -59,10 +110,11 @@ Run the following to uninstall: make uninstall ``` -If root permissions are required for installation, you'll need to run `make` with `sudo`. +### PREFIX + You can control the Unix hierarchy installation path with `PREFIX=`. For example: -``` +```sh # Install under ~/.local. # Binaries will be at ~/.local/bin # And manpages will be under ~/.local/share/man @@ -83,7 +135,15 @@ know where the release directory is for easy uninstall. You can always install from source: ```sh -go install oss.terrastruct.com/d2/cmd/d2@latest +go install oss.terrastruct.com/d2@latest +``` + +To install a proper release from source clone the repository and then: + +```sh +./ci/release/build.sh --install +# To uninstall: +# ./ci/release/build.sh --uninstall ``` ## Coming soon diff --git a/docs/examples/lib/1-d2lib/.gitignore b/docs/examples/lib/1-d2lib/.gitignore new file mode 100644 index 000000000..958df5ba9 --- /dev/null +++ b/docs/examples/lib/1-d2lib/.gitignore @@ -0,0 +1 @@ +out.svg diff --git a/docs/examples/lib/1-d2lib/d2lib.go b/docs/examples/lib/1-d2lib/d2lib.go new file mode 100644 index 000000000..a28b97f07 --- /dev/null +++ b/docs/examples/lib/1-d2lib/d2lib.go @@ -0,0 +1,25 @@ +package main + +import ( + "context" + "io/ioutil" + "path/filepath" + + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2lib" + "oss.terrastruct.com/d2/d2renderers/d2svg" + "oss.terrastruct.com/d2/d2themes/d2themescatalog" + "oss.terrastruct.com/d2/lib/textmeasure" +) + +// Remember to add if err != nil checks in production. +func main() { + ruler, _ := textmeasure.NewRuler() + diagram, _, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{ + Layout: d2dagrelayout.Layout, + Ruler: ruler, + ThemeID: d2themescatalog.GrapeSoda.ID, + }) + out, _ := d2svg.Render(diagram) + _ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) +} diff --git a/docs/examples/lib/1-d2lib/d2lib_test.go b/docs/examples/lib/1-d2lib/d2lib_test.go new file mode 100644 index 000000000..53b3d5bcc --- /dev/null +++ b/docs/examples/lib/1-d2lib/d2lib_test.go @@ -0,0 +1,9 @@ +package main + +import ( + "testing" +) + +func TestMain_(t *testing.T) { + main() +} diff --git a/docs/examples/lib/2-d2oracle/d2oracle.go b/docs/examples/lib/2-d2oracle/d2oracle.go new file mode 100644 index 000000000..1f265a05e --- /dev/null +++ b/docs/examples/lib/2-d2oracle/d2oracle.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "fmt" + + "oss.terrastruct.com/d2/d2format" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2lib" + "oss.terrastruct.com/d2/d2oracle" + "oss.terrastruct.com/d2/d2themes/d2themescatalog" + "oss.terrastruct.com/d2/lib/textmeasure" +) + +// Remember to add if err != nil checks in production. +func main() { + // From one.go + ruler, _ := textmeasure.NewRuler() + _, graph, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{ + Layout: d2dagrelayout.Layout, + Ruler: ruler, + ThemeID: d2themescatalog.GrapeSoda.ID, + }) + + // Create a shape with the ID, "meow" + graph, _, _ = d2oracle.Create(graph, "meow") + // Style the shape green + color := "green" + graph, _ = d2oracle.Set(graph, "meow.style.fill", nil, &color) + // Create a shape with the ID, "cat" + graph, _, _ = d2oracle.Create(graph, "cat") + // Move the shape "meow" inside the container "cat" + graph, _ = d2oracle.Move(graph, "meow", "cat.meow") + // Prints formatted D2 script + fmt.Print(d2format.Format(graph.AST)) +} diff --git a/docs/examples/lib/2-d2oracle/d2oracle_test.go b/docs/examples/lib/2-d2oracle/d2oracle_test.go new file mode 100644 index 000000000..53b3d5bcc --- /dev/null +++ b/docs/examples/lib/2-d2oracle/d2oracle_test.go @@ -0,0 +1,9 @@ +package main + +import ( + "testing" +) + +func TestMain_(t *testing.T) { + main() +} diff --git a/docs/examples/lib/3-lowlevel/.gitignore b/docs/examples/lib/3-lowlevel/.gitignore new file mode 100644 index 000000000..958df5ba9 --- /dev/null +++ b/docs/examples/lib/3-lowlevel/.gitignore @@ -0,0 +1 @@ +out.svg diff --git a/docs/examples/lib/3-lowlevel/lowlevel.go b/docs/examples/lib/3-lowlevel/lowlevel.go new file mode 100644 index 000000000..e8f7a70a8 --- /dev/null +++ b/docs/examples/lib/3-lowlevel/lowlevel.go @@ -0,0 +1,26 @@ +package main + +import ( + "context" + "io/ioutil" + "path/filepath" + "strings" + + "oss.terrastruct.com/d2/d2compiler" + "oss.terrastruct.com/d2/d2exporter" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2renderers/d2svg" + "oss.terrastruct.com/d2/d2themes/d2themescatalog" + "oss.terrastruct.com/d2/lib/textmeasure" +) + +// Remember to add if err != nil checks in production. +func main() { + graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil) + ruler, _ := textmeasure.NewRuler() + _ = graph.SetDimensions(nil, ruler) + _ = d2dagrelayout.Layout(context.Background(), graph) + diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID) + out, _ := d2svg.Render(diagram) + _ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) +} diff --git a/docs/examples/lib/3-lowlevel/lowlevel_test.go b/docs/examples/lib/3-lowlevel/lowlevel_test.go new file mode 100644 index 000000000..53b3d5bcc --- /dev/null +++ b/docs/examples/lib/3-lowlevel/lowlevel_test.go @@ -0,0 +1,9 @@ +package main + +import ( + "testing" +) + +func TestMain_(t *testing.T) { + main() +} diff --git a/docs/examples/lib/README.md b/docs/examples/lib/README.md new file mode 100644 index 000000000..979e9af7e --- /dev/null +++ b/docs/examples/lib/README.md @@ -0,0 +1,30 @@ +# D2 library examples + +We have a few examples in this directory on how to use the D2 library to turn D2 scripts +into rendered svg diagrams and more. + +Each example is runnable though does not include error handling for readability. + +### [./1-d2lib](./1-d2lib) + +A minimal example showing you how to compile the diagram `x -> y` into an svg. + +### [./2-d2oracle](./2-d2oracle) + +D2 is built to be hackable -- the language has an API built on top of it to make edits +programmatically. + +Modifying the previous example, this example demonstrates how +[d2oracle](../../../d2oracle) can be used to create a new shape, style it programatically +and then output the modified d2 script. + +This makes it easy to build functionality on top of D2. Terrastruct uses the +[d2oracle](../../../d2oracle) API to implement editing of D2 from mouse actions in a +visual interface. + +### [./3-lowlevel](./3-lowlevel) + +`d2lib` from the first example is just a wrapper around the lower level APIs. They +can be used directly and this example demonstrates such usage. + +This shouldn't be necessary for most usecases. diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 513451247..567008cd6 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -12,19 +12,19 @@ import ( "cdr.dev/slog" - "github.com/stretchr/testify/assert" + tassert "github.com/stretchr/testify/assert" - "oss.terrastruct.com/diff" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" - "oss.terrastruct.com/d2" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2elklayout" + "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2renderers/d2svg" - "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2target" - xdiff "oss.terrastruct.com/d2/lib/diff" "oss.terrastruct.com/d2/lib/log" + "oss.terrastruct.com/d2/lib/textmeasure" ) func TestE2E(t *testing.T) { @@ -90,7 +90,7 @@ func run(t *testing.T, tc testCase) { ctx = log.Leveled(ctx, slog.LevelDebug) ruler, err := textmeasure.NewRuler() - if !assert.Nil(t, err) { + if !tassert.Nil(t, err) { return } @@ -104,13 +104,13 @@ func run(t *testing.T, tc testCase) { } else if layoutName == "elk" { layout = d2elklayout.Layout } - diagram, err := d2.Compile(ctx, tc.script, &d2.CompileOptions{ + diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ UTF16: true, Ruler: ruler, ThemeID: 0, Layout: layout, }) - if !assert.Nil(t, err) { + if !tassert.Nil(t, err) { return } @@ -122,34 +122,23 @@ func run(t *testing.T, tc testCase) { dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestE2E/"), layoutName) pathGotSVG := filepath.Join(dataPath, "sketch.got.svg") - pathExpSVG := filepath.Join(dataPath, "sketch.exp.svg") + svgBytes, err := d2svg.Render(diagram) - if err != nil { - t.Fatal(err) - } + assert.Success(t, err) + err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600) + assert.Success(t, err) + defer os.Remove(pathGotSVG) var xmlParsed interface{} - if err := xml.Unmarshal(svgBytes, &xmlParsed); err != nil { - t.Fatalf("invalid SVG: %v", err) - } + err = xml.Unmarshal(svgBytes, &xmlParsed) + assert.Success(t, err) - err = diff.Testdata(filepath.Join(dataPath, "board"), diagram) - if err != nil { - ioutil.WriteFile(pathGotSVG, svgBytes, 0600) - t.Fatal(err) - } + err = diff.TestdataJSON(filepath.Join(dataPath, "board"), diagram) + assert.Success(t, err) if os.Getenv("SKIP_SVG_CHECK") == "" { - err = xdiff.TestdataGeneric(filepath.Join(dataPath, "sketch"), ".svg", svgBytes) - if err != nil { - ioutil.WriteFile(pathGotSVG, svgBytes, 0600) - t.Fatal(err) - } + err = diff.Testdata(filepath.Join(dataPath, "sketch"), ".svg", svgBytes) + assert.Success(t, err) } - err = ioutil.WriteFile(pathExpSVG, svgBytes, 0600) - if err != nil { - t.Fatal(err) - } - os.Remove(filepath.Join(dataPath, "sketch.got.svg")) } } diff --git a/fmt.go b/fmt.go new file mode 100644 index 000000000..e3a755a79 --- /dev/null +++ b/fmt.go @@ -0,0 +1,37 @@ +package main + +import ( + "bytes" + "context" + + "oss.terrastruct.com/util-go/xdefer" + + "oss.terrastruct.com/util-go/xmain" + + "oss.terrastruct.com/d2/d2format" + "oss.terrastruct.com/d2/d2parser" +) + +func fmtCmd(ctx context.Context, ms *xmain.State) (err error) { + defer xdefer.Errorf(&err, "failed to fmt") + + ms.Opts = xmain.NewOpts(ms.Env, ms.Log, ms.Opts.Flags.Args()[1:]) + if len(ms.Opts.Args) == 0 { + return xmain.UsageErrorf("fmt must be passed the file to be formatted") + } else if len(ms.Opts.Args) > 1 { + return xmain.UsageErrorf("fmt accepts only one argument for the file to be formatted") + } + + inputPath := ms.Opts.Args[0] + input, err := ms.ReadPath(inputPath) + if err != nil { + return err + } + + m, err := d2parser.Parse(inputPath, bytes.NewReader(input), nil) + if err != nil { + return err + } + + return ms.WritePath(inputPath, []byte(d2format.Format(m))) +} diff --git a/go.mod b/go.mod index fc891de0f..6ac33f5bd 100644 --- a/go.mod +++ b/go.mod @@ -10,25 +10,17 @@ require ( github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mazznoer/csscolorparser v0.1.3 - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/playwright-community/playwright-go v0.2000.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/yuin/goldmark v1.5.3 go.uber.org/multierr v1.8.0 - golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 golang.org/x/image v0.1.0 golang.org/x/net v0.2.0 - golang.org/x/text v0.4.0 + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 gonum.org/v1/plot v0.12.0 nhooyr.io/websocket v1.8.7 - oss.terrastruct.com/cmdlog v0.0.0-20221129200109-540ef52ff07d - oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541 - oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f - oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557 - oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a - oss.terrastruct.com/xos v0.0.0-20221018030138-c96e7ae96e5d - oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333 + oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95 ) @@ -49,15 +41,17 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/ugorji/go/codec v1.2.6 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.3.0 // indirect + golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/term v0.2.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/text v0.4.0 // indirect google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 9eca1dfde..22827dd80 100644 --- a/go.sum +++ b/go.sum @@ -798,20 +798,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -oss.terrastruct.com/cmdlog v0.0.0-20221129200109-540ef52ff07d h1:oc3cqW4bWaosVpfM8yNLAPSDMjLrVYP2ztF7w3tRvws= -oss.terrastruct.com/cmdlog v0.0.0-20221129200109-540ef52ff07d/go.mod h1:ROL3yxl2X+S3O+Rls00qdX6aMh+p1dF8IdxDRwDDpsg= -oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541 h1:I9B1O1IJ6spivIQxbFRZmbhAwVeLwrcQRR1JbYUOvrI= -oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541/go.mod h1:ags2QDy/T6jr69hT6bpmAmhr2H98n9o8Atf3QlUJPiU= -oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f h1:7voRCwKM7TZkTo9u7hj+uV/zXoVB8czWrTq6MVIh3dg= -oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f/go.mod h1:Y0coTLsWwX0q3a+/Ndq797t+vWyxm42T49Ik3bzaDKY= -oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557 h1:rPbhJbN1q7B4tnppSPoAMwq0t6Pk5SrQDQ5S6uoNNHg= -oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557/go.mod h1:plvfydF5METAlsbpeuSz44jckaOwrCWX3M0kTLoCA4I= -oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a h1:AAcupsjBwpbcyLASX0ppDlxbfHWb5Neq5gWdGpLfaSA= -oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a/go.mod h1:XJ71qiTzk/dbTWuYbuLJuRpBdKFN06Sk5FdFpq2TNmE= -oss.terrastruct.com/xos v0.0.0-20221018030138-c96e7ae96e5d h1:rrPTkbAfsRTW1WLoTzEofS9AprsHovy9bwvA/wC8Dys= -oss.terrastruct.com/xos v0.0.0-20221018030138-c96e7ae96e5d/go.mod h1:uSONPDInIwglnC+0zYs8YOjiUD8ZUSnqDTTI82j7Oro= -oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333 h1:7EdxwXM75Id1VIN71QbE8bLzZRMs0qD7olnDw5gbI7w= -oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333/go.mod h1:O7TAoBmlQhoi46RdgVikDcoLRb/vLflhkXCAd+nO4SM= +oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b h1:o8+5KfZpQyaw7uKcPIdc9HOqVjVDEdsPZpdRV1k0rmc= +oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b/go.mod h1:Fwy72FDIOOM4K8F96ScXkxHHppR1CPfUyo9+x9c1PBU= rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95 h1:r89YHVIWeQj/A3Nu6462eqARUECJlJkLRk36pfML1xA= rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/cmd/d2/help.go b/help.go similarity index 85% rename from cmd/d2/help.go rename to help.go index 98390495d..bd6b455d0 100644 --- a/cmd/d2/help.go +++ b/help.go @@ -9,29 +9,37 @@ import ( "path/filepath" "strings" + "oss.terrastruct.com/util-go/xmain" + "oss.terrastruct.com/d2/d2plugin" - "oss.terrastruct.com/d2/lib/xmain" ) func help(ms *xmain.State) { fmt.Fprintf(ms.Stdout, `Usage: - %s [--watch=false] [--theme=0] file.d2 [file.svg|file.png] + %[1]s [--watch=false] [--theme=0] file.d2 [file.svg | file.png] + %[1]s layout [name] + %[1]s fmt file.d2 + +%[1]s compiles and renders file.d2 to file.svg | file.png +It defaults to file.svg if an output path is not provided. -%[1]s compiles and renders file.d2 to file.svg|file.png. Use - to have d2 read from stdin or write to stdout. +See man d2 for more detailed docs. + Flags: %s Subcommands: %[1]s layout - Lists available layout engine options with short help - %[1]s layout [layout name] - Display long help for a particular layout engine + %[1]s layout [name] - Display long help for a particular layout engine + %[1]s fmt file.d2 - Format file.d2 See more docs and the source code at https://oss.terrastruct.com/d2 -`, ms.Name, ms.Opts.Defaults()) +`, filepath.Base(ms.Name), ms.Opts.Defaults()) } -func layoutHelp(ctx context.Context, ms *xmain.State) error { +func layoutCmd(ctx context.Context, ms *xmain.State) error { if len(ms.Opts.Flags.Args()) == 1 { return shortLayoutHelp(ctx, ms) } else if len(ms.Opts.Flags.Args()) == 2 { diff --git a/install.sh b/install.sh index 7afad1716..f6f1c4620 100755 --- a/install.sh +++ b/install.sh @@ -61,22 +61,26 @@ tput() { should_color() { if [ -n "${COLOR-}" ]; then - if [ "$COLOR" = 0 -o "$COLOR" = false ]; then - _COLOR= - return 1 - elif [ "$COLOR" = 1 -o "$COLOR" = true ]; then + if [ "$COLOR" = 1 -o "$COLOR" = true ]; then _COLOR=1 + __COLOR=1 return 0 + elif [ "$COLOR" = 0 -o "$COLOR" = false ]; then + _COLOR= + __COLOR=0 + return 1 else printf '$COLOR must be 0, 1, false or true but got %s\n' "$COLOR" >&2 fi fi - if [ -t 1 ]; then + if [ -t 1 -a "${TERM-}" != dumb ]; then _COLOR=1 + __COLOR=1 return 0 else _COLOR= + __COLOR=0 return 1 fi } @@ -95,7 +99,8 @@ _echo() { get_rand_color() { # 1-6 are regular and 9-14 are bright. # 1,2 and 9,10 are red and green but we use those for success and failure. - pick "$*" 3 4 5 6 11 12 13 14 + pick "$*" 1 2 3 4 5 6 \ + 9 10 11 12 13 14 } echop() { @@ -119,9 +124,9 @@ printfp() {( fi should_color || true if [ $# -eq 0 ]; then - printf '%s' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")" + printf '%s' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" else - printf '%s: %s\n' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")" "$(printf "$@")" + printf '%s: %s\n' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" "$(printf "$@")" fi )} @@ -130,7 +135,7 @@ catp() { shift should_color || true - sed "s/^/$(COLOR=${_COLOR-} printfp "$prefix" '')/" + sed "s/^/$(COLOR=$__COLOR printfp "$prefix" '')/" } repeat() { @@ -157,17 +162,17 @@ printferr() { logp() { should_color >&2 || true - COLOR=${_COLOR-} echop "$@" | humanpath >&2 + COLOR=$__COLOR echop "$@" | humanpath >&2 } logfp() { should_color >&2 || true - COLOR=${_COLOR-} printfp "$@" | humanpath >&2 + COLOR=$__COLOR printfp "$@" | humanpath >&2 } logpcat() { should_color >&2 || true - COLOR=${_COLOR-} catp "$@" | humanpath >&2 + COLOR=$__COLOR catp "$@" | humanpath >&2 } log() { @@ -292,6 +297,13 @@ runtty() { return 1 esac } + +capcode() { + set +e + "$@" + code=$? + set -e +} #!/bin/sh if [ "${LIB_FLAG-}" ]; then return 0 @@ -468,6 +480,14 @@ manpath() { echo "${MANPATH-}" fi } + +is_writable_dir() { + # The path has to exist for -w to succeed. + sh_c "mkdir -p '$1' 2>/dev/null" || true + if [ ! -w "$1" ]; then + return 1 + fi +} #!/bin/sh set -eu @@ -556,6 +576,9 @@ note: Deleting the unarchived releases will cause --uninstall to stop working. You can rerun install.sh to update your version of D2. install.sh will avoid reinstalling if the installed version is the latest unless --force is passed. + +See https://github.com/terrastruct/d2/blob/master/docs/INSTALL.md#security for +documentation on its security. EOF } @@ -915,13 +938,10 @@ uninstall_tala_brew() { } is_prefix_writable() { - sh_c "mkdir -p '$INSTALL_DIR' 2>/dev/null" || true # The reason for checking whether $INSTALL_DIR is writable is that on macOS you have # /usr/local owned by root but you don't need root to write to its subdirectories which # is all we want to do. - if [ ! -w "$INSTALL_DIR" ]; then - return 1 - fi + is_writable_dir "$INSTALL_DIR" } cache_dir() { @@ -974,4 +994,7 @@ brew() { HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_AUTO_UPDATE=1 command brew "$@" } +# The main function does more than provide organization. It provides robustness in that if +# the install script was to only partial download into sh, sh will not execute it because +# main is not invoked until the very last byte. main "$@" diff --git a/lib/compress/compress_test.go b/lib/compress/compress_test.go deleted file mode 100644 index b1fe82e12..000000000 --- a/lib/compress/compress_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package compress - -import ( - "testing" - - "oss.terrastruct.com/diff" -) - -func TestCompression(t *testing.T) { - script := `x -> y -I just forgot my whole philosophy of life!!!: { - s: TV is chewing gum for the eyes -} -` - - encoded, err := Compress(script) - if err != nil { - t.Fatal(err) - } - - decoded, err := Decompress(encoded) - if err != nil { - t.Fatal(err) - } - - diff.AssertStringEq(t, script, decoded) -} diff --git a/lib/diff/diff.go b/lib/diff/diff.go deleted file mode 100644 index 43204e272..000000000 --- a/lib/diff/diff.go +++ /dev/null @@ -1,38 +0,0 @@ -package diff - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "oss.terrastruct.com/diff" -) - -// TODO refactor with diff repo -func TestdataGeneric(path, fileExtension string, got []byte) (err error) { - expPath := fmt.Sprintf("%s.exp%s", path, fileExtension) - gotPath := fmt.Sprintf("%s.got%s", path, fileExtension) - - err = os.MkdirAll(filepath.Dir(gotPath), 0755) - if err != nil { - return err - } - err = ioutil.WriteFile(gotPath, got, 0600) - if err != nil { - return err - } - - ds, err := diff.Files(expPath, gotPath) - if err != nil { - return err - } - - if ds != "" { - if os.Getenv("TESTDATA_ACCEPT") != "" { - return os.Rename(gotPath, expPath) - } - return fmt.Errorf("diff (rerun with $TESTDATA_ACCEPT=1 to accept):\n%s", ds) - } - return os.Remove(gotPath) -} diff --git a/lib/go2/go2.go b/lib/go2/go2.go deleted file mode 100644 index 20482efa2..000000000 --- a/lib/go2/go2.go +++ /dev/null @@ -1,60 +0,0 @@ -// Package go2 contains general utility helpers that should've been in Go. Maybe they'll be in Go 2.0. -package go2 - -import ( - "hash/fnv" - "math" - - "golang.org/x/exp/constraints" -) - -func Pointer[T any](v T) *T { - return &v -} - -func Min[T constraints.Ordered](a, b T) T { - if a < b { - return a - } - return b -} - -func Max[T constraints.Ordered](a, b T) T { - if a > b { - return a - } - return b -} - -func StringToIntHash(s string) int { - h := fnv.New32a() - h.Write([]byte(s)) - return int(h.Sum32()) -} - -func Contains[T comparable](els []T, el T) bool { - for _, el2 := range els { - if el2 == el { - return true - } - } - return false -} - -func Filter[T any](els []T, fn func(T) bool) []T { - out := []T{} - for _, el := range els { - if fn(el) { - out = append(out, el) - } - } - return out -} - -func IntMax(x, y int) int { - return int(math.Max(float64(x), float64(y))) -} - -func IntMin(x, y int) int { - return int(math.Min(float64(x), float64(y))) -} diff --git a/lib/imgbundler/imgbundler.go b/lib/imgbundler/imgbundler.go index 82e7105b9..14ff92cd8 100644 --- a/lib/imgbundler/imgbundler.go +++ b/lib/imgbundler/imgbundler.go @@ -6,156 +6,204 @@ import ( "encoding/base64" "fmt" "io/ioutil" + "mime" "net/http" "net/url" "os" + "path" "regexp" "strings" "sync" "time" - "go.uber.org/multierr" - "oss.terrastruct.com/xdefer" + "golang.org/x/xerrors" + "oss.terrastruct.com/util-go/xdefer" - "oss.terrastruct.com/d2/lib/xmain" + "oss.terrastruct.com/util-go/xmain" ) const maxImageSize int64 = 1 << 25 // 33_554_432 -var imageRe = regexp.MustCompile(` 0 { + return svg, xerrors.Errorf("%v", errhrefs) + } + return svg, nil } - if resp.err != nil { - err = multierr.Combine(err, resp.err) - continue - } - svg = bytes.Replace(svg, []byte(resp.srctxt), []byte(fmt.Sprintf(``, href) - matches := imageRe.FindAllStringSubmatch(str, -1) + matches := imageRegex.FindAllStringSubmatch(str, -1) if len(matches) != 1 { t.Fatalf("uri regex didn't match %s", str) } @@ -88,9 +88,9 @@ width="328" height="587" viewBox="-100 -131 328 587">