Merge branch 'master' into bernie/export-png

This commit is contained in:
Bernard Xie 2022-11-21 11:00:26 -08:00
commit e8340ff610
No known key found for this signature in database
GPG key ID: 3C3E0036CE0F892C
28 changed files with 1099 additions and 394 deletions

View file

@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- run: TERM=xterm-256color ./make.sh assert-linear - run: COLOR=1 ./make.sh assert-linear
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
@ -25,7 +25,21 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: TERM=xterm-256color ./make.sh fmt - run: COLOR=1 ./make.sh fmt
env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
gen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-go@v3
with:
go-version-file: ./go.mod
cache: true
- run: COLOR=1 ./make.sh gen
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
@ -39,7 +53,7 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: TERM=xterm-256color ./make.sh lint - run: COLOR=1 ./make.sh lint
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
@ -53,7 +67,7 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: TERM=xterm-256color ./make.sh build - run: COLOR=1 ./make.sh build
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
@ -67,7 +81,7 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: TERM=xterm-256color ./make.sh test - run: COLOR=1 ./make.sh test
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
@ -86,7 +100,7 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: TERM=xterm-256color ./make.sh race - run: COLOR=1 ./make.sh race
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}

View file

@ -20,7 +20,7 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: CI_ALL=1 TERM=xterm-256color ./make.sh - run: CI_ALL=1 COLOR=1 ./make.sh
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}

View file

@ -1,7 +1,7 @@
.POSIX: .POSIX:
.PHONY: all .PHONY: all
all: fmt lint build test all: fmt gen lint build test
ifdef CI ifdef CI
all: assert-linear all: assert-linear
endif endif
@ -9,6 +9,9 @@ endif
.PHONY: fmt .PHONY: fmt
fmt: fmt:
prefix "$@" ./ci/sub/fmt/make.sh prefix "$@" ./ci/sub/fmt/make.sh
.PHONY: gen
gen:
prefix "$@" ./ci/gen.sh
.PHONY: lint .PHONY: lint
lint: lint:
prefix "$@" go vet --composites=false ./... prefix "$@" go vet --composites=false ./...

View file

@ -22,8 +22,6 @@
- [Quickstart](#quickstart) - [Quickstart](#quickstart)
- [Install](#install) - [Install](#install)
* [Install script](#install-script)
* [Install from source](#install-from-source)
- [D2 as a library](#d2-as-a-library) - [D2 as a library](#d2-as-a-library)
- [Themes](#themes) - [Themes](#themes)
- [Fonts](#fonts) - [Fonts](#fonts)
@ -58,42 +56,20 @@ A browser window will open with `out.svg` and live-reload on changes to `in.d2`.
## Install ## Install
### Install script The easiest way to install is with our install script:
The recommended way to install is to run our install script, which will figure out the
best way to install based on your machine.
```sh ```sh
# With --dry-run the install script will print the commands it will use
# to install without actually installing so you know what it's going to do.
curl -fsSL https://d2lang.com/install.sh | sh -s -- --dry-run
# If things look good, install for real.
curl -fsSL https://d2lang.com/install.sh | sh -s -- curl -fsSL https://d2lang.com/install.sh | sh -s --
``` ```
We have precompiled binaries on the [releases](https://github.com/terrastruct/d2/releases)
page for macOS and Linux. For both amd64 and arm64. We will release package manager
distributions like .rpm, .deb soon. We also want to get D2 on Homebrew for macOS
and release a docker image.
To uninstall: To uninstall:
```sh ```sh
curl -fsSL https://d2lang.com/install.sh | sh -s -- --uninstall --dry-run
# If things look good, uninstall for real.
curl -fsSL https://d2lang.com/install.sh | sh -s -- --uninstall curl -fsSL https://d2lang.com/install.sh | sh -s -- --uninstall
``` ```
> warn: Our binary releases aren't fully portable like normal Go binaries due to the C For detailed installation docs, with alternative methods and examples for each OS, see
> dependency on v8go for executing dagre. [./docs/INSTALL.md](./docs/INSTALL.md).
### Install from source
Alternatively, you can install from source:
```sh
go install oss.terrastruct.com/d2
```
## D2 as a library ## D2 as a library

11
ci/gen.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/sh
set -eu
cd -- "$(dirname "$0")/.."
. ./ci/sub/lib.sh
./ci/release/gen_install.sh
./ci/release/gen_template_lib.sh
if [ -n "${CI-}" ]; then
git_assert_clean
fi

View file

@ -18,8 +18,11 @@ usage: $arg0 [--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix
[--tala latest] [--force] [--uninstall] [--tala latest] [--force] [--uninstall]
install.sh automates the installation of D2 onto your system. It currently only supports install.sh automates the installation of D2 onto your system. It currently only supports
the installation of standalone releases from GitHub. If you pass --edge, it will clone the the installation of standalone releases from GitHub and via Homebrew on macOS. See the
source, build a release and install from it. docs for --detect below for more information
If you pass --edge, it will clone the source, build a release and install from it.
--edge is incompatible with --tala and currently unimplemented.
Flags: Flags:
@ -29,6 +32,8 @@ Flags:
--version vX.X.X --version vX.X.X
Pass to have install.sh install the given version instead of the latest version. Pass to have install.sh install the given version instead of the latest version.
warn: The version may not be obeyed with package manager installations. Use
--method=standalone to enforce the version.
--edge --edge
Pass to build and install D2 from source. This will still use --method if set to detect Pass to build and install D2 from source. This will still use --method if set to detect
@ -36,14 +41,15 @@ Flags:
if an unsupported package manager is used. To install from source like a dev would, if an unsupported package manager is used. To install from source like a dev would,
use go install oss.terrastruct.com/d2 use go install oss.terrastruct.com/d2
note: currently unimplemented. note: currently unimplemented.
warn: incompatible with --tala as TALA is closed source.
--method [detect | standalone] --method [detect | standalone | homebrew ]
Pass to control the method by which to install. Right now we only support standalone Pass to control the method by which to install. Right now we only support standalone
releases from GitHub but later we'll add support for brew, rpm, deb and more. releases from GitHub but later we'll add support for brew, rpm, deb and more.
note: currently unimplemented.
- detect is currently unimplemented but would use your OS's package manager - detect will use your OS's package manager automatically.
automatically. So far it only detects macOS and automatically uses homebrew.
- homebrew uses https://brew.sh/ which is a macOS and Linux package manager.
- standalone installs a standalone release archive into the unix hierarchy path - standalone installs a standalone release archive into the unix hierarchy path
specified by --prefix which defaults to /usr/local specified by --prefix which defaults to /usr/local
Ensure /usr/local/bin is in your \$PATH to use it. Ensure /usr/local/bin is in your \$PATH to use it.
@ -51,16 +57,19 @@ Flags:
--prefix /usr/local --prefix /usr/local
Controls the unix hierarchy path into which standalone releases are installed. Controls the unix hierarchy path into which standalone releases are installed.
Defaults to /usr/local. You may also want to use ~/.local to avoid needing sudo. Defaults to /usr/local. You may also want to use ~/.local to avoid needing sudo.
Remember that whatever you use, you must have the bin directory of your prefix We use ~/.local by default on arm64 macOS machines as SIP now disables access to
path in \$PATH to execute the d2 binary. For example, if my prefix directory is /usr/local. Remember that whatever you use, you must have the bin directory of your
prefix path in \$PATH to execute the d2 binary. For example, if my prefix directory is
/usr/local then my \$PATH must contain /usr/local/bin. /usr/local then my \$PATH must contain /usr/local/bin.
--tala [latest] --tala [latest]
Install Terrastruct's closed source TALA for improved layouts. Install Terrastruct's closed source TALA for improved layouts.
See https://github.com/terrastruct/TALA See https://github.com/terrastruct/tala
It optionally takes an argument of the TALA version to install. It optionally takes an argument of the TALA version to install.
Installation obeys all other flags, just like the installation of d2. For example, Installation obeys all other flags, just like the installation of d2. For example,
the d2plugin-tala binary will be installed into /usr/local/bin/d2plugin-tala the d2plugin-tala binary will be installed into /usr/local/bin/d2plugin-tala
warn: The version may not be obeyed with package manager installations. Use
--method=standalone to enforce the version.
--force: --force:
Force installation over the existing version even if they match. It will attempt a Force installation over the existing version even if they match. It will attempt a
@ -73,6 +82,7 @@ Flags:
as for installation. i.e if you used --method standalone you must again use --method as for installation. i.e if you used --method standalone you must again use --method
standalone for uninstallation. With detect, the install script will try to use the OS standalone for uninstallation. With detect, the install script will try to use the OS
package manager to uninstall instead. package manager to uninstall instead.
note: tala will also be uninstalled if installed.
All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change
path of the cached assets. Release archives are unarchived into /usr/local/lib/d2/d2-<VERSION> path of the cached assets. Release archives are unarchived into /usr/local/lib/d2/d2-<VERSION>
@ -85,9 +95,7 @@ EOF
} }
main() { main() {
METHOD=standalone while flag_parse "$@"; do
while :; do
flag_parse "$@"
case "$FLAG" in case "$FLAG" in
h|help) h|help)
help help
@ -114,8 +122,6 @@ main() {
method) method)
flag_nonemptyarg && shift "$FLAGSHIFT" flag_nonemptyarg && shift "$FLAGSHIFT"
METHOD=$FLAGARG METHOD=$FLAGARG
echoerr "$FLAGRAW is currently unimplemented"
return 1
;; ;;
prefix) prefix)
flag_nonemptyarg && shift "$FLAGSHIFT" flag_nonemptyarg && shift "$FLAGSHIFT"
@ -129,15 +135,12 @@ main() {
flag_noarg && shift "$FLAGSHIFT" flag_noarg && shift "$FLAGSHIFT"
UNINSTALL=1 UNINSTALL=1
;; ;;
'')
shift "$FLAGSHIFT"
break
;;
*) *)
flag_errusage "unrecognized flag $FLAGRAW" flag_errusage "unrecognized flag $FLAGRAW"
;; ;;
esac esac
done done
shift "$FLAGSHIFT"
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
flag_errusage "no arguments are accepted" flag_errusage "no arguments are accepted"
@ -153,49 +156,86 @@ main() {
PREFIX=${PREFIX:-/usr/local} PREFIX=${PREFIX:-/usr/local}
CACHE_DIR=$(cache_dir) CACHE_DIR=$(cache_dir)
mkdir -p "$CACHE_DIR" mkdir -p "$CACHE_DIR"
METHOD=${METHOD:-detect}
INSTALL_DIR=$PREFIX/lib/d2 INSTALL_DIR=$PREFIX/lib/d2
case $METHOD in
detect)
case "$OS" in
macos)
if command -v brew >/dev/null; then
log "detected macOS with homebrew, using homebrew for (un)installation"
METHOD=homebrew
else
warn "detected macOS without homebrew, falling back to --method=standalone"
METHOD=standalone
fi
;;
*)
warn "unrecognized OS $OS, falling back to --method=standalone"
METHOD=standalone
;;
esac
;;
standalone) ;;
homebrew) ;;
*)
echoerr "unknown (un)installation method $METHOD"
return 1
;;
esac
if [ -n "${UNINSTALL-}" ]; then if [ -n "${UNINSTALL-}" ]; then
uninstall uninstall
return 0 if [ -n "${DRY_RUN-}" ]; then
log "Rerun without --dry-run to execute printed commands and perform uninstall."
fi fi
else
VERSION=${VERSION:-latest}
if [ "$VERSION" = latest ]; then
header "fetching latest release info"
fetch_release_info
fi
install install
if [ -n "${DRY_RUN-}" ]; then
log "Rerun without --dry-run to execute printed commands and perform install."
fi
fi
} }
install() { install() {
install_d2 case $METHOD in
standalone)
install_d2_standalone
if [ -n "${TALA-}" ]; then if [ -n "${TALA-}" ]; then
# Run in subshell to avoid overwriting VERSION. # Run in subshell to avoid overwriting VERSION.
TALA_VERSION="$( install_tala && echo "$VERSION" )" TALA_VERSION="$( RELEASE_INFO= install_tala_standalone && echo "$VERSION" )"
fi fi
;;
homebrew)
install_d2_brew
if [ -n "${TALA-}" ]; then install_tala_brew; fi
;;
esac
COLOR=2 header success FGCOLOR=2 bigheader 'next steps'
case $METHOD in
standalone) install_post_standalone ;;
homebrew) install_post_brew ;;
esac
}
install_post_standalone() {
log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX" log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX"
if [ -n "${TALA-}" ]; then if [ -n "${TALA-}" ]; then
log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX" log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX"
fi fi
log "Rerun this install script with --uninstall to uninstall" log "Rerun this install script with --uninstall to uninstall."
log
if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then
logcat >&2 <<EOF logcat >&2 <<EOF
%%%%%%%%%%%%%%%%%%%%%%%%%
NEXT STEPS
%%%%%%%%%%%%%%%%%%%%%%%%%
Extend your \$PATH to use d2: Extend your \$PATH to use d2:
export PATH=$PREFIX/bin:\$PATH export PATH=$PREFIX/bin:\$PATH
Then run: Then run:
${TALA+D2_LAYOUT=tala }d2 --help ${TALA+D2_LAYOUT=tala }d2 --help
EOF EOF
else else
log " Run ${TALA+D2_LAYOUT=tala }d2 --help for usage." log "Run ${TALA+D2_LAYOUT=tala }d2 --help for usage."
fi fi
if ! manpath | grep -qF "$PREFIX/share/man"; then if ! manpath | grep -qF "$PREFIX/share/man"; then
logcat >&2 <<EOF logcat >&2 <<EOF
@ -208,36 +248,61 @@ EOF
log " man d2plugin-tala" log " man d2plugin-tala"
fi fi
else else
log " Run man d2 for detailed docs." log "Run man d2 for detailed docs."
if [ -n "${TALA-}" ]; then if [ -n "${TALA-}" ]; then
log " Run man d2plugin-tala for detailed docs." log "Run man d2plugin-tala for detailed TALA docs."
fi fi
fi fi
logcat >&2 <<EOF logcat >&2 <<EOF
Something not working? Please let us know: Something not working? Please let us know:
https://github.com/terrastruct/d2/issues/new https://github.com/terrastruct/d2/issues
https://github.com/terrastruct/d2/discussions
https://discord.gg/NF6X8K4eDq
EOF EOF
} }
install_d2() { install_post_brew() {
log "d2 has been successfully installed with homebrew."
if [ -n "${TALA-}" ]; then
log "tala has been successfully installed with homebrew."
fi
log "Rerun this install script with --uninstall to uninstall."
log
log "Run ${TALA+D2_LAYOUT=tala }d2 --help for usage."
log "Run man d2 for detailed docs."
if [ -n "${TALA-}" ]; then
log "Run man d2plugin-tala for detailed TALA docs."
fi
logcat >&2 <<EOF
Something not working? Please let us know:
https://github.com/terrastruct/d2/issues
https://github.com/terrastruct/d2/discussions
https://discord.gg/NF6X8K4eDq
EOF
}
install_d2_standalone() {
VERSION=${VERSION:-latest}
header "installing d2-$VERSION"
if [ "$VERSION" = latest ]; then
fetch_release_info
fi
if command -v d2 >/dev/null; then if command -v d2 >/dev/null; then
INSTALLED_VERSION="$(d2 version)" INSTALLED_VERSION="$(d2 version)"
if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then
log "skipping installation as version $VERSION is already installed." log "skipping installation as d2 $VERSION is already installed."
return 0 return 0
fi fi
log "uninstalling $INSTALLED_VERSION to install $VERSION" log "uninstalling d2 $INSTALLED_VERSION to install $VERSION"
if ! uninstall_d2; then if ! uninstall_d2_standalone; then
warn "failed to uninstall $INSTALLED_VERSION" warn "failed to uninstall d2 $INSTALLED_VERSION"
fi fi
fi fi
header "installing d2-$VERSION"
install_standalone_d2
}
install_standalone_d2() {
ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz" ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz"
log "installing standalone release $ARCHIVE from github" log "installing standalone release $ARCHIVE from github"
@ -256,19 +321,38 @@ install_standalone_d2() {
"$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
} }
install_tala() { install_d2_brew() {
REPO="${REPO_TALA:-terrastruct/TALA}" header "installing d2 with homebrew"
VERSION=$TALA sh_c brew tap terrastruct/d2
RELEASE_INFO= sh_c brew install d2
fetch_release_info
header "installing tala-$VERSION"
install_standalone_tala
} }
install_standalone_tala() { install_tala_standalone() {
REPO="${REPO_TALA:-terrastruct/tala}"
VERSION=$TALA
header "installing tala-$VERSION"
if [ "$VERSION" = latest ]; then
fetch_release_info
fi
if command -v d2plugin-tala >/dev/null; then
INSTALLED_VERSION="$(d2plugin-tala --version)"
if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then
log "skipping installation as tala $VERSION is already installed."
return 0
fi
log "uninstalling tala $INSTALLED_VERSION to install $VERSION"
if ! uninstall_tala_standalone; then
warn "failed to uninstall tala $INSTALLED_VERSION"
fi
fi
ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz" ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz"
log "installing standalone release $ARCHIVE from github" log "installing standalone release $ARCHIVE from github"
fetch_release_info
asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1') asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1')
asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"')
@ -284,36 +368,40 @@ install_standalone_tala() {
"$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
} }
install_tala_brew() {
header "installing tala with homebrew"
sh_c brew tap terrastruct/d2
sh_c brew install tala
}
uninstall() { uninstall() {
# We uninstall tala first as package managers require that it be uninstalled before
# uninstalling d2 as TALA depends on d2.
if command -v d2plugin-tala >/dev/null; then
INSTALLED_VERSION="$(d2plugin-tala --version)"
header "uninstalling tala-$INSTALLED_VERSION"
case $METHOD in
standalone) uninstall_tala_standalone ;;
homebrew) uninstall_tala_brew ;;
esac
elif [ "${TALA-}" ]; then
warn "no version of tala installed"
fi
if ! command -v d2 >/dev/null; then if ! command -v d2 >/dev/null; then
warn "no version of d2 installed" warn "no version of d2 installed"
return 0 return 0
fi fi
INSTALLED_VERSION="$(d2 --version)" INSTALLED_VERSION="$(d2 --version)"
if ! uninstall_d2; then
echoerr "failed to uninstall $INSTALLED_VERSION"
return 1
fi
if [ "${TALA-}" ]; then
if ! command -v d2plugin-tala >/dev/null; then
warn "no version of tala installed"
return 0
fi
INSTALLED_VERSION="$(d2plugin-tala --version)"
if ! uninstall_tala; then
echoerr "failed to uninstall tala $INSTALLED_VERSION"
return 1
fi
fi
return 0
}
uninstall_d2() {
header "uninstalling d2-$INSTALLED_VERSION" header "uninstalling d2-$INSTALLED_VERSION"
uninstall_standalone_d2 case $METHOD in
standalone) uninstall_d2_standalone ;;
homebrew) uninstall_d2_brew ;;
esac
} }
uninstall_standalone_d2() { uninstall_d2_standalone() {
log "uninstalling standalone release of d2-$INSTALLED_VERSION" log "uninstalling standalone release of d2-$INSTALLED_VERSION"
if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then
@ -331,12 +419,11 @@ uninstall_standalone_d2() {
"$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION"
} }
uninstall_tala() { uninstall_d2_brew() {
header "uninstalling tala-$INSTALLED_VERSION" sh_c brew remove d2
uninstall_standalone_tala
} }
uninstall_standalone_tala() { uninstall_tala_standalone() {
log "uninstalling standalone release tala-$INSTALLED_VERSION" log "uninstalling standalone release tala-$INSTALLED_VERSION"
if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then
@ -354,6 +441,10 @@ uninstall_standalone_tala() {
"$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION"
} }
uninstall_tala_brew() {
sh_c brew remove tala
}
is_prefix_writable() { is_prefix_writable() {
sh_c "mkdir -p '$INSTALL_DIR' 2>/dev/null" || true 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 # The reason for checking whether $INSTALL_DIR is writable is that on macOS you have
@ -409,4 +500,9 @@ fetch_gh() {
sh_c mv "$file.inprogress" "$file" sh_c mv "$file.inprogress" "$file"
} }
brew() {
# Makes brew sane.
HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_AUTO_UPDATE=1 command brew "$@"
}
main "$@" main "$@"

View file

@ -42,8 +42,7 @@ EOF
} }
main() { main() {
while :; do while flag_parse "$@"; do
flag_parse "$@"
case "$FLAG" in case "$FLAG" in
h|help) h|help)
help help
@ -78,16 +77,12 @@ main() {
flag_noarg && shift "$FLAGSHIFT" flag_noarg && shift "$FLAGSHIFT"
LOCKFILE_FORCE=1 LOCKFILE_FORCE=1
;; ;;
'')
shift "$FLAGSHIFT"
break
;;
*) *)
flag_errusage "unrecognized flag $FLAGRAW" flag_errusage "unrecognized flag $FLAGRAW"
;; ;;
esac esac
done done
shift "$FLAGSHIFT"
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
flag_errusage "no arguments are accepted" flag_errusage "no arguments are accepted"
fi fi
@ -169,16 +164,16 @@ build_local() {
build_remote_macos() { build_remote_macos() {
sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock
trap unlockfile_ssh EXIT
sh_c ssh "$REMOTE_HOST" mkdir -p src sh_c ssh "$REMOTE_HOST" mkdir -p src
sh_c rsync --archive --human-readable --delete ./ "$REMOTE_HOST:src/d2/" sh_c rsync --archive --human-readable --delete ./ "$REMOTE_HOST:src/d2/"
sh_c ssh "$REMOTE_HOST" "DRY_RUN=${DRY_RUN-} \ sh_c ssh "$REMOTE_HOST" "COLOR=${COLOR-} \
TERM=${TERM-} \
DRY_RUN=${DRY_RUN-} \
HW_BUILD_DIR=$HW_BUILD_DIR \ HW_BUILD_DIR=$HW_BUILD_DIR \
VERSION=$VERSION \ VERSION=$VERSION \
OS=$OS \ OS=$OS \
ARCH=$ARCH \ ARCH=$ARCH \
ARCHIVE=$ARCHIVE \ ARCHIVE=$ARCHIVE \
TERM=$TERM \
PATH=\\\"/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin\\\${PATH+:\\\$PATH}\\\" \ PATH=\\\"/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin\\\${PATH+:\\\$PATH}\\\" \
./src/d2/ci/release/_build.sh" ./src/d2/ci/release/_build.sh"
sh_c mkdir -p "$HW_BUILD_DIR" sh_c mkdir -p "$HW_BUILD_DIR"
@ -187,16 +182,16 @@ PATH=\\\"/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin\\\$
build_remote_linux() { build_remote_linux() {
sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock
trap unlockfile_ssh EXIT
sh_c ssh "$REMOTE_HOST" mkdir -p src sh_c ssh "$REMOTE_HOST" mkdir -p src
sh_c rsync --archive --human-readable --delete ./ "$REMOTE_HOST:src/d2/" sh_c rsync --archive --human-readable --delete ./ "$REMOTE_HOST:src/d2/"
sh_c ssh "$REMOTE_HOST" "DRY_RUN=${DRY_RUN-} \ sh_c ssh "$REMOTE_HOST" "COLOR=${COLOR-} \
TERM=${TERM-} \
DRY_RUN=${DRY_RUN-} \
HW_BUILD_DIR=$HW_BUILD_DIR \ HW_BUILD_DIR=$HW_BUILD_DIR \
VERSION=$VERSION \ VERSION=$VERSION \
OS=$OS \ OS=$OS \
ARCH=$ARCH \ ARCH=$ARCH \
ARCHIVE=$ARCHIVE \ ARCHIVE=$ARCHIVE \
TERM=$TERM \
./src/d2/ci/release/build_docker.sh" ./src/d2/ci/release/build_docker.sh"
sh_c mkdir -p "$HW_BUILD_DIR" sh_c mkdir -p "$HW_BUILD_DIR"
sh_c rsync --archive --human-readable "$REMOTE_HOST:src/d2/$ARCHIVE" "$ARCHIVE" sh_c rsync --archive --human-readable "$REMOTE_HOST:src/d2/$ARCHIVE" "$ARCHIVE"

View file

@ -13,5 +13,4 @@ docker_run \
-e OS \ -e OS \
-e ARCH \ -e ARCH \
-e ARCHIVE \ -e ARCHIVE \
-e TERM \
"$tag" ./src/d2/ci/release/_build.sh "$tag" ./src/d2/ci/release/_build.sh

View file

@ -12,8 +12,7 @@ EOF
} }
main() { main() {
while :; do while flag_parse "$@"; do
flag_parse "$@"
case "$FLAG" in case "$FLAG" in
h|help) h|help)
help help
@ -27,15 +26,12 @@ main() {
flag_nonemptyarg && shift "$FLAGSHIFT" flag_nonemptyarg && shift "$FLAGSHIFT"
KEY_FILE=$FLAGARG KEY_FILE=$FLAGARG
;; ;;
'')
shift "$FLAGSHIFT"
break
;;
*) *)
flag_errusage "unrecognized flag $FLAGRAW" flag_errusage "unrecognized flag $FLAGRAW"
;; ;;
esac esac
done done
shift "$FLAGSHIFT"
if [ -z "${KEY_FILE-}" ]; then if [ -z "${KEY_FILE-}" ]; then
echoerr "-i is required" echoerr "-i is required"
exit 1 exit 1

View file

@ -12,8 +12,7 @@ EOF
} }
main() { main() {
while :; do while flag_parse "$@"; do
flag_parse "$@"
case "$FLAG" in case "$FLAG" in
h|help) h|help)
help help
@ -27,15 +26,12 @@ main() {
flag_noarg && shift "$FLAGSHIFT" flag_noarg && shift "$FLAGSHIFT"
SKIP_CREATE=1 SKIP_CREATE=1
;; ;;
'')
shift "$FLAGSHIFT"
break
;;
*) *)
flag_errusage "unrecognized flag $FLAGRAW" flag_errusage "unrecognized flag $FLAGRAW"
;; ;;
esac esac
done done
shift "$FLAGSHIFT"
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
flag_errusage "no arguments are accepted" flag_errusage "no arguments are accepted"
fi fi
@ -204,7 +200,7 @@ init_remote_hosts() {
header macos-arm64 header macos-arm64
REMOTE_HOST=$TSTRUCT_MACOS_ARM64_BUILDER init_remote_macos REMOTE_HOST=$TSTRUCT_MACOS_ARM64_BUILDER init_remote_macos
COLOR=2 header summary FGCOLOR=2 header summary
log "export TSTRUCT_LINUX_AMD64_BUILDER=$TSTRUCT_LINUX_AMD64_BUILDER" log "export TSTRUCT_LINUX_AMD64_BUILDER=$TSTRUCT_LINUX_AMD64_BUILDER"
log "export TSTRUCT_LINUX_ARM64_BUILDER=$TSTRUCT_LINUX_ARM64_BUILDER" log "export TSTRUCT_LINUX_ARM64_BUILDER=$TSTRUCT_LINUX_ARM64_BUILDER"
log "export TSTRUCT_MACOS_AMD64_BUILDER=$TSTRUCT_MACOS_AMD64_BUILDER" log "export TSTRUCT_MACOS_AMD64_BUILDER=$TSTRUCT_MACOS_AMD64_BUILDER"

View file

@ -12,8 +12,7 @@ EOF
} }
main() { main() {
while :; do while flag_parse "$@"; do
flag_parse "$@"
case "$FLAG" in case "$FLAG" in
h|help) h|help)
help help
@ -27,15 +26,12 @@ main() {
flag_reqarg && shift "$FLAGSHIFT" flag_reqarg && shift "$FLAGSHIFT"
JOBFILTER="$FLAGARG" JOBFILTER="$FLAGARG"
;; ;;
'')
shift "$FLAGSHIFT"
break
;;
*) *)
flag_errusage "unrecognized flag $FLAGRAW" flag_errusage "unrecognized flag $FLAGRAW"
;; ;;
esac esac
done done
shift "$FLAGSHIFT"
REMOTE_HOST=$TSTRUCT_LINUX_AMD64_BUILDER; runjob linux-amd64 ssh "$REMOTE_HOST" "$@" REMOTE_HOST=$TSTRUCT_LINUX_AMD64_BUILDER; runjob linux-amd64 ssh "$REMOTE_HOST" "$@"
REMOTE_HOST=$TSTRUCT_LINUX_ARM64_BUILDER; runjob linux-arm64 ssh "$REMOTE_HOST" "$@" REMOTE_HOST=$TSTRUCT_LINUX_ARM64_BUILDER; runjob linux-arm64 ssh "$REMOTE_HOST" "$@"

View file

@ -7,7 +7,8 @@ For v0.0.99 we focused on X, Y and Z. Enjoy!
#### Improvements 🔧 #### Improvements 🔧
- Add table columns indices in edges between SQL Tables so that layout engines can route exactly between them - Equivalency between flags and environment variables. You can set either one for all
options (flags take precedence).
#### Bugfixes 🔴 #### Bugfixes 🔴

29
ci/release/gen_template_lib.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/sh
set -eu
cd -- "$(dirname "$0")/../.."
. ./ci/sub/lib.sh
sh_c chmod +w ./ci/release/template/scripts/lib.sh
sh_c cat >./ci/release/template/scripts/lib.sh <<EOF
#!/bin/sh
# *************
# DO NOT EDIT
#
# lib.sh was bundled together from
#
# - ./ci/sub/lib/rand.sh
# - ./ci/sub/lib/log.sh
#
# Generated by ./ci/release/gen_template_lib.sh.
# *************
EOF
# sed removes the sourcing dependency lines as we're bundled everything into a single
# script.
sh_c cat \
./ci/sub/lib/rand.sh \
./ci/sub/lib/log.sh \
\| sed "-e'/^\. /d'" \>\>./ci/release/template/scripts/lib.sh
sh_c chmod -w ./ci/release/template/scripts/lib.sh

View file

@ -40,10 +40,22 @@ See more docs, the source code and license at
.It Fl w , -watch Ar false .It Fl w , -watch Ar false
Watch for changes to input and live reload. Use Watch for changes to input and live reload. Use
.Ev $PORT and Ev $HOST to specify the listening address. .Ev $PORT and Ev $HOST to specify the listening address.
.Ev $D2_PORT and $D2_HOST are also accepted and take priority. Default is localhost:0 .It Fl h , -host Ar localhost
Host listening address when used with
.Ar watch
.Ns .
.It Fl p , -port Ar 0
Port listening address when used with
.Ar watch
.Ns .
.It Fl t , -theme Ar 0 .It Fl t , -theme Ar 0
Set the diagram theme to the passed integer. For a list of available options, see Set the diagram theme to the passed integer. For a list of available options, see
.Lk https://oss.terrastruct.com/d2 .Lk https://oss.terrastruct.com/d2
.Ns .
.It Fl l , -layout Ar dagre
Set the diagram layout engine to the passed string. For a list of available options, run
.Ar layout
.Ns .
.It Fl b , -bundle Ar true .It Fl b , -bundle Ar true
Bundle all assets and layers into the output svg. Bundle all assets and layers into the output svg.
.It Fl d , -debug .It Fl d , -debug

View file

@ -1,8 +1,21 @@
#!/bin/sh #!/bin/sh
if [ -n "${DEBUG-}" ]; then # *************
set -x # DO NOT EDIT
#
# lib.sh was bundled together from
#
# - ./ci/sub/lib/rand.sh
# - ./ci/sub/lib/log.sh
#
# Generated by ./ci/release/gen_template_lib.sh.
# *************
#!/bin/sh
if [ "${LIB_RAND-}" ]; then
return 0
fi fi
LIB_RAND=1
rand() { rand() {
seed="$1" seed="$1"
@ -14,15 +27,51 @@ rand() {
} }
pick() { pick() {
if ! command -v shuf >/dev/null || ! command -v md5sum >/dev/null; then
eval "_echo \"\$3\""
return
fi
seed="$1" seed="$1"
shift shift
i="$(rand "$seed" "1-$#")" i="$(rand "$seed" "1-$#")"
eval "_echo \"\$$i\"" eval "_echo \"\$$i\""
} }
#!/bin/sh
if [ "${LIB_LOG-}" ]; then
return 0
fi
LIB_LOG=1
if [ -n "${DEBUG-}" ]; then
set -x
fi
tput() { tput() {
if [ -n "$TERM" ]; then if should_color; then
command tput "$@" TERM=${TERM:-xterm-256color} command tput "$@"
fi
}
should_color() {
if [ -n "${COLOR-}" ]; then
if [ "$COLOR" = 0 -o "$COLOR" = false ]; then
_COLOR=
return 1
elif [ "$COLOR" = 1 -o "$COLOR" = true ]; then
_COLOR=1
return 0
else
printf '$COLOR must be 0, 1, false or true but got %s' "$COLOR" >&2
fi
fi
if [ -t 1 ]; then
_COLOR=1
return 0
else
_COLOR=
return 1
fi fi
} }
@ -59,14 +108,14 @@ printfp() {(
prefix="$1" prefix="$1"
shift shift
if [ -z "${COLOR:-}" ]; then if [ -z "${FGCOLOR-}" ]; then
COLOR="$(get_rand_color "$prefix")" FGCOLOR="$(get_rand_color "$prefix")"
fi fi
printf '%s' "$(setaf "$COLOR" "$prefix")" should_color || true
if [ $# -eq 0 ]; then
if [ $# -gt 0 ]; then printf '%s' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")"
printf ': ' else
printf "$@" printf '%s: %s\n' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")" "$(printf "$@")"
fi fi
)} )}
@ -74,13 +123,8 @@ catp() {
prefix="$1" prefix="$1"
shift shift
printfp "$prefix" should_color || true
printf ': ' sed "s/^/$(COLOR=${_COLOR-} printfp "$prefix" '')/"
read -r line
_echo "$line"
indent=$(repeat ' ' 2)
sed "s/^/$indent/"
} }
repeat() { repeat() {
@ -94,48 +138,150 @@ strlen() {
} }
echoerr() { echoerr() {
COLOR=1 echop err "$*" >&2 FGCOLOR=1 logp err "$*" | humanpath>&2
} }
caterr() { caterr() {
COLOR=1 catp err "$@" >&2 FGCOLOR=1 logpcat err "$@" | humanpath >&2
} }
printferr() { printferr() {
COLOR=1 printfp err "$@" >&2 FGCOLOR=1 logfp err "$@" | humanpath >&2
} }
logp() { logp() {
echop "$@" >&2 should_color >&2 || true
COLOR=${_COLOR-} echop "$@" | humanpath >&2
} }
logfp() { logfp() {
printfp "$@" >&2 should_color >&2 || true
COLOR=${_COLOR-} printfp "$@" | humanpath >&2
} }
logpcat() { logpcat() {
catp "$@" >&2 should_color >&2 || true
COLOR=${_COLOR-} catp "$@" | humanpath >&2
} }
log() { log() {
COLOR=5 logp log "$@" FGCOLOR=5 logp log "$@"
} }
logf() { logf() {
COLOR=5 logfp log "$@" FGCOLOR=5 logfp log "$@"
} }
logcat() { logcat() {
COLOR=5 catp log "$@" >&2 FGCOLOR=5 logpcat log "$@"
}
warn() {
FGCOLOR=3 logp warn "$@"
}
warnf() {
FGCOLOR=3 logfp warn "$@"
}
warncat() {
FGCOLOR=3 logpcat warn "$@"
} }
sh_c() { sh_c() {
COLOR=3 logp exec "$*" FGCOLOR=3 logp exec "$*"
if [ -z "${DRY_RUN-}" ]; then if [ -z "${DRY_RUN-}" ]; then
"$@" eval "$@"
fi
}
sudo_sh_c() {
if [ "$(id -u)" -eq 0 ]; then
sh_c "$@"
elif command -v doas >/dev/null; then
sh_c "doas $*"
elif command -v sudo >/dev/null; then
sh_c "sudo $*"
elif command -v su >/dev/null; then
sh_c "su root -c '$*'"
else
caterr <<EOF
This script needs to run the following command as root:
$*
Please install doas, sudo, or su.
EOF
return 1
fi fi
} }
header() { header() {
logp "/* $1 */" logp "/* $1 */"
} }
bigheader() {
logp "/**
* $1
**/"
}
# humanpath replaces all occurrences of " $HOME" with " ~"
# and all occurrences of '$HOME' with the literal '$HOME'.
humanpath() {
if [ -z "${HOME-}" ]; then
cat
else
sed -e "s# $HOME# ~#g" -e "s#$HOME#\$HOME#g"
fi
}
hide() {
out="$(mktemp)"
set +e
"$@" >"$out" 2>&1
code="$?"
set -e
if [ "$code" -eq 0 ]; then
return
fi
cat "$out" >&2
return "$code"
}
echo_dur() {
local dur=$1
local h=$((dur/60/60))
local m=$((dur/60%60))
local s=$((dur%60))
printf '%dh%dm%ds' "$h" "$m" "$s"
}
sponge() {
dst="$1"
tmp="$(mktemp)"
cat > "$tmp"
cat "$tmp" > "$dst"
}
stripansi() {
# First regex gets rid of standard xterm escape sequences for controlling
# visual attributes.
# The second regex I'm not 100% sure, the reference says it selects the US
# encoding but I'm not sure why that's necessary or why it always occurs
# in tput sgr0 before the standard escape sequence.
# See tput sgr0 | xxd
sed -e $'s/\x1b\[[0-9;]*m//g' -e $'s/\x1b(.//g'
}
runtty() {
case "$(uname)" in
Darwin)
script -q /dev/null "$@"
;;
Linux)
script -eqc "$*"
;;
*)
echoerr "runtty: unsupported OS $(uname)"
return 1
esac
}

2
ci/sub

@ -1 +1 @@
Subproject commit df51b90892737ebe9feca3dd982bcdfc7f684834 Subproject commit 824046d952b1442c76a057553591652c889fb7cb

View file

@ -28,13 +28,13 @@ Subcommands:
%[1]s layout [layout name] - Display long help for a particular layout engine %[1]s layout [layout name] - Display long help for a particular layout engine
See more docs and the source code at https://oss.terrastruct.com/d2 See more docs and the source code at https://oss.terrastruct.com/d2
`, ms.Name, ms.FlagHelp()) `, ms.Name, ms.Opts.Defaults())
} }
func layoutHelp(ctx context.Context, ms *xmain.State) error { func layoutHelp(ctx context.Context, ms *xmain.State) error {
if len(ms.FlagSet.Args()) == 1 { if len(ms.Opts.Flags.Args()) == 1 {
return shortLayoutHelp(ctx, ms) return shortLayoutHelp(ctx, ms)
} else if len(ms.FlagSet.Args()) == 2 { } else if len(ms.Opts.Flags.Args()) == 2 {
return longLayoutHelp(ctx, ms) return longLayoutHelp(ctx, ms)
} else { } else {
return pluginSubcommand(ctx, ms) return pluginSubcommand(ctx, ms)
@ -61,7 +61,7 @@ func shortLayoutHelp(ctx context.Context, ms *xmain.State) error {
%s %s
Usage: Usage:
To use a particular layout engine, set the environment variable D2_LAYOUT=[layout name]. To use a particular layout engine, set the environment variable D2_LAYOUT=[name] or flag --layout=[name].
Example: Example:
D2_LAYOUT=dagre d2 in.d2 out.svg D2_LAYOUT=dagre d2 in.d2 out.svg
@ -75,7 +75,7 @@ See more docs at https://oss.terrastruct.com/d2
} }
func longLayoutHelp(ctx context.Context, ms *xmain.State) error { func longLayoutHelp(ctx context.Context, ms *xmain.State) error {
layout := ms.FlagSet.Arg(1) layout := ms.Opts.Flags.Arg(1)
plugin, path, err := d2plugin.FindPlugin(ctx, layout) plugin, path, err := d2plugin.FindPlugin(ctx, layout)
if errors.Is(err, exec.ErrNotFound) { if errors.Is(err, exec.ErrNotFound) {
return layoutNotFound(ctx, layout) return layoutNotFound(ctx, layout)
@ -119,13 +119,13 @@ For more information on setup, please visit https://github.com/terrastruct/d2.`,
} }
func pluginSubcommand(ctx context.Context, ms *xmain.State) error { func pluginSubcommand(ctx context.Context, ms *xmain.State) error {
layout := ms.FlagSet.Arg(1) layout := ms.Opts.Flags.Arg(1)
plugin, _, err := d2plugin.FindPlugin(ctx, layout) plugin, _, err := d2plugin.FindPlugin(ctx, layout)
if errors.Is(err, exec.ErrNotFound) { if errors.Is(err, exec.ErrNotFound) {
return layoutNotFound(ctx, layout) return layoutNotFound(ctx, layout)
} }
ms.Args = ms.FlagSet.Args()[2:] ms.Opts.Args = ms.Opts.Flags.Args()[2:]
return d2plugin.Serve(plugin)(ctx, ms) return d2plugin.Serve(plugin)(ctx, ms)
} }

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
@ -32,19 +31,38 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
// :( // :(
ctx = xmain.DiscardSlog(ctx) ctx = xmain.DiscardSlog(ctx)
watchFlag := ms.FlagSet.BoolP("watch", "w", false, "watch for changes to input and live reload. Use $PORT and $HOST to specify the listening address.\n$D2_PORT and $D2_HOST are also accepted and take priority. Default is localhost:0") // These should be kept up-to-date with the d2 man page
themeFlag := ms.FlagSet.Int64P("theme", "t", 0, "set the diagram theme. For a list of available options, see https://oss.terrastruct.com/d2") watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which is will open on a randomly available local port).")
bundleFlag := ms.FlagSet.BoolP("bundle", "b", true, "when outputting SVG, bundle all assets and layers into the output file") if err != nil {
versionFlag := ms.FlagSet.BoolP("version", "v", false, "get the version") return xmain.UsageErrorf(err.Error())
debugFlag := ms.FlagSet.BoolP("debug", "d", false, "print debug logs") }
err = ms.FlagSet.Parse(ms.Args) hostFlag := ms.Opts.String("HOST", "host", "h", "localhost", "host listening address when used with watch")
portFlag := ms.Opts.String("PORT", "port", "p", "0", "port listening address when used with watch")
bundleFlag, err := ms.Opts.Bool("D2_BUNDLE", "bundle", "b", true, "when outputting SVG, bundle all assets and layers into the output file.")
if err != nil {
return xmain.UsageErrorf(err.Error())
}
debugFlag, err := ms.Opts.Bool("DEBUG", "debug", "d", false, "print debug logs.")
if err != nil {
return xmain.UsageErrorf(err.Error())
}
layoutFlag := ms.Opts.String("D2_LAYOUT", "layout", "l", "dagre", `the layout engine used.`)
themeFlag, err := ms.Opts.Int64("D2_THEME", "theme", "t", 0, "the diagram theme ID. For a list of available options, see https://oss.terrastruct.com/d2")
if err != nil {
return xmain.UsageErrorf(err.Error())
}
versionFlag, err := ms.Opts.Bool("", "version", "v", false, "get the version")
if err != nil {
return xmain.UsageErrorf(err.Error())
}
err = ms.Opts.Flags.Parse(ms.Opts.Args)
if !errors.Is(err, pflag.ErrHelp) && err != nil { if !errors.Is(err, pflag.ErrHelp) && err != nil {
return xmain.UsageErrorf("failed to parse flags: %v", err) return xmain.UsageErrorf("failed to parse flags: %v", err)
} }
if len(ms.FlagSet.Args()) > 0 { if len(ms.Opts.Flags.Args()) > 0 {
switch ms.FlagSet.Arg(0) { switch ms.Opts.Flags.Arg(0) {
case "layout": case "layout":
return layoutHelp(ctx, ms) return layoutHelp(ctx, ms)
} }
@ -62,25 +80,26 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
var inputPath string var inputPath string
var outputPath string var outputPath string
if len(ms.FlagSet.Args()) == 0 { if len(ms.Opts.Flags.Args()) == 0 {
if versionFlag != nil && *versionFlag { if versionFlag != nil && *versionFlag {
fmt.Println(version.Version) fmt.Println(version.Version)
return nil return nil
} }
help(ms) help(ms)
return nil return nil
} else if len(ms.FlagSet.Args()) >= 3 { } else if len(ms.Opts.Flags.Args()) >= 3 {
return xmain.UsageErrorf("too many arguments passed") return xmain.UsageErrorf("too many arguments passed")
} }
if len(ms.FlagSet.Args()) >= 1 {
if ms.FlagSet.Arg(0) == "version" { if len(ms.Opts.Flags.Args()) >= 1 {
if ms.Opts.Flags.Arg(0) == "version" {
fmt.Println(version.Version) fmt.Println(version.Version)
return nil return nil
} }
inputPath = ms.FlagSet.Arg(0) inputPath = ms.Opts.Flags.Arg(0)
} }
if len(ms.FlagSet.Args()) >= 2 { if len(ms.Opts.Flags.Args()) >= 2 {
outputPath = ms.FlagSet.Arg(1) outputPath = ms.Opts.Flags.Arg(1)
} else { } else {
if inputPath == "-" { if inputPath == "-" {
outputPath = "-" outputPath = "-"
@ -93,16 +112,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
if match == (d2themes.Theme{}) { if match == (d2themes.Theme{}) {
return xmain.UsageErrorf("-t[heme] could not be found. The available options are:\n%s\nYou provided: %d", d2themescatalog.CLIString(), *themeFlag) return xmain.UsageErrorf("-t[heme] could not be found. The available options are:\n%s\nYou provided: %d", d2themescatalog.CLIString(), *themeFlag)
} }
ms.Env.Setenv("D2_THEME", fmt.Sprintf("%d", *themeFlag)) ms.Log.Debug.Printf("using theme %s (ID: %d)", match.Name, *themeFlag)
envD2Layout := ms.Env.Getenv("D2_LAYOUT") plugin, path, err := d2plugin.FindPlugin(ctx, *layoutFlag)
if envD2Layout == "" {
envD2Layout = "dagre"
}
plugin, path, err := d2plugin.FindPlugin(ctx, envD2Layout)
if errors.Is(err, exec.ErrNotFound) { if errors.Is(err, exec.ErrNotFound) {
return layoutNotFound(ctx, envD2Layout) return layoutNotFound(ctx, *layoutFlag)
} else if err != nil { } else if err != nil {
return err return err
} }
@ -111,7 +125,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
if path != "" { if path != "" {
pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path)) pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path))
} }
ms.Log.Debug.Printf("using layout plugin %s (%s)", envD2Layout, pluginLocation) ms.Log.Debug.Printf("using layout plugin %s (%s)", *layoutFlag, pluginLocation)
var pw png.Playwright var pw png.Playwright
if filepath.Ext(outputPath) == ".png" { if filepath.Ext(outputPath) == ".png" {
@ -133,7 +147,15 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin") return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin")
} }
ms.Env.Setenv("LOG_TIMESTAMPS", "1") ms.Env.Setenv("LOG_TIMESTAMPS", "1")
w, err := newWatcher(ctx, ms, plugin, inputPath, outputPath, pw) w, err := newWatcher(ctx, ms, watcherOpts{
layoutPlugin: plugin,
themeID: *themeFlag,
host: *hostFlag,
port: *portFlag,
inputPath: inputPath,
outputPath: outputPath,
pw: pw,
})
if err != nil { if err != nil {
return err return err
} }
@ -147,7 +169,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
_ = 343 _ = 343
} }
_, err = compile(ctx, ms, plugin, inputPath, outputPath, pw.Page) _, err = compile(ctx, ms, plugin, *themeFlag, inputPath, outputPath, pw.Page)
if err != nil { if err != nil {
return err return err
} }
@ -156,7 +178,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
return nil return nil
} }
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, inputPath, outputPath string, page playwright.Page) ([]byte, error) { func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, themeID int64, inputPath, outputPath string, page playwright.Page) ([]byte, error) {
input, err := ms.ReadPath(inputPath) input, err := ms.ReadPath(inputPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -167,7 +189,6 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input
return nil, err return nil, err
} }
themeID, _ := strconv.ParseInt(ms.Env.Getenv("D2_THEME"), 10, 64)
d, err := d2.Compile(ctx, string(input), &d2.CompileOptions{ d, err := d2.Compile(ctx, string(input), &d2.CompileOptions{
Layout: plugin.Layout, Layout: plugin.Layout,
Ruler: ruler, Ruler: ruler,
@ -197,7 +218,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, input
if err != nil { if err != nil {
return nil, err return nil, err
} }
return out, nil return svg, nil
} }
// newExt must include leading . // newExt must include leading .

View file

@ -35,6 +35,16 @@ var devMode = false
//go:embed static //go:embed static
var staticFS embed.FS var staticFS embed.FS
type watcherOpts struct {
layoutPlugin d2plugin.Plugin
themeID int64
host string
port string
inputPath string
outputPath string
pw png.Playwright
}
type watcher struct { type watcher struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@ -42,9 +52,7 @@ type watcher struct {
devMode bool devMode bool
ms *xmain.State ms *xmain.State
layoutPlugin d2plugin.Plugin watcherOpts
inputPath string
outputPath string
compileCh chan struct{} compileCh chan struct{}
@ -62,8 +70,6 @@ type watcher struct {
resMu sync.Mutex resMu sync.Mutex
res *compileResult res *compileResult
pw png.Playwright
} }
type compileResult struct { type compileResult struct {
@ -71,7 +77,7 @@ type compileResult struct {
SVG string `json:"svg"` SVG string `json:"svg"`
} }
func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plugin, inputPath, outputPath string, pw png.Playwright) (*watcher, error) { func newWatcher(ctx context.Context, ms *xmain.State, opts watcherOpts) (*watcher, error) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
w := &watcher{ w := &watcher{
@ -80,13 +86,10 @@ func newWatcher(ctx context.Context, ms *xmain.State, layoutPlugin d2plugin.Plug
devMode: devMode, devMode: devMode,
ms: ms, ms: ms,
layoutPlugin: layoutPlugin, watcherOpts: opts,
inputPath: inputPath,
outputPath: outputPath,
compileCh: make(chan struct{}, 1), compileCh: make(chan struct{}, 1),
wsclients: make(map[*wsclient]struct{}), wsclients: make(map[*wsclient]struct{}),
pw: pw,
} }
err := w.init() err := w.init()
if err != nil { if err != nil {
@ -342,7 +345,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
w.pw = newPW w.pw = newPW
} }
b, err := compile(ctx, w.ms, w.layoutPlugin, w.inputPath, w.outputPath, w.pw.Page) b, err := compile(ctx, w.ms, w.layoutPlugin, w.themeID, w.inputPath, w.outputPath, w.pw.Page)
if err != nil { if err != nil {
err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err) err = fmt.Errorf("failed to %scompile: %w", recompiledPrefix, err)
w.ms.Log.Error.Print(err) w.ms.Log.Error.Print(err)
@ -368,18 +371,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
} }
func (w *watcher) listen() error { func (w *watcher) listen() error {
host := "localhost" l, err := net.Listen("tcp", net.JoinHostPort(w.host, w.port))
port := "0"
hostEnv := w.ms.Env.Getenv("HOST")
if hostEnv != "" {
host = hostEnv
}
portEnv := w.ms.Env.Getenv("PORT")
if portEnv != "" {
port = portEnv
}
l, err := net.Listen("tcp", net.JoinHostPort(host, port))
if err != nil { if err != nil {
return err return err
} }

View file

@ -19,12 +19,12 @@ import (
// Also see execPlugin in exec.go for the d2 binary plugin protocol. // Also see execPlugin in exec.go for the d2 binary plugin protocol.
func Serve(p Plugin) func(context.Context, *xmain.State) error { func Serve(p Plugin) func(context.Context, *xmain.State) error {
return func(ctx context.Context, ms *xmain.State) (err error) { return func(ctx context.Context, ms *xmain.State) (err error) {
if len(ms.Args) < 1 { if len(ms.Opts.Flags.Args()) < 1 {
return errors.New("expected first argument to plugin binary to be function name") return errors.New("expected first argument to plugin binary to be function name")
} }
reqFunc := ms.Args[0] reqFunc := ms.Opts.Flags.Arg(0)
switch ms.Args[0] { switch ms.Opts.Flags.Arg(0) {
case "info": case "info":
return info(ctx, p, ms) return info(ctx, p, ms)
case "layout": case "layout":

View file

@ -65,8 +65,7 @@ language. Sometimes it gives controversial sentences -- don't use those.
Script to generate one line of random text: Script to generate one line of random text:
``` ```
ipsum1() { ipsum1() {
fortune | head -n1 | sed 's/^ *//;s/ *$//' | tr -d '\n' | pbcopy fortune | head -n1 | sed 's/^ *//;s/ *$//' | tr -d '\n' | tee /dev/stderr | pbcopy
echo "$(pbpaste -Prefer txt)"
} }
``` ```

91
docs/INSTALL.md Normal file
View file

@ -0,0 +1,91 @@
# install
This file documents all the ways by which you can install D2.
<!-- toc -->
- [install.sh](#installsh)
- [macOS (Homebrew)](#macos-homebrew)
- [Standalone](#standalone)
- [From source](#from-source)
<!-- tocstop -->
## install.sh
The recommended and easiest way to install is with our install script, which will detect
the OS and architecture you're on and use the best method:
```sh
# With --dry-run the install script will print the commands it will use
# to install without actually installing so you know what it's going to do.
curl -fsSL https://d2lang.com/install.sh | sh -s -- --dry-run
# If things look good, install for real.
curl -fsSL https://d2lang.com/install.sh | sh -s --
```
For help on the terminal run, including the supported package managers and detection
methods:
```sh
curl -fsSL https://d2lang.com/install.sh | sh -s -- --help
```
## macOS (Homebrew)
If you're on macOS, you can alternatively install with `brew`. (the install script above
does this automatically if you have `brew` installed).
```sh
brew tap terrastruct/d2
brew install d2
```
## Standalone
We publish standalone release archives with 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:
```sh
make install
```
Run the following to uninstall:
```sh
make uninstall
```
You will be prompted for sudo/su/doas if root permissions are required for installation.
You can control the Unix hierarchy installation path with `PREFIX=`. For example:
```
# Install under ~/.local.
# Binaries will be at ~/.local/bin
# And manpages will be under ~/.local/share/man
# And supporting data like icons and fonts at ~/.local/share/d2
make install PREFIX=$HOME/.local
```
The install script places the standalone release into `$PREFIX/lib/d2/d2-<version>`
and we recommend doing the same with manually installed releases so that you
know where the release directory is for easy uninstall.
> warn: Our binary releases aren't fully portable like normal Go binaries due to the C
> dependency on v8go for executing dagre.
## From source
Alternatively, you can always install from source:
```sh
go install oss.terrastruct.com/d2/cmd/d2@latest
```
## Coming soon
- Docker image
- Windows install
- rpm and deb packages
- homebrew core

2
go.sum
View file

@ -778,6 +778,8 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
oss.terrastruct.com/cmdlog v0.0.0-20221116181457-07977d95ac37 h1:Xy1JKJHc4hcuwi57s0BvGUY16GjxTtBmLUybsuGDU7E= oss.terrastruct.com/cmdlog v0.0.0-20221116181457-07977d95ac37 h1:Xy1JKJHc4hcuwi57s0BvGUY16GjxTtBmLUybsuGDU7E=
oss.terrastruct.com/cmdlog v0.0.0-20221116181457-07977d95ac37 h1:Xy1JKJHc4hcuwi57s0BvGUY16GjxTtBmLUybsuGDU7E=
oss.terrastruct.com/cmdlog v0.0.0-20221116181457-07977d95ac37/go.mod h1:ROL3yxl2X+S3O+Rls00qdX6aMh+p1dF8IdxDRwDDpsg=
oss.terrastruct.com/cmdlog v0.0.0-20221116181457-07977d95ac37/go.mod h1:ROL3yxl2X+S3O+Rls00qdX6aMh+p1dF8IdxDRwDDpsg= oss.terrastruct.com/cmdlog v0.0.0-20221116181457-07977d95ac37/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 h1:I9B1O1IJ6spivIQxbFRZmbhAwVeLwrcQRR1JbYUOvrI=
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541/go.mod h1:ags2QDy/T6jr69hT6bpmAmhr2H98n9o8Atf3QlUJPiU= oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541/go.mod h1:ags2QDy/T6jr69hT6bpmAmhr2H98n9o8Atf3QlUJPiU=

View file

@ -54,8 +54,30 @@ if [ -n "${DEBUG-}" ]; then
fi fi
tput() { tput() {
if [ -n "$TERM" ]; then if should_color; then
command tput "$@" TERM=${TERM:-xterm-256color} command tput "$@"
fi
}
should_color() {
if [ -n "${COLOR-}" ]; then
if [ "$COLOR" = 0 -o "$COLOR" = false ]; then
_COLOR=
return 1
elif [ "$COLOR" = 1 -o "$COLOR" = true ]; then
_COLOR=1
return 0
else
printf '$COLOR must be 0, 1, false or true but got %s' "$COLOR" >&2
fi
fi
if [ -t 1 ]; then
_COLOR=1
return 0
else
_COLOR=
return 1
fi fi
} }
@ -92,14 +114,14 @@ printfp() {(
prefix="$1" prefix="$1"
shift shift
if [ -z "${COLOR:-}" ]; then if [ -z "${FGCOLOR-}" ]; then
COLOR="$(get_rand_color "$prefix")" FGCOLOR="$(get_rand_color "$prefix")"
fi fi
printf '%s' "$(setaf "$COLOR" "$prefix")" should_color || true
if [ $# -eq 0 ]; then
if [ $# -gt 0 ]; then printf '%s' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")"
printf ': ' else
printf "$@" printf '%s: %s\n' "$(COLOR=${_COLOR-} setaf "$FGCOLOR" "$prefix")" "$(printf "$@")"
fi fi
)} )}
@ -107,7 +129,8 @@ catp() {
prefix="$1" prefix="$1"
shift shift
sed "s/^/$(printfp "$prefix" '')/" should_color || true
sed "s/^/$(COLOR=${_COLOR-} printfp "$prefix" '')/"
} }
repeat() { repeat() {
@ -121,51 +144,58 @@ strlen() {
} }
echoerr() { echoerr() {
COLOR=1 echop err "$*" | humanpath>&2 FGCOLOR=1 logp err "$*" | humanpath>&2
} }
caterr() { caterr() {
COLOR=1 catp err "$@" | humanpath >&2 FGCOLOR=1 logpcat err "$@" | humanpath >&2
} }
printferr() { printferr() {
COLOR=1 printfp err "$@" | humanpath >&2 FGCOLOR=1 logfp err "$@" | humanpath >&2
} }
logp() { logp() {
echop "$@" | humanpath >&2 should_color >&2 || true
COLOR=${_COLOR-} echop "$@" | humanpath >&2
} }
logfp() { logfp() {
printfp "$@" | humanpath >&2 should_color >&2 || true
COLOR=${_COLOR-} printfp "$@" | humanpath >&2
} }
logpcat() { logpcat() {
catp "$@" | humanpath >&2 should_color >&2 || true
COLOR=${_COLOR-} catp "$@" | humanpath >&2
} }
log() { log() {
COLOR=5 logp log "$@" FGCOLOR=5 logp log "$@"
} }
logf() { logf() {
COLOR=5 logfp log "$@" FGCOLOR=5 logfp log "$@"
} }
logcat() { logcat() {
COLOR=5 logpcat log "$@" FGCOLOR=5 logpcat log "$@"
} }
warn() { warn() {
COLOR=3 logp warn "$@" FGCOLOR=3 logp warn "$@"
} }
warnf() { warnf() {
COLOR=3 logfp warn "$@" FGCOLOR=3 logfp warn "$@"
}
warncat() {
FGCOLOR=3 logpcat warn "$@"
} }
sh_c() { sh_c() {
COLOR=3 logp exec "$*" FGCOLOR=3 logp exec "$*"
if [ -z "${DRY_RUN-}" ]; then if [ -z "${DRY_RUN-}" ]; then
eval "$@" eval "$@"
fi fi
@ -194,6 +224,12 @@ header() {
logp "/* $1 */" logp "/* $1 */"
} }
bigheader() {
logp "/**
* $1
**/"
}
# humanpath replaces all occurrences of " $HOME" with " ~" # humanpath replaces all occurrences of " $HOME" with " ~"
# and all occurrences of '$HOME' with the literal '$HOME'. # and all occurrences of '$HOME' with the literal '$HOME'.
humanpath() { humanpath() {
@ -280,9 +316,8 @@ LIB_FLAG=1
# FLAGSHIFT contains the number by which the arguments should be shifted to # FLAGSHIFT contains the number by which the arguments should be shifted to
# start at the next flag/argument # start at the next flag/argument
# #
# After each call check $FLAG for the name of the parsed flag. # flag_parse exits with a non zero code when there are no more flags
# If empty, then no more flags are left. # to be parsed. Still, call shift "$FLAGSHIFT" in case there was a --
# Still, call shift "$FLAGSHIFT" in case there was a --
# #
# If the argument for the flag is optional, then use ${FLAGARG-} to access # If the argument for the flag is optional, then use ${FLAGARG-} to access
# the argument if one was passed. Use ${FLAGARG+x} = x to check if it was set. # the argument if one was passed. Use ${FLAGARG+x} = x to check if it was set.
@ -310,18 +345,15 @@ flag_parse() {
# Remove everything before first equal sign. # Remove everything before first equal sign.
FLAGARG="${1#*=}" FLAGARG="${1#*=}"
FLAGSHIFT=1 FLAGSHIFT=1
return 0
;; ;;
-) -)
FLAG=
FLAGRAW=
unset FLAGARG
FLAGSHIFT=0 FLAGSHIFT=0
return 1
;; ;;
--) --)
FLAG=
FLAGRAW=
unset FLAGARG
FLAGSHIFT=1 FLAGSHIFT=1
return 1
;; ;;
-*) -*)
# Remove leading hyphens. # Remove leading hyphens.
@ -343,15 +375,13 @@ flag_parse() {
;; ;;
esac esac
fi fi
return 0
;; ;;
*) *)
FLAG=
FLAGRAW=
unset FLAGARG
FLAGSHIFT=0 FLAGSHIFT=0
return 1
;; ;;
esac esac
return 0
} }
flag_reqarg() { flag_reqarg() {
@ -452,8 +482,11 @@ usage: $arg0 [--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix
[--tala latest] [--force] [--uninstall] [--tala latest] [--force] [--uninstall]
install.sh automates the installation of D2 onto your system. It currently only supports install.sh automates the installation of D2 onto your system. It currently only supports
the installation of standalone releases from GitHub. If you pass --edge, it will clone the the installation of standalone releases from GitHub and via Homebrew on macOS. See the
source, build a release and install from it. docs for --detect below for more information
If you pass --edge, it will clone the source, build a release and install from it.
--edge is incompatible with --tala and currently unimplemented.
Flags: Flags:
@ -463,6 +496,8 @@ Flags:
--version vX.X.X --version vX.X.X
Pass to have install.sh install the given version instead of the latest version. Pass to have install.sh install the given version instead of the latest version.
warn: The version may not be obeyed with package manager installations. Use
--method=standalone to enforce the version.
--edge --edge
Pass to build and install D2 from source. This will still use --method if set to detect Pass to build and install D2 from source. This will still use --method if set to detect
@ -470,14 +505,15 @@ Flags:
if an unsupported package manager is used. To install from source like a dev would, if an unsupported package manager is used. To install from source like a dev would,
use go install oss.terrastruct.com/d2 use go install oss.terrastruct.com/d2
note: currently unimplemented. note: currently unimplemented.
warn: incompatible with --tala as TALA is closed source.
--method [detect | standalone] --method [detect | standalone | homebrew ]
Pass to control the method by which to install. Right now we only support standalone Pass to control the method by which to install. Right now we only support standalone
releases from GitHub but later we'll add support for brew, rpm, deb and more. releases from GitHub but later we'll add support for brew, rpm, deb and more.
note: currently unimplemented.
- detect is currently unimplemented but would use your OS's package manager - detect will use your OS's package manager automatically.
automatically. So far it only detects macOS and automatically uses homebrew.
- homebrew uses https://brew.sh/ which is a macOS and Linux package manager.
- standalone installs a standalone release archive into the unix hierarchy path - standalone installs a standalone release archive into the unix hierarchy path
specified by --prefix which defaults to /usr/local specified by --prefix which defaults to /usr/local
Ensure /usr/local/bin is in your \$PATH to use it. Ensure /usr/local/bin is in your \$PATH to use it.
@ -485,16 +521,19 @@ Flags:
--prefix /usr/local --prefix /usr/local
Controls the unix hierarchy path into which standalone releases are installed. Controls the unix hierarchy path into which standalone releases are installed.
Defaults to /usr/local. You may also want to use ~/.local to avoid needing sudo. Defaults to /usr/local. You may also want to use ~/.local to avoid needing sudo.
Remember that whatever you use, you must have the bin directory of your prefix We use ~/.local by default on arm64 macOS machines as SIP now disables access to
path in \$PATH to execute the d2 binary. For example, if my prefix directory is /usr/local. Remember that whatever you use, you must have the bin directory of your
prefix path in \$PATH to execute the d2 binary. For example, if my prefix directory is
/usr/local then my \$PATH must contain /usr/local/bin. /usr/local then my \$PATH must contain /usr/local/bin.
--tala [latest] --tala [latest]
Install Terrastruct's closed source TALA for improved layouts. Install Terrastruct's closed source TALA for improved layouts.
See https://github.com/terrastruct/TALA See https://github.com/terrastruct/tala
It optionally takes an argument of the TALA version to install. It optionally takes an argument of the TALA version to install.
Installation obeys all other flags, just like the installation of d2. For example, Installation obeys all other flags, just like the installation of d2. For example,
the d2plugin-tala binary will be installed into /usr/local/bin/d2plugin-tala the d2plugin-tala binary will be installed into /usr/local/bin/d2plugin-tala
warn: The version may not be obeyed with package manager installations. Use
--method=standalone to enforce the version.
--force: --force:
Force installation over the existing version even if they match. It will attempt a Force installation over the existing version even if they match. It will attempt a
@ -507,6 +546,7 @@ Flags:
as for installation. i.e if you used --method standalone you must again use --method as for installation. i.e if you used --method standalone you must again use --method
standalone for uninstallation. With detect, the install script will try to use the OS standalone for uninstallation. With detect, the install script will try to use the OS
package manager to uninstall instead. package manager to uninstall instead.
note: tala will also be uninstalled if installed.
All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change
path of the cached assets. Release archives are unarchived into /usr/local/lib/d2/d2-<VERSION> path of the cached assets. Release archives are unarchived into /usr/local/lib/d2/d2-<VERSION>
@ -519,9 +559,7 @@ EOF
} }
main() { main() {
METHOD=standalone while flag_parse "$@"; do
while :; do
flag_parse "$@"
case "$FLAG" in case "$FLAG" in
h|help) h|help)
help help
@ -548,8 +586,6 @@ main() {
method) method)
flag_nonemptyarg && shift "$FLAGSHIFT" flag_nonemptyarg && shift "$FLAGSHIFT"
METHOD=$FLAGARG METHOD=$FLAGARG
echoerr "$FLAGRAW is currently unimplemented"
return 1
;; ;;
prefix) prefix)
flag_nonemptyarg && shift "$FLAGSHIFT" flag_nonemptyarg && shift "$FLAGSHIFT"
@ -563,15 +599,12 @@ main() {
flag_noarg && shift "$FLAGSHIFT" flag_noarg && shift "$FLAGSHIFT"
UNINSTALL=1 UNINSTALL=1
;; ;;
'')
shift "$FLAGSHIFT"
break
;;
*) *)
flag_errusage "unrecognized flag $FLAGRAW" flag_errusage "unrecognized flag $FLAGRAW"
;; ;;
esac esac
done done
shift "$FLAGSHIFT"
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
flag_errusage "no arguments are accepted" flag_errusage "no arguments are accepted"
@ -587,49 +620,86 @@ main() {
PREFIX=${PREFIX:-/usr/local} PREFIX=${PREFIX:-/usr/local}
CACHE_DIR=$(cache_dir) CACHE_DIR=$(cache_dir)
mkdir -p "$CACHE_DIR" mkdir -p "$CACHE_DIR"
METHOD=${METHOD:-detect}
INSTALL_DIR=$PREFIX/lib/d2 INSTALL_DIR=$PREFIX/lib/d2
case $METHOD in
detect)
case "$OS" in
macos)
if command -v brew >/dev/null; then
log "detected macOS with homebrew, using homebrew for (un)installation"
METHOD=homebrew
else
warn "detected macOS without homebrew, falling back to --method=standalone"
METHOD=standalone
fi
;;
*)
warn "unrecognized OS $OS, falling back to --method=standalone"
METHOD=standalone
;;
esac
;;
standalone) ;;
homebrew) ;;
*)
echoerr "unknown (un)installation method $METHOD"
return 1
;;
esac
if [ -n "${UNINSTALL-}" ]; then if [ -n "${UNINSTALL-}" ]; then
uninstall uninstall
return 0 if [ -n "${DRY_RUN-}" ]; then
log "Rerun without --dry-run to execute printed commands and perform uninstall."
fi fi
else
VERSION=${VERSION:-latest}
if [ "$VERSION" = latest ]; then
header "fetching latest release info"
fetch_release_info
fi
install install
if [ -n "${DRY_RUN-}" ]; then
log "Rerun without --dry-run to execute printed commands and perform install."
fi
fi
} }
install() { install() {
install_d2 case $METHOD in
standalone)
install_d2_standalone
if [ -n "${TALA-}" ]; then if [ -n "${TALA-}" ]; then
# Run in subshell to avoid overwriting VERSION. # Run in subshell to avoid overwriting VERSION.
TALA_VERSION="$( install_tala && echo "$VERSION" )" TALA_VERSION="$( RELEASE_INFO= install_tala_standalone && echo "$VERSION" )"
fi fi
;;
homebrew)
install_d2_brew
if [ -n "${TALA-}" ]; then install_tala_brew; fi
;;
esac
COLOR=2 header success FGCOLOR=2 bigheader 'next steps'
case $METHOD in
standalone) install_post_standalone ;;
homebrew) install_post_brew ;;
esac
}
install_post_standalone() {
log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX" log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX"
if [ -n "${TALA-}" ]; then if [ -n "${TALA-}" ]; then
log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX" log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX"
fi fi
log "Rerun this install script with --uninstall to uninstall" log "Rerun this install script with --uninstall to uninstall."
log
if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then
logcat >&2 <<EOF logcat >&2 <<EOF
%%%%%%%%%%%%%%%%%%%%%%%%%
NEXT STEPS
%%%%%%%%%%%%%%%%%%%%%%%%%
Extend your \$PATH to use d2: Extend your \$PATH to use d2:
export PATH=$PREFIX/bin:\$PATH export PATH=$PREFIX/bin:\$PATH
Then run: Then run:
${TALA+D2_LAYOUT=tala }d2 --help ${TALA+D2_LAYOUT=tala }d2 --help
EOF EOF
else else
log " Run ${TALA+D2_LAYOUT=tala }d2 --help for usage." log "Run ${TALA+D2_LAYOUT=tala }d2 --help for usage."
fi fi
if ! manpath | grep -qF "$PREFIX/share/man"; then if ! manpath | grep -qF "$PREFIX/share/man"; then
logcat >&2 <<EOF logcat >&2 <<EOF
@ -642,36 +712,61 @@ EOF
log " man d2plugin-tala" log " man d2plugin-tala"
fi fi
else else
log " Run man d2 for detailed docs." log "Run man d2 for detailed docs."
if [ -n "${TALA-}" ]; then if [ -n "${TALA-}" ]; then
log " Run man d2plugin-tala for detailed docs." log "Run man d2plugin-tala for detailed TALA docs."
fi fi
fi fi
logcat >&2 <<EOF logcat >&2 <<EOF
Something not working? Please let us know: Something not working? Please let us know:
https://github.com/terrastruct/d2/issues/new https://github.com/terrastruct/d2/issues
https://github.com/terrastruct/d2/discussions
https://discord.gg/NF6X8K4eDq
EOF EOF
} }
install_d2() { install_post_brew() {
log "d2 has been successfully installed with homebrew."
if [ -n "${TALA-}" ]; then
log "tala has been successfully installed with homebrew."
fi
log "Rerun this install script with --uninstall to uninstall."
log
log "Run ${TALA+D2_LAYOUT=tala }d2 --help for usage."
log "Run man d2 for detailed docs."
if [ -n "${TALA-}" ]; then
log "Run man d2plugin-tala for detailed TALA docs."
fi
logcat >&2 <<EOF
Something not working? Please let us know:
https://github.com/terrastruct/d2/issues
https://github.com/terrastruct/d2/discussions
https://discord.gg/NF6X8K4eDq
EOF
}
install_d2_standalone() {
VERSION=${VERSION:-latest}
header "installing d2-$VERSION"
if [ "$VERSION" = latest ]; then
fetch_release_info
fi
if command -v d2 >/dev/null; then if command -v d2 >/dev/null; then
INSTALLED_VERSION="$(d2 version)" INSTALLED_VERSION="$(d2 version)"
if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then
log "skipping installation as version $VERSION is already installed." log "skipping installation as d2 $VERSION is already installed."
return 0 return 0
fi fi
log "uninstalling $INSTALLED_VERSION to install $VERSION" log "uninstalling d2 $INSTALLED_VERSION to install $VERSION"
if ! uninstall_d2; then if ! uninstall_d2_standalone; then
warn "failed to uninstall $INSTALLED_VERSION" warn "failed to uninstall d2 $INSTALLED_VERSION"
fi fi
fi fi
header "installing d2-$VERSION"
install_standalone_d2
}
install_standalone_d2() {
ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz" ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz"
log "installing standalone release $ARCHIVE from github" log "installing standalone release $ARCHIVE from github"
@ -690,19 +785,38 @@ install_standalone_d2() {
"$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
} }
install_tala() { install_d2_brew() {
REPO="${REPO_TALA:-terrastruct/TALA}" header "installing d2 with homebrew"
VERSION=$TALA sh_c brew tap terrastruct/d2
RELEASE_INFO= sh_c brew install d2
fetch_release_info
header "installing tala-$VERSION"
install_standalone_tala
} }
install_standalone_tala() { install_tala_standalone() {
REPO="${REPO_TALA:-terrastruct/tala}"
VERSION=$TALA
header "installing tala-$VERSION"
if [ "$VERSION" = latest ]; then
fetch_release_info
fi
if command -v d2plugin-tala >/dev/null; then
INSTALLED_VERSION="$(d2plugin-tala --version)"
if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then
log "skipping installation as tala $VERSION is already installed."
return 0
fi
log "uninstalling tala $INSTALLED_VERSION to install $VERSION"
if ! uninstall_tala_standalone; then
warn "failed to uninstall tala $INSTALLED_VERSION"
fi
fi
ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz" ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz"
log "installing standalone release $ARCHIVE from github" log "installing standalone release $ARCHIVE from github"
fetch_release_info
asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1') asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1')
asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"')
@ -718,36 +832,40 @@ install_standalone_tala() {
"$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
} }
install_tala_brew() {
header "installing tala with homebrew"
sh_c brew tap terrastruct/d2
sh_c brew install tala
}
uninstall() { uninstall() {
# We uninstall tala first as package managers require that it be uninstalled before
# uninstalling d2 as TALA depends on d2.
if command -v d2plugin-tala >/dev/null; then
INSTALLED_VERSION="$(d2plugin-tala --version)"
header "uninstalling tala-$INSTALLED_VERSION"
case $METHOD in
standalone) uninstall_tala_standalone ;;
homebrew) uninstall_tala_brew ;;
esac
elif [ "${TALA-}" ]; then
warn "no version of tala installed"
fi
if ! command -v d2 >/dev/null; then if ! command -v d2 >/dev/null; then
warn "no version of d2 installed" warn "no version of d2 installed"
return 0 return 0
fi fi
INSTALLED_VERSION="$(d2 --version)" INSTALLED_VERSION="$(d2 --version)"
if ! uninstall_d2; then
echoerr "failed to uninstall $INSTALLED_VERSION"
return 1
fi
if [ "${TALA-}" ]; then
if ! command -v d2plugin-tala >/dev/null; then
warn "no version of tala installed"
return 0
fi
INSTALLED_VERSION="$(d2plugin-tala --version)"
if ! uninstall_tala; then
echoerr "failed to uninstall tala $INSTALLED_VERSION"
return 1
fi
fi
return 0
}
uninstall_d2() {
header "uninstalling d2-$INSTALLED_VERSION" header "uninstalling d2-$INSTALLED_VERSION"
uninstall_standalone_d2 case $METHOD in
standalone) uninstall_d2_standalone ;;
homebrew) uninstall_d2_brew ;;
esac
} }
uninstall_standalone_d2() { uninstall_d2_standalone() {
log "uninstalling standalone release of d2-$INSTALLED_VERSION" log "uninstalling standalone release of d2-$INSTALLED_VERSION"
if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then
@ -765,12 +883,11 @@ uninstall_standalone_d2() {
"$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION"
} }
uninstall_tala() { uninstall_d2_brew() {
header "uninstalling tala-$INSTALLED_VERSION" sh_c brew remove d2
uninstall_standalone_tala
} }
uninstall_standalone_tala() { uninstall_tala_standalone() {
log "uninstalling standalone release tala-$INSTALLED_VERSION" log "uninstalling standalone release tala-$INSTALLED_VERSION"
if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then
@ -788,6 +905,10 @@ uninstall_standalone_tala() {
"$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION"
} }
uninstall_tala_brew() {
sh_c brew remove tala
}
is_prefix_writable() { is_prefix_writable() {
sh_c "mkdir -p '$INSTALL_DIR' 2>/dev/null" || true 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 # The reason for checking whether $INSTALL_DIR is writable is that on macOS you have
@ -843,4 +964,9 @@ fetch_gh() {
sh_c mv "$file.inprogress" "$file" sh_c mv "$file.inprogress" "$file"
} }
brew() {
# Makes brew sane.
HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_AUTO_UPDATE=1 command brew "$@"
}
main "$@" main "$@"

View file

@ -1,4 +1,4 @@
package version package version
// Pre-built binaries will have version set during build time. // Pre-built binaries will have version set during build time.
var Version = "master (built from source)" var Version = "????"

45
lib/xmain/flag_helpers.go Normal file
View file

@ -0,0 +1,45 @@
// flag_helpers.go are private functions from pflag/flag.go
package xmain
import "strings"
func wrap(i, w int, s string) string {
if w == 0 {
return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
}
wrap := w - i
var r, l string
if wrap < 24 {
i = 16
wrap = w - i
r += "\n" + strings.Repeat(" ", i)
}
if wrap < 24 {
return strings.Replace(s, "\n", r, -1)
}
slop := 5
wrap = wrap - slop
l, s = wrapN(wrap, slop, s)
r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
for s != "" {
var t string
t, s = wrapN(wrap, slop, s)
r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
}
return r
}
func wrapN(i, slop int, s string) (string, string) {
if i+slop > len(s) {
return s, ""
}
w := strings.LastIndexAny(s[:i], " \t\n")
if w <= 0 {
return s, ""
}
nlPos := strings.LastIndex(s[:i], "\n")
if nlPos > 0 && nlPos < w {
return s[:nlPos], s[nlPos+1:]
}
return s[:w], s[w+1:]
}

173
lib/xmain/opts.go Normal file
View file

@ -0,0 +1,173 @@
package xmain
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"github.com/spf13/pflag"
"oss.terrastruct.com/cmdlog"
"oss.terrastruct.com/xos"
)
type Opts struct {
Args []string
Flags *pflag.FlagSet
env *xos.Env
log *cmdlog.Logger
flagEnv map[string]string
}
func NewOpts(env *xos.Env, log *cmdlog.Logger, args []string) *Opts {
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
flags.SortFlags = false
flags.Usage = func() {}
flags.SetOutput(io.Discard)
return &Opts{
Args: args,
Flags: flags,
env: env,
log: log,
flagEnv: make(map[string]string),
}
}
// Mostly copy pasted pasted from pflag.FlagUsagesWrapped
// with modifications for env var
func (o *Opts) Defaults() string {
buf := new(bytes.Buffer)
var lines []string
maxlen := 0
maxEnvLen := 0
o.Flags.VisitAll(func(flag *pflag.Flag) {
if flag.Hidden {
return
}
line := ""
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
} else {
line = fmt.Sprintf(" --%s", flag.Name)
}
varname, usage := pflag.UnquoteUsage(flag)
if varname != "" {
line += " " + varname
}
if flag.NoOptDefVal != "" {
switch flag.Value.Type() {
case "string":
line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
case "bool":
if flag.NoOptDefVal != "true" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
case "count":
if flag.NoOptDefVal != "+1" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
default:
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
}
line += "\x00"
if len(line) > maxlen {
maxlen = len(line)
}
if e, ok := o.flagEnv[flag.Name]; ok {
line += fmt.Sprintf("$%s", e)
}
line += "\x01"
if len(line) > maxEnvLen {
maxEnvLen = len(line)
}
line += usage
if flag.Value.Type() == "string" {
line += fmt.Sprintf(" (default %q)", flag.DefValue)
} else {
line += fmt.Sprintf(" (default %s)", flag.DefValue)
}
if len(flag.Deprecated) != 0 {
line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
}
lines = append(lines, line)
})
for _, line := range lines {
sidx1 := strings.Index(line, "\x00")
sidx2 := strings.Index(line, "\x01")
spacing1 := strings.Repeat(" ", maxlen-sidx1)
spacing2 := strings.Repeat(" ", (maxEnvLen-maxlen)-sidx2+sidx1)
fmt.Fprintln(buf, line[:sidx1], spacing1, line[sidx1+1:sidx2], spacing2, wrap(maxEnvLen+3, 0, line[sidx2+1:]))
}
return buf.String()
}
func (o *Opts) getEnv(flag, k string) string {
if k != "" {
o.flagEnv[flag] = k
return o.env.Getenv(k)
}
return ""
}
func (o *Opts) Int64(envKey, flag, shortFlag string, defaultVal int64, usage string) (*int64, error) {
if env := o.getEnv(flag, envKey); env != "" {
envVal, err := strconv.ParseInt(env, 10, 64)
if err != nil {
return nil, fmt.Errorf(`invalid environment variable %s. Expected int64. Found "%v".`, envKey, envVal)
}
defaultVal = envVal
}
return o.Flags.Int64P(flag, shortFlag, defaultVal, usage), nil
}
func (o *Opts) String(envKey, flag, shortFlag string, defaultVal, usage string) *string {
if env := o.getEnv(flag, envKey); env != "" {
defaultVal = env
}
return o.Flags.StringP(flag, shortFlag, defaultVal, usage)
}
func (o *Opts) Bool(envKey, flag, shortFlag string, defaultVal bool, usage string) (*bool, error) {
if env := o.getEnv(flag, envKey); env != "" {
if !boolyEnv(env) {
return nil, fmt.Errorf(`invalid environment variable %s. Expected bool. Found "%s".`, envKey, env)
}
if truthyEnv(env) {
defaultVal = true
} else {
defaultVal = false
}
}
return o.Flags.BoolP(flag, shortFlag, defaultVal, usage), nil
}
func boolyEnv(s string) bool {
return falseyEnv(s) || truthyEnv(s)
}
func falseyEnv(s string) bool {
return s == "0" || s == "false"
}
func truthyEnv(s string) bool {
return s == "1" || s == "true"
}

View file

@ -9,13 +9,11 @@ import (
"io" "io"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
"cdr.dev/slog" "cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman" "cdr.dev/slog/sloggers/sloghuman"
"github.com/spf13/pflag"
"oss.terrastruct.com/xos" "oss.terrastruct.com/xos"
@ -42,13 +40,9 @@ func Main(run RunFunc) {
Stderr: os.Stderr, Stderr: os.Stderr,
Env: xos.NewEnv(os.Environ()), Env: xos.NewEnv(os.Environ()),
FlagSet: pflag.NewFlagSet("", pflag.ContinueOnError),
Args: args,
} }
ms.Log = cmdlog.Log(ms.Env, os.Stderr) ms.Log = cmdlog.Log(ms.Env, os.Stderr)
ms.FlagSet.SortFlags = false ms.Opts = NewOpts(ms.Env, ms.Log, args)
ms.FlagSet.Usage = func() {}
ms.FlagSet.SetOutput(io.Discard)
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
@ -90,8 +84,7 @@ type State struct {
Log *cmdlog.Logger Log *cmdlog.Logger
Env *xos.Env Env *xos.Env
Args []string Opts *Opts
FlagSet *pflag.FlagSet
} }
func (ms *State) Main(ctx context.Context, sigs <-chan os.Signal, run func(context.Context, *State) error) error { func (ms *State) Main(ctx context.Context, sigs <-chan os.Signal, run func(context.Context, *State) error) error {
@ -129,13 +122,6 @@ func (ms *State) Main(ctx context.Context, sigs <-chan os.Signal, run func(conte
} }
} }
func (ms *State) FlagHelp() string {
b := &strings.Builder{}
ms.FlagSet.SetOutput(b)
ms.FlagSet.PrintDefaults()
return b.String()
}
type ExitError struct { type ExitError struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`